From a12b097ff76270d6108e97335a3a6ea0adf0b460 Mon Sep 17 00:00:00 2001 From: Tyler Date: Thu, 5 Sep 2024 11:31:54 +0900 Subject: [PATCH 01/29] feat: Added onSystemEvents listener (#1025) * feat: Added onSystemEvents listener * change the type of payload * change the type of parameter for onSystemEvents --- .../realtime_client/lib/src/realtime_channel.dart | 11 +++++++++++ packages/realtime_client/lib/src/types.dart | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/realtime_client/lib/src/realtime_channel.dart b/packages/realtime_client/lib/src/realtime_channel.dart index c7cd8568..42a2470c 100644 --- a/packages/realtime_client/lib/src/realtime_channel.dart +++ b/packages/realtime_client/lib/src/realtime_channel.dart @@ -410,6 +410,17 @@ class RealtimeChannel { ); } + /// Sets up a listener for realtime system events for debugging purposes. + RealtimeChannel onSystemEvents( + void Function(dynamic payload) callback, + ) { + return onEvents( + 'system', + ChannelFilter(), + (payload, [ref]) => callback(payload), + ); + } + @internal RealtimeChannel onEvents( String type, ChannelFilter filter, BindingCallback callback) { diff --git a/packages/realtime_client/lib/src/types.dart b/packages/realtime_client/lib/src/types.dart index 19fb68b9..32a0cea2 100644 --- a/packages/realtime_client/lib/src/types.dart +++ b/packages/realtime_client/lib/src/types.dart @@ -110,7 +110,7 @@ enum ChannelResponse { error } -enum RealtimeListenTypes { postgresChanges, broadcast, presence } +enum RealtimeListenTypes { postgresChanges, broadcast, presence, system } enum PresenceEvent { sync, join, leave } From 588b5000d64a5d71b45ed57f70f0ed812dd34619 Mon Sep 17 00:00:00 2001 From: Vinzent Date: Sat, 14 Sep 2024 02:51:43 +0200 Subject: [PATCH 02/29] fix: Update example native code (#1029) fix: update example native code --- packages/supabase_flutter/example/.metadata | 34 +++++----- .../example/analysis_options.yaml | 3 +- .../example/android/.gitignore | 2 +- .../example/android/app/build.gradle | 65 ++++++------------- .../android/app/src/debug/AndroidManifest.xml | 3 +- .../android/app/src/main/AndroidManifest.xml | 17 ++++- .../com/example/example/MainActivity.kt | 3 +- .../app/src/profile/AndroidManifest.xml | 3 +- .../example/android/build.gradle | 19 +----- .../example/android/gradle.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 3 +- .../example/android/settings.gradle | 30 ++++++--- .../example/ios/RunnerTests/RunnerTests.swift | 12 ++++ .../macos/RunnerTests/RunnerTests.swift | 12 ++++ .../supabase_flutter/example/web/index.html | 22 +------ 15 files changed, 107 insertions(+), 123 deletions(-) create mode 100644 packages/supabase_flutter/example/ios/RunnerTests/RunnerTests.swift create mode 100644 packages/supabase_flutter/example/macos/RunnerTests/RunnerTests.swift diff --git a/packages/supabase_flutter/example/.metadata b/packages/supabase_flutter/example/.metadata index 48c6a020..391c336b 100644 --- a/packages/supabase_flutter/example/.metadata +++ b/packages/supabase_flutter/example/.metadata @@ -1,11 +1,11 @@ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # -# This file should be version controlled. +# This file should be version controlled and should not be manually edited. version: - revision: 676cefaaff197f27424942307668886253e1ec35 - channel: stable + revision: "5874a72aa4c779a02553007c47dacbefba2374dc" + channel: "stable" project_type: app @@ -13,26 +13,26 @@ project_type: app migration: platforms: - platform: root - create_revision: 676cefaaff197f27424942307668886253e1ec35 - base_revision: 676cefaaff197f27424942307668886253e1ec35 + create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc - platform: android - create_revision: 676cefaaff197f27424942307668886253e1ec35 - base_revision: 676cefaaff197f27424942307668886253e1ec35 + create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc - platform: ios - create_revision: 676cefaaff197f27424942307668886253e1ec35 - base_revision: 676cefaaff197f27424942307668886253e1ec35 + create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc - platform: linux - create_revision: 676cefaaff197f27424942307668886253e1ec35 - base_revision: 676cefaaff197f27424942307668886253e1ec35 + create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc - platform: macos - create_revision: 676cefaaff197f27424942307668886253e1ec35 - base_revision: 676cefaaff197f27424942307668886253e1ec35 + create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc - platform: web - create_revision: 676cefaaff197f27424942307668886253e1ec35 - base_revision: 676cefaaff197f27424942307668886253e1ec35 + create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc - platform: windows - create_revision: 676cefaaff197f27424942307668886253e1ec35 - base_revision: 676cefaaff197f27424942307668886253e1ec35 + create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc # User provided section diff --git a/packages/supabase_flutter/example/analysis_options.yaml b/packages/supabase_flutter/example/analysis_options.yaml index 1bd3ae32..f1394e4f 100644 --- a/packages/supabase_flutter/example/analysis_options.yaml +++ b/packages/supabase_flutter/example/analysis_options.yaml @@ -26,8 +26,7 @@ linter: # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule analyzer: - exclude: + exclude: - lib/generated_plugin_registrant.dart - # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options diff --git a/packages/supabase_flutter/example/android/.gitignore b/packages/supabase_flutter/example/android/.gitignore index 6f568019..55afd919 100644 --- a/packages/supabase_flutter/example/android/.gitignore +++ b/packages/supabase_flutter/example/android/.gitignore @@ -7,7 +7,7 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/supabase_flutter/example/android/app/build.gradle b/packages/supabase_flutter/example/android/app/build.gradle index 0833ecfc..b5511a9a 100644 --- a/packages/supabase_flutter/example/android/app/build.gradle +++ b/packages/supabase_flutter/example/android/app/build.gradle @@ -1,71 +1,44 @@ -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { - compileSdkVersion flutter.compileSdkVersion - ndkVersion flutter.ndkVersion + namespace = "com.example.example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' + jvmTarget = JavaVersion.VERSION_1_8 } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.example" + applicationId = "com.example.example" // You can update the following values to match your application needs. - // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug + signingConfig = signingConfigs.debug } } } flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + source = "../.." } diff --git a/packages/supabase_flutter/example/android/app/src/debug/AndroidManifest.xml b/packages/supabase_flutter/example/android/app/src/debug/AndroidManifest.xml index 45d523a2..399f6981 100644 --- a/packages/supabase_flutter/example/android/app/src/debug/AndroidManifest.xml +++ b/packages/supabase_flutter/example/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,4 @@ - + + + + + + + diff --git a/packages/supabase_flutter/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/packages/supabase_flutter/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt index e793a000..70f8f08f 100644 --- a/packages/supabase_flutter/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt +++ b/packages/supabase_flutter/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -2,5 +2,4 @@ package com.example.example import io.flutter.embedding.android.FlutterActivity -class MainActivity: FlutterActivity() { -} +class MainActivity: FlutterActivity() diff --git a/packages/supabase_flutter/example/android/app/src/profile/AndroidManifest.xml b/packages/supabase_flutter/example/android/app/src/profile/AndroidManifest.xml index 45d523a2..399f6981 100644 --- a/packages/supabase_flutter/example/android/app/src/profile/AndroidManifest.xml +++ b/packages/supabase_flutter/example/android/app/src/profile/AndroidManifest.xml @@ -1,5 +1,4 @@ - + - - + From 773b7de74461ca3ea857d11b1abdfdf35fb540d4 Mon Sep 17 00:00:00 2001 From: Vinzent Date: Fri, 20 Sep 2024 12:52:50 +0200 Subject: [PATCH 03/29] fix: Support all mfa auth methods (#1030) * fix: support all mfa auth methods * fix: add unknown mfa type * style: use orElse instead of firstWhereOrNull * style: remove import --- packages/gotrue/lib/src/types/mfa.dart | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/gotrue/lib/src/types/mfa.dart b/packages/gotrue/lib/src/types/mfa.dart index cfdd2f05..148f3b1f 100644 --- a/packages/gotrue/lib/src/types/mfa.dart +++ b/packages/gotrue/lib/src/types/mfa.dart @@ -257,7 +257,25 @@ class AuthMFAGetAuthenticatorAssuranceLevelResponse { }); } -enum AMRMethod { password, otp, oauth, totp, magiclink } +enum AMRMethod { + password('password'), + otp('otp'), + oauth('oauth'), + totp('totp'), + magiclink('magiclink'), + recovery('recovery'), + invite('invite'), + ssoSaml('sso/saml'), + emailSignUp('email/signup'), + emailChange('email_change'), + tokenRefresh('token_refresh'), + anonymous('anonymous'), + mfaPhone('mfa/phone'), + unknown('unknown'); + + final String code; + const AMRMethod(this.code); +} /// An authentication method reference (AMR) entry. /// @@ -278,7 +296,8 @@ class AMREntry { factory AMREntry.fromJson(Map json) { return AMREntry( method: AMRMethod.values.firstWhere( - (e) => e.name == json['method'], + (e) => e.code == json['method'], + orElse: () => AMRMethod.unknown, ), timestamp: DateTime.fromMillisecondsSinceEpoch(json['timestamp'] * 1000), ); From 8f473f1a99e0cbb9d570eb3fff0786ed5084351c Mon Sep 17 00:00:00 2001 From: Vinzent Date: Fri, 20 Sep 2024 12:53:36 +0200 Subject: [PATCH 04/29] feat: Broadcast auth events to other tabs on web (#1005) * feat: broadcast auth events on web * refactor: consolidate types in one file * fix: add dispose to auth client * refactor: use js object for messaging * style: remove unused userDeleted event * refactor: remove deprecation of supabasePersistSessionKey * fix: store session in client * fix: allow removing session on broadcast and add bool to AuthState * fix: catch session being null * fix: improvements from code review * fix: remove broadcastSession from constructor --- packages/gotrue/lib/gotrue.dart | 3 +- packages/gotrue/lib/src/broadcast_stub.dart | 6 ++ packages/gotrue/lib/src/broadcast_web.dart | 23 +++++ packages/gotrue/lib/src/constants.dart | 21 +++-- packages/gotrue/lib/src/gotrue_client.dart | 85 ++++++++++++++++++- packages/gotrue/lib/src/types/auth_state.dart | 11 ++- .../gotrue/lib/src/types/oauth_flow_type.dart | 4 - .../{o_auth_provider.dart => types.dart} | 11 +++ .../supabase/lib/src/supabase_client.dart | 4 +- .../lib/src/local_storage.dart | 1 + 10 files changed, 150 insertions(+), 19 deletions(-) create mode 100644 packages/gotrue/lib/src/broadcast_stub.dart create mode 100644 packages/gotrue/lib/src/broadcast_web.dart delete mode 100644 packages/gotrue/lib/src/types/oauth_flow_type.dart rename packages/gotrue/lib/src/types/{o_auth_provider.dart => types.dart} (56%) diff --git a/packages/gotrue/lib/gotrue.dart b/packages/gotrue/lib/gotrue.dart index 7e27c223..0799ee52 100644 --- a/packages/gotrue/lib/gotrue.dart +++ b/packages/gotrue/lib/gotrue.dart @@ -9,8 +9,7 @@ export 'src/types/auth_response.dart' hide ToSnakeCase; export 'src/types/auth_state.dart'; export 'src/types/gotrue_async_storage.dart'; export 'src/types/mfa.dart'; -export 'src/types/o_auth_provider.dart'; -export 'src/types/oauth_flow_type.dart'; +export 'src/types/types.dart'; export 'src/types/session.dart'; export 'src/types/user.dart'; export 'src/types/user_attributes.dart'; diff --git a/packages/gotrue/lib/src/broadcast_stub.dart b/packages/gotrue/lib/src/broadcast_stub.dart new file mode 100644 index 00000000..95ee5d0e --- /dev/null +++ b/packages/gotrue/lib/src/broadcast_stub.dart @@ -0,0 +1,6 @@ +import 'package:gotrue/src/types/types.dart'; + +/// Stub implementation of [BroadcastChannel] for platforms that don't support it. +BroadcastChannel getBroadcastChannel(String broadcastKey) { + throw UnimplementedError(); +} diff --git a/packages/gotrue/lib/src/broadcast_web.dart b/packages/gotrue/lib/src/broadcast_web.dart new file mode 100644 index 00000000..b754666a --- /dev/null +++ b/packages/gotrue/lib/src/broadcast_web.dart @@ -0,0 +1,23 @@ +import 'dart:convert'; +import 'dart:html' as html; +import 'dart:js_util' as js_util; + +import 'package:gotrue/src/types/types.dart'; + +BroadcastChannel getBroadcastChannel(String broadcastKey) { + final broadcast = html.BroadcastChannel(broadcastKey); + return ( + onMessage: broadcast.onMessage.map((event) { + final dataMap = js_util.dartify(event.data); + + // some parts have the wrong map type. This is an easy workaround and + // should be efficient enough for the small session and user data + return json.decode(json.encode(dataMap)); + }), + postMessage: (message) { + final jsMessage = js_util.jsify(message); + broadcast.postMessage(jsMessage); + }, + close: broadcast.close, + ); +} diff --git a/packages/gotrue/lib/src/constants.dart b/packages/gotrue/lib/src/constants.dart index e1bdc87d..c44d5ff4 100644 --- a/packages/gotrue/lib/src/constants.dart +++ b/packages/gotrue/lib/src/constants.dart @@ -34,14 +34,19 @@ class ApiVersions { } enum AuthChangeEvent { - initialSession, - passwordRecovery, - signedIn, - signedOut, - tokenRefreshed, - userUpdated, - userDeleted, - mfaChallengeVerified, + initialSession('INITIAL_SESSION'), + passwordRecovery('PASSWORD_RECOVERY'), + signedIn('SIGNED_IN'), + signedOut('SIGNED_OUT'), + tokenRefreshed('TOKEN_REFRESHED'), + userUpdated('USER_UPDATED'), + + @Deprecated('Was never in use and might be removed in the future.') + userDeleted(''), + mfaChallengeVerified('MFA_CHALLENGE_VERIFIED'); + + final String jsName; + const AuthChangeEvent(this.jsName); } extension AuthChangeEventExtended on AuthChangeEvent { diff --git a/packages/gotrue/lib/src/gotrue_client.dart b/packages/gotrue/lib/src/gotrue_client.dart index 08c4e90e..8a2c0e2b 100644 --- a/packages/gotrue/lib/src/gotrue_client.dart +++ b/packages/gotrue/lib/src/gotrue_client.dart @@ -15,6 +15,9 @@ import 'package:meta/meta.dart'; import 'package:retry/retry.dart'; import 'package:rxdart/subjects.dart'; +import 'broadcast_stub.dart' if (dart.library.html) './broadcast_web.dart' + as web; + part 'gotrue_mfa_api.dart'; /// {@template gotrue_client} @@ -84,6 +87,11 @@ class GoTrueClient { final AuthFlowType _flowType; + /// Proxy to the web BroadcastChannel API. Should be null on non-web platforms. + BroadcastChannel? _broadcastChannel; + + StreamSubscription? _broadcastChannelSubscription; + /// {@macro gotrue_client} GoTrueClient({ String? url, @@ -116,6 +124,8 @@ class GoTrueClient { if (_autoRefreshToken) { startAutoRefresh(); } + + _mayStartBroadcastChannel(); } /// Getter for the headers @@ -1128,6 +1138,63 @@ class GoTrueClient { _currentUser = null; } + void _mayStartBroadcastChannel() { + if (const bool.fromEnvironment('dart.library.html')) { + // Used by the js library as well + final broadcastKey = + "sb-${Uri.parse(_url).host.split(".").first}-auth-token"; + + assert(_broadcastChannel == null, + 'Broadcast channel should not be started more than once.'); + try { + _broadcastChannel = web.getBroadcastChannel(broadcastKey); + _broadcastChannelSubscription = + _broadcastChannel?.onMessage.listen((messageEvent) { + final rawEvent = messageEvent['event']; + final event = switch (rawEvent) { + // This library sends the js name of the event to be comptabile with + // the js library, so we need to convert it back to the dart name + 'INITIAL_SESSION' => AuthChangeEvent.initialSession, + 'PASSWORD_RECOVERY' => AuthChangeEvent.passwordRecovery, + 'SIGNED_IN' => AuthChangeEvent.signedIn, + 'SIGNED_OUT' => AuthChangeEvent.signedOut, + 'TOKEN_REFRESHED' => AuthChangeEvent.tokenRefreshed, + 'USER_UPDATED' => AuthChangeEvent.userUpdated, + 'MFA_CHALLENGE_VERIFIED' => AuthChangeEvent.mfaChallengeVerified, + // This case should never happen though + _ => AuthChangeEvent.values + .firstWhereOrNull((event) => event.name == rawEvent), + }; + + if (event != null) { + Session? session; + if (messageEvent['session'] != null) { + session = Session.fromJson(messageEvent['session']); + } + if (session != null) { + _saveSession(session); + } else { + _removeSession(); + } + notifyAllSubscribers(event, session: session, broadcast: false); + } + }); + } catch (e) { + // Ignoring + } + } + } + + @mustCallSuper + void dispose() { + _onAuthStateChangeController.close(); + _onAuthStateChangeControllerSync.close(); + _broadcastChannel?.close(); + _broadcastChannelSubscription?.cancel(); + _refreshTokenCompleter?.completeError(AuthException('Disposed')); + _autoRefreshTicker?.cancel(); + } + /// Generates a new JWT. /// /// To prevent multiple simultaneous requests it catches an already ongoing request by using the global [_refreshTokenCompleter]. @@ -1181,9 +1248,23 @@ class GoTrueClient { } /// For internal use only. + /// + /// [broadcast] is used to determine if the event should be broadcasted to + /// other tabs. @internal - void notifyAllSubscribers(AuthChangeEvent event) { - final state = AuthState(event, currentSession); + void notifyAllSubscribers( + AuthChangeEvent event, { + Session? session, + bool broadcast = true, + }) { + session ??= currentSession; + if (broadcast && event != AuthChangeEvent.initialSession) { + _broadcastChannel?.postMessage({ + 'event': event.jsName, + 'session': session?.toJson(), + }); + } + final state = AuthState(event, session, fromBroadcast: !broadcast); _onAuthStateChangeController.add(state); _onAuthStateChangeControllerSync.add(state); } diff --git a/packages/gotrue/lib/src/types/auth_state.dart b/packages/gotrue/lib/src/types/auth_state.dart index b23b612a..c610790a 100644 --- a/packages/gotrue/lib/src/types/auth_state.dart +++ b/packages/gotrue/lib/src/types/auth_state.dart @@ -5,5 +5,14 @@ class AuthState { final AuthChangeEvent event; final Session? session; - AuthState(this.event, this.session); + /// Whether this state was broadcasted via `html.ChannelBroadcast` on web from + /// another tab or window. + final bool fromBroadcast; + + const AuthState(this.event, this.session, {this.fromBroadcast = false}); + + @override + String toString() { + return 'AuthState{event: $event, session: $session, fromBroadcast: $fromBroadcast}'; + } } diff --git a/packages/gotrue/lib/src/types/oauth_flow_type.dart b/packages/gotrue/lib/src/types/oauth_flow_type.dart deleted file mode 100644 index 51803805..00000000 --- a/packages/gotrue/lib/src/types/oauth_flow_type.dart +++ /dev/null @@ -1,4 +0,0 @@ -enum AuthFlowType { - implicit, - pkce, -} diff --git a/packages/gotrue/lib/src/types/o_auth_provider.dart b/packages/gotrue/lib/src/types/types.dart similarity index 56% rename from packages/gotrue/lib/src/types/o_auth_provider.dart rename to packages/gotrue/lib/src/types/types.dart index 1851bc7a..a2d11a69 100644 --- a/packages/gotrue/lib/src/types/o_auth_provider.dart +++ b/packages/gotrue/lib/src/types/types.dart @@ -1,3 +1,14 @@ +typedef BroadcastChannel = ({ + Stream> onMessage, + void Function(Map) postMessage, + void Function() close, +}); + +enum AuthFlowType { + implicit, + pkce, +} + enum OAuthProvider { apple, azure, diff --git a/packages/supabase/lib/src/supabase_client.dart b/packages/supabase/lib/src/supabase_client.dart index 3c5a9c63..b51b4ca8 100644 --- a/packages/supabase/lib/src/supabase_client.dart +++ b/packages/supabase/lib/src/supabase_client.dart @@ -250,6 +250,7 @@ class SupabaseClient { Future dispose() async { await _authStateSubscription?.cancel(); await _isolate.dispose(); + auth.dispose(); } GoTrueClient _initSupabaseAuthClient({ @@ -339,8 +340,7 @@ class SupabaseClient { event == AuthChangeEvent.tokenRefreshed || event == AuthChangeEvent.signedIn) { realtime.setAuth(token); - } else if (event == AuthChangeEvent.signedOut || - event == AuthChangeEvent.userDeleted) { + } else if (event == AuthChangeEvent.signedOut) { // Token is removed realtime.setAuth(_supabaseKey); diff --git a/packages/supabase_flutter/lib/src/local_storage.dart b/packages/supabase_flutter/lib/src/local_storage.dart index 607d4a15..7b201584 100644 --- a/packages/supabase_flutter/lib/src/local_storage.dart +++ b/packages/supabase_flutter/lib/src/local_storage.dart @@ -8,6 +8,7 @@ import 'package:supabase_flutter/supabase_flutter.dart'; import './local_storage_stub.dart' if (dart.library.html) './local_storage_web.dart' as web; +/// Only used for migration from Hive to SharedPreferences. Not actually in use. const supabasePersistSessionKey = 'SUPABASE_PERSIST_SESSION_KEY'; /// LocalStorage is used to persist the user session in the device. From e095c14e29e82cceb96220b5d73e67d991909478 Mon Sep 17 00:00:00 2001 From: Vinzent Date: Wed, 25 Sep 2024 17:49:22 +0200 Subject: [PATCH 05/29] feat(storage_client): Support copy/move to different bucket (#1043) * ci: update infra to state of storage-js * feat(storage_client): support copy/move to different bucket --- infra/storage_client/docker-compose.yml | 20 +++++++- infra/storage_client/postgres/Dockerfile | 3 +- .../postgres/storage-schema.sql | 18 +++----- infra/storage_client/storage/Dockerfile | 4 +- .../lib/src/storage_file_api.dart | 18 +++++++- packages/storage_client/test/client_test.dart | 46 +++++++++++++++++++ 6 files changed, 91 insertions(+), 18 deletions(-) diff --git a/infra/storage_client/docker-compose.yml b/infra/storage_client/docker-compose.yml index 78c60a94..44b31189 100644 --- a/infra/storage_client/docker-compose.yml +++ b/infra/storage_client/docker-compose.yml @@ -38,10 +38,14 @@ services: FILE_STORAGE_BACKEND_PATH: /tmp/storage ENABLE_IMAGE_TRANSFORMATION: "true" IMGPROXY_URL: http://imgproxy:8080 + DEBUG: "knex:*" + volumes: - assets-volume:/tmp/storage healthcheck: test: ['CMD-SHELL', 'curl -f -LI http://localhost:5000/status'] + interval: 2s + db: build: context: ./postgres @@ -62,6 +66,20 @@ services: timeout: 5s retries: 5 + dummy_data: + build: + context: ./postgres + depends_on: + storage: + condition: service_healthy + volumes: + - ./postgres:/sql + command: + - psql + - "postgresql://postgres:postgres@db:5432/postgres" + - -f + - /sql/dummy-data.sql + imgproxy: image: darthsim/imgproxy ports: @@ -73,4 +91,4 @@ services: - IMGPROXY_USE_ETAG=true - IMGPROXY_ENABLE_WEBP_DETECTION=true volumes: - assets-volume: \ No newline at end of file + assets-volume: diff --git a/infra/storage_client/postgres/Dockerfile b/infra/storage_client/postgres/Dockerfile index bb2198b8..6364316c 100644 --- a/infra/storage_client/postgres/Dockerfile +++ b/infra/storage_client/postgres/Dockerfile @@ -3,7 +3,6 @@ FROM supabase/postgres:0.13.0 COPY 00-initial-schema.sql /docker-entrypoint-initdb.d/00-initial-schema.sql COPY auth-schema.sql /docker-entrypoint-initdb.d/01-auth-schema.sql COPY storage-schema.sql /docker-entrypoint-initdb.d/02-storage-schema.sql -COPY dummy-data.sql /docker-entrypoint-initdb.d/03-dummy-data.sql # Build time defaults ARG build_POSTGRES_DB=postgres @@ -17,4 +16,4 @@ ENV POSTGRES_USER=$build_POSTGRES_USER ENV POSTGRES_PASSWORD=$build_POSTGRES_PASSWORD ENV POSTGRES_PORT=$build_POSTGRES_PORT -EXPOSE 5432 \ No newline at end of file +EXPOSE 5432 diff --git a/infra/storage_client/postgres/storage-schema.sql b/infra/storage_client/postgres/storage-schema.sql index c879c6b6..08142a13 100644 --- a/infra/storage_client/postgres/storage-schema.sql +++ b/infra/storage_client/postgres/storage-schema.sql @@ -28,7 +28,6 @@ CREATE TABLE "storage"."objects" ( "last_accessed_at" timestamptz DEFAULT now(), "metadata" jsonb, CONSTRAINT "objects_bucketId_fkey" FOREIGN KEY ("bucket_id") REFERENCES "storage"."buckets"("id"), - CONSTRAINT "objects_owner_fkey" FOREIGN KEY ("owner") REFERENCES "auth"."users"("id"), PRIMARY KEY ("id") ); CREATE UNIQUE INDEX "bucketid_objname" ON "storage"."objects" USING BTREE ("bucket_id","name"); @@ -85,27 +84,24 @@ CREATE OR REPLACE FUNCTION storage.search(prefix text, bucketname text, limits i ) LANGUAGE plpgsql AS $function$ -DECLARE -_bucketId text; BEGIN - select buckets."id" from buckets where buckets.name=bucketname limit 1 into _bucketId; - return query + return query with files_folders as ( select ((string_to_array(objects.name, '/'))[levels]) as folder from objects where objects.name ilike prefix || '%' - and bucket_id = _bucketId + and bucket_id = bucketname GROUP by folder limit limits offset offsets - ) - select files_folders.folder as name, objects.id, objects.updated_at, objects.created_at, objects.last_accessed_at, objects.metadata from files_folders + ) + select files_folders.folder as name, objects.id, objects.updated_at, objects.created_at, objects.last_accessed_at, objects.metadata from files_folders left join objects - on prefix || files_folders.folder = objects.name - where objects.id is null or objects.bucket_id=_bucketId; + on prefix || files_folders.folder = objects.name and objects.bucket_id=bucketname; END $function$; GRANT ALL PRIVILEGES ON SCHEMA storage TO postgres; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA storage TO postgres; -GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA storage TO postgres; \ No newline at end of file +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA storage TO postgres; + diff --git a/infra/storage_client/storage/Dockerfile b/infra/storage_client/storage/Dockerfile index c14b3d9e..666b7ad5 100644 --- a/infra/storage_client/storage/Dockerfile +++ b/infra/storage_client/storage/Dockerfile @@ -1,3 +1,3 @@ -FROM supabase/storage-api:v0.35.1 +FROM supabase/storage-api:v1.8.2 -RUN apk add curl --no-cache \ No newline at end of file +RUN apk add curl --no-cache diff --git a/packages/storage_client/lib/src/storage_file_api.dart b/packages/storage_client/lib/src/storage_file_api.dart index 39ae6433..848cd98b 100644 --- a/packages/storage_client/lib/src/storage_file_api.dart +++ b/packages/storage_client/lib/src/storage_file_api.dart @@ -276,7 +276,13 @@ class StorageFileApi { /// example `folder/image.png`. /// [toPath] is the new file path, including the new file name. For example /// `folder/image-new.png`. - Future move(String fromPath, String toPath) async { + /// + /// When copying to a different bucket, you have to specify the [destinationBucket]. + Future move( + String fromPath, + String toPath, { + String? destinationBucket, + }) async { final options = FetchOptions(headers: headers); final response = await _storageFetch.post( '$url/object/move', @@ -284,6 +290,7 @@ class StorageFileApi { 'bucketId': bucketId, 'sourceKey': fromPath, 'destinationKey': toPath, + if (destinationBucket != null) 'destinationBucket': destinationBucket, }, options: options, ); @@ -297,7 +304,13 @@ class StorageFileApi { /// /// [toPath] is the new file path, including the new file name. For example /// `folder/image-copy.png`. - Future copy(String fromPath, String toPath) async { + /// + /// When copying to a different bucket, you have to specify the [destinationBucket]. + Future copy( + String fromPath, + String toPath, { + String? destinationBucket, + }) async { final options = FetchOptions(headers: headers); final response = await _storageFetch.post( '$url/object/copy', @@ -305,6 +318,7 @@ class StorageFileApi { 'bucketId': bucketId, 'sourceKey': fromPath, 'destinationKey': toPath, + if (destinationBucket != null) 'destinationBucket': destinationBucket, }, options: options, ); diff --git a/packages/storage_client/test/client_test.dart b/packages/storage_client/test/client_test.dart index 0560facc..4cca4bd3 100644 --- a/packages/storage_client/test/client_test.dart +++ b/packages/storage_client/test/client_test.dart @@ -388,5 +388,51 @@ void main() { await storage.from(newBucketName).copy(uploadPath, "$uploadPath 2"); }); + + test('copy to different bucket', () async { + final storage = SupabaseStorageClient( + storageUrl, {'Authorization': 'Bearer $storageKey'}); + + try { + await storage.from('bucket2').download(uploadPath); + fail('File that does not exist was found'); + } on StorageException catch (error) { + expect(error.error, 'not_found'); + } + await storage + .from(newBucketName) + .copy(uploadPath, uploadPath, destinationBucket: 'bucket2'); + try { + await storage.from('bucket2').download(uploadPath); + } catch (error) { + fail('File that was copied was not found'); + } + }); + + test('move to different bucket', () async { + final storage = SupabaseStorageClient( + storageUrl, {'Authorization': 'Bearer $storageKey'}); + + try { + await storage.from('bucket2').download('$uploadPath 3'); + fail('File that does not exist was found'); + } on StorageException catch (error) { + expect(error.error, 'not_found'); + } + await storage + .from(newBucketName) + .move(uploadPath, '$uploadPath 3', destinationBucket: 'bucket2'); + try { + await storage.from('bucket2').download('$uploadPath 3'); + } catch (error) { + fail('File that was moved was not found'); + } + try { + await storage.from(newBucketName).download(uploadPath); + fail('File that was moved was found'); + } on StorageException catch (error) { + expect(error.error, 'not_found'); + } + }); }); } From 4a8b641661da4ce9b6ddaea64793df58411809f7 Mon Sep 17 00:00:00 2001 From: Vinzent Date: Thu, 26 Sep 2024 17:08:21 +0200 Subject: [PATCH 06/29] fix: Better stream and access token management (#1019) * fix: use correct join payload * fix: keep auth token valid while in background when using realtime * fix: correct supabase_flutter init log * refactor: add toString method to RealtimeCloseEvent * fix: reload data from postgrest after new realtime connection * test: fix mock test by increasing delay * fix: load postgrest before realtime conn and close realtime on error * test: restore delay and expect access_token instead of user_token * test: fix typo * refactor: small rename * fix: wait for conn being ready and re-add error to _triggerChanError * fix: don't stringify errors and fix tests * test: close conn from server * fix: disconnect when in background and await connecting to be ready * fix: rejoin channels in edge case * docs: improve method comments --- .../realtime_client/lib/realtime_client.dart | 3 +- .../realtime_client/lib/src/constants.dart | 4 +- .../lib/src/realtime_channel.dart | 23 +++++- .../lib/src/realtime_client.dart | 76 +++++++++++++------ packages/realtime_client/test/mock_test.dart | 8 +- .../realtime_client/test/socket_test.dart | 12 ++- .../lib/src/supabase_query_builder.dart | 9 ++- .../lib/src/supabase_stream_builder.dart | 50 ++++++++++-- .../supabase_flutter/lib/src/supabase.dart | 2 +- .../lib/src/supabase_auth.dart | 71 +++++++++++++++-- 10 files changed, 208 insertions(+), 50 deletions(-) diff --git a/packages/realtime_client/lib/realtime_client.dart b/packages/realtime_client/lib/realtime_client.dart index 660dd2f6..84f158c5 100644 --- a/packages/realtime_client/lib/realtime_client.dart +++ b/packages/realtime_client/lib/realtime_client.dart @@ -1,4 +1,5 @@ -export 'src/constants.dart' show RealtimeConstants, RealtimeLogLevel; +export 'src/constants.dart' + show RealtimeConstants, RealtimeLogLevel, SocketStates; export 'src/realtime_channel.dart'; export 'src/realtime_client.dart'; export 'src/realtime_presence.dart'; diff --git a/packages/realtime_client/lib/src/constants.dart b/packages/realtime_client/lib/src/constants.dart index 1d14f9d6..1f9e0b87 100644 --- a/packages/realtime_client/lib/src/constants.dart +++ b/packages/realtime_client/lib/src/constants.dart @@ -18,8 +18,8 @@ enum SocketStates { /// Connection is live and connected open, - /// Socket is closing. - closing, + /// Socket is closing by the user + disconnecting, /// Socket being close not by the user. Realtime should attempt to reconnect. closed, diff --git a/packages/realtime_client/lib/src/realtime_channel.dart b/packages/realtime_client/lib/src/realtime_channel.dart index 42a2470c..7c37d800 100644 --- a/packages/realtime_client/lib/src/realtime_channel.dart +++ b/packages/realtime_client/lib/src/realtime_channel.dart @@ -70,7 +70,7 @@ class RealtimeChannel { socket.remove(this); }); - _onError((String? reason) { + _onError((reason) { if (isLeaving || isClosed) { return; } @@ -260,9 +260,9 @@ class RealtimeChannel { } /// Registers a callback that will be executed when the channel encounteres an error. - void _onError(void Function(String?) callback) { + void _onError(Function callback) { onEvents(ChannelEvents.error.eventName(), ChannelFilter(), - (reason, [ref]) => callback(reason?.toString())); + (reason, [ref]) => callback(reason)); } /// Sets up a listener on your Supabase database. @@ -646,6 +646,23 @@ class RealtimeChannel { joinPush.resend(timeout ?? _timeout); } + /// Resends [joinPush] to tell the server we join this channel again and marks + /// the channel as [ChannelStates.joining]. + /// + /// Usually [rejoin] only happens when the channel timeouts or errors out. + /// When manually disconnecting, the channel is still marked as + /// [ChannelStates.joined]. Calling [RealtimeClient.leaveOpenTopic] will + /// unsubscribe itself, which causes issues when trying to rejoin. This method + /// therefore doesn't call [RealtimeClient.leaveOpenTopic]. + @internal + void forceRejoin([Duration? timeout]) { + if (isLeaving) { + return; + } + _state = ChannelStates.joining; + joinPush.resend(timeout ?? _timeout); + } + void trigger(String type, [dynamic payload, String? ref]) { final typeLower = type.toLowerCase(); diff --git a/packages/realtime_client/lib/src/realtime_client.dart b/packages/realtime_client/lib/src/realtime_client.dart index 6873a2ca..fe7eefd3 100644 --- a/packages/realtime_client/lib/src/realtime_client.dart +++ b/packages/realtime_client/lib/src/realtime_client.dart @@ -45,6 +45,11 @@ class RealtimeCloseEvent { required this.code, required this.reason, }); + + @override + String toString() { + return 'RealtimeCloseEvent(code: $code, reason: $reason)'; + } } class RealtimeClient { @@ -134,9 +139,9 @@ class RealtimeClient { (String payload, Function(dynamic result) callback) => callback(json.decode(payload)); reconnectTimer = RetryTimer( - () { - disconnect(); - connect(); + () async { + await disconnect(); + await connect(); }, this.reconnectAfterMs, ); @@ -144,7 +149,7 @@ class RealtimeClient { /// Connects the socket. @internal - void connect() async { + Future connect() async { if (conn != null) { return; } @@ -153,8 +158,20 @@ class RealtimeClient { connState = SocketStates.connecting; conn = transport(endPointURL, headers); - // handle connection errors - conn!.ready.catchError(_onConnError); + try { + await conn!.ready; + } catch (error) { + // Don't schedule a reconnect and emit error if connection has been + // closed by the user or [disconnect] waits for the connection to be + // ready before closing it. + if (connState != SocketStates.disconnected && + connState != SocketStates.disconnecting) { + connState = SocketStates.closed; + _onConnError(error); + reconnectTimer.scheduleTimeout(); + } + return; + } connState = SocketStates.open; @@ -166,7 +183,8 @@ class RealtimeClient { onError: _onConnError, onDone: () { // communication has been closed - if (connState != SocketStates.disconnected) { + if (connState != SocketStates.disconnected && + connState != SocketStates.disconnecting) { connState = SocketStates.closed; } _onConnClose(); @@ -179,20 +197,32 @@ class RealtimeClient { } /// Disconnects the socket with status [code] and [reason] for the disconnect - void disconnect({int? code, String? reason}) { + Future disconnect({int? code, String? reason}) async { final conn = this.conn; if (conn != null) { - connState = SocketStates.disconnected; - if (code != null) { - conn.sink.close(code, reason ?? ''); - } else { - conn.sink.close(); + final oldState = connState; + connState = SocketStates.disconnecting; + + // Connection cannot be closed while it's still connecting. Wait for connection to + // be ready and then close it. + if (oldState == SocketStates.connecting) { + await conn.ready.catchError((_) {}); + } + + if (oldState == SocketStates.open || + oldState == SocketStates.connecting) { + if (code != null) { + await conn.sink.close(code, reason ?? ''); + } else { + await conn.sink.close(); + } + connState = SocketStates.disconnected; + reconnectTimer.reset(); } this.conn = null; // remove open handles if (heartbeatTimer != null) heartbeatTimer?.cancel(); - reconnectTimer.reset(); } } @@ -251,8 +281,8 @@ class RealtimeClient { return 'connecting'; case SocketStates.open: return 'open'; - case SocketStates.closing: - return 'closing'; + case SocketStates.disconnecting: + return 'disconnecting'; case SocketStates.disconnected: return 'disconnected'; case SocketStates.closed: @@ -262,7 +292,7 @@ class RealtimeClient { } /// Retuns `true` is the connection is open. - bool get isConnected => connectionState == 'open'; + bool get isConnected => connState == SocketStates.open; /// Removes a subscription from the socket. @internal @@ -353,7 +383,7 @@ class RealtimeClient { for (final channel in channels) { if (token != null) { - channel.updateJoinPayload({'user_token': token}); + channel.updateJoinPayload({'access_token': token}); } if (channel.joinedOnce && channel.isJoined) { channel.push(ChannelEvents.accessToken, {'access_token': token}); @@ -361,7 +391,7 @@ class RealtimeClient { } } - /// Unsubscribe from channels with the specified topic. + /// Unsubscribe from joined or joining channels with the specified topic. @internal void leaveOpenTopic(String topic) { final dupChannel = channels.firstWhereOrNull( @@ -399,7 +429,7 @@ class RealtimeClient { /// SocketStates.disconnected: by user with socket.disconnect() /// SocketStates.closed: NOT by user, should try to reconnect if (connState == SocketStates.closed) { - _triggerChanError(); + _triggerChanError(event); reconnectTimer.scheduleTimeout(); } if (heartbeatTimer != null) heartbeatTimer!.cancel(); @@ -410,15 +440,15 @@ class RealtimeClient { void _onConnError(dynamic error) { log('transport', error.toString()); - _triggerChanError(); + _triggerChanError(error); for (final callback in stateChangeCallbacks['error']!) { callback(error); } } - void _triggerChanError() { + void _triggerChanError([dynamic error]) { for (final channel in channels) { - channel.trigger(ChannelEvents.error.eventName()); + channel.trigger(ChannelEvents.error.eventName(), error); } } diff --git a/packages/realtime_client/test/mock_test.dart b/packages/realtime_client/test/mock_test.dart index ccd77089..89455fb7 100644 --- a/packages/realtime_client/test/mock_test.dart +++ b/packages/realtime_client/test/mock_test.dart @@ -268,7 +268,9 @@ void main() { final subscribeCallback = expectAsync2((RealtimeSubscribeStatus event, error) { if (event == RealtimeSubscribeStatus.channelError) { - expect(error, isNull); + expect(error, isA()); + error as RealtimeCloseEvent; + expect(error.reason, "heartbeat timeout"); } else { expect(event, RealtimeSubscribeStatus.closed); } @@ -285,8 +287,8 @@ void main() { channel.subscribe(subscribeCallback); - await client.conn!.sink - .close(Constants.wsCloseNormal, "heartbeat timeout"); + await Future.delayed(Duration(milliseconds: 200)); + await webSocket?.close(Constants.wsCloseNormal, "heartbeat timeout"); }); }); diff --git a/packages/realtime_client/test/socket_test.dart b/packages/realtime_client/test/socket_test.dart index a5463ab3..79fe1306 100644 --- a/packages/realtime_client/test/socket_test.dart +++ b/packages/realtime_client/test/socket_test.dart @@ -171,6 +171,7 @@ void main() { }); socket.connect(); + await Future.delayed(const Duration(milliseconds: 200)); expect(opens, 1); socket.sendHeartbeat(); @@ -214,8 +215,8 @@ void main() { }); test('removes existing connection', () async { - socket.connect(); - socket.disconnect(); + await socket.connect(); + await socket.disconnect(); expect(socket.conn, null); }); @@ -229,7 +230,7 @@ void main() { expect(closes, 1); }); - test('calls connection close callback', () { + test('calls connection close callback', () async { final mockedSocketChannel = MockIOWebSocketChannel(); final mockedSocket = RealtimeClient( socketEndpoint, @@ -247,7 +248,10 @@ void main() { const tReason = 'reason'; mockedSocket.connect(); + mockedSocket.connState = SocketStates.open; + await Future.delayed(const Duration(milliseconds: 200)); mockedSocket.disconnect(code: tCode, reason: tReason); + await Future.delayed(const Duration(milliseconds: 200)); verify( () => mockedSink.close( @@ -423,7 +427,7 @@ void main() { }); group('setAuth', () { - final updateJoinPayload = {'user_token': 'token123'}; + final updateJoinPayload = {'access_token': 'token123'}; final pushPayload = {'access_token': 'token123'}; test( diff --git a/packages/supabase/lib/src/supabase_query_builder.dart b/packages/supabase/lib/src/supabase_query_builder.dart index 1c9b4bcb..dd31713a 100644 --- a/packages/supabase/lib/src/supabase_query_builder.dart +++ b/packages/supabase/lib/src/supabase_query_builder.dart @@ -23,11 +23,16 @@ class SupabaseQueryBuilder extends PostgrestQueryBuilder { url: Uri.parse(url), ); - /// Returns real-time data from your table as a `Stream`. + /// Combines the current state of your table from PostgREST with changes from the realtime server to return real-time data from your table as a [Stream]. /// /// Realtime is disabled by default for new tables. You can turn it on by [managing replication](https://supabase.com/docs/guides/realtime/extensions/postgres-changes#replication-setup). /// - /// Pass the list of primary key column names to [primaryKey], which will be used to updating and deleting the proper records internally as the library receives real-time updates. + /// Pass the list of primary key column names to [primaryKey], which will be used to update and delete the proper records internally as the stream receives real-time updates. + /// + /// It handles the lifecycle of the realtime connection and automatically refetches data from PostgREST when needed. + /// + /// Make sure to provide `onError` and `onDone` callbacks to [Stream.listen] to handle errors and completion of the stream. + /// The stream gets closed when the realtime connection is closed. /// /// ```dart /// supabase.from('chats').stream(primaryKey: ['id']).listen(_onChatsReceived); diff --git a/packages/supabase/lib/src/supabase_stream_builder.dart b/packages/supabase/lib/src/supabase_stream_builder.dart index 0807c734..5e0aac6a 100644 --- a/packages/supabase/lib/src/supabase_stream_builder.dart +++ b/packages/supabase/lib/src/supabase_stream_builder.dart @@ -31,6 +31,18 @@ class _Order { final bool ascending; } +class RealtimeSubscribeException implements Exception { + RealtimeSubscribeException(this.status, [this.details]); + + final RealtimeSubscribeStatus status; + final Object? details; + + @override + String toString() { + return 'RealtimeSubscribeException(status: $status, details: $details)'; + } +} + typedef SupabaseStreamEvent = List>; class SupabaseStreamBuilder extends Stream { @@ -64,6 +76,9 @@ class SupabaseStreamBuilder extends Stream { /// Count of record to be returned int? _limit; + /// Flag that the stream has at least one time been subscribed to realtime + bool _wasSubscribed = false; + SupabaseStreamBuilder({ required PostgrestQueryBuilder queryBuilder, required String realtimeTopic, @@ -195,12 +210,31 @@ class SupabaseStreamBuilder extends Stream { } }) .subscribe((status, [error]) { - if (error != null) { - _addException(error); + switch (status) { + case RealtimeSubscribeStatus.subscribed: + // Reload all data after a reconnect from postgrest + // First data from postgrest gets loaded before the realtime connect + if (_wasSubscribed) { + _getPostgrestData(); + } + _wasSubscribed = true; + break; + case RealtimeSubscribeStatus.closed: + _streamController?.close(); + break; + case RealtimeSubscribeStatus.timedOut: + _addException(RealtimeSubscribeException(status, error)); + break; + case RealtimeSubscribeStatus.channelError: + _addException(RealtimeSubscribeException(status, error)); + break; } }); + _getPostgrestData(); + } - PostgrestFilterBuilder query = _queryBuilder.select(); + Future _getPostgrestData() async { + PostgrestFilterBuilder query = _queryBuilder.select(); if (_streamFilter != null) { switch (_streamFilter!.type) { case PostgresChangeFilterType.eq: @@ -226,7 +260,7 @@ class SupabaseStreamBuilder extends Stream { break; } } - PostgrestTransformBuilder? transformQuery; + PostgrestTransformBuilder? transformQuery; if (_orderBy != null) { transformQuery = query.order(_orderBy!.column, ascending: _orderBy!.ascending); @@ -237,11 +271,15 @@ class SupabaseStreamBuilder extends Stream { try { final data = await (transformQuery ?? query); - final rows = SupabaseStreamEvent.from(data as List); - _streamData.addAll(rows); + final rows = SupabaseStreamEvent.from(data); + _streamData = rows; _addStream(); } catch (error, stackTrace) { _addException(error, stackTrace); + // In case the postgrest call fails, there is no need to keep the + // realtime connection open + _channel?.unsubscribe(); + _streamController?.close(); } } diff --git a/packages/supabase_flutter/lib/src/supabase.dart b/packages/supabase_flutter/lib/src/supabase.dart index 7cb2930e..0608707c 100644 --- a/packages/supabase_flutter/lib/src/supabase.dart +++ b/packages/supabase_flutter/lib/src/supabase.dart @@ -107,7 +107,7 @@ class Supabase { accessToken: accessToken, ); _instance._debugEnable = debug ?? kDebugMode; - _instance.log('***** Supabase init completed $_instance'); + _instance.log('***** Supabase init completed *****'); _instance._supabaseAuth = SupabaseAuth(); await _instance._supabaseAuth.initialize(options: authOptions); diff --git a/packages/supabase_flutter/lib/src/supabase_auth.dart b/packages/supabase_flutter/lib/src/supabase_auth.dart index fa3d72e8..80cf6248 100644 --- a/packages/supabase_flutter/lib/src/supabase_auth.dart +++ b/packages/supabase_flutter/lib/src/supabase_auth.dart @@ -4,6 +4,7 @@ import 'dart:io' show Platform; import 'dart:math'; import 'package:app_links/app_links.dart'; +import 'package:async/async.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -29,6 +30,8 @@ class SupabaseAuth with WidgetsBindingObserver { StreamSubscription? _deeplinkSubscription; + CancelableOperation? _realtimeReconnectOperation; + final _appLinks = AppLinks(); /// - Obtains session from local storage and sets it as the current session @@ -113,17 +116,75 @@ class SupabaseAuth with WidgetsBindingObserver { void didChangeAppLifecycleState(AppLifecycleState state) { switch (state) { case AppLifecycleState.resumed: - if (_autoRefreshToken) { - Supabase.instance.client.auth.startAutoRefresh(); - } + onResumed(); case AppLifecycleState.detached: - case AppLifecycleState.inactive: case AppLifecycleState.paused: - Supabase.instance.client.auth.stopAutoRefresh(); + if (kIsWeb || Platform.isAndroid || Platform.isIOS) { + Supabase.instance.client.auth.stopAutoRefresh(); + _realtimeReconnectOperation?.cancel(); + Supabase.instance.client.realtime.disconnect(); + } default: } } + Future onResumed() async { + if (_autoRefreshToken) { + Supabase.instance.client.auth.startAutoRefresh(); + } + final realtime = Supabase.instance.client.realtime; + if (realtime.channels.isNotEmpty) { + if (realtime.connState == SocketStates.disconnecting) { + // If the socket is still disconnecting from e.g. + // [AppLifecycleState.paused] we should wait for it to finish before + // reconnecting. + + bool cancel = false; + final connectFuture = realtime.conn!.sink.done.then( + (_) async { + // Make this connect cancelable so that it does not connect if the + // disconnect took so long that the app is already in background + // again. + + if (!cancel) { + // ignore: invalid_use_of_internal_member + await realtime.connect(); + for (final channel in realtime.channels) { + // ignore: invalid_use_of_internal_member + if (channel.isJoined) { + channel.forceRejoin(); + } + } + } + }, + onError: (error) {}, + ); + _realtimeReconnectOperation = CancelableOperation.fromFuture( + connectFuture, + onCancel: () => cancel = true, + ); + } else if (!realtime.isConnected) { + // Reconnect if the socket is currently not connected. + // When coming from [AppLifecycleState.paused] this should be the case, + // but when coming from [AppLifecycleState.inactive] no disconnect + // happened and therefore connection should still be intanct and we + // should not reconnect. + + // ignore: invalid_use_of_internal_member + await realtime.connect(); + for (final channel in realtime.channels) { + // Only rejoin channels that think they are still joined and not + // which were manually unsubscribed by the user while in background + + // ignore: invalid_use_of_internal_member + if (channel.isJoined) { + channel.forceRejoin(); + } + } + } + } + } + void _onAuthStateChange(AuthChangeEvent event, Session? session) { Supabase.instance.log('**** onAuthStateChange: $event'); if (session != null) { From 6c80a745cd387250995fa3140aac54169466f5bb Mon Sep 17 00:00:00 2001 From: Ryotaro Onoue <73390859+YumNumm@users.noreply.github.com> Date: Mon, 30 Sep 2024 20:19:04 +0900 Subject: [PATCH 07/29] fix(yet_another_json_isolate): Conditional export now works correctly with Dart 3.5+ (#1048) * fix: change `dart.library.js` -> `dart.library.js_interop` * fix: conditional export for before dart 3.3 * add: changelog * Update packages/yet_another_json_isolate/CHANGELOG.md --------- Co-authored-by: Tyler --- .../lib/yet_another_json_isolate.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/yet_another_json_isolate/lib/yet_another_json_isolate.dart b/packages/yet_another_json_isolate/lib/yet_another_json_isolate.dart index da6c9676..a5f214ce 100644 --- a/packages/yet_another_json_isolate/lib/yet_another_json_isolate.dart +++ b/packages/yet_another_json_isolate/lib/yet_another_json_isolate.dart @@ -1,3 +1,5 @@ library yet_another_json_isolate; -export 'src/_isolates_io.dart' if (dart.library.js) 'src/_isolates_web.dart'; +export 'src/_isolates_io.dart' + if (dart.library.js_interop) 'src/_isolates_web.dart' // After Dart 3.3 + if (dart.library.js) 'src/_isolates_web.dart'; // Before Dart 3.3 (for backwards compatibility) From 1a3c3f73915a5df21700b2fa8b0ba69b3e113f8e Mon Sep 17 00:00:00 2001 From: Tyler Date: Mon, 30 Sep 2024 21:30:19 +0900 Subject: [PATCH 08/29] chore(release): Publish packages v2.7.0 (#1050) chore(release): publish packages - gotrue@2.9.0 - postgrest@2.2.0 - realtime_client@2.3.0 - storage_client@2.1.0 - supabase@2.4.0 - supabase_flutter@2.7.0 - yet_another_json_isolate@2.0.3 - functions_client@2.3.3 --- CHANGELOG.md | 63 +++++++++++++++++++ packages/functions_client/CHANGELOG.md | 4 ++ .../functions_client/lib/src/version.dart | 2 +- packages/functions_client/pubspec.yaml | 4 +- packages/gotrue/CHANGELOG.md | 5 ++ packages/gotrue/lib/src/version.dart | 2 +- packages/gotrue/pubspec.yaml | 2 +- packages/postgrest/CHANGELOG.md | 4 ++ packages/postgrest/lib/src/version.dart | 2 +- packages/postgrest/pubspec.yaml | 4 +- packages/realtime_client/CHANGELOG.md | 5 ++ packages/realtime_client/lib/src/version.dart | 2 +- packages/realtime_client/pubspec.yaml | 2 +- packages/storage_client/CHANGELOG.md | 4 ++ packages/storage_client/lib/src/version.dart | 2 +- packages/storage_client/pubspec.yaml | 2 +- packages/supabase/CHANGELOG.md | 5 ++ packages/supabase/lib/src/version.dart | 2 +- packages/supabase/pubspec.yaml | 14 ++--- packages/supabase_flutter/CHANGELOG.md | 6 ++ .../supabase_flutter/example/pubspec.yaml | 2 +- .../supabase_flutter/lib/src/version.dart | 2 +- packages/supabase_flutter/pubspec.yaml | 4 +- .../yet_another_json_isolate/CHANGELOG.md | 4 ++ .../yet_another_json_isolate/pubspec.yaml | 2 +- 25 files changed, 125 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a0ba5d9..702ab4db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,69 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## 2024-09-30 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`gotrue` - `v2.9.0`](#gotrue---v290) + - [`postgrest` - `v2.2.0`](#postgrest---v220) + - [`realtime_client` - `v2.3.0`](#realtime_client---v230) + - [`storage_client` - `v2.1.0`](#storage_client---v210) + - [`supabase` - `v2.4.0`](#supabase---v240) + - [`supabase_flutter` - `v2.7.0`](#supabase_flutter---v270) + - [`yet_another_json_isolate` - `v2.0.3`](#yet_another_json_isolate---v203) + - [`functions_client` - `v2.3.3`](#functions_client---v233) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `functions_client` - `v2.3.3` + +--- + +#### `gotrue` - `v2.9.0` + + - **FIX**: Support all mfa auth methods ([#1030](https://github.com/supabase/supabase-flutter/issues/1030)). ([773b7de7](https://github.com/supabase/supabase-flutter/commit/773b7de74461ca3ea857d11b1abdfdf35fb540d4)) + - **FEAT**: Broadcast auth events to other tabs on web ([#1005](https://github.com/supabase/supabase-flutter/issues/1005)). ([8f473f1a](https://github.com/supabase/supabase-flutter/commit/8f473f1a99e0cbb9d570eb3fff0786ed5084351c)) + +#### `postgrest` - `v2.2.0` + + - **FEAT**: Add setHeader method on postgrest builder ([#1003](https://github.com/supabase/supabase-flutter/issues/1003)). ([efe8e5df](https://github.com/supabase/supabase-flutter/commit/efe8e5df7935b75b580e2ead01b9c08ac7b94c2c)) + +#### `realtime_client` - `v2.3.0` + + - **FIX**: Better stream and access token management ([#1019](https://github.com/supabase/supabase-flutter/issues/1019)). ([4a8b6416](https://github.com/supabase/supabase-flutter/commit/4a8b641661da4ce9b6ddaea64793df58411809f7)) + - **FEAT**: Added onSystemEvents listener ([#1025](https://github.com/supabase/supabase-flutter/issues/1025)). ([a12b097f](https://github.com/supabase/supabase-flutter/commit/a12b097ff76270d6108e97335a3a6ea0adf0b460)) + +#### `storage_client` - `v2.1.0` + + - **FEAT**(storage_client): Support copy/move to different bucket ([#1043](https://github.com/supabase/supabase-flutter/issues/1043)). ([e095c14e](https://github.com/supabase/supabase-flutter/commit/e095c14e29e82cceb96220b5d73e67d991909478)) + +#### `supabase` - `v2.4.0` + + - **FIX**: Better stream and access token management ([#1019](https://github.com/supabase/supabase-flutter/issues/1019)). ([4a8b6416](https://github.com/supabase/supabase-flutter/commit/4a8b641661da4ce9b6ddaea64793df58411809f7)) + - **FEAT**: Broadcast auth events to other tabs on web ([#1005](https://github.com/supabase/supabase-flutter/issues/1005)). ([8f473f1a](https://github.com/supabase/supabase-flutter/commit/8f473f1a99e0cbb9d570eb3fff0786ed5084351c)) + +#### `supabase_flutter` - `v2.7.0` + + - **FIX**: Better stream and access token management ([#1019](https://github.com/supabase/supabase-flutter/issues/1019)). ([4a8b6416](https://github.com/supabase/supabase-flutter/commit/4a8b641661da4ce9b6ddaea64793df58411809f7)) + - **FIX**: Update example native code ([#1029](https://github.com/supabase/supabase-flutter/issues/1029)). ([588b5000](https://github.com/supabase/supabase-flutter/commit/588b5000d64a5d71b45ed57f70f0ed812dd34619)) + - **FEAT**: Broadcast auth events to other tabs on web ([#1005](https://github.com/supabase/supabase-flutter/issues/1005)). ([8f473f1a](https://github.com/supabase/supabase-flutter/commit/8f473f1a99e0cbb9d570eb3fff0786ed5084351c)) + +#### `yet_another_json_isolate` - `v2.0.3` + + - **FIX**(yet_another_json_isolate): Conditional export now works correctly with Dart 3.5+ ([#1048](https://github.com/supabase/supabase-flutter/issues/1048)). ([6c80a745](https://github.com/supabase/supabase-flutter/commit/6c80a745cd387250995fa3140aac54169466f5bb)) + + ## 2024-08-13 ### Changes diff --git a/packages/functions_client/CHANGELOG.md b/packages/functions_client/CHANGELOG.md index 6dc4feda..e193b627 100644 --- a/packages/functions_client/CHANGELOG.md +++ b/packages/functions_client/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.3.3 + + - Update a dependency to the latest release. + ## 2.3.2 - **FIX**: Upgrade `web_socket_channel` for supporting `web: ^1.0.0` and therefore WASM compilation on web ([#992](https://github.com/supabase/supabase-flutter/issues/992)). ([7da68565](https://github.com/supabase/supabase-flutter/commit/7da68565a7aa578305b099d7af755a7b0bcaca46)) diff --git a/packages/functions_client/lib/src/version.dart b/packages/functions_client/lib/src/version.dart index c380776c..93792933 100644 --- a/packages/functions_client/lib/src/version.dart +++ b/packages/functions_client/lib/src/version.dart @@ -1 +1 @@ -const version = '2.3.2'; +const version = '2.3.3'; diff --git a/packages/functions_client/pubspec.yaml b/packages/functions_client/pubspec.yaml index 710a1220..547fe631 100644 --- a/packages/functions_client/pubspec.yaml +++ b/packages/functions_client/pubspec.yaml @@ -1,6 +1,6 @@ name: functions_client description: A dart client library for the Supabase functions. -version: 2.3.2 +version: 2.3.3 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/functions_client' documentation: 'https://supabase.com/docs/reference/dart/functions-invoke' @@ -10,7 +10,7 @@ environment: dependencies: http: '>=0.13.4 <2.0.0' - yet_another_json_isolate: 2.0.2 + yet_another_json_isolate: 2.0.3 dev_dependencies: lints: ^3.0.0 diff --git a/packages/gotrue/CHANGELOG.md b/packages/gotrue/CHANGELOG.md index 47d7a53e..c4da629c 100644 --- a/packages/gotrue/CHANGELOG.md +++ b/packages/gotrue/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.9.0 + + - **FIX**: Support all mfa auth methods ([#1030](https://github.com/supabase/supabase-flutter/issues/1030)). ([773b7de7](https://github.com/supabase/supabase-flutter/commit/773b7de74461ca3ea857d11b1abdfdf35fb540d4)) + - **FEAT**: Broadcast auth events to other tabs on web ([#1005](https://github.com/supabase/supabase-flutter/issues/1005)). ([8f473f1a](https://github.com/supabase/supabase-flutter/commit/8f473f1a99e0cbb9d570eb3fff0786ed5084351c)) + ## 2.8.4 - **FIX**: Added missing error codes for AuthException ([#995](https://github.com/supabase/supabase-flutter/issues/995)). ([4e0270a0](https://github.com/supabase/supabase-flutter/commit/4e0270a069ecf5aae42031c77200d268519ac99b)) diff --git a/packages/gotrue/lib/src/version.dart b/packages/gotrue/lib/src/version.dart index 4f433300..629ad7b5 100644 --- a/packages/gotrue/lib/src/version.dart +++ b/packages/gotrue/lib/src/version.dart @@ -1 +1 @@ -const version = '2.8.4'; +const version = '2.9.0'; diff --git a/packages/gotrue/pubspec.yaml b/packages/gotrue/pubspec.yaml index 47e6eef9..ed4b9889 100644 --- a/packages/gotrue/pubspec.yaml +++ b/packages/gotrue/pubspec.yaml @@ -1,6 +1,6 @@ name: gotrue description: A dart client library for the GoTrue API. -version: 2.8.4 +version: 2.9.0 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/gotrue' documentation: 'https://supabase.com/docs/reference/dart/auth-signup' diff --git a/packages/postgrest/CHANGELOG.md b/packages/postgrest/CHANGELOG.md index 4ca5cf3b..2d712dba 100644 --- a/packages/postgrest/CHANGELOG.md +++ b/packages/postgrest/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.2.0 + + - **FEAT**: Add setHeader method on postgrest builder ([#1003](https://github.com/supabase/supabase-flutter/issues/1003)). ([efe8e5df](https://github.com/supabase/supabase-flutter/commit/efe8e5df7935b75b580e2ead01b9c08ac7b94c2c)) + ## 2.1.4 - **FIX**: Upgrade `web_socket_channel` for supporting `web: ^1.0.0` and therefore WASM compilation on web ([#992](https://github.com/supabase/supabase-flutter/issues/992)). ([7da68565](https://github.com/supabase/supabase-flutter/commit/7da68565a7aa578305b099d7af755a7b0bcaca46)) diff --git a/packages/postgrest/lib/src/version.dart b/packages/postgrest/lib/src/version.dart index 2fe22b9c..63ac0966 100644 --- a/packages/postgrest/lib/src/version.dart +++ b/packages/postgrest/lib/src/version.dart @@ -1 +1 @@ -const version = '2.1.4'; +const version = '2.2.0'; diff --git a/packages/postgrest/pubspec.yaml b/packages/postgrest/pubspec.yaml index b1966f5d..be5903aa 100644 --- a/packages/postgrest/pubspec.yaml +++ b/packages/postgrest/pubspec.yaml @@ -1,6 +1,6 @@ name: postgrest description: PostgREST client for Dart. This library provides an ORM interface to PostgREST. -version: 2.1.4 +version: 2.2.0 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/postgrest' documentation: 'https://supabase.com/docs/reference/dart/select' @@ -10,7 +10,7 @@ environment: dependencies: http: '>=0.13.0 <2.0.0' - yet_another_json_isolate: 2.0.2 + yet_another_json_isolate: 2.0.3 meta: ^1.9.1 dev_dependencies: diff --git a/packages/realtime_client/CHANGELOG.md b/packages/realtime_client/CHANGELOG.md index 4e48259a..30579a52 100644 --- a/packages/realtime_client/CHANGELOG.md +++ b/packages/realtime_client/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.3.0 + + - **FIX**: Better stream and access token management ([#1019](https://github.com/supabase/supabase-flutter/issues/1019)). ([4a8b6416](https://github.com/supabase/supabase-flutter/commit/4a8b641661da4ce9b6ddaea64793df58411809f7)) + - **FEAT**: Added onSystemEvents listener ([#1025](https://github.com/supabase/supabase-flutter/issues/1025)). ([a12b097f](https://github.com/supabase/supabase-flutter/commit/a12b097ff76270d6108e97335a3a6ea0adf0b460)) + ## 2.2.1 - **FIX**: Upgrade `web_socket_channel` for supporting `web: ^1.0.0` and therefore WASM compilation on web ([#992](https://github.com/supabase/supabase-flutter/issues/992)). ([7da68565](https://github.com/supabase/supabase-flutter/commit/7da68565a7aa578305b099d7af755a7b0bcaca46)) diff --git a/packages/realtime_client/lib/src/version.dart b/packages/realtime_client/lib/src/version.dart index 70c8d68b..5f7e4e35 100644 --- a/packages/realtime_client/lib/src/version.dart +++ b/packages/realtime_client/lib/src/version.dart @@ -1 +1 @@ -const version = '2.2.1'; +const version = '2.3.0'; diff --git a/packages/realtime_client/pubspec.yaml b/packages/realtime_client/pubspec.yaml index 1138e910..7e85cad6 100644 --- a/packages/realtime_client/pubspec.yaml +++ b/packages/realtime_client/pubspec.yaml @@ -1,6 +1,6 @@ name: realtime_client description: Listens to changes in a PostgreSQL Database and via websockets. This is for usage with Supabase Realtime server. -version: 2.2.1 +version: 2.3.0 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/realtime_client' documentation: 'https://supabase.com/docs/reference/dart/subscribe' diff --git a/packages/storage_client/CHANGELOG.md b/packages/storage_client/CHANGELOG.md index 527f92cf..e479660e 100644 --- a/packages/storage_client/CHANGELOG.md +++ b/packages/storage_client/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.1.0 + + - **FEAT**(storage_client): Support copy/move to different bucket ([#1043](https://github.com/supabase/supabase-flutter/issues/1043)). ([e095c14e](https://github.com/supabase/supabase-flutter/commit/e095c14e29e82cceb96220b5d73e67d991909478)) + ## 2.0.3 - **FIX**: Upgrade `web_socket_channel` for supporting `web: ^1.0.0` and therefore WASM compilation on web ([#992](https://github.com/supabase/supabase-flutter/issues/992)). ([7da68565](https://github.com/supabase/supabase-flutter/commit/7da68565a7aa578305b099d7af755a7b0bcaca46)) diff --git a/packages/storage_client/lib/src/version.dart b/packages/storage_client/lib/src/version.dart index 4275224c..fcf11af0 100644 --- a/packages/storage_client/lib/src/version.dart +++ b/packages/storage_client/lib/src/version.dart @@ -1 +1 @@ -const version = '2.0.3'; +const version = '2.1.0'; diff --git a/packages/storage_client/pubspec.yaml b/packages/storage_client/pubspec.yaml index 9b550688..f1922a78 100644 --- a/packages/storage_client/pubspec.yaml +++ b/packages/storage_client/pubspec.yaml @@ -1,6 +1,6 @@ name: storage_client description: Dart client library to interact with Supabase Storage. Supabase Storage provides an interface for managing Files stored in S3, using Postgres to manage permissions. -version: 2.0.3 +version: 2.1.0 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/storage_client' documentation: 'https://supabase.com/docs/reference/dart/storage-createbucket' diff --git a/packages/supabase/CHANGELOG.md b/packages/supabase/CHANGELOG.md index 145d031c..7aa7db41 100644 --- a/packages/supabase/CHANGELOG.md +++ b/packages/supabase/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.4.0 + + - **FIX**: Better stream and access token management ([#1019](https://github.com/supabase/supabase-flutter/issues/1019)). ([4a8b6416](https://github.com/supabase/supabase-flutter/commit/4a8b641661da4ce9b6ddaea64793df58411809f7)) + - **FEAT**: Broadcast auth events to other tabs on web ([#1005](https://github.com/supabase/supabase-flutter/issues/1005)). ([8f473f1a](https://github.com/supabase/supabase-flutter/commit/8f473f1a99e0cbb9d570eb3fff0786ed5084351c)) + ## 2.3.0 - **FIX**: Upgrade `web_socket_channel` for supporting `web: ^1.0.0` and therefore WASM compilation on web ([#992](https://github.com/supabase/supabase-flutter/issues/992)). ([7da68565](https://github.com/supabase/supabase-flutter/commit/7da68565a7aa578305b099d7af755a7b0bcaca46)) diff --git a/packages/supabase/lib/src/version.dart b/packages/supabase/lib/src/version.dart index 5f7e4e35..3dcb91ee 100644 --- a/packages/supabase/lib/src/version.dart +++ b/packages/supabase/lib/src/version.dart @@ -1 +1 @@ -const version = '2.3.0'; +const version = '2.4.0'; diff --git a/packages/supabase/pubspec.yaml b/packages/supabase/pubspec.yaml index 9510e9c7..24d21302 100644 --- a/packages/supabase/pubspec.yaml +++ b/packages/supabase/pubspec.yaml @@ -1,6 +1,6 @@ name: supabase description: A dart client for Supabase. This client makes it simple for developers to build secure and scalable products. -version: 2.3.0 +version: 2.4.0 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/supabase' documentation: 'https://supabase.com/docs/reference/dart/introduction' @@ -9,14 +9,14 @@ environment: sdk: '>=3.0.0 <4.0.0' dependencies: - functions_client: 2.3.2 - gotrue: 2.8.4 + functions_client: 2.3.3 + gotrue: 2.9.0 http: '>=0.13.5 <2.0.0' - postgrest: 2.1.4 - realtime_client: 2.2.1 - storage_client: 2.0.3 + postgrest: 2.2.0 + realtime_client: 2.3.0 + storage_client: 2.1.0 rxdart: '>=0.27.5 <0.29.0' - yet_another_json_isolate: 2.0.2 + yet_another_json_isolate: 2.0.3 dev_dependencies: lints: ^3.0.0 diff --git a/packages/supabase_flutter/CHANGELOG.md b/packages/supabase_flutter/CHANGELOG.md index 2fe33b6d..41eb7fb5 100644 --- a/packages/supabase_flutter/CHANGELOG.md +++ b/packages/supabase_flutter/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.7.0 + + - **FIX**: Better stream and access token management ([#1019](https://github.com/supabase/supabase-flutter/issues/1019)). ([4a8b6416](https://github.com/supabase/supabase-flutter/commit/4a8b641661da4ce9b6ddaea64793df58411809f7)) + - **FIX**: Update example native code ([#1029](https://github.com/supabase/supabase-flutter/issues/1029)). ([588b5000](https://github.com/supabase/supabase-flutter/commit/588b5000d64a5d71b45ed57f70f0ed812dd34619)) + - **FEAT**: Broadcast auth events to other tabs on web ([#1005](https://github.com/supabase/supabase-flutter/issues/1005)). ([8f473f1a](https://github.com/supabase/supabase-flutter/commit/8f473f1a99e0cbb9d570eb3fff0786ed5084351c)) + ## 2.6.0 - **FIX**: Upgrade `web_socket_channel` for supporting `web: ^1.0.0` and therefore WASM compilation on web ([#992](https://github.com/supabase/supabase-flutter/issues/992)). ([7da68565](https://github.com/supabase/supabase-flutter/commit/7da68565a7aa578305b099d7af755a7b0bcaca46)) diff --git a/packages/supabase_flutter/example/pubspec.yaml b/packages/supabase_flutter/example/pubspec.yaml index 3a943ec7..c8aeaa6d 100644 --- a/packages/supabase_flutter/example/pubspec.yaml +++ b/packages/supabase_flutter/example/pubspec.yaml @@ -11,7 +11,7 @@ environment: dependencies: flutter: sdk: flutter - supabase_flutter: ^2.6.0 + supabase_flutter: ^2.7.0 dev_dependencies: flutter_test: diff --git a/packages/supabase_flutter/lib/src/version.dart b/packages/supabase_flutter/lib/src/version.dart index 1abb1f33..71c30ec0 100644 --- a/packages/supabase_flutter/lib/src/version.dart +++ b/packages/supabase_flutter/lib/src/version.dart @@ -1 +1 @@ -const version = '2.6.0'; +const version = '2.7.0'; diff --git a/packages/supabase_flutter/pubspec.yaml b/packages/supabase_flutter/pubspec.yaml index 34fc4c98..c5c26cea 100644 --- a/packages/supabase_flutter/pubspec.yaml +++ b/packages/supabase_flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: supabase_flutter description: Flutter integration for Supabase. This package makes it simple for developers to build secure and scalable products. -version: 2.6.0 +version: 2.7.0 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/supabase_flutter' documentation: 'https://supabase.com/docs/reference/dart/introduction' @@ -17,7 +17,7 @@ dependencies: sdk: flutter http: '>=0.13.4 <2.0.0' meta: ^1.7.0 - supabase: 2.3.0 + supabase: 2.4.0 url_launcher: ^6.1.2 path_provider: ^2.0.0 shared_preferences: ^2.0.0 diff --git a/packages/yet_another_json_isolate/CHANGELOG.md b/packages/yet_another_json_isolate/CHANGELOG.md index 1866c5e3..d2bc2bbe 100644 --- a/packages/yet_another_json_isolate/CHANGELOG.md +++ b/packages/yet_another_json_isolate/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.3 + + - **FIX**(yet_another_json_isolate): Conditional export now works correctly with Dart 3.5+ ([#1048](https://github.com/supabase/supabase-flutter/issues/1048)). ([6c80a745](https://github.com/supabase/supabase-flutter/commit/6c80a745cd387250995fa3140aac54169466f5bb)) + ## 2.0.2 - **FIX**: Upgrade `web_socket_channel` for supporting `web: ^1.0.0` and therefore WASM compilation on web ([#992](https://github.com/supabase/supabase-flutter/issues/992)). ([7da68565](https://github.com/supabase/supabase-flutter/commit/7da68565a7aa578305b099d7af755a7b0bcaca46)) diff --git a/packages/yet_another_json_isolate/pubspec.yaml b/packages/yet_another_json_isolate/pubspec.yaml index 936d677a..bf861b16 100644 --- a/packages/yet_another_json_isolate/pubspec.yaml +++ b/packages/yet_another_json_isolate/pubspec.yaml @@ -1,6 +1,6 @@ name: yet_another_json_isolate description: Package to simplify and improve JSON parsing in isolates by keeping one isolate running per instance. -version: 2.0.2 +version: 2.0.3 homepage: https://github.com/supabase-community/json-isolate-dart environment: From d1ecabd77881a0488d2d4b41ea5ee5abda6c5c35 Mon Sep 17 00:00:00 2001 From: Vinzent Date: Sat, 5 Oct 2024 00:55:44 +0200 Subject: [PATCH 09/29] feat: Add logging (#1042) * feat: add logging to gotrue * feat: add logging to postgrest * feat: add logging to supabase package * fix: log sensitive data at level finer * feat: add logging to supabase_flutter * feat: add logging to functions_client * feat: add logging to realtime_client * refactor: log sensitive data with level finest * feat: add logging to storage_client * docs: add documentation to supabase_flutter readme * fix: log headers with level FINEST * fix: save log stream subscription and print all from all loggers * fix: log some realtime records with level FINE --- .../lib/src/functions_client.dart | 11 +++- packages/functions_client/pubspec.yaml | 1 + packages/gotrue/lib/src/broadcast_web.dart | 5 ++ packages/gotrue/lib/src/gotrue_client.dart | 44 +++++++++++---- packages/gotrue/pubspec.yaml | 1 + packages/postgrest/lib/src/postgrest.dart | 9 +++- .../postgrest/lib/src/postgrest_builder.dart | 12 ++++- packages/postgrest/pubspec.yaml | 1 + .../lib/src/realtime_client.dart | 54 ++++++++++++++----- packages/realtime_client/pubspec.yaml | 1 + packages/storage_client/lib/src/fetch.dart | 21 ++++++-- .../lib/src/storage_client.dart | 9 +++- packages/storage_client/pubspec.yaml | 1 + packages/supabase/lib/src/auth_user.dart | 1 + .../lib/src/remove_subscription_result.dart | 1 + .../supabase/lib/src/supabase_client.dart | 19 ++++++- .../lib/src/supabase_event_types.dart | 2 + .../lib/src/supabase_realtime_error.dart | 1 + .../lib/src/supabase_stream_builder.dart | 4 ++ packages/supabase/pubspec.yaml | 1 + packages/supabase_flutter/README.md | 30 +++++++++++ .../supabase_flutter/lib/src/supabase.dart | 37 ++++++++----- .../lib/src/supabase_auth.dart | 42 +++++++-------- packages/supabase_flutter/pubspec.yaml | 1 + 24 files changed, 244 insertions(+), 65 deletions(-) diff --git a/packages/functions_client/lib/src/functions_client.dart b/packages/functions_client/lib/src/functions_client.dart index e9c3b52d..c83cc368 100644 --- a/packages/functions_client/lib/src/functions_client.dart +++ b/packages/functions_client/lib/src/functions_client.dart @@ -3,8 +3,10 @@ import 'dart:typed_data'; import 'package:functions_client/src/constants.dart'; import 'package:functions_client/src/types.dart'; +import 'package:functions_client/src/version.dart'; import 'package:http/http.dart' as http; import 'package:http/http.dart' show MultipartRequest; +import 'package:logging/logging.dart'; import 'package:yet_another_json_isolate/yet_another_json_isolate.dart'; class FunctionsClient { @@ -13,6 +15,7 @@ class FunctionsClient { final http.Client? _httpClient; final YAJsonIsolate _isolate; final bool _hasCustomIsolate; + final _log = Logger("supabase.functions"); /// In case you don't provide your own isolate, call [dispose] when you're done FunctionsClient( @@ -24,7 +27,10 @@ class FunctionsClient { _headers = {...Constants.defaultHeaders, ...headers}, _isolate = isolate ?? (YAJsonIsolate()..initialize()), _hasCustomIsolate = isolate != null, - _httpClient = httpClient; + _httpClient = httpClient { + _log.config("Initialize FunctionsClient v$version with url: $url"); + _log.finest("Initialize with headers: $headers"); + } /// Getter for the headers Map get headers { @@ -129,6 +135,8 @@ class FunctionsClient { request.headers[key] = value; }); + _log.finest('Request: ${request.method} ${request.url} ${request.headers}'); + final response = await (_httpClient?.send(request) ?? request.send()); final responseType = (response.headers['Content-Type'] ?? response.headers['content-type'] ?? @@ -167,6 +175,7 @@ class FunctionsClient { /// /// Does nothing if you pass your own isolate Future dispose() async { + _log.fine("Dispose FunctionsClient"); if (!_hasCustomIsolate) { return _isolate.dispose(); } diff --git a/packages/functions_client/pubspec.yaml b/packages/functions_client/pubspec.yaml index 547fe631..338e294e 100644 --- a/packages/functions_client/pubspec.yaml +++ b/packages/functions_client/pubspec.yaml @@ -10,6 +10,7 @@ environment: dependencies: http: '>=0.13.4 <2.0.0' + logging: ^1.2.0 yet_another_json_isolate: 2.0.3 dev_dependencies: diff --git a/packages/gotrue/lib/src/broadcast_web.dart b/packages/gotrue/lib/src/broadcast_web.dart index b754666a..d53be14b 100644 --- a/packages/gotrue/lib/src/broadcast_web.dart +++ b/packages/gotrue/lib/src/broadcast_web.dart @@ -3,6 +3,9 @@ import 'dart:html' as html; import 'dart:js_util' as js_util; import 'package:gotrue/src/types/types.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('supabase.gotrue'); BroadcastChannel getBroadcastChannel(String broadcastKey) { final broadcast = html.BroadcastChannel(broadcastKey); @@ -15,6 +18,8 @@ BroadcastChannel getBroadcastChannel(String broadcastKey) { return json.decode(json.encode(dataMap)); }), postMessage: (message) { + _log.finest('Broadcasting message: $message'); + _log.fine('Broadcasting event: ${message['event']}'); final jsMessage = js_util.jsify(message); broadcast.postMessage(jsMessage); }, diff --git a/packages/gotrue/lib/src/gotrue_client.dart b/packages/gotrue/lib/src/gotrue_client.dart index 8a2c0e2b..ba5a0afb 100644 --- a/packages/gotrue/lib/src/gotrue_client.dart +++ b/packages/gotrue/lib/src/gotrue_client.dart @@ -14,9 +14,11 @@ import 'package:jwt_decode/jwt_decode.dart'; import 'package:meta/meta.dart'; import 'package:retry/retry.dart'; import 'package:rxdart/subjects.dart'; +import 'package:logging/logging.dart'; import 'broadcast_stub.dart' if (dart.library.html) './broadcast_web.dart' as web; +import 'version.dart'; part 'gotrue_mfa_api.dart'; @@ -87,6 +89,8 @@ class GoTrueClient { final AuthFlowType _flowType; + final _log = Logger('supabase.gotrue'); + /// Proxy to the web BroadcastChannel API. Should be null on non-web platforms. BroadcastChannel? _broadcastChannel; @@ -101,20 +105,22 @@ class GoTrueClient { GotrueAsyncStorage? asyncStorage, AuthFlowType flowType = AuthFlowType.pkce, }) : _url = url ?? Constants.defaultGotrueUrl, - _headers = headers ?? {}, + _headers = { + ...Constants.defaultHeaders, + ...?headers, + }, _httpClient = httpClient, _asyncStorage = asyncStorage, _flowType = flowType { _autoRefreshToken = autoRefreshToken ?? true; final gotrueUrl = url ?? Constants.defaultGotrueUrl; - final gotrueHeader = { - ...Constants.defaultHeaders, - if (headers != null) ...headers, - }; + _log.config( + 'Initialize GoTrueClient v$version with url: $_url, autoRefreshToken: $_autoRefreshToken, flowType: $_flowType, tickDuration: ${Constants.autoRefreshTickDuration}, tickThreshold: ${Constants.autoRefreshTickThreshold}'); + _log.finest('Initialize with headers: $_headers'); admin = GoTrueAdminApi( gotrueUrl, - headers: gotrueHeader, + headers: _headers, httpClient: httpClient, ); mfa = GoTrueMFAApi( @@ -617,8 +623,10 @@ class GoTrueClient { /// If the current session's refresh token is invalid, an error will be thrown. Future refreshSession([String? refreshToken]) async { if (currentSession?.accessToken == null) { + _log.warning("Can't refresh session, no current session found."); throw AuthSessionMissingException(); } + _log.info('Refresh session'); final currentSessionRefreshToken = refreshToken ?? _currentSession?.refreshToken; @@ -842,6 +850,7 @@ class GoTrueClient { Future signOut({ SignOutScope scope = SignOutScope.local, }) async { + _log.info('Signing out user with scope: $scope'); final accessToken = currentSession?.accessToken; if (scope != SignOutScope.others) { @@ -966,6 +975,7 @@ class GoTrueClient { try { final session = Session.fromJson(json.decode(jsonStr)); if (session == null) { + _log.warning("Can't recover session from string, session is null"); await signOut(); throw notifyException( AuthException('Current session is missing data.'), @@ -973,6 +983,7 @@ class GoTrueClient { } if (session.isExpired) { + _log.fine('Session from recovery is expired'); final refreshToken = session.refreshToken; if (_autoRefreshToken && refreshToken != null) { return await _callRefreshToken(refreshToken); @@ -1002,6 +1013,7 @@ class GoTrueClient { void startAutoRefresh() async { stopAutoRefresh(); + _log.fine('Starting auto refresh'); _autoRefreshTicker = Timer.periodic( Constants.autoRefreshTickDuration, (Timer t) => _autoRefreshTokenTick(), @@ -1013,6 +1025,7 @@ class GoTrueClient { /// Stops an active auto refresh process running in the background (if any). void stopAutoRefresh() { + _log.fine('Stopping auto refresh'); _autoRefreshTicker?.cancel(); _autoRefreshTicker = null; } @@ -1037,12 +1050,15 @@ class GoTrueClient { Constants.autoRefreshTickDuration.inMilliseconds) .floor(); + _log.finer('Access token expires in $expiresInTicks ticks'); + // Only tick if the next tick comes after the retry threshold if (expiresInTicks <= Constants.autoRefreshTickThreshold) { await _callRefreshToken(refreshToken); } } catch (error) { - // Do nothing. JS client prints here + // Do nothing. JS client prints here, but error is already tracked via + // [notifyException] } } @@ -1055,6 +1071,7 @@ class GoTrueClient { // Make a GET request () async { attempt++; + _log.fine('Attempt $attempt to refresh token'); final options = GotrueRequestOptions( headers: _headers, body: {'refresh_token': refreshToken}, @@ -1129,11 +1146,14 @@ class GoTrueClient { /// set currentSession and currentUser void _saveSession(Session session) { + _log.finest('Saving session: $session'); + _log.fine('Saving session'); _currentSession = session; _currentUser = session.user; } void _removeSession() { + _log.fine('Removing session'); _currentSession = null; _currentUser = null; } @@ -1151,6 +1171,8 @@ class GoTrueClient { _broadcastChannelSubscription = _broadcastChannel?.onMessage.listen((messageEvent) { final rawEvent = messageEvent['event']; + _log.finest('Received broadcast message: $messageEvent'); + _log.info('Received broadcast event: $rawEvent'); final event = switch (rawEvent) { // This library sends the js name of the event to be comptabile with // the js library, so we need to convert it back to the dart name @@ -1202,6 +1224,7 @@ class GoTrueClient { Future _callRefreshToken(String refreshToken) async { // Refreshing is already in progress if (_refreshTokenCompleter != null) { + _log.finer("Don't call refresh token, already in progress"); return _refreshTokenCompleter!.future; } @@ -1213,6 +1236,7 @@ class GoTrueClient { (_) => null, onError: (_, __) => null, ); + _log.fine('Refresh access token'); final data = await _refreshAccessToken(refreshToken); @@ -1232,7 +1256,7 @@ class GoTrueClient { _removeSession(); notifyAllSubscribers(AuthChangeEvent.signedOut); } else { - _onAuthStateChangeController.addError(error, stack); + notifyException(error, stack); } _refreshTokenCompleter?.completeError(error); @@ -1240,7 +1264,7 @@ class GoTrueClient { rethrow; } catch (error, stack) { _refreshTokenCompleter?.completeError(error); - _onAuthStateChangeController.addError(error, stack); + notifyException(error, stack); rethrow; } finally { _refreshTokenCompleter = null; @@ -1265,6 +1289,7 @@ class GoTrueClient { }); } final state = AuthState(event, session, fromBroadcast: !broadcast); + _log.finest('onAuthStateChange: $state'); _onAuthStateChangeController.add(state); _onAuthStateChangeControllerSync.add(state); } @@ -1272,6 +1297,7 @@ class GoTrueClient { /// For internal use only. @internal Object notifyException(Object exception, [StackTrace? stackTrace]) { + _log.warning('Notifying exception', exception, stackTrace); _onAuthStateChangeController.addError( exception, stackTrace ?? StackTrace.current, diff --git a/packages/gotrue/pubspec.yaml b/packages/gotrue/pubspec.yaml index ed4b9889..89e8a7d7 100644 --- a/packages/gotrue/pubspec.yaml +++ b/packages/gotrue/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: retry: ^3.1.0 rxdart: '>=0.27.7 <0.29.0' meta: ^1.7.0 + logging: ^1.2.0 dev_dependencies: dart_jsonwebtoken: ^2.4.1 diff --git a/packages/postgrest/lib/src/postgrest.dart b/packages/postgrest/lib/src/postgrest.dart index dca9b31e..6bace201 100644 --- a/packages/postgrest/lib/src/postgrest.dart +++ b/packages/postgrest/lib/src/postgrest.dart @@ -1,4 +1,5 @@ import 'package:http/http.dart'; +import 'package:logging/logging.dart'; import 'package:postgrest/postgrest.dart'; import 'package:postgrest/src/constants.dart'; import 'package:yet_another_json_isolate/yet_another_json_isolate.dart'; @@ -11,6 +12,7 @@ class PostgrestClient { final Client? httpClient; final YAJsonIsolate _isolate; final bool _hasCustomIsolate; + final _log = Logger('supabase.postgrest'); /// To create a [PostgrestClient], you need to provide an [url] endpoint. /// @@ -32,7 +34,10 @@ class PostgrestClient { }) : _schema = schema, headers = {...defaultHeaders, if (headers != null) ...headers}, _isolate = isolate ?? (YAJsonIsolate()..initialize()), - _hasCustomIsolate = isolate != null; + _hasCustomIsolate = isolate != null { + _log.config('Initialize PostgrestClient with url: $url, schema: $_schema'); + _log.finest('Initialize with headers: $headers'); + } /// Authenticates the request with JWT. @Deprecated("Use setAuth() instead") @@ -42,6 +47,7 @@ class PostgrestClient { } PostgrestClient setAuth(String? token) { + _log.finest("setAuth with: $token"); if (token != null) { headers['Authorization'] = 'Bearer $token'; } else { @@ -95,6 +101,7 @@ class PostgrestClient { } Future dispose() async { + _log.fine("dispose PostgrestClient"); if (!_hasCustomIsolate) { return _isolate.dispose(); } diff --git a/packages/postgrest/lib/src/postgrest_builder.dart b/packages/postgrest/lib/src/postgrest_builder.dart index dab11063..d7ef48e3 100644 --- a/packages/postgrest/lib/src/postgrest_builder.dart +++ b/packages/postgrest/lib/src/postgrest_builder.dart @@ -6,6 +6,7 @@ import 'dart:core'; import 'package:http/http.dart' as http; import 'package:http/http.dart'; +import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; import 'package:postgrest/postgrest.dart'; import 'package:yet_another_json_isolate/yet_another_json_isolate.dart'; @@ -44,6 +45,7 @@ class PostgrestBuilder implements Future { final Client? _httpClient; final YAJsonIsolate? _isolate; final CountOption? _count; + final _log = Logger('supabase.postgrest'); PostgrestBuilder({ required Uri url, @@ -132,6 +134,8 @@ class PostgrestBuilder implements Future { _headers['Content-Type'] = 'application/json'; } final bodyStr = jsonEncode(_body); + _log.finest("Request: $uppercaseMethod $_url"); + if (uppercaseMethod == METHOD_GET) { response = await (_httpClient?.get ?? http.get)( _url, @@ -203,7 +207,7 @@ class PostgrestBuilder implements Future { // Workaround for https://github.com/supabase/supabase-flutter/issues/560 if (_maybeSingle && method.toUpperCase() == 'GET' && body is List) { if (body.length > 1) { - throw PostgrestException( + final exception = PostgrestException( // https://github.com/PostgREST/postgrest/blob/a867d79c42419af16c18c3fb019eba8df992626f/src/PostgREST/Error.hs#L553 code: '406', details: @@ -211,6 +215,9 @@ class PostgrestBuilder implements Future { hint: null, message: 'JSON object requested, multiple (or no) rows returned', ); + + _log.finest('$exception for request $_url'); + throw exception; } else if (body.length == 1) { body = body.first; } else { @@ -286,6 +293,9 @@ class PostgrestBuilder implements Future { ); } + _log.finest('$error from request: $_url'); + _log.fine('$error from request'); + throw error; } } diff --git a/packages/postgrest/pubspec.yaml b/packages/postgrest/pubspec.yaml index be5903aa..ade23ef1 100644 --- a/packages/postgrest/pubspec.yaml +++ b/packages/postgrest/pubspec.yaml @@ -12,6 +12,7 @@ dependencies: http: '>=0.13.0 <2.0.0' yet_another_json_isolate: 2.0.3 meta: ^1.9.1 + logging: ^1.2.0 dev_dependencies: collection: ^1.16.0 diff --git a/packages/realtime_client/lib/src/realtime_client.dart b/packages/realtime_client/lib/src/realtime_client.dart index fe7eefd3..f5e6f5fa 100644 --- a/packages/realtime_client/lib/src/realtime_client.dart +++ b/packages/realtime_client/lib/src/realtime_client.dart @@ -4,6 +4,7 @@ import 'dart:core'; import 'package:collection/collection.dart'; import 'package:http/http.dart'; +import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; import 'package:realtime_client/realtime_client.dart'; import 'package:realtime_client/src/constants.dart'; @@ -62,6 +63,7 @@ class RealtimeClient { final Duration timeout; final WebSocketTransport transport; final Client? httpClient; + final _log = Logger('supabase.realtime'); int heartbeatIntervalMs = 30000; Timer? heartbeatTimer; @@ -90,18 +92,29 @@ class RealtimeClient { /// Initializes the Socket /// - /// `endPoint` The string WebSocket endpoint, ie, "ws://example.com/socket", "wss://example.com", "/socket" (inherited host & protocol) - /// `httpEndpoint` The string HTTP endpoint, ie, "https://example.com", "/" (inherited host & protocol) - /// `transport` The Websocket Transport, for example WebSocket. - /// `timeout` The default timeout in milliseconds to trigger push timeouts. - /// `params` The optional params to pass when connecting. - /// `headers` The optional headers to pass when connecting. - /// `heartbeatIntervalMs` The millisec interval to send a heartbeat message. - /// `logger` The optional function for specialized logging, ie: logger: (kind, msg, data) => { console.log(`$kind: $msg`, data) } - /// `encode` The function to encode outgoing messages. Defaults to JSON: (payload, callback) => callback(JSON.stringify(payload)) - /// `decode` The function to decode incoming messages. Defaults to JSON: (payload, callback) => callback(JSON.parse(payload)) - /// `longpollerTimeout` The maximum timeout of a long poll AJAX request. Defaults to 20s (double the server long poll timer). - /// `reconnectAfterMs` The optional function that returns the millsec reconnect interval. Defaults to stepped backoff off. + /// [endPoint] The string WebSocket endpoint, ie, "ws://example.com/socket", "wss://example.com", "/socket" (inherited host & protocol + /// + /// [transport] The Websocket Transport, for example WebSocket. + /// + /// [timeout] The default timeout in milliseconds to trigger push timeouts. + /// + /// [params] The optional params to pass when connecting. + /// + /// [headers] The optional headers to pass when connecting. + /// + /// [heartbeatIntervalMs] The millisec interval to send a heartbeat message. + /// + /// [logger] The optional function for specialized logging, ie: logger: (kind, msg, data) => { console.log(`$kind: $msg`, data) } + /// + /// [encode] The function to encode outgoing messages. Defaults to JSON: (payload, callback) => callback(JSON.stringify(payload)) + /// + /// [decode] The function to decode incoming messages. Defaults to JSON: (payload, callback) => callback(JSON.parse(payload)) + /// + /// [longpollerTimeout] The maximum timeout of a long poll AJAX request. Defaults to 20s (double the server long poll timer). + /// + /// [reconnectAfterMs] The optional function that returns the millsec reconnect interval. Defaults to stepped backoff off. + /// + /// [logLevel] Specifies the log level for the connection on the server. RealtimeClient( String endPoint, { WebSocketTransport? transport, @@ -127,6 +140,9 @@ class RealtimeClient { if (headers != null) ...headers, }, transport = transport ?? createWebSocketClient { + _log.config( + 'Initialize RealtimeClient with endpoint: $endPoint, timeout: $timeout, heartbeatIntervalMs: $heartbeatIntervalMs, longpollerTimeout: $longpollerTimeout, logLevel: $logLevel'); + _log.finest('Initialize with headers: $headers, params: $params'); final customJWT = this.headers['Authorization']?.split(' ').last; accessToken = customJWT ?? params['apikey']; @@ -155,6 +171,8 @@ class RealtimeClient { } try { + log('transport', 'connecting to $endPointURL', null); + log('transport', 'connecting', null, Level.FINE); connState = SocketStates.connecting; conn = transport(endPointURL, headers); @@ -202,6 +220,8 @@ class RealtimeClient { if (conn != null) { final oldState = connState; connState = SocketStates.disconnecting; + log('transport', 'disconnecting', {'code': code, 'reason': reason}, + Level.FINE); // Connection cannot be closed while it's still connecting. Wait for connection to // be ready and then close it. @@ -218,6 +238,7 @@ class RealtimeClient { } connState = SocketStates.disconnected; reconnectTimer.reset(); + log('transport', 'disconnected', null, Level.FINE); } this.conn = null; @@ -246,7 +267,11 @@ class RealtimeClient { } /// Logs the message. Override `this.logger` for specialized logging. - void log([String? kind, String? msg, dynamic data]) { + /// + /// [level] must be [Level.FINEST] for senitive data + void log( + [String? kind, String? msg, dynamic data, Level level = Level.FINEST]) { + _log.log(level, '$kind: $msg', data); logger?.call(kind, msg, data); } @@ -405,6 +430,7 @@ class RealtimeClient { void _onConnOpen() { log('transport', 'connected to $endPointURL'); + log('transport', 'connected', null, Level.FINE); _flushSendBuffer(); reconnectTimer.reset(); if (heartbeatTimer != null) heartbeatTimer!.cancel(); @@ -424,7 +450,7 @@ class RealtimeClient { if (statusCode != null) { event = RealtimeCloseEvent(code: statusCode, reason: conn?.closeReason); } - log('transport', 'close', event); + log('transport', 'close', event, Level.FINE); /// SocketStates.disconnected: by user with socket.disconnect() /// SocketStates.closed: NOT by user, should try to reconnect diff --git a/packages/realtime_client/pubspec.yaml b/packages/realtime_client/pubspec.yaml index 7e85cad6..3c67055c 100644 --- a/packages/realtime_client/pubspec.yaml +++ b/packages/realtime_client/pubspec.yaml @@ -11,6 +11,7 @@ environment: dependencies: collection: ^1.15.0 http: '>=0.13.0 <2.0.0' + logging: ^1.2.0 meta: ^1.7.0 web_socket_channel: '>=2.3.0 <4.0.0' diff --git a/packages/storage_client/lib/src/fetch.dart b/packages/storage_client/lib/src/fetch.dart index a39c136e..1d4bce5f 100644 --- a/packages/storage_client/lib/src/fetch.dart +++ b/packages/storage_client/lib/src/fetch.dart @@ -5,6 +5,7 @@ import 'dart:typed_data'; import 'package:http/http.dart' as http; import 'package:http/http.dart'; import 'package:http_parser/http_parser.dart' show MediaType; +import 'package:logging/logging.dart'; import 'package:mime/mime.dart'; import 'package:retry/retry.dart'; import 'package:storage_client/src/types.dart'; @@ -13,6 +14,7 @@ import 'file_io.dart' if (dart.library.js) './file_stub.dart'; class Fetch { final Client? httpClient; + final _log = Logger('supabase.storage'); Fetch([this.httpClient]); @@ -25,18 +27,24 @@ class Fetch { return MediaType.parse(mime ?? 'application/octet-stream'); } - StorageException _handleError(dynamic error, StackTrace stack) { + StorageException _handleError(dynamic error, StackTrace stack, Uri? url) { if (error is http.Response) { try { final data = json.decode(error.body) as Map; - return StorageException.fromJson(data, '${error.statusCode}'); + + final exception = + StorageException.fromJson(data, '${error.statusCode}'); + _log.fine('StorageException for $url', exception, stack); + return exception; } on FormatException catch (_) { + _log.fine('StorageException for $url', error.body, stack); return StorageException( error.body, statusCode: '${error.statusCode}', ); } } else { + _log.fine('StorageException for $url', error, stack); return StorageException( error.toString(), statusCode: error.runtimeType.toString(), @@ -61,6 +69,7 @@ class Fetch { request.body = json.encode(body); } + _log.finest('Request: $method $url $headers'); final http.StreamedResponse streamedResponse; if (httpClient != null) { streamedResponse = await httpClient!.send(request); @@ -97,8 +106,11 @@ class Fetch { final http.StreamedResponse streamedResponse; final r = RetryOptions(maxAttempts: (retryAttempts + 1)); + var attempts = 0; streamedResponse = await r.retry( () async { + attempts++; + _log.finest('Request: attempt: $attempts $method $url $headers'); if (httpClient != null) { return httpClient!.send(request); } else { @@ -141,8 +153,11 @@ class Fetch { final http.StreamedResponse streamedResponse; final r = RetryOptions(maxAttempts: (retryAttempts + 1)); + var attempts = 0; streamedResponse = await r.retry( () async { + attempts++; + _log.finest('Request: attempt: $attempts $method $url $headers'); if (httpClient != null) { return httpClient!.send(request); } else { @@ -170,7 +185,7 @@ class Fetch { return jsonBody; } } else { - throw _handleError(response, StackTrace.current); + throw _handleError(response, StackTrace.current, response.request?.url); } } diff --git a/packages/storage_client/lib/src/storage_client.dart b/packages/storage_client/lib/src/storage_client.dart index e9edf4bd..b51af7e4 100644 --- a/packages/storage_client/lib/src/storage_client.dart +++ b/packages/storage_client/lib/src/storage_client.dart @@ -1,10 +1,13 @@ import 'package:http/http.dart'; +import 'package:logging/logging.dart'; import 'package:storage_client/src/constants.dart'; import 'package:storage_client/src/storage_bucket_api.dart'; import 'package:storage_client/src/storage_file_api.dart'; +import 'package:storage_client/src/version.dart'; class SupabaseStorageClient extends StorageBucketApi { final int _defaultRetryAttempts; + final _log = Logger('supabase.storage'); /// To create a [SupabaseStorageClient], you need to provide an [url] and [headers]. /// @@ -42,7 +45,11 @@ class SupabaseStorageClient extends StorageBucketApi { url, {...Constants.defaultHeaders, ...headers}, httpClient: httpClient, - ); + ) { + _log.config( + 'Initialize SupabaseStorageClient v$version with url: $url, retryAttempts: $_defaultRetryAttempts'); + _log.finest('Initialize with headers: $headers'); + } /// Perform file operation in a bucket. /// diff --git a/packages/storage_client/pubspec.yaml b/packages/storage_client/pubspec.yaml index f1922a78..f8b41163 100644 --- a/packages/storage_client/pubspec.yaml +++ b/packages/storage_client/pubspec.yaml @@ -14,6 +14,7 @@ dependencies: mime: ^1.0.2 retry: ^3.1.0 meta: ^1.7.0 + logging: ^1.2.0 dev_dependencies: test: ^1.21.4 diff --git a/packages/supabase/lib/src/auth_user.dart b/packages/supabase/lib/src/auth_user.dart index b915764e..f85b5d90 100644 --- a/packages/supabase/lib/src/auth_user.dart +++ b/packages/supabase/lib/src/auth_user.dart @@ -1,5 +1,6 @@ import 'package:gotrue/gotrue.dart' show User; +@Deprecated('No longer used. May be removed in the future.') class AuthUser extends User { AuthUser({ required super.id, diff --git a/packages/supabase/lib/src/remove_subscription_result.dart b/packages/supabase/lib/src/remove_subscription_result.dart index d03c747b..e72f3e2c 100644 --- a/packages/supabase/lib/src/remove_subscription_result.dart +++ b/packages/supabase/lib/src/remove_subscription_result.dart @@ -1,5 +1,6 @@ import 'package:supabase/src/supabase_realtime_error.dart'; +@Deprecated("No longer used. May be removed in the future.") class RemoveSubscriptionResult { const RemoveSubscriptionResult({required this.openSubscriptions, this.error}); final int openSubscriptions; diff --git a/packages/supabase/lib/src/supabase_client.dart b/packages/supabase/lib/src/supabase_client.dart index b51b4ca8..db4ca3b4 100644 --- a/packages/supabase/lib/src/supabase_client.dart +++ b/packages/supabase/lib/src/supabase_client.dart @@ -1,7 +1,9 @@ import 'dart:async'; import 'package:http/http.dart'; +import 'package:logging/logging.dart'; import 'package:supabase/src/constants.dart'; +import 'package:supabase/src/version.dart'; import 'package:supabase/supabase.dart'; import 'package:yet_another_json_isolate/yet_another_json_isolate.dart'; @@ -66,6 +68,8 @@ class SupabaseClient { /// Increment ID of the stream to create different realtime topic for each stream final _incrementId = Counter(); + final _log = Logger('supabase.supabase'); + /// Getter for the HTTP headers Map get headers { return _headers; @@ -145,7 +149,12 @@ class SupabaseClient { storage = _initStorageClient(storageOptions.retryAttempts); realtime = _initRealtimeClient(options: realtimeClientOptions); if (accessToken == null) { + _log.config( + 'Initialize SupabaseClient v$version with no custom access token'); _listenForAuthEvents(); + } else { + _log.config( + 'Initialize SupabaseClient v$version with custom access token'); } } @@ -223,6 +232,8 @@ class SupabaseClient { return realtime.removeAllChannels(); } + /// Get either the custom access token from [accessToken] or the supabase one + /// from [_authInstance] Future _getAccessToken() async { if (accessToken != null) { return await accessToken!(); @@ -231,7 +242,7 @@ class SupabaseClient { if (_authInstance.currentSession?.isExpired ?? false) { try { await _authInstance.refreshSession(); - } catch (error) { + } catch (error, stackTrace) { final expiresAt = _authInstance.currentSession?.expiresAt; if (expiresAt != null) { // Failed to refresh the token. @@ -239,6 +250,11 @@ class SupabaseClient { .isAfter(DateTime.fromMillisecondsSinceEpoch(expiresAt * 1000)); if (isExpiredWithoutMargin) { // Throw the error instead of making an API request with an expired token. + _log.warning( + 'Access token is expired and refreshing failed, aborting api request', + error, + stackTrace, + ); rethrow; } } @@ -248,6 +264,7 @@ class SupabaseClient { } Future dispose() async { + _log.fine('Dispose SupabaseClient'); await _authStateSubscription?.cancel(); await _isolate.dispose(); auth.dispose(); diff --git a/packages/supabase/lib/src/supabase_event_types.dart b/packages/supabase/lib/src/supabase_event_types.dart index 60c53412..72607bbb 100644 --- a/packages/supabase/lib/src/supabase_event_types.dart +++ b/packages/supabase/lib/src/supabase_event_types.dart @@ -1,5 +1,7 @@ +@Deprecated('No longer used. May be removed in the future.') enum SupabaseEventTypes { insert, update, delete, all, broadcast, presence } +// ignore: deprecated_member_use_from_same_package extension SupabaseEventTypesName on SupabaseEventTypes { String name() { final name = toString().split('.').last; diff --git a/packages/supabase/lib/src/supabase_realtime_error.dart b/packages/supabase/lib/src/supabase_realtime_error.dart index 7f271ab1..b9ddf7d2 100644 --- a/packages/supabase/lib/src/supabase_realtime_error.dart +++ b/packages/supabase/lib/src/supabase_realtime_error.dart @@ -1,3 +1,4 @@ +@Deprecated('No longer used. May be removed in the future.') class SupabaseRealtimeError extends Error { /// Creates an Unsubscribe error with the provided [message]. SupabaseRealtimeError([this.message]); diff --git a/packages/supabase/lib/src/supabase_stream_builder.dart b/packages/supabase/lib/src/supabase_stream_builder.dart index 5e0aac6a..c27d9bd5 100644 --- a/packages/supabase/lib/src/supabase_stream_builder.dart +++ b/packages/supabase/lib/src/supabase_stream_builder.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:logging/logging.dart'; import 'package:rxdart/rxdart.dart'; import 'package:supabase/supabase.dart'; @@ -61,6 +62,8 @@ class SupabaseStreamBuilder extends Stream { /// Used to identify which row has changed final List _uniqueColumns; + final _log = Logger('supabase.supabase'); + /// StreamController for `stream()` method. BehaviorSubject? _streamController; @@ -144,6 +147,7 @@ class SupabaseStreamBuilder extends Stream { _getStreamData(); }, onCancel: () { + _log.fine('stream controller for table: $_table got closed'); _channel?.unsubscribe(); _streamController?.close(); _streamController = null; diff --git a/packages/supabase/pubspec.yaml b/packages/supabase/pubspec.yaml index 24d21302..8932b4e9 100644 --- a/packages/supabase/pubspec.yaml +++ b/packages/supabase/pubspec.yaml @@ -17,6 +17,7 @@ dependencies: storage_client: 2.1.0 rxdart: '>=0.27.5 <0.29.0' yet_another_json_isolate: 2.0.3 + logging: ^1.2.0 dev_dependencies: lints: ^3.0.0 diff --git a/packages/supabase_flutter/README.md b/packages/supabase_flutter/README.md index 23acc59b..66ea479d 100644 --- a/packages/supabase_flutter/README.md +++ b/packages/supabase_flutter/README.md @@ -596,6 +596,36 @@ Supabase.initialize( ); ``` +## Logging + +All Supabase packages use the [logging](https://pub.dev/packages/logging) package to log information. Each sub-package has its own logger instance. You can listen to logs and set custom log levels for each logger. + +In debug mode, or depending on the value for `debug` from `Supabase.initialize()`, records with `Level.INFO` and above are printed to the console. + +Records containing sensitive data like access tokens and which requests are made are logged with `Level.FINEST`, so you can handle them accordingly. + +### Listen to all Supabase logs + +```dart +import 'package:logging/logging.dart'; + +final supabaseLogger = Logger('supabase'); +supabaseLogger.level = Level.ALL; // custom log level filtering, default is Level.INFO +supabaseLogger.onRecord.listen((record) { + print('${record.level.name}: ${record.time}: ${record.message}'); +}); +``` + +### Sub-package loggers + +- `supabase_flutter`: `Logger('supabase.supabase_flutter')` +- `supabase`: `Logger('supabase.supabase')` +- `postgrest`: `Logger('supabase.postgrest')` +- `gotrue`: `Logger('supabase.gotrue')` +- `realtime_client`: `Logger('supabase.realtime')` +- `storage_client`: `Logger('supabase.storage')` +- `functions_client`: `Logger('supabase.functions')` + --- ## Migrating Guide diff --git a/packages/supabase_flutter/lib/src/supabase.dart b/packages/supabase_flutter/lib/src/supabase.dart index 0608707c..1ff4b3cb 100644 --- a/packages/supabase_flutter/lib/src/supabase.dart +++ b/packages/supabase_flutter/lib/src/supabase.dart @@ -1,14 +1,20 @@ import 'dart:async'; +import 'dart:developer' as dev; import 'package:async/async.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart'; +import 'package:logging/logging.dart'; import 'package:supabase/supabase.dart'; import 'package:supabase_flutter/src/constants.dart'; import 'package:supabase_flutter/src/flutter_go_true_client_options.dart'; import 'package:supabase_flutter/src/local_storage.dart'; import 'package:supabase_flutter/src/supabase_auth.dart'; +import 'version.dart'; + +final _log = Logger('supabase.supabase_flutter'); + /// Supabase instance. /// /// It must be initialized before used, otherwise an error is thrown. @@ -65,7 +71,7 @@ class Supabase { /// PKCE flow uses shared preferences for storing the code verifier by default. /// Pass a custom storage to [pkceAsyncStorage] to override the behavior. /// - /// If [debug] is set to `true`, debug logs will be printed in debug console. + /// If [debug] is set to `true`, debug logs will be printed in debug console. Default is `kDebugMode`. static Future initialize({ required String url, required String anonKey, @@ -82,6 +88,19 @@ class Supabase { !_instance._initialized, 'This instance is already initialized', ); + _instance._debugEnable = debug ?? kDebugMode; + + if (_instance._debugEnable) { + _instance._logSubscription = Logger('supabase').onRecord.listen((record) { + if (record.level >= Level.INFO) { + debugPrint( + '${record.loggerName}: ${record.level.name}: ${record.message} ${record.error ?? ""}'); + } + }); + } + + _log.config("Initialize Supabase v$version"); + if (authOptions.pkceAsyncStorage == null) { authOptions = authOptions.copyWith( pkceAsyncStorage: SharedPreferencesGotrueAsyncStorage(), @@ -106,8 +125,6 @@ class Supabase { storageOptions: storageOptions, accessToken: accessToken, ); - _instance._debugEnable = debug ?? kDebugMode; - _instance.log('***** Supabase init completed *****'); _instance._supabaseAuth = SupabaseAuth(); await _instance._supabaseAuth.initialize(options: authOptions); @@ -119,6 +136,8 @@ class Supabase { _instance._supabaseAuth.recoverSession(), ); + _log.info('***** Supabase init completed *****'); + return _instance; } @@ -139,9 +158,12 @@ class Supabase { /// Wraps the `recoverSession()` call so that it can be terminated when `dispose()` is called late CancelableOperation _restoreSessionCancellableOperation; + StreamSubscription? _logSubscription; + /// Dispose the instance to free up resources. Future dispose() async { await _restoreSessionCancellableOperation.cancel(); + _logSubscription?.cancel(); client.dispose(); _instance._supabaseAuth.dispose(); _initialized = false; @@ -175,13 +197,4 @@ class Supabase { ); _initialized = true; } - - void log(String msg, [StackTrace? stackTrace]) { - if (_debugEnable) { - debugPrint(msg); - if (stackTrace != null) { - debugPrintStack(stackTrace: stackTrace); - } - } - } } diff --git a/packages/supabase_flutter/lib/src/supabase_auth.dart b/packages/supabase_flutter/lib/src/supabase_auth.dart index 80cf6248..221a6ed9 100644 --- a/packages/supabase_flutter/lib/src/supabase_auth.dart +++ b/packages/supabase_flutter/lib/src/supabase_auth.dart @@ -8,6 +8,7 @@ import 'package:async/async.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:logging/logging.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -34,6 +35,8 @@ class SupabaseAuth with WidgetsBindingObserver { final _appLinks = AppLinks(); + final _log = Logger('supabase.supabase_flutter'); + /// - Obtains session from local storage and sets it as the current session /// - Starts a deep link observer /// - Emits an initial session if there were no session stored in local storage @@ -48,9 +51,7 @@ class SupabaseAuth with WidgetsBindingObserver { (data) { _onAuthStateChange(data.event, data.session); }, - onError: (error, stackTrace) { - Supabase.instance.log(error.toString(), stackTrace); - }, + onError: (error, stackTrace) {}, ); await _localStorage.initialize(); @@ -65,7 +66,8 @@ class SupabaseAuth with WidgetsBindingObserver { .setInitialSession(persistedSession); shouldEmitInitialSession = false; } catch (error, stackTrace) { - Supabase.instance.log(error.toString(), stackTrace); + _log.warning( + 'Error while setting initial session', error, stackTrace); } } } @@ -96,9 +98,9 @@ class SupabaseAuth with WidgetsBindingObserver { } } } on AuthException catch (error, stackTrace) { - Supabase.instance.log(error.message, stackTrace); + _log.warning(error.message, error, stackTrace); } catch (error, stackTrace) { - Supabase.instance.log(error.toString(), stackTrace); + _log.warning("Error while recovering session", error, stackTrace); } } @@ -152,6 +154,7 @@ class SupabaseAuth with WidgetsBindingObserver { for (final channel in realtime.channels) { // ignore: invalid_use_of_internal_member if (channel.isJoined) { + // ignore: invalid_use_of_internal_member channel.forceRejoin(); } } @@ -178,6 +181,7 @@ class SupabaseAuth with WidgetsBindingObserver { // ignore: invalid_use_of_internal_member if (channel.isJoined) { + // ignore: invalid_use_of_internal_member channel.forceRejoin(); } } @@ -186,9 +190,7 @@ class SupabaseAuth with WidgetsBindingObserver { } void _onAuthStateChange(AuthChangeEvent event, Session? session) { - Supabase.instance.log('**** onAuthStateChange: $event'); if (session != null) { - Supabase.instance.log(jsonEncode(session.toJson())); _localStorage.persistSession(jsonEncode(session.toJson())); } else if (event == AuthChangeEvent.signedOut) { _localStorage.removePersistedSession(); @@ -206,7 +208,7 @@ class SupabaseAuth with WidgetsBindingObserver { /// Enable deep link observer to handle deep links Future _startDeeplinkObserver() async { - Supabase.instance.log('***** SupabaseDeepLinkingMixin startAuthObserver'); + _log.fine('Starting deeplink observer'); _handleIncomingLinks(); await _handleInitialUri(); } @@ -216,7 +218,7 @@ class SupabaseAuth with WidgetsBindingObserver { /// Automatically called on dispose(). void _stopDeeplinkObserver() { if (_deeplinkSubscription != null) { - Supabase.instance.log('***** SupabaseDeepLinkingMixin stopAuthObserver'); + _log.fine('Stopping deeplink observer'); _deeplinkSubscription?.cancel(); } } @@ -234,7 +236,7 @@ class SupabaseAuth with WidgetsBindingObserver { } }, onError: (Object err, StackTrace stackTrace) { - _onErrorReceivingDeeplink(err.toString(), stackTrace); + _onErrorReceivingDeeplink(err, stackTrace); }, ); } @@ -273,12 +275,12 @@ class SupabaseAuth with WidgetsBindingObserver { await _handleDeeplink(uri); } } on PlatformException catch (err, stackTrace) { - _onErrorReceivingDeeplink(err.message ?? err.toString(), stackTrace); + _onErrorReceivingDeeplink(err.message ?? err, stackTrace); // Platform messages may fail but we ignore the exception } on FormatException catch (err, stackTrace) { _onErrorReceivingDeeplink(err.message, stackTrace); } catch (err, stackTrace) { - _onErrorReceivingDeeplink(err.toString(), stackTrace); + _onErrorReceivingDeeplink(err, stackTrace); } } @@ -286,26 +288,22 @@ class SupabaseAuth with WidgetsBindingObserver { Future _handleDeeplink(Uri uri) async { if (!_isAuthCallbackDeeplink(uri)) return; - Supabase.instance.log('***** SupabaseAuthState handleDeeplink $uri'); - - // notify auth deeplink received - Supabase.instance.log('onReceivedAuthDeeplink uri: $uri'); + _log.finest('handle deeplink uri: $uri'); + _log.info('handle deeplink uri'); try { await Supabase.instance.client.auth.getSessionFromUrl(uri); } on AuthException catch (error, stackTrace) { // ignore: invalid_use_of_internal_member Supabase.instance.client.auth.notifyException(error, stackTrace); - Supabase.instance.log(error.toString(), stackTrace); } catch (error, stackTrace) { - Supabase.instance.log(error.toString(), stackTrace); + _log.warning('Error while getSessionFromUrl', error, stackTrace); } } /// Callback when deeplink receiving throw error - void _onErrorReceivingDeeplink(String message, StackTrace stackTrace) { - Supabase.instance - .log('onErrorReceivingDeepLink message: $message', stackTrace); + void _onErrorReceivingDeeplink(Object error, StackTrace stackTrace) { + _log.warning('Error while receiving deeplink', error, stackTrace); } } diff --git a/packages/supabase_flutter/pubspec.yaml b/packages/supabase_flutter/pubspec.yaml index c5c26cea..fd48b230 100644 --- a/packages/supabase_flutter/pubspec.yaml +++ b/packages/supabase_flutter/pubspec.yaml @@ -21,6 +21,7 @@ dependencies: url_launcher: ^6.1.2 path_provider: ^2.0.0 shared_preferences: ^2.0.0 + logging: ^1.2.0 dev_dependencies: dart_jsonwebtoken: ^2.4.1 From eea1ea6c4e200e8ffc52d8343c0684c19670e3ed Mon Sep 17 00:00:00 2001 From: Vinzent Date: Mon, 7 Oct 2024 09:38:19 +0200 Subject: [PATCH 10/29] fix: Rename logger from gotrue to auth (#1055) fix: rename logger from gotrue to auth --- packages/gotrue/lib/src/broadcast_web.dart | 2 +- packages/gotrue/lib/src/gotrue_client.dart | 2 +- packages/supabase_flutter/README.md | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/gotrue/lib/src/broadcast_web.dart b/packages/gotrue/lib/src/broadcast_web.dart index d53be14b..0c2c2a53 100644 --- a/packages/gotrue/lib/src/broadcast_web.dart +++ b/packages/gotrue/lib/src/broadcast_web.dart @@ -5,7 +5,7 @@ import 'dart:js_util' as js_util; import 'package:gotrue/src/types/types.dart'; import 'package:logging/logging.dart'; -final _log = Logger('supabase.gotrue'); +final _log = Logger('supabase.auth'); BroadcastChannel getBroadcastChannel(String broadcastKey) { final broadcast = html.BroadcastChannel(broadcastKey); diff --git a/packages/gotrue/lib/src/gotrue_client.dart b/packages/gotrue/lib/src/gotrue_client.dart index ba5a0afb..e46afecc 100644 --- a/packages/gotrue/lib/src/gotrue_client.dart +++ b/packages/gotrue/lib/src/gotrue_client.dart @@ -89,7 +89,7 @@ class GoTrueClient { final AuthFlowType _flowType; - final _log = Logger('supabase.gotrue'); + final _log = Logger('supabase.auth'); /// Proxy to the web BroadcastChannel API. Should be null on non-web platforms. BroadcastChannel? _broadcastChannel; diff --git a/packages/supabase_flutter/README.md b/packages/supabase_flutter/README.md index 66ea479d..6b3d19ae 100644 --- a/packages/supabase_flutter/README.md +++ b/packages/supabase_flutter/README.md @@ -52,6 +52,7 @@ final supabase = Supabase.instance.client; * [Edge Functions](#edge-functions) * [Deep Links](#deep-links) * [Custom LocalStorage](#custom-localstorage) +- [Logging](#logging) ### [Authentication](https://supabase.com/docs/guides/auth) @@ -621,7 +622,7 @@ supabaseLogger.onRecord.listen((record) { - `supabase_flutter`: `Logger('supabase.supabase_flutter')` - `supabase`: `Logger('supabase.supabase')` - `postgrest`: `Logger('supabase.postgrest')` -- `gotrue`: `Logger('supabase.gotrue')` +- `gotrue`: `Logger('supabase.auth')` - `realtime_client`: `Logger('supabase.realtime')` - `storage_client`: `Logger('supabase.storage')` - `functions_client`: `Logger('supabase.functions')` From da7109f68cdd837f8b9822897fb110563ef377a7 Mon Sep 17 00:00:00 2001 From: Tyler Date: Mon, 7 Oct 2024 17:19:41 +0900 Subject: [PATCH 11/29] chore(release): Publish packages supabase_flutter@2.8.0 (#1056) chore(release): publish packages - functions_client@2.4.0 - gotrue@2.10.0 - postgrest@2.3.0 - realtime_client@2.4.0 - storage_client@2.2.0 - supabase@2.5.0 - supabase_flutter@2.8.0 --- CHANGELOG.md | 53 +++++++++++++++++++ packages/functions_client/CHANGELOG.md | 4 ++ .../functions_client/lib/src/version.dart | 2 +- packages/functions_client/pubspec.yaml | 2 +- packages/gotrue/CHANGELOG.md | 5 ++ packages/gotrue/lib/src/version.dart | 2 +- packages/gotrue/pubspec.yaml | 2 +- packages/postgrest/CHANGELOG.md | 4 ++ packages/postgrest/lib/src/version.dart | 2 +- packages/postgrest/pubspec.yaml | 2 +- packages/realtime_client/CHANGELOG.md | 4 ++ packages/realtime_client/lib/src/version.dart | 2 +- packages/realtime_client/pubspec.yaml | 2 +- packages/storage_client/CHANGELOG.md | 4 ++ packages/storage_client/lib/src/version.dart | 2 +- packages/storage_client/pubspec.yaml | 2 +- packages/supabase/CHANGELOG.md | 4 ++ packages/supabase/lib/src/version.dart | 2 +- packages/supabase/pubspec.yaml | 12 ++--- packages/supabase_flutter/CHANGELOG.md | 5 ++ .../supabase_flutter/example/pubspec.yaml | 2 +- .../supabase_flutter/lib/src/version.dart | 2 +- packages/supabase_flutter/pubspec.yaml | 4 +- 23 files changed, 104 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 702ab4db..18c273b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,59 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## 2024-10-07 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`functions_client` - `v2.4.0`](#functions_client---v240) + - [`gotrue` - `v2.10.0`](#gotrue---v2100) + - [`postgrest` - `v2.3.0`](#postgrest---v230) + - [`realtime_client` - `v2.4.0`](#realtime_client---v240) + - [`storage_client` - `v2.2.0`](#storage_client---v220) + - [`supabase` - `v2.5.0`](#supabase---v250) + - [`supabase_flutter` - `v2.8.0`](#supabase_flutter---v280) + +--- + +#### `functions_client` - `v2.4.0` + + - **FEAT**: Add logging ([#1042](https://github.com/supabase/supabase-flutter/issues/1042)). ([d1ecabd7](https://github.com/supabase/supabase-flutter/commit/d1ecabd77881a0488d2d4b41ea5ee5abda6c5c35)) + +#### `gotrue` - `v2.10.0` + + - **FIX**: Rename logger from gotrue to auth ([#1055](https://github.com/supabase/supabase-flutter/issues/1055)). ([eea1ea6c](https://github.com/supabase/supabase-flutter/commit/eea1ea6c4e200e8ffc52d8343c0684c19670e3ed)) + - **FEAT**: Add logging ([#1042](https://github.com/supabase/supabase-flutter/issues/1042)). ([d1ecabd7](https://github.com/supabase/supabase-flutter/commit/d1ecabd77881a0488d2d4b41ea5ee5abda6c5c35)) + +#### `postgrest` - `v2.3.0` + + - **FEAT**: Add logging ([#1042](https://github.com/supabase/supabase-flutter/issues/1042)). ([d1ecabd7](https://github.com/supabase/supabase-flutter/commit/d1ecabd77881a0488d2d4b41ea5ee5abda6c5c35)) + +#### `realtime_client` - `v2.4.0` + + - **FEAT**: Add logging ([#1042](https://github.com/supabase/supabase-flutter/issues/1042)). ([d1ecabd7](https://github.com/supabase/supabase-flutter/commit/d1ecabd77881a0488d2d4b41ea5ee5abda6c5c35)) + +#### `storage_client` - `v2.2.0` + + - **FEAT**: Add logging ([#1042](https://github.com/supabase/supabase-flutter/issues/1042)). ([d1ecabd7](https://github.com/supabase/supabase-flutter/commit/d1ecabd77881a0488d2d4b41ea5ee5abda6c5c35)) + +#### `supabase` - `v2.5.0` + + - **FEAT**: Add logging ([#1042](https://github.com/supabase/supabase-flutter/issues/1042)). ([d1ecabd7](https://github.com/supabase/supabase-flutter/commit/d1ecabd77881a0488d2d4b41ea5ee5abda6c5c35)) + +#### `supabase_flutter` - `v2.8.0` + + - **FIX**: Rename logger from gotrue to auth ([#1055](https://github.com/supabase/supabase-flutter/issues/1055)). ([eea1ea6c](https://github.com/supabase/supabase-flutter/commit/eea1ea6c4e200e8ffc52d8343c0684c19670e3ed)) + - **FEAT**: Add logging ([#1042](https://github.com/supabase/supabase-flutter/issues/1042)). ([d1ecabd7](https://github.com/supabase/supabase-flutter/commit/d1ecabd77881a0488d2d4b41ea5ee5abda6c5c35)) + + ## 2024-09-30 ### Changes diff --git a/packages/functions_client/CHANGELOG.md b/packages/functions_client/CHANGELOG.md index e193b627..4a2c7204 100644 --- a/packages/functions_client/CHANGELOG.md +++ b/packages/functions_client/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.4.0 + + - **FEAT**: Add logging ([#1042](https://github.com/supabase/supabase-flutter/issues/1042)). ([d1ecabd7](https://github.com/supabase/supabase-flutter/commit/d1ecabd77881a0488d2d4b41ea5ee5abda6c5c35)) + ## 2.3.3 - Update a dependency to the latest release. diff --git a/packages/functions_client/lib/src/version.dart b/packages/functions_client/lib/src/version.dart index 93792933..3dcb91ee 100644 --- a/packages/functions_client/lib/src/version.dart +++ b/packages/functions_client/lib/src/version.dart @@ -1 +1 @@ -const version = '2.3.3'; +const version = '2.4.0'; diff --git a/packages/functions_client/pubspec.yaml b/packages/functions_client/pubspec.yaml index 338e294e..368488a4 100644 --- a/packages/functions_client/pubspec.yaml +++ b/packages/functions_client/pubspec.yaml @@ -1,6 +1,6 @@ name: functions_client description: A dart client library for the Supabase functions. -version: 2.3.3 +version: 2.4.0 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/functions_client' documentation: 'https://supabase.com/docs/reference/dart/functions-invoke' diff --git a/packages/gotrue/CHANGELOG.md b/packages/gotrue/CHANGELOG.md index c4da629c..21b2955a 100644 --- a/packages/gotrue/CHANGELOG.md +++ b/packages/gotrue/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.10.0 + + - **FIX**: Rename logger from gotrue to auth ([#1055](https://github.com/supabase/supabase-flutter/issues/1055)). ([eea1ea6c](https://github.com/supabase/supabase-flutter/commit/eea1ea6c4e200e8ffc52d8343c0684c19670e3ed)) + - **FEAT**: Add logging ([#1042](https://github.com/supabase/supabase-flutter/issues/1042)). ([d1ecabd7](https://github.com/supabase/supabase-flutter/commit/d1ecabd77881a0488d2d4b41ea5ee5abda6c5c35)) + ## 2.9.0 - **FIX**: Support all mfa auth methods ([#1030](https://github.com/supabase/supabase-flutter/issues/1030)). ([773b7de7](https://github.com/supabase/supabase-flutter/commit/773b7de74461ca3ea857d11b1abdfdf35fb540d4)) diff --git a/packages/gotrue/lib/src/version.dart b/packages/gotrue/lib/src/version.dart index 629ad7b5..dbfc0304 100644 --- a/packages/gotrue/lib/src/version.dart +++ b/packages/gotrue/lib/src/version.dart @@ -1 +1 @@ -const version = '2.9.0'; +const version = '2.10.0'; diff --git a/packages/gotrue/pubspec.yaml b/packages/gotrue/pubspec.yaml index 89e8a7d7..f5e9e9d2 100644 --- a/packages/gotrue/pubspec.yaml +++ b/packages/gotrue/pubspec.yaml @@ -1,6 +1,6 @@ name: gotrue description: A dart client library for the GoTrue API. -version: 2.9.0 +version: 2.10.0 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/gotrue' documentation: 'https://supabase.com/docs/reference/dart/auth-signup' diff --git a/packages/postgrest/CHANGELOG.md b/packages/postgrest/CHANGELOG.md index 2d712dba..4fc4f092 100644 --- a/packages/postgrest/CHANGELOG.md +++ b/packages/postgrest/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.3.0 + + - **FEAT**: Add logging ([#1042](https://github.com/supabase/supabase-flutter/issues/1042)). ([d1ecabd7](https://github.com/supabase/supabase-flutter/commit/d1ecabd77881a0488d2d4b41ea5ee5abda6c5c35)) + ## 2.2.0 - **FEAT**: Add setHeader method on postgrest builder ([#1003](https://github.com/supabase/supabase-flutter/issues/1003)). ([efe8e5df](https://github.com/supabase/supabase-flutter/commit/efe8e5df7935b75b580e2ead01b9c08ac7b94c2c)) diff --git a/packages/postgrest/lib/src/version.dart b/packages/postgrest/lib/src/version.dart index 63ac0966..5f7e4e35 100644 --- a/packages/postgrest/lib/src/version.dart +++ b/packages/postgrest/lib/src/version.dart @@ -1 +1 @@ -const version = '2.2.0'; +const version = '2.3.0'; diff --git a/packages/postgrest/pubspec.yaml b/packages/postgrest/pubspec.yaml index ade23ef1..a6b33ccc 100644 --- a/packages/postgrest/pubspec.yaml +++ b/packages/postgrest/pubspec.yaml @@ -1,6 +1,6 @@ name: postgrest description: PostgREST client for Dart. This library provides an ORM interface to PostgREST. -version: 2.2.0 +version: 2.3.0 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/postgrest' documentation: 'https://supabase.com/docs/reference/dart/select' diff --git a/packages/realtime_client/CHANGELOG.md b/packages/realtime_client/CHANGELOG.md index 30579a52..aaeeea7f 100644 --- a/packages/realtime_client/CHANGELOG.md +++ b/packages/realtime_client/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.4.0 + + - **FEAT**: Add logging ([#1042](https://github.com/supabase/supabase-flutter/issues/1042)). ([d1ecabd7](https://github.com/supabase/supabase-flutter/commit/d1ecabd77881a0488d2d4b41ea5ee5abda6c5c35)) + ## 2.3.0 - **FIX**: Better stream and access token management ([#1019](https://github.com/supabase/supabase-flutter/issues/1019)). ([4a8b6416](https://github.com/supabase/supabase-flutter/commit/4a8b641661da4ce9b6ddaea64793df58411809f7)) diff --git a/packages/realtime_client/lib/src/version.dart b/packages/realtime_client/lib/src/version.dart index 5f7e4e35..3dcb91ee 100644 --- a/packages/realtime_client/lib/src/version.dart +++ b/packages/realtime_client/lib/src/version.dart @@ -1 +1 @@ -const version = '2.3.0'; +const version = '2.4.0'; diff --git a/packages/realtime_client/pubspec.yaml b/packages/realtime_client/pubspec.yaml index 3c67055c..8c7ef8cd 100644 --- a/packages/realtime_client/pubspec.yaml +++ b/packages/realtime_client/pubspec.yaml @@ -1,6 +1,6 @@ name: realtime_client description: Listens to changes in a PostgreSQL Database and via websockets. This is for usage with Supabase Realtime server. -version: 2.3.0 +version: 2.4.0 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/realtime_client' documentation: 'https://supabase.com/docs/reference/dart/subscribe' diff --git a/packages/storage_client/CHANGELOG.md b/packages/storage_client/CHANGELOG.md index e479660e..af070acf 100644 --- a/packages/storage_client/CHANGELOG.md +++ b/packages/storage_client/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.2.0 + + - **FEAT**: Add logging ([#1042](https://github.com/supabase/supabase-flutter/issues/1042)). ([d1ecabd7](https://github.com/supabase/supabase-flutter/commit/d1ecabd77881a0488d2d4b41ea5ee5abda6c5c35)) + ## 2.1.0 - **FEAT**(storage_client): Support copy/move to different bucket ([#1043](https://github.com/supabase/supabase-flutter/issues/1043)). ([e095c14e](https://github.com/supabase/supabase-flutter/commit/e095c14e29e82cceb96220b5d73e67d991909478)) diff --git a/packages/storage_client/lib/src/version.dart b/packages/storage_client/lib/src/version.dart index fcf11af0..63ac0966 100644 --- a/packages/storage_client/lib/src/version.dart +++ b/packages/storage_client/lib/src/version.dart @@ -1 +1 @@ -const version = '2.1.0'; +const version = '2.2.0'; diff --git a/packages/storage_client/pubspec.yaml b/packages/storage_client/pubspec.yaml index f8b41163..7b8e164b 100644 --- a/packages/storage_client/pubspec.yaml +++ b/packages/storage_client/pubspec.yaml @@ -1,6 +1,6 @@ name: storage_client description: Dart client library to interact with Supabase Storage. Supabase Storage provides an interface for managing Files stored in S3, using Postgres to manage permissions. -version: 2.1.0 +version: 2.2.0 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/storage_client' documentation: 'https://supabase.com/docs/reference/dart/storage-createbucket' diff --git a/packages/supabase/CHANGELOG.md b/packages/supabase/CHANGELOG.md index 7aa7db41..a665bb61 100644 --- a/packages/supabase/CHANGELOG.md +++ b/packages/supabase/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.5.0 + + - **FEAT**: Add logging ([#1042](https://github.com/supabase/supabase-flutter/issues/1042)). ([d1ecabd7](https://github.com/supabase/supabase-flutter/commit/d1ecabd77881a0488d2d4b41ea5ee5abda6c5c35)) + ## 2.4.0 - **FIX**: Better stream and access token management ([#1019](https://github.com/supabase/supabase-flutter/issues/1019)). ([4a8b6416](https://github.com/supabase/supabase-flutter/commit/4a8b641661da4ce9b6ddaea64793df58411809f7)) diff --git a/packages/supabase/lib/src/version.dart b/packages/supabase/lib/src/version.dart index 3dcb91ee..726228b1 100644 --- a/packages/supabase/lib/src/version.dart +++ b/packages/supabase/lib/src/version.dart @@ -1 +1 @@ -const version = '2.4.0'; +const version = '2.5.0'; diff --git a/packages/supabase/pubspec.yaml b/packages/supabase/pubspec.yaml index 8932b4e9..8f74aa17 100644 --- a/packages/supabase/pubspec.yaml +++ b/packages/supabase/pubspec.yaml @@ -1,6 +1,6 @@ name: supabase description: A dart client for Supabase. This client makes it simple for developers to build secure and scalable products. -version: 2.4.0 +version: 2.5.0 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/supabase' documentation: 'https://supabase.com/docs/reference/dart/introduction' @@ -9,12 +9,12 @@ environment: sdk: '>=3.0.0 <4.0.0' dependencies: - functions_client: 2.3.3 - gotrue: 2.9.0 + functions_client: 2.4.0 + gotrue: 2.10.0 http: '>=0.13.5 <2.0.0' - postgrest: 2.2.0 - realtime_client: 2.3.0 - storage_client: 2.1.0 + postgrest: 2.3.0 + realtime_client: 2.4.0 + storage_client: 2.2.0 rxdart: '>=0.27.5 <0.29.0' yet_another_json_isolate: 2.0.3 logging: ^1.2.0 diff --git a/packages/supabase_flutter/CHANGELOG.md b/packages/supabase_flutter/CHANGELOG.md index 41eb7fb5..3ce7d30f 100644 --- a/packages/supabase_flutter/CHANGELOG.md +++ b/packages/supabase_flutter/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.8.0 + + - **FIX**: Rename logger from gotrue to auth ([#1055](https://github.com/supabase/supabase-flutter/issues/1055)). ([eea1ea6c](https://github.com/supabase/supabase-flutter/commit/eea1ea6c4e200e8ffc52d8343c0684c19670e3ed)) + - **FEAT**: Add logging ([#1042](https://github.com/supabase/supabase-flutter/issues/1042)). ([d1ecabd7](https://github.com/supabase/supabase-flutter/commit/d1ecabd77881a0488d2d4b41ea5ee5abda6c5c35)) + ## 2.7.0 - **FIX**: Better stream and access token management ([#1019](https://github.com/supabase/supabase-flutter/issues/1019)). ([4a8b6416](https://github.com/supabase/supabase-flutter/commit/4a8b641661da4ce9b6ddaea64793df58411809f7)) diff --git a/packages/supabase_flutter/example/pubspec.yaml b/packages/supabase_flutter/example/pubspec.yaml index c8aeaa6d..8c8a2f83 100644 --- a/packages/supabase_flutter/example/pubspec.yaml +++ b/packages/supabase_flutter/example/pubspec.yaml @@ -11,7 +11,7 @@ environment: dependencies: flutter: sdk: flutter - supabase_flutter: ^2.7.0 + supabase_flutter: ^2.8.0 dev_dependencies: flutter_test: diff --git a/packages/supabase_flutter/lib/src/version.dart b/packages/supabase_flutter/lib/src/version.dart index 71c30ec0..2cdfc59d 100644 --- a/packages/supabase_flutter/lib/src/version.dart +++ b/packages/supabase_flutter/lib/src/version.dart @@ -1 +1 @@ -const version = '2.7.0'; +const version = '2.8.0'; diff --git a/packages/supabase_flutter/pubspec.yaml b/packages/supabase_flutter/pubspec.yaml index fd48b230..f569b6ba 100644 --- a/packages/supabase_flutter/pubspec.yaml +++ b/packages/supabase_flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: supabase_flutter description: Flutter integration for Supabase. This package makes it simple for developers to build secure and scalable products. -version: 2.7.0 +version: 2.8.0 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/supabase_flutter' documentation: 'https://supabase.com/docs/reference/dart/introduction' @@ -17,7 +17,7 @@ dependencies: sdk: flutter http: '>=0.13.4 <2.0.0' meta: ^1.7.0 - supabase: 2.4.0 + supabase: 2.5.0 url_launcher: ^6.1.2 path_provider: ^2.0.0 shared_preferences: ^2.0.0 From 598540d2aac27d64e6be5d4a2e855d928f87fda6 Mon Sep 17 00:00:00 2001 From: daffypiwiit Date: Wed, 9 Oct 2024 13:26:21 -0600 Subject: [PATCH 12/29] fix: Send metadata inviteUserByEmail() (#1061) FIX send metadata inviteUserByEmail() This fix sends metadata when use inviteUserByEmail() --- packages/gotrue/lib/src/gotrue_admin_api.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/gotrue/lib/src/gotrue_admin_api.dart b/packages/gotrue/lib/src/gotrue_admin_api.dart index 4fc987d9..2f9a1827 100644 --- a/packages/gotrue/lib/src/gotrue_admin_api.dart +++ b/packages/gotrue/lib/src/gotrue_admin_api.dart @@ -105,7 +105,10 @@ class GoTrueAdminApi { String? redirectTo, Map? data, }) async { - final body = {'email': email}; + final body = { + 'email': email, + if (data != null) 'data': data, + }; final fetchOptions = GotrueRequestOptions( headers: _headers, body: body, From 12007b3206c15a87c373af46bc5f4a17aa646f62 Mon Sep 17 00:00:00 2001 From: Oskar <9609187+Oskar-Nilsen-Roos@users.noreply.github.com> Date: Sun, 20 Oct 2024 04:05:30 +0200 Subject: [PATCH 13/29] fix: Updated gotrue types.dart to include slackOidc (#1066) Updated gotrue types.dart to include slackOidc Gotrue exported OAuthProvider doesn't currently include Slack OIDC provider option. Therefore, trying to pass OAuthProvider.slack to sign in will set the wrong "provider" url param. --- packages/gotrue/lib/src/types/types.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/gotrue/lib/src/types/types.dart b/packages/gotrue/lib/src/types/types.dart index a2d11a69..c1001433 100644 --- a/packages/gotrue/lib/src/types/types.dart +++ b/packages/gotrue/lib/src/types/types.dart @@ -25,6 +25,7 @@ enum OAuthProvider { linkedinOidc, notion, slack, + slackOidc, spotify, twitch, twitter, From be998fda6365bcf157dc96e3e4ea7009415045ee Mon Sep 17 00:00:00 2001 From: Tonnelier Mickael Date: Mon, 21 Oct 2024 15:04:07 +0200 Subject: [PATCH 14/29] feat: Add Keycloak Provider on signInWithIdToken (#1068) * feat: Add Keycloak Provider on signInWithIdToken * feat: change order verification --------- Co-authored-by: Mickael TONNELIER --- packages/gotrue/lib/src/gotrue_client.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/gotrue/lib/src/gotrue_client.dart b/packages/gotrue/lib/src/gotrue_client.dart index e46afecc..63f5c85f 100644 --- a/packages/gotrue/lib/src/gotrue_client.dart +++ b/packages/gotrue/lib/src/gotrue_client.dart @@ -382,7 +382,7 @@ class GoTrueClient { /// Allows signing in with an ID token issued by certain supported providers. /// The [idToken] is verified for validity and a new session is established. - /// This method of signing in only supports [OAuthProvider.google], [OAuthProvider.apple] or [OAuthProvider.kakao]. + /// This method of signing in only supports [OAuthProvider.google], [OAuthProvider.apple], [OAuthProvider.kakao] or [OAuthProvider.keycloak]. /// /// If the ID token contains an `at_hash` claim, then [accessToken] must be /// provided to compare its hash with the value in the ID token. @@ -404,9 +404,10 @@ class GoTrueClient { }) async { if (provider != OAuthProvider.google && provider != OAuthProvider.apple && - provider != OAuthProvider.kakao) { + provider != OAuthProvider.kakao && + provider != OAuthProvider.keycloak) { throw AuthException('Provider must be ' - '${OAuthProvider.google.name}, ${OAuthProvider.apple.name} or ${OAuthProvider.kakao.name}.'); + '${OAuthProvider.google.name}, ${OAuthProvider.apple.name}, ${OAuthProvider.kakao.name} or ${OAuthProvider.keycloak.name}.'); } final response = await _fetch.request( From 7e7bc0cac722c0f3404b3f9320c536454bc51cea Mon Sep 17 00:00:00 2001 From: Kalil <62212125+bkalil@users.noreply.github.com> Date: Wed, 6 Nov 2024 23:40:19 +0100 Subject: [PATCH 15/29] fix: Add equality to user attributes classes (#1070) --- .../gotrue/lib/src/types/user_attributes.dart | 49 +++++++ .../test/src/types/user_attributes_test.dart | 128 ++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 packages/gotrue/test/src/types/user_attributes_test.dart diff --git a/packages/gotrue/lib/src/types/user_attributes.dart b/packages/gotrue/lib/src/types/user_attributes.dart index 60fd12d2..25da49b4 100644 --- a/packages/gotrue/lib/src/types/user_attributes.dart +++ b/packages/gotrue/lib/src/types/user_attributes.dart @@ -1,3 +1,5 @@ +import 'package:collection/collection.dart'; + class UserAttributes { /// The user's email. String? email; @@ -35,6 +37,29 @@ class UserAttributes { if (data != null) 'data': data, }; } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! UserAttributes) return false; + + final mapEquals = const DeepCollectionEquality().equals; + + return other.email == email && + other.phone == phone && + other.password == password && + other.nonce == nonce && + mapEquals(other.data, data); + } + + @override + int get hashCode { + return email.hashCode ^ + phone.hashCode ^ + password.hashCode ^ + nonce.hashCode ^ + data.hashCode; + } } class AdminUserAttributes extends UserAttributes { @@ -102,4 +127,28 @@ class AdminUserAttributes extends UserAttributes { if (banDuration != null) 'ban_duration': banDuration, }; } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! AdminUserAttributes) return false; + + final mapEquals = const DeepCollectionEquality().equals; + + return mapEquals(other.userMetadata, userMetadata) && + mapEquals(other.appMetadata, appMetadata) && + other.emailConfirm == emailConfirm && + other.phoneConfirm == phoneConfirm && + other.banDuration == banDuration; + } + + @override + int get hashCode { + return super.hashCode ^ + userMetadata.hashCode ^ + appMetadata.hashCode ^ + emailConfirm.hashCode ^ + phoneConfirm.hashCode ^ + banDuration.hashCode; + } } diff --git a/packages/gotrue/test/src/types/user_attributes_test.dart b/packages/gotrue/test/src/types/user_attributes_test.dart new file mode 100644 index 00000000..e158e026 --- /dev/null +++ b/packages/gotrue/test/src/types/user_attributes_test.dart @@ -0,0 +1,128 @@ +import 'package:gotrue/src/types/user_attributes.dart'; +import 'package:test/test.dart'; + +void main() { + late String email; + late String phone; + late String password; + late String nonce; + late Map data; + + setUp(() { + email = 'john@supabase.com'; + phone = '+1234567890'; + password = 'password'; + nonce = 'nonce'; + data = {'first_name': 'John', 'last_name': 'Doe'}; + }); + + group('User attributes', () { + late UserAttributes userAttributesOne; + late UserAttributes userAttributesTwo; + + setUp(() { + userAttributesOne = UserAttributes( + email: email, + phone: phone, + password: password, + nonce: nonce, + data: data, + ); + + userAttributesTwo = UserAttributes( + email: email, + phone: phone, + password: password, + nonce: nonce, + data: data, + ); + }); + + test('Attributes are equals', () { + // assert + expect(userAttributesOne, equals(userAttributesTwo)); + }); + + test('Attributes are not equals', () { + // arrange + final userAttributesThree = UserAttributes( + email: 'email', + phone: phone, + password: password, + nonce: nonce, + data: {'first_name': 'Jane', 'last_name': 'Doe'}, + ); + + // assert + expect(userAttributesOne, isNot(equals(userAttributesThree))); + }); + }); + + group('Admin user attributes', () { + late AdminUserAttributes adminUserAttributesOne; + late AdminUserAttributes adminUserAttributesTwo; + + late Map userMetadata; + late Map appMetadata; + late bool emailConfirm; + late bool phoneConfirm; + late String banDuration; + + setUp(() { + userMetadata = {'first_name': 'John', 'last_name': 'Doe'}; + appMetadata = { + 'roles': ['admin'] + }; + emailConfirm = true; + phoneConfirm = true; + banDuration = '1d'; + + adminUserAttributesOne = AdminUserAttributes( + email: email, + phone: phone, + password: password, + data: data, + userMetadata: userMetadata, + appMetadata: appMetadata, + emailConfirm: emailConfirm, + phoneConfirm: phoneConfirm, + banDuration: banDuration, + ); + + adminUserAttributesTwo = AdminUserAttributes( + email: email, + phone: phone, + password: password, + data: data, + userMetadata: userMetadata, + appMetadata: appMetadata, + emailConfirm: emailConfirm, + phoneConfirm: phoneConfirm, + banDuration: banDuration, + ); + }); + + test('Attributes are equals', () { + // assert + expect(adminUserAttributesOne, equals(adminUserAttributesTwo)); + }); + + test('Attributes are not equals', () { + // arrange + final adminUserAttributesThree = AdminUserAttributes( + email: email, + phone: phone, + password: password, + data: data, + userMetadata: {'first_name': 'Jane', 'last_name': 'Doe'}, + appMetadata: appMetadata, + emailConfirm: false, + phoneConfirm: false, + banDuration: 'banDuration', + ); + + // assert + expect(adminUserAttributesOne, isNot(equals(adminUserAttributesThree))); + }); + }); +} From d0a04154ff56d40d00e1c9282d8ba859681c7275 Mon Sep 17 00:00:00 2001 From: Vinzent Date: Tue, 12 Nov 2024 00:09:54 +0100 Subject: [PATCH 16/29] feat: Read-only access mode rpc (#1081) --- infra/postgrest/db/00-schema.sql | 9 +++- packages/postgrest/lib/src/postgrest.dart | 15 ++++++- .../postgrest/lib/src/postgrest_builder.dart | 9 ++++ .../lib/src/postgrest_filter_builder.dart | 9 ---- .../lib/src/postgrest_rpc_builder.dart | 28 +++++++++++- packages/postgrest/test/basic_test.dart | 44 ++++++++++++++++++- .../postgrest/test/custom_http_client.dart | 7 ++- packages/postgrest/test/reset_helper.dart | 1 - .../supabase/lib/src/supabase_client.dart | 5 ++- .../lib/src/supabase_query_schema.dart | 9 +++- 10 files changed, 115 insertions(+), 21 deletions(-) diff --git a/infra/postgrest/db/00-schema.sql b/infra/postgrest/db/00-schema.sql index a1a2b6fa..b40df949 100644 --- a/infra/postgrest/db/00-schema.sql +++ b/infra/postgrest/db/00-schema.sql @@ -68,6 +68,13 @@ CREATE FUNCTION public.get_integer() End; $$ LANGUAGE plpgsql; +CREATE FUNCTION public.get_array_element(arr integer[], index integer) + RETURNS integer AS $$ + BEGIN + RETURN arr[index]; + END; +$$ LANGUAGE plpgsql; + -- SECOND SCHEMA USERS CREATE TYPE personal.user_status AS ENUM ('ONLINE', 'OFFLINE'); CREATE TABLE personal.users( @@ -103,4 +110,4 @@ CREATE TABLE public.addresses ( id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, username text REFERENCES users NOT NULL, location geometry(POINT,4326) -); \ No newline at end of file +); diff --git a/packages/postgrest/lib/src/postgrest.dart b/packages/postgrest/lib/src/postgrest.dart index 6bace201..9676854f 100644 --- a/packages/postgrest/lib/src/postgrest.dart +++ b/packages/postgrest/lib/src/postgrest.dart @@ -81,7 +81,17 @@ class PostgrestClient { ); } - /// Perform a stored procedure call. + /// {@template postgrest_rpc} + /// Performs a stored procedure call. + /// + /// [fn] is the name of the function to call. + /// + /// [params] is an optinal object to pass as arguments to the function call. + /// + /// When [get] is set to `true`, the function will be called with read-only + /// access mode. + /// + /// {@endtemplate} /// /// ```dart /// supabase.rpc('get_status', params: {'name_param': 'supabot'}) @@ -89,6 +99,7 @@ class PostgrestClient { PostgrestFilterBuilder rpc( String fn, { Map? params, + bool get = false, }) { final url = '${this.url}/rpc/$fn'; return PostgrestRpcBuilder( @@ -97,7 +108,7 @@ class PostgrestClient { schema: _schema, httpClient: httpClient, isolate: _isolate, - ).rpc(params); + ).rpc(params, get); } Future dispose() async { diff --git a/packages/postgrest/lib/src/postgrest_builder.dart b/packages/postgrest/lib/src/postgrest_builder.dart index d7ef48e3..822ac03d 100644 --- a/packages/postgrest/lib/src/postgrest_builder.dart +++ b/packages/postgrest/lib/src/postgrest_builder.dart @@ -348,6 +348,15 @@ class PostgrestBuilder implements Future { return _url.replace(queryParameters: searchParams); } + /// Convert list filter to query params string + String _cleanFilterArray(List filter) { + if (filter.every((element) => element is num)) { + return filter.map((s) => '$s').join(','); + } else { + return filter.map((s) => '"$s"').join(','); + } + } + @override Stream asStream() { final controller = StreamController.broadcast(); diff --git a/packages/postgrest/lib/src/postgrest_filter_builder.dart b/packages/postgrest/lib/src/postgrest_filter_builder.dart index a289f953..97798fe6 100644 --- a/packages/postgrest/lib/src/postgrest_filter_builder.dart +++ b/packages/postgrest/lib/src/postgrest_filter_builder.dart @@ -7,15 +7,6 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { PostgrestFilterBuilder copyWithUrl(Uri url) => PostgrestFilterBuilder(_copyWith(url: url)); - /// Convert list filter to query params string - String _cleanFilterArray(List filter) { - if (filter.every((element) => element is num)) { - return filter.map((s) => '$s').join(','); - } else { - return filter.map((s) => '"$s"').join(','); - } - } - /// Finds all rows which doesn't satisfy the filter. /// /// ```dart diff --git a/packages/postgrest/lib/src/postgrest_rpc_builder.dart b/packages/postgrest/lib/src/postgrest_rpc_builder.dart index 49df9eaf..9f5190e5 100644 --- a/packages/postgrest/lib/src/postgrest_rpc_builder.dart +++ b/packages/postgrest/lib/src/postgrest_rpc_builder.dart @@ -17,12 +17,36 @@ class PostgrestRpcBuilder extends RawPostgrestBuilder { ), ); - /// Performs stored procedures on the database. + /// {@macro postgrest_rpc} PostgrestFilterBuilder rpc([ Object? params, + bool get = false, ]) { + var newUrl = _url; + final String method; + if (get) { + method = METHOD_GET; + if (params is Map) { + for (final entry in params.entries) { + assert(entry.key is String, + "RPC params map keys must be of type String"); + + final MapEntry(:key, :value) = entry; + final formattedValue = + value is List ? '{${_cleanFilterArray(value)}}' : value; + newUrl = + appendSearchParams(key.toString(), '$formattedValue', newUrl); + } + } else { + throw ArgumentError.value(params, 'params', 'argument must be a Map'); + } + } else { + method = METHOD_POST; + } + return PostgrestFilterBuilder(_copyWithType( - method: METHOD_POST, + method: method, + url: newUrl, body: params, )); } diff --git a/packages/postgrest/test/basic_test.dart b/packages/postgrest/test/basic_test.dart index 02f98275..a65f88bc 100644 --- a/packages/postgrest/test/basic_test.dart +++ b/packages/postgrest/test/basic_test.dart @@ -63,6 +63,29 @@ void main() { expect(res, isA()); }); + test('stored procedure with array parameter', () async { + final res = await postgrest.rpc( + 'get_array_element', + params: { + 'arr': [37, 420, 64], + 'index': 2 + }, + ); + expect(res, 420); + }); + + test('stored procedure with read-only access mode', () async { + final res = await postgrest.rpc( + 'get_array_element', + params: { + 'arr': [37, 420, 64], + 'index': 2 + }, + get: true, + ); + expect(res, 420); + }); + test('custom headers', () async { final postgrest = PostgrestClient(rootUrl, headers: {'apikey': 'foo'}); expect(postgrest.headers['apikey'], 'foo'); @@ -448,10 +471,12 @@ void main() { }); }); group("Custom http client", () { + CustomHttpClient customHttpClient = CustomHttpClient(); setUp(() { + customHttpClient = CustomHttpClient(); postgrestCustomHttpClient = PostgrestClient( rootUrl, - httpClient: CustomHttpClient(), + httpClient: customHttpClient, ); }); @@ -486,6 +511,23 @@ void main() { 'Stored procedure was able to be called, even tho it does not exist'); } on PostgrestException catch (error) { expect(error.code, '420'); + expect(customHttpClient.lastRequest?.method, "POST"); + } + }); + + test('stored procedure call in read-only access mode', () async { + try { + await postgrestCustomHttpClient.rpc( + 'get_status', + params: {'name_param': 'supabot'}, + get: true, + ); + fail( + 'Stored procedure was able to be called, even tho it does not exist'); + } on PostgrestException catch (error) { + expect(error.code, '420'); + expect(customHttpClient.lastRequest?.method, "GET"); + expect(customHttpClient.lastBody, isEmpty); } }); }); diff --git a/packages/postgrest/test/custom_http_client.dart b/packages/postgrest/test/custom_http_client.dart index f2da3983..7ecc4b81 100644 --- a/packages/postgrest/test/custom_http_client.dart +++ b/packages/postgrest/test/custom_http_client.dart @@ -1,10 +1,15 @@ +import 'dart:typed_data'; + import 'package:http/http.dart'; class CustomHttpClient extends BaseClient { BaseRequest? lastRequest; + Uint8List? lastBody; @override Future send(BaseRequest request) async { lastRequest = request; + final bodyStream = request.finalize(); + lastBody = await bodyStream.toBytes(); if (request.url.path.endsWith("empty-succ")) { return StreamedResponse( @@ -15,7 +20,7 @@ class CustomHttpClient extends BaseClient { } //Return custom status code to check for usage of this client. return StreamedResponse( - request.finalize(), + Stream.value(lastBody!), 420, request: request, ); diff --git a/packages/postgrest/test/reset_helper.dart b/packages/postgrest/test/reset_helper.dart index 774d5044..456d68c3 100644 --- a/packages/postgrest/test/reset_helper.dart +++ b/packages/postgrest/test/reset_helper.dart @@ -15,7 +15,6 @@ class ResetHelper { _users = (await _postgrest.from('users').select()); _channels = await _postgrest.from('channels').select(); _messages = await _postgrest.from('messages').select(); - print('messages has ${_messages.length} items'); _reactions = await _postgrest.from('reactions').select(); _addresses = await _postgrest.from('addresses').select(); } diff --git a/packages/supabase/lib/src/supabase_client.dart b/packages/supabase/lib/src/supabase_client.dart index db4ca3b4..41b47794 100644 --- a/packages/supabase/lib/src/supabase_client.dart +++ b/packages/supabase/lib/src/supabase_client.dart @@ -200,13 +200,14 @@ class SupabaseClient { ); } - /// Perform a stored procedure call. + /// {@macro postgrest_rpc} PostgrestFilterBuilder rpc( String fn, { Map? params, + get = false, }) { rest.headers.addAll({...rest.headers, ...headers}); - return rest.rpc(fn, params: params); + return rest.rpc(fn, params: params, get: get); } /// Creates a Realtime channel with Broadcast, Presence, and Postgres Changes. diff --git a/packages/supabase/lib/src/supabase_query_schema.dart b/packages/supabase/lib/src/supabase_query_schema.dart index 0b73fa2f..d40c4940 100644 --- a/packages/supabase/lib/src/supabase_query_schema.dart +++ b/packages/supabase/lib/src/supabase_query_schema.dart @@ -48,13 +48,18 @@ class SupabaseQuerySchema { ); } - /// Perform a stored procedure call. + /// {@macro postgrest_rpc} PostgrestFilterBuilder rpc( String fn, { Map? params, + bool get = false, }) { _rest.headers.addAll({..._rest.headers, ..._headers}); - return _rest.rpc(fn, params: params); + return _rest.rpc( + fn, + params: params, + get: get, + ); } SupabaseQuerySchema schema(String schema) { From ba5ccd4e9fc1e51324f8818f6522fb40ade704e9 Mon Sep 17 00:00:00 2001 From: Vinzent Date: Tue, 12 Nov 2024 00:10:09 +0100 Subject: [PATCH 17/29] chore: Support mime 2.0.0 (#1079) --- packages/storage_client/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/storage_client/pubspec.yaml b/packages/storage_client/pubspec.yaml index 7b8e164b..7dc2e799 100644 --- a/packages/storage_client/pubspec.yaml +++ b/packages/storage_client/pubspec.yaml @@ -11,7 +11,7 @@ environment: dependencies: http: '>=0.13.4 <2.0.0' http_parser: ^4.0.1 - mime: ^1.0.2 + mime: '>=1.0.2 <3.0.0' retry: ^3.1.0 meta: ^1.7.0 logging: ^1.2.0 From fc9ad2c94a02921ca8ced4564d9bcd8cde2c2397 Mon Sep 17 00:00:00 2001 From: Vinzent Date: Tue, 12 Nov 2024 00:10:27 +0100 Subject: [PATCH 18/29] fix: Support custom access token (#1073) --- .../supabase/lib/src/supabase_client.dart | 17 +-- .../supabase_flutter/lib/src/supabase.dart | 102 +++++++++++++++--- .../lib/src/supabase_auth.dart | 68 +----------- .../test/supabase_flutter_test.dart | 23 +++- 4 files changed, 125 insertions(+), 85 deletions(-) diff --git a/packages/supabase/lib/src/supabase_client.dart b/packages/supabase/lib/src/supabase_client.dart index 41b47794..4f500b8b 100644 --- a/packages/supabase/lib/src/supabase_client.dart +++ b/packages/supabase/lib/src/supabase_client.dart @@ -52,7 +52,7 @@ class SupabaseClient { final Client? _httpClient; late final Client _authHttpClient; - late final GoTrueClient _authInstance; + GoTrueClient? _authInstance; /// Supabase Functions allows you to deploy and invoke edge functions. late final FunctionsClient functions; @@ -160,7 +160,7 @@ class SupabaseClient { GoTrueClient get auth { if (accessToken == null) { - return _authInstance; + return _authInstance!; } else { throw AuthException( 'Supabase Client is configured with the accessToken option, accessing supabase.auth is not possible.', @@ -240,11 +240,13 @@ class SupabaseClient { return await accessToken!(); } - if (_authInstance.currentSession?.isExpired ?? false) { + final authInstance = _authInstance!; + + if (authInstance.currentSession?.isExpired ?? false) { try { - await _authInstance.refreshSession(); + await authInstance.refreshSession(); } catch (error, stackTrace) { - final expiresAt = _authInstance.currentSession?.expiresAt; + final expiresAt = authInstance.currentSession?.expiresAt; if (expiresAt != null) { // Failed to refresh the token. final isExpiredWithoutMargin = DateTime.now() @@ -261,14 +263,14 @@ class SupabaseClient { } } } - return _authInstance.currentSession?.accessToken; + return authInstance.currentSession?.accessToken; } Future dispose() async { _log.fine('Dispose SupabaseClient'); await _authStateSubscription?.cancel(); await _isolate.dispose(); - auth.dispose(); + _authInstance?.dispose(); } GoTrueClient _initSupabaseAuthClient({ @@ -333,6 +335,7 @@ class SupabaseClient { ); } + /// Requires the `auth` instance, so no custom `accessToken` is allowed. Map _getAuthHeaders() { final authBearer = auth.currentSession?.accessToken ?? _supabaseKey; final defaultHeaders = { diff --git a/packages/supabase_flutter/lib/src/supabase.dart b/packages/supabase_flutter/lib/src/supabase.dart index 1ff4b3cb..952460b0 100644 --- a/packages/supabase_flutter/lib/src/supabase.dart +++ b/packages/supabase_flutter/lib/src/supabase.dart @@ -1,8 +1,8 @@ import 'dart:async'; -import 'dart:developer' as dev; import 'package:async/async.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; import 'package:http/http.dart'; import 'package:logging/logging.dart'; import 'package:supabase/supabase.dart'; @@ -32,7 +32,7 @@ final _log = Logger('supabase.supabase_flutter'); /// See also: /// /// * [SupabaseAuth] -class Supabase { +class Supabase with WidgetsBindingObserver { /// Gets the current supabase instance. /// /// An [AssertionError] is thrown if supabase isn't initialized yet. @@ -126,15 +126,18 @@ class Supabase { accessToken: accessToken, ); - _instance._supabaseAuth = SupabaseAuth(); - await _instance._supabaseAuth.initialize(options: authOptions); + if (accessToken == null) { + final supabaseAuth = SupabaseAuth(); + _instance._supabaseAuth = supabaseAuth; + await supabaseAuth.initialize(options: authOptions); - // Wrap `recoverSession()` in a `CancelableOperation` so that it can be canceled in dispose - // if still in progress - _instance._restoreSessionCancellableOperation = - CancelableOperation.fromFuture( - _instance._supabaseAuth.recoverSession(), - ); + // Wrap `recoverSession()` in a `CancelableOperation` so that it can be canceled in dispose + // if still in progress + _instance._restoreSessionCancellableOperation = + CancelableOperation.fromFuture( + supabaseAuth.recoverSession(), + ); + } _log.info('***** Supabase init completed *****'); @@ -144,6 +147,8 @@ class Supabase { Supabase._(); static final Supabase _instance = Supabase._(); + static WidgetsBinding? get _widgetsBindingInstance => WidgetsBinding.instance; + bool _initialized = false; /// The supabase client for this instance @@ -151,13 +156,15 @@ class Supabase { /// Throws an error if [Supabase.initialize] was not called. late SupabaseClient client; - late SupabaseAuth _supabaseAuth; + SupabaseAuth? _supabaseAuth; bool _debugEnable = false; /// Wraps the `recoverSession()` call so that it can be terminated when `dispose()` is called late CancelableOperation _restoreSessionCancellableOperation; + CancelableOperation? _realtimeReconnectOperation; + StreamSubscription? _logSubscription; /// Dispose the instance to free up resources. @@ -165,7 +172,8 @@ class Supabase { await _restoreSessionCancellableOperation.cancel(); _logSubscription?.cancel(); client.dispose(); - _instance._supabaseAuth.dispose(); + _instance._supabaseAuth?.dispose(); + _widgetsBindingInstance?.removeObserver(this); _initialized = false; } @@ -195,6 +203,76 @@ class Supabase { authOptions: authOptions, accessToken: accessToken, ); + _widgetsBindingInstance?.addObserver(this); _initialized = true; } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + switch (state) { + case AppLifecycleState.resumed: + onResumed(); + case AppLifecycleState.detached: + case AppLifecycleState.paused: + _realtimeReconnectOperation?.cancel(); + Supabase.instance.client.realtime.disconnect(); + default: + } + } + + Future onResumed() async { + final realtime = Supabase.instance.client.realtime; + if (realtime.channels.isNotEmpty) { + if (realtime.connState == SocketStates.disconnecting) { + // If the socket is still disconnecting from e.g. + // [AppLifecycleState.paused] we should wait for it to finish before + // reconnecting. + + bool cancel = false; + final connectFuture = realtime.conn!.sink.done.then( + (_) async { + // Make this connect cancelable so that it does not connect if the + // disconnect took so long that the app is already in background + // again. + + if (!cancel) { + // ignore: invalid_use_of_internal_member + await realtime.connect(); + for (final channel in realtime.channels) { + // ignore: invalid_use_of_internal_member + if (channel.isJoined) { + // ignore: invalid_use_of_internal_member + channel.forceRejoin(); + } + } + } + }, + onError: (error) {}, + ); + _realtimeReconnectOperation = CancelableOperation.fromFuture( + connectFuture, + onCancel: () => cancel = true, + ); + } else if (!realtime.isConnected) { + // Reconnect if the socket is currently not connected. + // When coming from [AppLifecycleState.paused] this should be the case, + // but when coming from [AppLifecycleState.inactive] no disconnect + // happened and therefore connection should still be intanct and we + // should not reconnect. + + // ignore: invalid_use_of_internal_member + await realtime.connect(); + for (final channel in realtime.channels) { + // Only rejoin channels that think they are still joined and not + // which were manually unsubscribed by the user while in background + + // ignore: invalid_use_of_internal_member + if (channel.isJoined) { + // ignore: invalid_use_of_internal_member + channel.forceRejoin(); + } + } + } + } + } } diff --git a/packages/supabase_flutter/lib/src/supabase_auth.dart b/packages/supabase_flutter/lib/src/supabase_auth.dart index 221a6ed9..ada94a24 100644 --- a/packages/supabase_flutter/lib/src/supabase_auth.dart +++ b/packages/supabase_flutter/lib/src/supabase_auth.dart @@ -4,7 +4,6 @@ import 'dart:io' show Platform; import 'dart:math'; import 'package:app_links/app_links.dart'; -import 'package:async/async.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -31,8 +30,6 @@ class SupabaseAuth with WidgetsBindingObserver { StreamSubscription? _deeplinkSubscription; - CancelableOperation? _realtimeReconnectOperation; - final _appLinks = AppLinks(); final _log = Logger('supabase.supabase_flutter'); @@ -118,77 +115,18 @@ class SupabaseAuth with WidgetsBindingObserver { void didChangeAppLifecycleState(AppLifecycleState state) { switch (state) { case AppLifecycleState.resumed: - onResumed(); + if (_autoRefreshToken) { + Supabase.instance.client.auth.startAutoRefresh(); + } case AppLifecycleState.detached: case AppLifecycleState.paused: if (kIsWeb || Platform.isAndroid || Platform.isIOS) { Supabase.instance.client.auth.stopAutoRefresh(); - _realtimeReconnectOperation?.cancel(); - Supabase.instance.client.realtime.disconnect(); } default: } } - Future onResumed() async { - if (_autoRefreshToken) { - Supabase.instance.client.auth.startAutoRefresh(); - } - final realtime = Supabase.instance.client.realtime; - if (realtime.channels.isNotEmpty) { - if (realtime.connState == SocketStates.disconnecting) { - // If the socket is still disconnecting from e.g. - // [AppLifecycleState.paused] we should wait for it to finish before - // reconnecting. - - bool cancel = false; - final connectFuture = realtime.conn!.sink.done.then( - (_) async { - // Make this connect cancelable so that it does not connect if the - // disconnect took so long that the app is already in background - // again. - - if (!cancel) { - // ignore: invalid_use_of_internal_member - await realtime.connect(); - for (final channel in realtime.channels) { - // ignore: invalid_use_of_internal_member - if (channel.isJoined) { - // ignore: invalid_use_of_internal_member - channel.forceRejoin(); - } - } - } - }, - onError: (error) {}, - ); - _realtimeReconnectOperation = CancelableOperation.fromFuture( - connectFuture, - onCancel: () => cancel = true, - ); - } else if (!realtime.isConnected) { - // Reconnect if the socket is currently not connected. - // When coming from [AppLifecycleState.paused] this should be the case, - // but when coming from [AppLifecycleState.inactive] no disconnect - // happened and therefore connection should still be intanct and we - // should not reconnect. - - // ignore: invalid_use_of_internal_member - await realtime.connect(); - for (final channel in realtime.channels) { - // Only rejoin channels that think they are still joined and not - // which were manually unsubscribed by the user while in background - - // ignore: invalid_use_of_internal_member - if (channel.isJoined) { - // ignore: invalid_use_of_internal_member - channel.forceRejoin(); - } - } - } - } - } - void _onAuthStateChange(AuthChangeEvent event, Session? session) { if (session != null) { _localStorage.persistSession(jsonEncode(session.toJson())); diff --git a/packages/supabase_flutter/test/supabase_flutter_test.dart b/packages/supabase_flutter/test/supabase_flutter_test.dart index 559d658c..920c2dbe 100644 --- a/packages/supabase_flutter/test/supabase_flutter_test.dart +++ b/packages/supabase_flutter/test/supabase_flutter_test.dart @@ -9,7 +9,7 @@ void main() { const supabaseKey = ''; tearDown(() async => await Supabase.instance.dispose()); - group("Valid session", () { + group("Initialize", () { setUp(() async { mockAppLink(); // Initialize the Supabase singleton @@ -48,6 +48,27 @@ void main() { }); }); + test('with custom access token', () async { + final supabase = await Supabase.initialize( + url: supabaseUrl, + anonKey: supabaseUrl, + debug: false, + authOptions: FlutterAuthClientOptions( + localStorage: MockLocalStorage(), + pkceAsyncStorage: MockAsyncStorage(), + ), + accessToken: () async => 'my-access-token', + ); + + // print(supabase.client.auth.runtimeType); + + void accessAuth() { + supabase.client.auth; + } + + expect(accessAuth, throwsA(isA())); + }); + group("Expired session", () { setUp(() async { mockAppLink(); From ccfcbf5f01b5c7163f1aa25859070724bfd534f5 Mon Sep 17 00:00:00 2001 From: Tyler Date: Mon, 18 Nov 2024 13:49:06 +0900 Subject: [PATCH 19/29] chore(release): Publish packages supabase_flutter@2.8.1 (#1086) chore(release): publish packages - gotrue@2.11.0 - postgrest@2.4.0 - supabase@2.6.0 - supabase_flutter@2.8.1 --- CHANGELOG.md | 40 +++++++++++++++++++ packages/gotrue/CHANGELOG.md | 7 ++++ packages/gotrue/lib/src/version.dart | 2 +- packages/gotrue/pubspec.yaml | 2 +- packages/postgrest/CHANGELOG.md | 4 ++ packages/postgrest/lib/src/version.dart | 2 +- packages/postgrest/pubspec.yaml | 2 +- packages/supabase/CHANGELOG.md | 5 +++ packages/supabase/lib/src/version.dart | 2 +- packages/supabase/pubspec.yaml | 6 +-- packages/supabase_flutter/CHANGELOG.md | 4 ++ .../supabase_flutter/example/pubspec.yaml | 2 +- .../supabase_flutter/lib/src/version.dart | 2 +- packages/supabase_flutter/pubspec.yaml | 4 +- 14 files changed, 72 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18c273b4..f85e0733 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,46 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## 2024-11-17 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`gotrue` - `v2.11.0`](#gotrue---v2110) + - [`postgrest` - `v2.4.0`](#postgrest---v240) + - [`supabase` - `v2.6.0`](#supabase---v260) + - [`supabase_flutter` - `v2.8.1`](#supabase_flutter---v281) + +--- + +#### `gotrue` - `v2.11.0` + + - **FIX**: Add equality to user attributes classes ([#1070](https://github.com/supabase/supabase-flutter/issues/1070)). ([7e7bc0ca](https://github.com/supabase/supabase-flutter/commit/7e7bc0cac722c0f3404b3f9320c536454bc51cea)) + - **FIX**: Updated gotrue types.dart to include slackOidc ([#1066](https://github.com/supabase/supabase-flutter/issues/1066)). ([12007b32](https://github.com/supabase/supabase-flutter/commit/12007b3206c15a87c373af46bc5f4a17aa646f62)) + - **FIX**: Send metadata inviteUserByEmail() ([#1061](https://github.com/supabase/supabase-flutter/issues/1061)). ([598540d2](https://github.com/supabase/supabase-flutter/commit/598540d2aac27d64e6be5d4a2e855d928f87fda6)) + - **FEAT**: Add Keycloak Provider on signInWithIdToken ([#1068](https://github.com/supabase/supabase-flutter/issues/1068)). ([be998fda](https://github.com/supabase/supabase-flutter/commit/be998fda6365bcf157dc96e3e4ea7009415045ee)) + +#### `postgrest` - `v2.4.0` + + - **FEAT**: Read-only access mode rpc ([#1081](https://github.com/supabase/supabase-flutter/issues/1081)). ([d0a04154](https://github.com/supabase/supabase-flutter/commit/d0a04154ff56d40d00e1c9282d8ba859681c7275)) + +#### `supabase` - `v2.6.0` + + - **FIX**: Support custom access token ([#1073](https://github.com/supabase/supabase-flutter/issues/1073)). ([fc9ad2c9](https://github.com/supabase/supabase-flutter/commit/fc9ad2c94a02921ca8ced4564d9bcd8cde2c2397)) + - **FEAT**: Read-only access mode rpc ([#1081](https://github.com/supabase/supabase-flutter/issues/1081)). ([d0a04154](https://github.com/supabase/supabase-flutter/commit/d0a04154ff56d40d00e1c9282d8ba859681c7275)) + +#### `supabase_flutter` - `v2.8.1` + + - **FIX**: Support custom access token ([#1073](https://github.com/supabase/supabase-flutter/issues/1073)). ([fc9ad2c9](https://github.com/supabase/supabase-flutter/commit/fc9ad2c94a02921ca8ced4564d9bcd8cde2c2397)) + + ## 2024-10-07 ### Changes diff --git a/packages/gotrue/CHANGELOG.md b/packages/gotrue/CHANGELOG.md index 21b2955a..c0804456 100644 --- a/packages/gotrue/CHANGELOG.md +++ b/packages/gotrue/CHANGELOG.md @@ -1,3 +1,10 @@ +## 2.11.0 + + - **FIX**: Add equality to user attributes classes ([#1070](https://github.com/supabase/supabase-flutter/issues/1070)). ([7e7bc0ca](https://github.com/supabase/supabase-flutter/commit/7e7bc0cac722c0f3404b3f9320c536454bc51cea)) + - **FIX**: Updated gotrue types.dart to include slackOidc ([#1066](https://github.com/supabase/supabase-flutter/issues/1066)). ([12007b32](https://github.com/supabase/supabase-flutter/commit/12007b3206c15a87c373af46bc5f4a17aa646f62)) + - **FIX**: Send metadata inviteUserByEmail() ([#1061](https://github.com/supabase/supabase-flutter/issues/1061)). ([598540d2](https://github.com/supabase/supabase-flutter/commit/598540d2aac27d64e6be5d4a2e855d928f87fda6)) + - **FEAT**: Add Keycloak Provider on signInWithIdToken ([#1068](https://github.com/supabase/supabase-flutter/issues/1068)). ([be998fda](https://github.com/supabase/supabase-flutter/commit/be998fda6365bcf157dc96e3e4ea7009415045ee)) + ## 2.10.0 - **FIX**: Rename logger from gotrue to auth ([#1055](https://github.com/supabase/supabase-flutter/issues/1055)). ([eea1ea6c](https://github.com/supabase/supabase-flutter/commit/eea1ea6c4e200e8ffc52d8343c0684c19670e3ed)) diff --git a/packages/gotrue/lib/src/version.dart b/packages/gotrue/lib/src/version.dart index dbfc0304..bcdb75cb 100644 --- a/packages/gotrue/lib/src/version.dart +++ b/packages/gotrue/lib/src/version.dart @@ -1 +1 @@ -const version = '2.10.0'; +const version = '2.11.0'; diff --git a/packages/gotrue/pubspec.yaml b/packages/gotrue/pubspec.yaml index f5e9e9d2..bc3d042f 100644 --- a/packages/gotrue/pubspec.yaml +++ b/packages/gotrue/pubspec.yaml @@ -1,6 +1,6 @@ name: gotrue description: A dart client library for the GoTrue API. -version: 2.10.0 +version: 2.11.0 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/gotrue' documentation: 'https://supabase.com/docs/reference/dart/auth-signup' diff --git a/packages/postgrest/CHANGELOG.md b/packages/postgrest/CHANGELOG.md index 4fc4f092..8bc7c39d 100644 --- a/packages/postgrest/CHANGELOG.md +++ b/packages/postgrest/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.4.0 + + - **FEAT**: Read-only access mode rpc ([#1081](https://github.com/supabase/supabase-flutter/issues/1081)). ([d0a04154](https://github.com/supabase/supabase-flutter/commit/d0a04154ff56d40d00e1c9282d8ba859681c7275)) + ## 2.3.0 - **FEAT**: Add logging ([#1042](https://github.com/supabase/supabase-flutter/issues/1042)). ([d1ecabd7](https://github.com/supabase/supabase-flutter/commit/d1ecabd77881a0488d2d4b41ea5ee5abda6c5c35)) diff --git a/packages/postgrest/lib/src/version.dart b/packages/postgrest/lib/src/version.dart index 5f7e4e35..3dcb91ee 100644 --- a/packages/postgrest/lib/src/version.dart +++ b/packages/postgrest/lib/src/version.dart @@ -1 +1 @@ -const version = '2.3.0'; +const version = '2.4.0'; diff --git a/packages/postgrest/pubspec.yaml b/packages/postgrest/pubspec.yaml index a6b33ccc..6a0c849d 100644 --- a/packages/postgrest/pubspec.yaml +++ b/packages/postgrest/pubspec.yaml @@ -1,6 +1,6 @@ name: postgrest description: PostgREST client for Dart. This library provides an ORM interface to PostgREST. -version: 2.3.0 +version: 2.4.0 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/postgrest' documentation: 'https://supabase.com/docs/reference/dart/select' diff --git a/packages/supabase/CHANGELOG.md b/packages/supabase/CHANGELOG.md index a665bb61..f3c7e274 100644 --- a/packages/supabase/CHANGELOG.md +++ b/packages/supabase/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.6.0 + + - **FIX**: Support custom access token ([#1073](https://github.com/supabase/supabase-flutter/issues/1073)). ([fc9ad2c9](https://github.com/supabase/supabase-flutter/commit/fc9ad2c94a02921ca8ced4564d9bcd8cde2c2397)) + - **FEAT**: Read-only access mode rpc ([#1081](https://github.com/supabase/supabase-flutter/issues/1081)). ([d0a04154](https://github.com/supabase/supabase-flutter/commit/d0a04154ff56d40d00e1c9282d8ba859681c7275)) + ## 2.5.0 - **FEAT**: Add logging ([#1042](https://github.com/supabase/supabase-flutter/issues/1042)). ([d1ecabd7](https://github.com/supabase/supabase-flutter/commit/d1ecabd77881a0488d2d4b41ea5ee5abda6c5c35)) diff --git a/packages/supabase/lib/src/version.dart b/packages/supabase/lib/src/version.dart index 726228b1..1abb1f33 100644 --- a/packages/supabase/lib/src/version.dart +++ b/packages/supabase/lib/src/version.dart @@ -1 +1 @@ -const version = '2.5.0'; +const version = '2.6.0'; diff --git a/packages/supabase/pubspec.yaml b/packages/supabase/pubspec.yaml index 8f74aa17..99d6b9bf 100644 --- a/packages/supabase/pubspec.yaml +++ b/packages/supabase/pubspec.yaml @@ -1,6 +1,6 @@ name: supabase description: A dart client for Supabase. This client makes it simple for developers to build secure and scalable products. -version: 2.5.0 +version: 2.6.0 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/supabase' documentation: 'https://supabase.com/docs/reference/dart/introduction' @@ -10,9 +10,9 @@ environment: dependencies: functions_client: 2.4.0 - gotrue: 2.10.0 + gotrue: 2.11.0 http: '>=0.13.5 <2.0.0' - postgrest: 2.3.0 + postgrest: 2.4.0 realtime_client: 2.4.0 storage_client: 2.2.0 rxdart: '>=0.27.5 <0.29.0' diff --git a/packages/supabase_flutter/CHANGELOG.md b/packages/supabase_flutter/CHANGELOG.md index 3ce7d30f..5e79edc5 100644 --- a/packages/supabase_flutter/CHANGELOG.md +++ b/packages/supabase_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.8.1 + + - **FIX**: Support custom access token ([#1073](https://github.com/supabase/supabase-flutter/issues/1073)). ([fc9ad2c9](https://github.com/supabase/supabase-flutter/commit/fc9ad2c94a02921ca8ced4564d9bcd8cde2c2397)) + ## 2.8.0 - **FIX**: Rename logger from gotrue to auth ([#1055](https://github.com/supabase/supabase-flutter/issues/1055)). ([eea1ea6c](https://github.com/supabase/supabase-flutter/commit/eea1ea6c4e200e8ffc52d8343c0684c19670e3ed)) diff --git a/packages/supabase_flutter/example/pubspec.yaml b/packages/supabase_flutter/example/pubspec.yaml index 8c8a2f83..f9dcf65c 100644 --- a/packages/supabase_flutter/example/pubspec.yaml +++ b/packages/supabase_flutter/example/pubspec.yaml @@ -11,7 +11,7 @@ environment: dependencies: flutter: sdk: flutter - supabase_flutter: ^2.8.0 + supabase_flutter: ^2.8.1 dev_dependencies: flutter_test: diff --git a/packages/supabase_flutter/lib/src/version.dart b/packages/supabase_flutter/lib/src/version.dart index 2cdfc59d..32722310 100644 --- a/packages/supabase_flutter/lib/src/version.dart +++ b/packages/supabase_flutter/lib/src/version.dart @@ -1 +1 @@ -const version = '2.8.0'; +const version = '2.8.1'; diff --git a/packages/supabase_flutter/pubspec.yaml b/packages/supabase_flutter/pubspec.yaml index f569b6ba..a0443fbf 100644 --- a/packages/supabase_flutter/pubspec.yaml +++ b/packages/supabase_flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: supabase_flutter description: Flutter integration for Supabase. This package makes it simple for developers to build secure and scalable products. -version: 2.8.0 +version: 2.8.1 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/supabase_flutter' documentation: 'https://supabase.com/docs/reference/dart/introduction' @@ -17,7 +17,7 @@ dependencies: sdk: flutter http: '>=0.13.4 <2.0.0' meta: ^1.7.0 - supabase: 2.5.0 + supabase: 2.6.0 url_launcher: ^6.1.2 path_provider: ^2.0.0 shared_preferences: ^2.0.0 From 4e3511551cb0d6da673fa5c4187f7ada2a1f8865 Mon Sep 17 00:00:00 2001 From: Tyler Date: Wed, 11 Dec 2024 18:17:03 +0900 Subject: [PATCH 20/29] fix(realtime_client): Consolidate realtime subscription for stream (#1096) * fix: Consolidate realtime subscription for stream * fix: adjust the mock data --- .../lib/src/supabase_stream_builder.dart | 70 +++++++++---------- packages/supabase/test/mock_test.dart | 24 ++----- 2 files changed, 39 insertions(+), 55 deletions(-) diff --git a/packages/supabase/lib/src/supabase_stream_builder.dart b/packages/supabase/lib/src/supabase_stream_builder.dart index c27d9bd5..b3a1c9f0 100644 --- a/packages/supabase/lib/src/supabase_stream_builder.dart +++ b/packages/supabase/lib/src/supabase_stream_builder.dart @@ -171,46 +171,44 @@ class SupabaseStreamBuilder extends Stream { _channel! .onPostgresChanges( - event: PostgresChangeEvent.insert, + event: PostgresChangeEvent.all, schema: _schema, table: _table, filter: realtimeFilter, callback: (payload) { - final newRecord = payload.newRecord; - _streamData.add(newRecord); - _addStream(); - }) - .onPostgresChanges( - event: PostgresChangeEvent.update, - schema: _schema, - table: _table, - filter: realtimeFilter, - callback: (payload) { - final updatedIndex = _streamData.indexWhere( - (element) => _isTargetRecord(record: element, payload: payload), - ); - - final updatedRecord = payload.newRecord; - if (updatedIndex >= 0) { - _streamData[updatedIndex] = updatedRecord; - } else { - _streamData.add(updatedRecord); - } - _addStream(); - }) - .onPostgresChanges( - event: PostgresChangeEvent.delete, - schema: _schema, - table: _table, - filter: realtimeFilter, - callback: (payload) { - final deletedIndex = _streamData.indexWhere( - (element) => _isTargetRecord(record: element, payload: payload), - ); - if (deletedIndex >= 0) { - /// Delete the data from in memory cache if it was found - _streamData.removeAt(deletedIndex); - _addStream(); + switch (payload.eventType) { + case PostgresChangeEvent.insert: + final newRecord = payload.newRecord; + _streamData.add(newRecord); + _addStream(); + break; + case PostgresChangeEvent.update: + final updatedIndex = _streamData.indexWhere( + (element) => + _isTargetRecord(record: element, payload: payload), + ); + + final updatedRecord = payload.newRecord; + if (updatedIndex >= 0) { + _streamData[updatedIndex] = updatedRecord; + } else { + _streamData.add(updatedRecord); + } + _addStream(); + break; + case PostgresChangeEvent.delete: + final deletedIndex = _streamData.indexWhere( + (element) => + _isTargetRecord(record: element, payload: payload), + ); + if (deletedIndex >= 0) { + /// Delete the data from in memory cache if it was found + _streamData.removeAt(deletedIndex); + _addStream(); + } + break; + default: + break; } }) .subscribe((status, [error]) { diff --git a/packages/supabase/test/mock_test.dart b/packages/supabase/test/mock_test.dart index 6c458c5f..f3f4f53e 100644 --- a/packages/supabase/test/mock_test.dart +++ b/packages/supabase/test/mock_test.dart @@ -136,25 +136,11 @@ void main() { 'postgres_changes': [ { 'id': 77086988, - 'event': 'INSERT', + 'event': '*', 'schema': 'public', 'table': 'todos', if (realtimeFilter != null) 'filter': realtimeFilter, }, - { - 'id': 25993878, - 'event': 'UPDATE', - 'schema': 'public', - 'table': 'todos', - if (realtimeFilter != null) 'filter': realtimeFilter, - }, - { - 'id': 48673474, - 'event': 'DELETE', - 'schema': 'public', - 'table': 'todos', - if (realtimeFilter != null) 'filter': realtimeFilter, - } ] }, 'status': 'ok' @@ -208,7 +194,7 @@ void main() { 'ref': null, 'event': 'postgres_changes', 'payload': { - 'ids': [25993878], + 'ids': [77086988], 'data': { 'columns': [ {'name': 'id', 'type': 'int4', 'type_modifier': 4294967295}, @@ -257,7 +243,7 @@ void main() { 'type': 'DELETE', if (realtimeFilter != null) 'filter': realtimeFilter, }, - 'ids': [48673474] + 'ids': [77086988] }, }); webSocket!.add(deleteString); @@ -271,7 +257,7 @@ void main() { 'ref': null, 'event': 'postgres_changes', 'payload': { - 'ids': [25993878], + 'ids': [77086988], 'data': { 'columns': [ {'name': 'id', 'type': 'int4', 'type_modifier': 4294967295}, @@ -321,7 +307,7 @@ void main() { 'type': 'DELETE', if (realtimeFilter != null) 'filter': realtimeFilter, }, - 'ids': [48673474] + 'ids': [77086988] }, }); webSocket!.add(ignoredDeleteString); From c97178610e8cd7a65a2f6a926ab559987e786d75 Mon Sep 17 00:00:00 2001 From: Dastaan2k <63939876+Dastaan2k@users.noreply.github.com> Date: Thu, 12 Dec 2024 20:17:05 +0530 Subject: [PATCH 21/29] fix: Ignore email and phone assertions when token hash is being verified (#1097) Co-authored-by: sarvesh_dalvi --- packages/gotrue/lib/src/gotrue_client.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/gotrue/lib/src/gotrue_client.dart b/packages/gotrue/lib/src/gotrue_client.dart index 63f5c85f..fcf4182d 100644 --- a/packages/gotrue/lib/src/gotrue_client.dart +++ b/packages/gotrue/lib/src/gotrue_client.dart @@ -533,7 +533,10 @@ class GoTrueClient { String? captchaToken, String? tokenHash, }) async { - assert((email != null && phone == null) || (email == null && phone != null), + assert( + ((email != null && phone == null) || + (email == null && phone != null)) || + (tokenHash != null), '`email` or `phone` needs to be specified.'); assert(token != null || tokenHash != null, '`token` or `tokenHash` needs to be specified.'); From 1bb034f0f82b03d629edc733688c8648cf01e5b9 Mon Sep 17 00:00:00 2001 From: Tyler Date: Tue, 17 Dec 2024 01:02:57 +0900 Subject: [PATCH 22/29] fix(realtime_client): Prevent sending expired tokens (#1095) * fix: prevent sending expired tokens * widen the constraint for crypto dev dependencies on realtime * fix: properly handle exception on supabase-client for realtime set auth * fix: handle realtime token exception on SupabaseClient * await all setAuth calls * pass custom access token as params * properly parse JWT within realtime client --- .../lib/src/realtime_channel.dart | 6 +- .../lib/src/realtime_client.dart | 48 ++++++-- packages/realtime_client/pubspec.yaml | 1 + .../realtime_client/test/socket_test.dart | 103 ++++++++++++++++-- .../supabase/lib/src/supabase_client.dart | 21 +++- 5 files changed, 155 insertions(+), 24 deletions(-) diff --git a/packages/realtime_client/lib/src/realtime_channel.dart b/packages/realtime_client/lib/src/realtime_channel.dart index 7c37d800..02c7aa86 100644 --- a/packages/realtime_client/lib/src/realtime_channel.dart +++ b/packages/realtime_client/lib/src/realtime_channel.dart @@ -150,9 +150,11 @@ class RealtimeChannel { joinPush.receive( 'ok', - (response) { + (response) async { final serverPostgresFilters = response['postgres_changes']; - if (socket.accessToken != null) socket.setAuth(socket.accessToken); + if (socket.accessToken != null) { + await socket.setAuth(socket.accessToken); + } if (serverPostgresFilters == null) { if (callback != null) { diff --git a/packages/realtime_client/lib/src/realtime_client.dart b/packages/realtime_client/lib/src/realtime_client.dart index f5e6f5fa..3ecfb612 100644 --- a/packages/realtime_client/lib/src/realtime_client.dart +++ b/packages/realtime_client/lib/src/realtime_client.dart @@ -54,6 +54,7 @@ class RealtimeCloseEvent { } class RealtimeClient { + // This is named `accessTokenValue` in supabase-js String? accessToken; List channels = []; final String endPoint; @@ -89,6 +90,8 @@ class RealtimeClient { }; int longpollerTimeout = 20000; SocketStates? connState; + // This is called `accessToken` in realtime-js + Future Function()? customAccessToken; /// Initializes the Socket /// @@ -129,6 +132,7 @@ class RealtimeClient { this.longpollerTimeout = 20000, RealtimeLogLevel? logLevel, this.httpClient, + this.customAccessToken, }) : endPoint = Uri.parse('$endPoint/${Transports.websocket}') .replace( queryParameters: @@ -403,15 +407,43 @@ class RealtimeClient { /// Sets the JWT access token used for channel subscription authorization and Realtime RLS. /// /// `token` A JWT strings. - void setAuth(String? token) { - accessToken = token; + Future setAuth(String? token) async { + final tokenToSend = + token ?? (await customAccessToken?.call()) ?? accessToken; + + if (tokenToSend != null) { + Map? parsed; + try { + final decoded = + base64.decode(base64.normalize(tokenToSend.split('.')[1])); + parsed = json.decode(utf8.decode(decoded)); + } catch (e) { + // ignore parsing errors + } + if (parsed != null && parsed['exp'] != null) { + final now = (DateTime.now().millisecondsSinceEpoch / 1000).floor(); + final valid = now - parsed['exp'] < 0; + if (!valid) { + log( + 'auth', + 'InvalidJWTToken: Invalid value for JWT claim "exp" with value ${parsed['exp']}', + null, + Level.FINE, + ); + throw FormatException( + 'InvalidJWTToken: Invalid value for JWT claim "exp" with value ${parsed['exp']}'); + } + } + } + + accessToken = tokenToSend; for (final channel in channels) { - if (token != null) { - channel.updateJoinPayload({'access_token': token}); + if (tokenToSend != null) { + channel.updateJoinPayload({'access_token': tokenToSend}); } if (channel.joinedOnce && channel.isJoined) { - channel.push(ChannelEvents.accessToken, {'access_token': token}); + channel.push(ChannelEvents.accessToken, {'access_token': tokenToSend}); } } } @@ -436,7 +468,7 @@ class RealtimeClient { if (heartbeatTimer != null) heartbeatTimer!.cancel(); heartbeatTimer = Timer.periodic( Duration(milliseconds: heartbeatIntervalMs), - (Timer t) => sendHeartbeat(), + (Timer t) async => await sendHeartbeat(), ); for (final callback in stateChangeCallbacks['open']!) { callback(); @@ -502,7 +534,7 @@ class RealtimeClient { } @internal - void sendHeartbeat() { + Future sendHeartbeat() async { if (!isConnected) { return; } @@ -524,6 +556,6 @@ class RealtimeClient { payload: {}, ref: pendingHeartbeatRef!, )); - setAuth(accessToken); + await setAuth(accessToken); } } diff --git a/packages/realtime_client/pubspec.yaml b/packages/realtime_client/pubspec.yaml index 8c7ef8cd..6ef77e14 100644 --- a/packages/realtime_client/pubspec.yaml +++ b/packages/realtime_client/pubspec.yaml @@ -19,3 +19,4 @@ dev_dependencies: lints: ^3.0.0 mocktail: ^1.0.0 test: ^1.16.5 + crypto: ^3.0.0 diff --git a/packages/realtime_client/test/socket_test.dart b/packages/realtime_client/test/socket_test.dart index 79fe1306..55c50914 100644 --- a/packages/realtime_client/test/socket_test.dart +++ b/packages/realtime_client/test/socket_test.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:io'; +import 'package:crypto/crypto.dart'; import 'package:mocktail/mocktail.dart'; import 'package:realtime_client/realtime_client.dart'; import 'package:realtime_client/src/constants.dart'; @@ -16,6 +17,31 @@ typedef WebSocketChannelClosure = WebSocketChannel Function( Map headers, ); +/// Generate a JWT token for testing purposes +/// +/// [exp] in seconds since Epoch +String generateJwt([int? exp]) { + final header = {'alg': 'HS256', 'typ': 'JWT'}; + + final now = DateTime.now(); + final expiry = exp ?? + (now.add(Duration(hours: 1)).millisecondsSinceEpoch / 1000).floor(); + + final payload = {'exp': expiry}; + + final key = 'your-256-bit-secret'; + + final encodedHeader = base64Url.encode(utf8.encode(json.encode(header))); + final encodedPayload = base64Url.encode(utf8.encode(json.encode(payload))); + + final signatureInput = '$encodedHeader.$encodedPayload'; + final hmac = Hmac(sha256, utf8.encode(key)); + final digest = hmac.convert(utf8.encode(signatureInput)); + final signature = base64Url.encode(digest.bytes); + + return '$encodedHeader.$encodedPayload.$signature'; +} + void main() { const int int64MaxValue = 9223372036854775807; @@ -174,7 +200,7 @@ void main() { await Future.delayed(const Duration(milliseconds: 200)); expect(opens, 1); - socket.sendHeartbeat(); + await socket.sendHeartbeat(); // need to wait for event to trigger await Future.delayed(const Duration(seconds: 1)); expect(lastMsg['event'], 'heartbeat'); @@ -427,12 +453,13 @@ void main() { }); group('setAuth', () { - final updateJoinPayload = {'access_token': 'token123'}; - final pushPayload = {'access_token': 'token123'}; + final token = generateJwt(); + final updateJoinPayload = {'access_token': token}; + final pushPayload = {'access_token': token}; test( "sets access token, updates channels' join payload, and pushes token to channels", - () { + () async { final mockedChannel1 = MockChannel(); when(() => mockedChannel1.joinedOnce).thenReturn(true); when(() => mockedChannel1.isJoined).thenReturn(true); @@ -457,7 +484,9 @@ void main() { final channel1 = mockedSocket.channel(tTopic1); final channel2 = mockedSocket.channel(tTopic2); - mockedSocket.setAuth('token123'); + await mockedSocket.setAuth(token); + + expect(mockedSocket.accessToken, token); verify(() => channel1.updateJoinPayload(updateJoinPayload)).called(1); verify(() => channel2.updateJoinPayload(updateJoinPayload)).called(1); @@ -466,6 +495,62 @@ void main() { verify(() => channel2.push(ChannelEvents.accessToken, pushPayload)) .called(1); }); + + test( + "sets access token, updates channels' join payload, and pushes token to channels if is not a jwt", + () async { + final mockedChannel1 = MockChannel(); + final mockedChannel2 = MockChannel(); + final mockedChannel3 = MockChannel(); + + when(() => mockedChannel1.joinedOnce).thenReturn(true); + when(() => mockedChannel1.isJoined).thenReturn(true); + when(() => mockedChannel1.push(ChannelEvents.accessToken, any())) + .thenReturn(MockPush()); + + when(() => mockedChannel2.joinedOnce).thenReturn(false); + when(() => mockedChannel2.isJoined).thenReturn(false); + when(() => mockedChannel2.push(ChannelEvents.accessToken, any())) + .thenReturn(MockPush()); + + when(() => mockedChannel3.joinedOnce).thenReturn(true); + when(() => mockedChannel3.isJoined).thenReturn(true); + when(() => mockedChannel3.push(ChannelEvents.accessToken, any())) + .thenReturn(MockPush()); + + const tTopic1 = 'test-topic1'; + const tTopic2 = 'test-topic2'; + const tTopic3 = 'test-topic3'; + + final mockedSocket = SocketWithMockedChannel(socketEndpoint); + mockedSocket.mockedChannelLooker.addAll({ + tTopic1: mockedChannel1, + tTopic2: mockedChannel2, + tTopic3: mockedChannel3, + }); + + final channel1 = mockedSocket.channel(tTopic1); + final channel2 = mockedSocket.channel(tTopic2); + final channel3 = mockedSocket.channel(tTopic3); + + const token = 'sb-key'; + final pushPayload = {'access_token': token}; + final updateJoinPayload = {'access_token': token}; + + await mockedSocket.setAuth(token); + + expect(mockedSocket.accessToken, token); + + verify(() => channel1.updateJoinPayload(updateJoinPayload)).called(1); + verify(() => channel2.updateJoinPayload(updateJoinPayload)).called(1); + verify(() => channel3.updateJoinPayload(updateJoinPayload)).called(1); + + verify(() => channel1.push(ChannelEvents.accessToken, pushPayload)) + .called(1); + verifyNever(() => channel2.push(ChannelEvents.accessToken, pushPayload)); + verify(() => channel3.push(ChannelEvents.accessToken, pushPayload)) + .called(1); + }); }); group('sendHeartbeat', () { @@ -496,18 +581,18 @@ void main() { //! Unimplemented Test: closes socket when heartbeat is not ack'd within heartbeat window - test('pushes heartbeat data when connected', () { + test('pushes heartbeat data when connected', () async { mockedSocket.connState = SocketStates.open; - mockedSocket.sendHeartbeat(); + await mockedSocket.sendHeartbeat(); verify(() => mockedSink.add(captureAny(that: equals(data)))).called(1); }); - test('no ops when not connected', () { + test('no ops when not connected', () async { mockedSocket.connState = SocketStates.connecting; - mockedSocket.sendHeartbeat(); + await mockedSocket.sendHeartbeat(); verifyNever(() => mockedSink.add(any())); }); }); diff --git a/packages/supabase/lib/src/supabase_client.dart b/packages/supabase/lib/src/supabase_client.dart index 4f500b8b..4a7d52de 100644 --- a/packages/supabase/lib/src/supabase_client.dart +++ b/packages/supabase/lib/src/supabase_client.dart @@ -332,6 +332,7 @@ class SupabaseClient { logLevel: options.logLevel, httpClient: _authHttpClient, timeout: options.timeout ?? RealtimeConstants.defaultTimeout, + customAccessToken: accessToken, ); } @@ -349,22 +350,32 @@ class SupabaseClient { void _listenForAuthEvents() { // ignore: invalid_use_of_internal_member _authStateSubscription = auth.onAuthStateChangeSync.listen( - (data) { - _handleTokenChanged(data.event, data.session?.accessToken); + (data) async { + await _handleTokenChanged(data.event, data.session?.accessToken); }, onError: (error, stack) {}, ); } - void _handleTokenChanged(AuthChangeEvent event, String? token) { + Future _handleTokenChanged(AuthChangeEvent event, String? token) async { if (event == AuthChangeEvent.initialSession || event == AuthChangeEvent.tokenRefreshed || event == AuthChangeEvent.signedIn) { - realtime.setAuth(token); + try { + await realtime.setAuth(token); + } on FormatException catch (e) { + if (e.message.contains('InvalidJWTToken')) { + // The exception is thrown by RealtimeClient when the token is + // expired for example on app launch after the app has been closed + // for a while. + } else { + rethrow; + } + } } else if (event == AuthChangeEvent.signedOut) { // Token is removed - realtime.setAuth(_supabaseKey); + await realtime.setAuth(_supabaseKey); } } } From 9b91ce8491a6aec35d3d59767cdff45827212fa3 Mon Sep 17 00:00:00 2001 From: Tyler Date: Wed, 18 Dec 2024 14:12:13 +0900 Subject: [PATCH 23/29] chore(release): Publish packages supabase_flutter@2.8.2 (#1101) * chore(release): publish packages - gotrue@2.11.1 - realtime_client@2.4.1 - supabase@2.6.1 - supabase_flutter@2.8.2 * bump storage_client version --- CHANGELOG.md | 44 +++++++++++++++++++ packages/gotrue/CHANGELOG.md | 4 ++ packages/gotrue/lib/src/version.dart | 2 +- packages/gotrue/pubspec.yaml | 2 +- packages/realtime_client/CHANGELOG.md | 4 ++ packages/realtime_client/lib/src/version.dart | 2 +- packages/realtime_client/pubspec.yaml | 2 +- packages/storage_client/CHANGELOG.md | 4 ++ packages/storage_client/lib/src/version.dart | 2 +- packages/storage_client/pubspec.yaml | 2 +- packages/supabase/CHANGELOG.md | 5 +++ packages/supabase/lib/src/version.dart | 2 +- packages/supabase/pubspec.yaml | 8 ++-- packages/supabase_flutter/CHANGELOG.md | 4 ++ .../supabase_flutter/example/pubspec.yaml | 2 +- .../supabase_flutter/lib/src/version.dart | 2 +- packages/supabase_flutter/pubspec.yaml | 4 +- 17 files changed, 80 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f85e0733..7734e596 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,50 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## 2024-12-17 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`gotrue` - `v2.11.1`](#gotrue---v2111) + - [`realtime_client` - `v2.4.1`](#realtime_client---v241) + - [`storage_client` - `v2.3.0`](#storage_client---v230) + - [`supabase` - `v2.6.1`](#supabase---v261) + - [`supabase_flutter` - `v2.8.2`](#supabase_flutter---v282) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `supabase_flutter` - `v2.8.2` + +--- + +#### `gotrue` - `v2.11.1` + + - **FIX**: Ignore email and phone assertions when token hash is being verified ([#1097](https://github.com/supabase/supabase-flutter/issues/1097)). ([c9717861](https://github.com/supabase/supabase-flutter/commit/c97178610e8cd7a65a2f6a926ab559987e786d75)) + +#### `realtime_client` - `v2.4.1` + + - **FIX**(realtime_client): Prevent sending expired tokens ([#1095](https://github.com/supabase/supabase-flutter/issues/1095)). ([1bb034f0](https://github.com/supabase/supabase-flutter/commit/1bb034f0f82b03d629edc733688c8648cf01e5b9)) + +#### `storage_client` - `v2.3.0` + +- **FEAT**: Support mime 2.0.0 ([#1079](https://github.com/supabase/supabase-flutter/pull/1079)). + +#### `supabase` - `v2.6.1` + + - **FIX**(realtime_client): Prevent sending expired tokens ([#1095](https://github.com/supabase/supabase-flutter/issues/1095)). ([1bb034f0](https://github.com/supabase/supabase-flutter/commit/1bb034f0f82b03d629edc733688c8648cf01e5b9)) + - **FIX**(realtime_client): Consolidate realtime subscription for stream ([#1096](https://github.com/supabase/supabase-flutter/issues/1096)). ([4e351155](https://github.com/supabase/supabase-flutter/commit/4e3511551cb0d6da673fa5c4187f7ada2a1f8865)) + + ## 2024-11-17 ### Changes diff --git a/packages/gotrue/CHANGELOG.md b/packages/gotrue/CHANGELOG.md index c0804456..65f66e42 100644 --- a/packages/gotrue/CHANGELOG.md +++ b/packages/gotrue/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.11.1 + + - **FIX**: Ignore email and phone assertions when token hash is being verified ([#1097](https://github.com/supabase/supabase-flutter/issues/1097)). ([c9717861](https://github.com/supabase/supabase-flutter/commit/c97178610e8cd7a65a2f6a926ab559987e786d75)) + ## 2.11.0 - **FIX**: Add equality to user attributes classes ([#1070](https://github.com/supabase/supabase-flutter/issues/1070)). ([7e7bc0ca](https://github.com/supabase/supabase-flutter/commit/7e7bc0cac722c0f3404b3f9320c536454bc51cea)) diff --git a/packages/gotrue/lib/src/version.dart b/packages/gotrue/lib/src/version.dart index bcdb75cb..3fa83f11 100644 --- a/packages/gotrue/lib/src/version.dart +++ b/packages/gotrue/lib/src/version.dart @@ -1 +1 @@ -const version = '2.11.0'; +const version = '2.11.1'; diff --git a/packages/gotrue/pubspec.yaml b/packages/gotrue/pubspec.yaml index bc3d042f..f17c86d9 100644 --- a/packages/gotrue/pubspec.yaml +++ b/packages/gotrue/pubspec.yaml @@ -1,6 +1,6 @@ name: gotrue description: A dart client library for the GoTrue API. -version: 2.11.0 +version: 2.11.1 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/gotrue' documentation: 'https://supabase.com/docs/reference/dart/auth-signup' diff --git a/packages/realtime_client/CHANGELOG.md b/packages/realtime_client/CHANGELOG.md index aaeeea7f..499a1005 100644 --- a/packages/realtime_client/CHANGELOG.md +++ b/packages/realtime_client/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.4.1 + + - **FIX**(realtime_client): Prevent sending expired tokens ([#1095](https://github.com/supabase/supabase-flutter/issues/1095)). ([1bb034f0](https://github.com/supabase/supabase-flutter/commit/1bb034f0f82b03d629edc733688c8648cf01e5b9)) + ## 2.4.0 - **FEAT**: Add logging ([#1042](https://github.com/supabase/supabase-flutter/issues/1042)). ([d1ecabd7](https://github.com/supabase/supabase-flutter/commit/d1ecabd77881a0488d2d4b41ea5ee5abda6c5c35)) diff --git a/packages/realtime_client/lib/src/version.dart b/packages/realtime_client/lib/src/version.dart index 3dcb91ee..b6a2119a 100644 --- a/packages/realtime_client/lib/src/version.dart +++ b/packages/realtime_client/lib/src/version.dart @@ -1 +1 @@ -const version = '2.4.0'; +const version = '2.4.1'; diff --git a/packages/realtime_client/pubspec.yaml b/packages/realtime_client/pubspec.yaml index 6ef77e14..08af4775 100644 --- a/packages/realtime_client/pubspec.yaml +++ b/packages/realtime_client/pubspec.yaml @@ -1,6 +1,6 @@ name: realtime_client description: Listens to changes in a PostgreSQL Database and via websockets. This is for usage with Supabase Realtime server. -version: 2.4.0 +version: 2.4.1 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/realtime_client' documentation: 'https://supabase.com/docs/reference/dart/subscribe' diff --git a/packages/storage_client/CHANGELOG.md b/packages/storage_client/CHANGELOG.md index af070acf..f2d65c57 100644 --- a/packages/storage_client/CHANGELOG.md +++ b/packages/storage_client/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.3.0 + + - **FEAT**: Support mime 2.0.0 ([#1079](https://github.com/supabase/supabase-flutter/pull/1079)). + ## 2.2.0 - **FEAT**: Add logging ([#1042](https://github.com/supabase/supabase-flutter/issues/1042)). ([d1ecabd7](https://github.com/supabase/supabase-flutter/commit/d1ecabd77881a0488d2d4b41ea5ee5abda6c5c35)) diff --git a/packages/storage_client/lib/src/version.dart b/packages/storage_client/lib/src/version.dart index 63ac0966..5f7e4e35 100644 --- a/packages/storage_client/lib/src/version.dart +++ b/packages/storage_client/lib/src/version.dart @@ -1 +1 @@ -const version = '2.2.0'; +const version = '2.3.0'; diff --git a/packages/storage_client/pubspec.yaml b/packages/storage_client/pubspec.yaml index 7dc2e799..d99812bd 100644 --- a/packages/storage_client/pubspec.yaml +++ b/packages/storage_client/pubspec.yaml @@ -1,6 +1,6 @@ name: storage_client description: Dart client library to interact with Supabase Storage. Supabase Storage provides an interface for managing Files stored in S3, using Postgres to manage permissions. -version: 2.2.0 +version: 2.3.0 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/storage_client' documentation: 'https://supabase.com/docs/reference/dart/storage-createbucket' diff --git a/packages/supabase/CHANGELOG.md b/packages/supabase/CHANGELOG.md index f3c7e274..f6b832fc 100644 --- a/packages/supabase/CHANGELOG.md +++ b/packages/supabase/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.6.1 + + - **FIX**(realtime_client): Prevent sending expired tokens ([#1095](https://github.com/supabase/supabase-flutter/issues/1095)). ([1bb034f0](https://github.com/supabase/supabase-flutter/commit/1bb034f0f82b03d629edc733688c8648cf01e5b9)) + - **FIX**(realtime_client): Consolidate realtime subscription for stream ([#1096](https://github.com/supabase/supabase-flutter/issues/1096)). ([4e351155](https://github.com/supabase/supabase-flutter/commit/4e3511551cb0d6da673fa5c4187f7ada2a1f8865)) + ## 2.6.0 - **FIX**: Support custom access token ([#1073](https://github.com/supabase/supabase-flutter/issues/1073)). ([fc9ad2c9](https://github.com/supabase/supabase-flutter/commit/fc9ad2c94a02921ca8ced4564d9bcd8cde2c2397)) diff --git a/packages/supabase/lib/src/version.dart b/packages/supabase/lib/src/version.dart index 1abb1f33..c70846d2 100644 --- a/packages/supabase/lib/src/version.dart +++ b/packages/supabase/lib/src/version.dart @@ -1 +1 @@ -const version = '2.6.0'; +const version = '2.6.1'; diff --git a/packages/supabase/pubspec.yaml b/packages/supabase/pubspec.yaml index 99d6b9bf..e6610aff 100644 --- a/packages/supabase/pubspec.yaml +++ b/packages/supabase/pubspec.yaml @@ -1,6 +1,6 @@ name: supabase description: A dart client for Supabase. This client makes it simple for developers to build secure and scalable products. -version: 2.6.0 +version: 2.6.1 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/supabase' documentation: 'https://supabase.com/docs/reference/dart/introduction' @@ -10,11 +10,11 @@ environment: dependencies: functions_client: 2.4.0 - gotrue: 2.11.0 + gotrue: 2.11.1 http: '>=0.13.5 <2.0.0' postgrest: 2.4.0 - realtime_client: 2.4.0 - storage_client: 2.2.0 + realtime_client: 2.4.1 + storage_client: 2.3.0 rxdart: '>=0.27.5 <0.29.0' yet_another_json_isolate: 2.0.3 logging: ^1.2.0 diff --git a/packages/supabase_flutter/CHANGELOG.md b/packages/supabase_flutter/CHANGELOG.md index 5e79edc5..834bfba8 100644 --- a/packages/supabase_flutter/CHANGELOG.md +++ b/packages/supabase_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.8.2 + + - Update a dependency to the latest release. + ## 2.8.1 - **FIX**: Support custom access token ([#1073](https://github.com/supabase/supabase-flutter/issues/1073)). ([fc9ad2c9](https://github.com/supabase/supabase-flutter/commit/fc9ad2c94a02921ca8ced4564d9bcd8cde2c2397)) diff --git a/packages/supabase_flutter/example/pubspec.yaml b/packages/supabase_flutter/example/pubspec.yaml index f9dcf65c..a68445e3 100644 --- a/packages/supabase_flutter/example/pubspec.yaml +++ b/packages/supabase_flutter/example/pubspec.yaml @@ -11,7 +11,7 @@ environment: dependencies: flutter: sdk: flutter - supabase_flutter: ^2.8.1 + supabase_flutter: ^2.8.2 dev_dependencies: flutter_test: diff --git a/packages/supabase_flutter/lib/src/version.dart b/packages/supabase_flutter/lib/src/version.dart index 32722310..e8f0f42d 100644 --- a/packages/supabase_flutter/lib/src/version.dart +++ b/packages/supabase_flutter/lib/src/version.dart @@ -1 +1 @@ -const version = '2.8.1'; +const version = '2.8.2'; diff --git a/packages/supabase_flutter/pubspec.yaml b/packages/supabase_flutter/pubspec.yaml index a0443fbf..7b613b71 100644 --- a/packages/supabase_flutter/pubspec.yaml +++ b/packages/supabase_flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: supabase_flutter description: Flutter integration for Supabase. This package makes it simple for developers to build secure and scalable products. -version: 2.8.1 +version: 2.8.2 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/supabase_flutter' documentation: 'https://supabase.com/docs/reference/dart/introduction' @@ -17,7 +17,7 @@ dependencies: sdk: flutter http: '>=0.13.4 <2.0.0' meta: ^1.7.0 - supabase: 2.6.0 + supabase: 2.6.1 url_launcher: ^6.1.2 path_provider: ^2.0.0 shared_preferences: ^2.0.0 From 7c8c8630257984f429406b0d85a8601881712343 Mon Sep 17 00:00:00 2001 From: Blin Qipa Date: Tue, 31 Dec 2024 17:05:57 +0100 Subject: [PATCH 24/29] docs: Fix typo for RPC docs (#1105) --- packages/postgrest/lib/src/postgrest.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/postgrest/lib/src/postgrest.dart b/packages/postgrest/lib/src/postgrest.dart index 9676854f..a598d439 100644 --- a/packages/postgrest/lib/src/postgrest.dart +++ b/packages/postgrest/lib/src/postgrest.dart @@ -86,7 +86,7 @@ class PostgrestClient { /// /// [fn] is the name of the function to call. /// - /// [params] is an optinal object to pass as arguments to the function call. + /// [params] is an optional object to pass as arguments to the function call. /// /// When [get] is set to `true`, the function will be called with read-only /// access mode. From 7e8d7a7809bd7217ecfdd1bfd3ef5ec3e60d24ed Mon Sep 17 00:00:00 2001 From: Vinzent Date: Wed, 1 Jan 2025 12:52:25 +0100 Subject: [PATCH 25/29] chore(supabase_flutter): State that linux is supported (#1104) chore(supabase_flutter): state that linux is supported --- packages/supabase_flutter/pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/supabase_flutter/pubspec.yaml b/packages/supabase_flutter/pubspec.yaml index 7b613b71..09b875a4 100644 --- a/packages/supabase_flutter/pubspec.yaml +++ b/packages/supabase_flutter/pubspec.yaml @@ -36,5 +36,6 @@ platforms: macos: web: windows: + linux: flutter: From ece9ab2cf38106c795bbcbca82d64d0336ddfa6f Mon Sep 17 00:00:00 2001 From: Tyler Date: Mon, 6 Jan 2025 11:13:08 +0900 Subject: [PATCH 26/29] chore(release): Publish packages supabase_flutter@2.8.3 (#1106) chore(release): publish packages - postgrest@2.4.1 - supabase@2.6.2 - supabase_flutter@2.8.3 --- CHANGELOG.md | 30 +++++++++++++++++++ packages/postgrest/CHANGELOG.md | 4 +++ packages/postgrest/lib/src/version.dart | 2 +- packages/postgrest/pubspec.yaml | 2 +- packages/supabase/CHANGELOG.md | 4 +++ packages/supabase/lib/src/version.dart | 2 +- packages/supabase/pubspec.yaml | 4 +-- packages/supabase_flutter/CHANGELOG.md | 4 +++ .../supabase_flutter/example/pubspec.yaml | 2 +- .../supabase_flutter/lib/src/version.dart | 2 +- packages/supabase_flutter/pubspec.yaml | 4 +-- 11 files changed, 51 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7734e596..28d7cc93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,36 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## 2025-01-01 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`postgrest` - `v2.4.1`](#postgrest---v241) + - [`supabase` - `v2.6.2`](#supabase---v262) + - [`supabase_flutter` - `v2.8.3`](#supabase_flutter---v283) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `supabase` - `v2.6.2` + - `supabase_flutter` - `v2.8.3` + +--- + +#### `postgrest` - `v2.4.1` + + - **DOCS**: Fix typo for RPC docs ([#1105](https://github.com/supabase/supabase-flutter/issues/1105)). ([7c8c8630](https://github.com/supabase/supabase-flutter/commit/7c8c8630257984f429406b0d85a8601881712343)) + + ## 2024-12-17 ### Changes diff --git a/packages/postgrest/CHANGELOG.md b/packages/postgrest/CHANGELOG.md index 8bc7c39d..d5fea62c 100644 --- a/packages/postgrest/CHANGELOG.md +++ b/packages/postgrest/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.4.1 + + - **DOCS**: Fix typo for RPC docs ([#1105](https://github.com/supabase/supabase-flutter/issues/1105)). ([7c8c8630](https://github.com/supabase/supabase-flutter/commit/7c8c8630257984f429406b0d85a8601881712343)) + ## 2.4.0 - **FEAT**: Read-only access mode rpc ([#1081](https://github.com/supabase/supabase-flutter/issues/1081)). ([d0a04154](https://github.com/supabase/supabase-flutter/commit/d0a04154ff56d40d00e1c9282d8ba859681c7275)) diff --git a/packages/postgrest/lib/src/version.dart b/packages/postgrest/lib/src/version.dart index 3dcb91ee..b6a2119a 100644 --- a/packages/postgrest/lib/src/version.dart +++ b/packages/postgrest/lib/src/version.dart @@ -1 +1 @@ -const version = '2.4.0'; +const version = '2.4.1'; diff --git a/packages/postgrest/pubspec.yaml b/packages/postgrest/pubspec.yaml index 6a0c849d..5c3446a7 100644 --- a/packages/postgrest/pubspec.yaml +++ b/packages/postgrest/pubspec.yaml @@ -1,6 +1,6 @@ name: postgrest description: PostgREST client for Dart. This library provides an ORM interface to PostgREST. -version: 2.4.0 +version: 2.4.1 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/postgrest' documentation: 'https://supabase.com/docs/reference/dart/select' diff --git a/packages/supabase/CHANGELOG.md b/packages/supabase/CHANGELOG.md index f6b832fc..c5169055 100644 --- a/packages/supabase/CHANGELOG.md +++ b/packages/supabase/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.6.2 + + - Update a dependency to the latest release. + ## 2.6.1 - **FIX**(realtime_client): Prevent sending expired tokens ([#1095](https://github.com/supabase/supabase-flutter/issues/1095)). ([1bb034f0](https://github.com/supabase/supabase-flutter/commit/1bb034f0f82b03d629edc733688c8648cf01e5b9)) diff --git a/packages/supabase/lib/src/version.dart b/packages/supabase/lib/src/version.dart index c70846d2..d0eae208 100644 --- a/packages/supabase/lib/src/version.dart +++ b/packages/supabase/lib/src/version.dart @@ -1 +1 @@ -const version = '2.6.1'; +const version = '2.6.2'; diff --git a/packages/supabase/pubspec.yaml b/packages/supabase/pubspec.yaml index e6610aff..beb9e153 100644 --- a/packages/supabase/pubspec.yaml +++ b/packages/supabase/pubspec.yaml @@ -1,6 +1,6 @@ name: supabase description: A dart client for Supabase. This client makes it simple for developers to build secure and scalable products. -version: 2.6.1 +version: 2.6.2 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/supabase' documentation: 'https://supabase.com/docs/reference/dart/introduction' @@ -12,7 +12,7 @@ dependencies: functions_client: 2.4.0 gotrue: 2.11.1 http: '>=0.13.5 <2.0.0' - postgrest: 2.4.0 + postgrest: 2.4.1 realtime_client: 2.4.1 storage_client: 2.3.0 rxdart: '>=0.27.5 <0.29.0' diff --git a/packages/supabase_flutter/CHANGELOG.md b/packages/supabase_flutter/CHANGELOG.md index 834bfba8..857a1823 100644 --- a/packages/supabase_flutter/CHANGELOG.md +++ b/packages/supabase_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.8.3 + + - Update a dependency to the latest release. + ## 2.8.2 - Update a dependency to the latest release. diff --git a/packages/supabase_flutter/example/pubspec.yaml b/packages/supabase_flutter/example/pubspec.yaml index a68445e3..c8ffda1a 100644 --- a/packages/supabase_flutter/example/pubspec.yaml +++ b/packages/supabase_flutter/example/pubspec.yaml @@ -11,7 +11,7 @@ environment: dependencies: flutter: sdk: flutter - supabase_flutter: ^2.8.2 + supabase_flutter: ^2.8.3 dev_dependencies: flutter_test: diff --git a/packages/supabase_flutter/lib/src/version.dart b/packages/supabase_flutter/lib/src/version.dart index e8f0f42d..b98ae837 100644 --- a/packages/supabase_flutter/lib/src/version.dart +++ b/packages/supabase_flutter/lib/src/version.dart @@ -1 +1 @@ -const version = '2.8.2'; +const version = '2.8.3'; diff --git a/packages/supabase_flutter/pubspec.yaml b/packages/supabase_flutter/pubspec.yaml index 09b875a4..271442cc 100644 --- a/packages/supabase_flutter/pubspec.yaml +++ b/packages/supabase_flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: supabase_flutter description: Flutter integration for Supabase. This package makes it simple for developers to build secure and scalable products. -version: 2.8.2 +version: 2.8.3 homepage: 'https://supabase.com' repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/supabase_flutter' documentation: 'https://supabase.com/docs/reference/dart/introduction' @@ -17,7 +17,7 @@ dependencies: sdk: flutter http: '>=0.13.4 <2.0.0' meta: ^1.7.0 - supabase: 2.6.1 + supabase: 2.6.2 url_launcher: ^6.1.2 path_provider: ^2.0.0 shared_preferences: ^2.0.0 From 46b483f83a70fb7785ef3bccca6849fa6b07852c Mon Sep 17 00:00:00 2001 From: mmvergara <104471209+mmvergara@users.noreply.github.com> Date: Sun, 12 Jan 2025 05:54:34 +0800 Subject: [PATCH 27/29] docs: Fix typos (#1108) --- packages/functions_client/lib/src/functions_client.dart | 4 ++-- packages/storage_client/lib/src/storage_client.dart | 2 +- packages/supabase/lib/src/realtime_client_options.dart | 2 +- packages/supabase_flutter/lib/src/supabase.dart | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/functions_client/lib/src/functions_client.dart b/packages/functions_client/lib/src/functions_client.dart index c83cc368..7506e223 100644 --- a/packages/functions_client/lib/src/functions_client.dart +++ b/packages/functions_client/lib/src/functions_client.dart @@ -39,7 +39,7 @@ class FunctionsClient { /// Updates the authorization header /// - /// [token] - the new jwt token sent in the authorisation header + /// [token] - the new jwt token sent in the authorization header void setAuth(String token) { _headers['Authorization'] = 'Bearer $token'; } @@ -53,7 +53,7 @@ class FunctionsClient { /// [body] of the request when [files] is null and can be of type String /// or an Object that is encodable to JSON with `jsonEncode`. /// If [files] is not null, [body] represents the fields of the - /// [MultipartRequest] and must be be of type `Map`. + /// [MultipartRequest] and must be of type `Map`. /// /// [files] to send in a `MultipartRequest`. [body] is used for the fields. /// diff --git a/packages/storage_client/lib/src/storage_client.dart b/packages/storage_client/lib/src/storage_client.dart index b51af7e4..89b7db95 100644 --- a/packages/storage_client/lib/src/storage_client.dart +++ b/packages/storage_client/lib/src/storage_client.dart @@ -38,7 +38,7 @@ class SupabaseStorageClient extends StorageBucketApi { int retryAttempts = 0, }) : assert( retryAttempts >= 0, - 'retryAttempts has to be great than or equal to 0', + 'retryAttempts has to be greater than or equal to 0', ), _defaultRetryAttempts = retryAttempts, super( diff --git a/packages/supabase/lib/src/realtime_client_options.dart b/packages/supabase/lib/src/realtime_client_options.dart index 7b4e593b..2c1ffb39 100644 --- a/packages/supabase/lib/src/realtime_client_options.dart +++ b/packages/supabase/lib/src/realtime_client_options.dart @@ -11,7 +11,7 @@ class RealtimeClientOptions { 'Client side rate limit has been removed. This option will be ignored.') final int? eventsPerSecond; - /// Level of realtime server logs to to be logged + /// Level of realtime server logs to be logged final RealtimeLogLevel? logLevel; /// the timeout to trigger push timeouts diff --git a/packages/supabase_flutter/lib/src/supabase.dart b/packages/supabase_flutter/lib/src/supabase.dart index 952460b0..a759d093 100644 --- a/packages/supabase_flutter/lib/src/supabase.dart +++ b/packages/supabase_flutter/lib/src/supabase.dart @@ -257,7 +257,7 @@ class Supabase with WidgetsBindingObserver { // Reconnect if the socket is currently not connected. // When coming from [AppLifecycleState.paused] this should be the case, // but when coming from [AppLifecycleState.inactive] no disconnect - // happened and therefore connection should still be intanct and we + // happened and therefore connection should still be intact and we // should not reconnect. // ignore: invalid_use_of_internal_member From d04d9e63bcc46c2ee71e311b4c3addb216f0f520 Mon Sep 17 00:00:00 2001 From: Tyler Date: Thu, 16 Jan 2025 19:09:46 +0900 Subject: [PATCH 28/29] fix: Allow null to be returned from accessToken function when the user is not signed in (#1099) --- packages/supabase/lib/src/supabase_client.dart | 2 +- packages/supabase_flutter/lib/src/supabase.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/supabase/lib/src/supabase_client.dart b/packages/supabase/lib/src/supabase_client.dart index 4a7d52de..8f095238 100644 --- a/packages/supabase/lib/src/supabase_client.dart +++ b/packages/supabase/lib/src/supabase_client.dart @@ -63,7 +63,7 @@ class SupabaseClient { late final PostgrestClient rest; StreamSubscription? _authStateSubscription; late final YAJsonIsolate _isolate; - final Future Function()? accessToken; + final Future Function()? accessToken; /// Increment ID of the stream to create different realtime topic for each stream final _incrementId = Counter(); diff --git a/packages/supabase_flutter/lib/src/supabase.dart b/packages/supabase_flutter/lib/src/supabase.dart index a759d093..45a7cc29 100644 --- a/packages/supabase_flutter/lib/src/supabase.dart +++ b/packages/supabase_flutter/lib/src/supabase.dart @@ -81,7 +81,7 @@ class Supabase with WidgetsBindingObserver { PostgrestClientOptions postgrestOptions = const PostgrestClientOptions(), StorageClientOptions storageOptions = const StorageClientOptions(), FlutterAuthClientOptions authOptions = const FlutterAuthClientOptions(), - Future Function()? accessToken, + Future Function()? accessToken, bool? debug, }) async { assert( @@ -186,7 +186,7 @@ class Supabase with WidgetsBindingObserver { required PostgrestClientOptions postgrestOptions, required StorageClientOptions storageOptions, required AuthClientOptions authOptions, - required Future Function()? accessToken, + required Future Function()? accessToken, }) { final headers = { ...Constants.defaultHeaders, From 6976faebd643b490f1660b54c2a5ce436d7ce021 Mon Sep 17 00:00:00 2001 From: Tyler Date: Thu, 30 Jan 2025 20:34:07 +0900 Subject: [PATCH 29/29] chore: Generate coverage reports and upload them. (#1110) * chore: Update CI to generate coverage report. * chore: Upload coverage results * removed unneccessary code * update command name * test command * verify melos path * Update workflow to format and upload the report * fix functions ci * update path of functionc ci * fix path for all ci * fix: Update type for customAccessToken * set concurrency to 1 for realtime tests * fix: failing realtime tests * add stub to realtime * more stubs * update analyzer check in ci * Run tests on every PR for every packages and upload combined coverage report * Let the coverage workflow take care of everything about test coverage * update coverage workflow to setup test infra * add sleep after docker compose up * move the sleep * take down docker after testing * go back to the infra directory for taking down docker * revert changes on the individual CI files * Properly upload code coverage to coverall --- .github/workflows/coverage.yml | 96 +++++++++++++++++++ .github/workflows/functions_client.yml | 5 + .github/workflows/gotrue.yml | 5 + .github/workflows/postgrest.yml | 5 + .github/workflows/realtime_client.yml | 7 +- .github/workflows/storage_client.yml | 5 + .github/workflows/supabase.yml | 5 + .github/workflows/supabase_flutter.yml | 2 +- .../workflows/yet_another_json_isolate.yml | 5 + melos.yaml | 3 +- .../lib/src/realtime_client.dart | 2 +- .../realtime_client/test/socket_test.dart | 6 +- 12 files changed, 141 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/coverage.yml diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 00000000..8f32fe19 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,96 @@ +name: Combined Coverage Report + +on: + push: + branches: + - main + pull_request: + +jobs: + coverage: + name: Generate Combined Coverage + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: subosito/flutter-action@v2 + with: + flutter-version: '3.x' + channel: 'stable' + + - name: Install dependencies + run: | + dart pub global activate melos + dart pub global activate coverage + dart pub global activate combine_coverage + melos bootstrap + + - name: Run tests with coverage for all packages + run: | + # Create directory for combined coverage + mkdir coverage + + # Run tests for each package and generate coverage + cd packages + for d in */ ; do + cd "$d" + if [ -f "pubspec.yaml" ]; then + echo "Running tests for $d" + if [[ "$d" == "supabase_flutter/"* ]]; then + flutter test --coverage --concurrency=1 + else + # Set up Docker containers based on package + if [[ "$d" == "postgrest/"* ]]; then + cd ../../infra/postgrest + docker compose down + docker compose up -d + cd ../../packages/postgrest + dart test --coverage=coverage --concurrency=1 + dart pub global run coverage:format_coverage --packages=.dart_tool/package_config.json --report-on=lib --lcov -o coverage/lcov.info -i coverage + cd ../../infra/postgrest + docker compose down + sleep 5s + cd ../../packages/postgrest + elif [[ "$d" == "gotrue/"* ]]; then + cd ../../infra/gotrue + docker compose down + docker compose up -d + cd ../../packages/gotrue + dart test --coverage=coverage --concurrency=1 + dart pub global run coverage:format_coverage --packages=.dart_tool/package_config.json --report-on=lib --lcov -o coverage/lcov.info -i coverage + cd ../../infra/gotrue + docker compose down + sleep 5s + cd ../../packages/gotrue + elif [[ "$d" == "storage_client/"* ]]; then + cd ../../infra/storage_client + docker compose down + docker compose up -d + cd ../../packages/storage_client + dart test --coverage=coverage --concurrency=1 + dart pub global run coverage:format_coverage --packages=.dart_tool/package_config.json --report-on=lib --lcov -o coverage/lcov.info -i coverage + cd ../../infra/storage_client + docker compose down + sleep 5s + cd ../../packages/storage_client + else + cd ../../packages/$d + dart test --coverage=coverage --concurrency=1 + dart pub global run coverage:format_coverage --packages=.dart_tool/package_config.json --report-on=lib --lcov -o coverage/lcov.info -i coverage + fi + fi + fi + cd .. + done + cd .. + + - name: Combine coverage reports + run: | + dart pub global run combine_coverage:combine_coverage --repo-path="./" --output-directory="coverage" + + - name: Upload combined coverage report + uses: coverallsapp/github-action@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + path-to-lcov: ./coverage/lcov.info diff --git a/.github/workflows/functions_client.yml b/.github/workflows/functions_client.yml index 7db4a9f7..365f37fb 100644 --- a/.github/workflows/functions_client.yml +++ b/.github/workflows/functions_client.yml @@ -51,7 +51,12 @@ jobs: run: dart format lib test -l 80 --set-exit-if-changed - name: analyzer + if: ${{ matrix.sdk == 'stable'}} run: dart analyze --fatal-warnings --fatal-infos . + - name: analyzer + if: ${{ matrix.sdk == 'beta' || matrix.sdk == 'dev' }} + run: dart analyze + - name: Run tests run: dart test diff --git a/.github/workflows/gotrue.yml b/.github/workflows/gotrue.yml index d7df105f..c6e6e86e 100644 --- a/.github/workflows/gotrue.yml +++ b/.github/workflows/gotrue.yml @@ -48,8 +48,13 @@ jobs: run: dart format lib test -l 80 --set-exit-if-changed - name: analyzer + if: ${{ matrix.sdk == 'stable'}} run: dart analyze --fatal-warnings --fatal-infos . + - name: analyzer + if: ${{ matrix.sdk == 'beta' || matrix.sdk == 'dev' }} + run: dart analyze + - name: Build Docker image run: | cd ../../infra/gotrue diff --git a/.github/workflows/postgrest.yml b/.github/workflows/postgrest.yml index 49e6f637..57fad3ac 100644 --- a/.github/workflows/postgrest.yml +++ b/.github/workflows/postgrest.yml @@ -50,8 +50,13 @@ jobs: run: dart format lib test -l 80 --set-exit-if-changed - name: analyzer + if: ${{ matrix.sdk == 'stable'}} run: dart analyze --fatal-warnings --fatal-infos . + - name: analyzer + if: ${{ matrix.sdk == 'beta' || matrix.sdk == 'dev' }} + run: dart analyze + - name: Build Docker image run: | cd ../../infra/postgrest diff --git a/.github/workflows/realtime_client.yml b/.github/workflows/realtime_client.yml index 48216378..12b6ab8b 100644 --- a/.github/workflows/realtime_client.yml +++ b/.github/workflows/realtime_client.yml @@ -48,7 +48,12 @@ jobs: run: dart format lib test -l 80 --set-exit-if-changed - name: analyzer + if: ${{ matrix.sdk == 'stable'}} run: dart analyze --fatal-warnings --fatal-infos . + - name: analyzer + if: ${{ matrix.sdk == 'beta' || matrix.sdk == 'dev' }} + run: dart analyze + - name: Run tests - run: dart test + run: dart test --concurrency=1 diff --git a/.github/workflows/storage_client.yml b/.github/workflows/storage_client.yml index 33e0b989..2aec79e4 100644 --- a/.github/workflows/storage_client.yml +++ b/.github/workflows/storage_client.yml @@ -47,8 +47,13 @@ jobs: run: dart format lib test -l 80 --set-exit-if-changed - name: analyzer + if: ${{ matrix.sdk == 'stable'}} run: dart analyze --fatal-warnings --fatal-infos . + - name: analyzer + if: ${{ matrix.sdk == 'beta' || matrix.sdk == 'dev' }} + run: dart analyze + - name: Build Docker image run: | cd ../../infra/storage_client diff --git a/.github/workflows/supabase.yml b/.github/workflows/supabase.yml index 80b8b59d..c6b89046 100644 --- a/.github/workflows/supabase.yml +++ b/.github/workflows/supabase.yml @@ -58,7 +58,12 @@ jobs: run: dart format lib test -l 80 --set-exit-if-changed - name: analyzer + if: ${{ matrix.sdk == 'stable'}} run: dart analyze --fatal-warnings --fatal-infos . + - name: analyzer + if: ${{ matrix.sdk == 'beta' || matrix.sdk == 'dev' }} + run: dart analyze + - name: Run tests run: dart test --concurrency=1 diff --git a/.github/workflows/supabase_flutter.yml b/.github/workflows/supabase_flutter.yml index 1ce67e14..1bd63b1c 100644 --- a/.github/workflows/supabase_flutter.yml +++ b/.github/workflows/supabase_flutter.yml @@ -78,4 +78,4 @@ jobs: - name: Verify if Flutter web build is successful run: | cd example - flutter build web + flutter build web \ No newline at end of file diff --git a/.github/workflows/yet_another_json_isolate.yml b/.github/workflows/yet_another_json_isolate.yml index 83408586..f424ded0 100644 --- a/.github/workflows/yet_another_json_isolate.yml +++ b/.github/workflows/yet_another_json_isolate.yml @@ -48,7 +48,12 @@ jobs: run: dart format lib test -l 80 --set-exit-if-changed - name: analyzer + if: ${{ matrix.sdk == 'stable'}} run: dart analyze --fatal-warnings --fatal-infos . + - name: analyzer + if: ${{ matrix.sdk == 'beta' || matrix.sdk == 'dev' }} + run: dart analyze + - name: Run tests run: dart test diff --git a/melos.yaml b/melos.yaml index 798b8843..2feb7757 100644 --- a/melos.yaml +++ b/melos.yaml @@ -39,6 +39,7 @@ scripts: exec: dart pub outdated update-version: + description: Updates the version.dart file for each packages except yet_another_json_isolate run: | # Loop through the packages directory for d in packages/*/ ; do @@ -53,4 +54,4 @@ scripts: rm packages/yet_another_json_isolate/lib/src/version.dart # Stage the version.dart file change git add packages/*/lib/src/version.dart - description: Updates the version.dart file for each packages except yet_another_json_isolate + \ No newline at end of file diff --git a/packages/realtime_client/lib/src/realtime_client.dart b/packages/realtime_client/lib/src/realtime_client.dart index 3ecfb612..b565def9 100644 --- a/packages/realtime_client/lib/src/realtime_client.dart +++ b/packages/realtime_client/lib/src/realtime_client.dart @@ -91,7 +91,7 @@ class RealtimeClient { int longpollerTimeout = 20000; SocketStates? connState; // This is called `accessToken` in realtime-js - Future Function()? customAccessToken; + Future Function()? customAccessToken; /// Initializes the Socket /// diff --git a/packages/realtime_client/test/socket_test.dart b/packages/realtime_client/test/socket_test.dart index 55c50914..a71f4f33 100644 --- a/packages/realtime_client/test/socket_test.dart +++ b/packages/realtime_client/test/socket_test.dart @@ -393,6 +393,8 @@ void main() { mockedSink = MockWebSocketSink(); when(() => mockedSocketChannel.sink).thenReturn(mockedSink); + when(() => mockedSocketChannel.ready).thenAnswer((_) => Future.value()); + when(() => mockedSink.close()).thenAnswer((_) => Future.value()); }); test('sends data to connection when connected', () { @@ -407,7 +409,7 @@ void main() { .called(1); }); - test('buffers data when not connected', () { + test('buffers data when not connected', () async { mockedSocket.connect(); mockedSocket.connState = SocketStates.connecting; @@ -575,6 +577,8 @@ void main() { mockedSink = MockWebSocketSink(); when(() => mockedSocketChannel.sink).thenReturn(mockedSink); + when(() => mockedSink.close()).thenAnswer((_) => Future.value()); + when(() => mockedSocketChannel.ready).thenAnswer((_) => Future.value()); mockedSocket.connect(); });