From b8e5adf47679fc139e05cff3640bd36a2892a203 Mon Sep 17 00:00:00 2001 From: dshukertjr Date: Thu, 1 Feb 2024 10:45:07 +0900 Subject: [PATCH 1/6] refactor: Remove hive dependency and add instructinos to migration from v1 while persisting the auth state --- packages/supabase_flutter/README.md | 154 ++++++++++++++++++ .../lib/src/local_storage.dart | 136 ---------------- .../supabase_flutter/lib/src/supabase.dart | 2 +- packages/supabase_flutter/pubspec.yaml | 2 - .../supabase_flutter/test/widget_test.dart | 29 ---- .../test/widget_test_stubs.dart | 25 --- 6 files changed, 155 insertions(+), 193 deletions(-) diff --git a/packages/supabase_flutter/README.md b/packages/supabase_flutter/README.md index b2e631b7..48c3bb6b 100644 --- a/packages/supabase_flutter/README.md +++ b/packages/supabase_flutter/README.md @@ -427,6 +427,160 @@ Supabase.initialize( ); ``` +### Persisting the user session from supabase_flutter v1 + +supabase_flutter v1 used hive to persist the user session. In the current versino of supabase_flutter it uses shared_preferences. If you are updating your app from v1 to v2, you can use the following custom `LocalStorage` implementation to automatically migrate the user session from hive to shared_preferences. + +```dart +const _hiveBoxName = 'supabase_authentication'; + +class MigrationLocalStorage extends LocalStorage { + final SharedPreferencesLocalStorage sharedPreferencesLocalStorage; + late final HiveLocalStorage hiveLocalStorage; + + MigrationLocalStorage({required String persistSessionKey}) + : sharedPreferencesLocalStorage = + SharedPreferencesLocalStorage(persistSessionKey: persistSessionKey); + + @override + Future initialize() async { + await Hive.initFlutter('auth'); + hiveLocalStorage = const HiveLocalStorage(); + await sharedPreferencesLocalStorage.initialize(); + try { + await migrate(); + } on TimeoutException { + // Ignore TimeoutException thrown by Hive methods + // https://github.com/supabase/supabase-flutter/issues/794 + } + } + + @visibleForTesting + Future migrate() async { + // Migrate from Hive to SharedPreferences + if (await Hive.boxExists(_hiveBoxName)) { + await hiveLocalStorage.initialize(); + + final hasHive = await hiveLocalStorage.hasAccessToken(); + if (hasHive) { + final accessToken = await hiveLocalStorage.accessToken(); + final session = + Session.fromJson(jsonDecode(accessToken!)['currentSession']); + if (session == null) { + return; + } + await sharedPreferencesLocalStorage + .persistSession(jsonEncode(session.toJson())); + await hiveLocalStorage.removePersistedSession(); + } + if (Hive.box(_hiveBoxName).isEmpty) { + final boxPath = Hive.box(_hiveBoxName).path; + await Hive.deleteBoxFromDisk(_hiveBoxName); + + //Delete `auth` folder if it's empty + if (!kIsWeb && boxPath != null) { + final boxDir = File(boxPath).parent; + final dirIsEmpty = await boxDir.list().length == 0; + if (dirIsEmpty) { + await boxDir.delete(); + } + } + } + } + } + + @override + Future accessToken() { + return sharedPreferencesLocalStorage.accessToken(); + } + + @override + Future hasAccessToken() { + return sharedPreferencesLocalStorage.hasAccessToken(); + } + + @override + Future persistSession(String persistSessionString) { + return sharedPreferencesLocalStorage.persistSession(persistSessionString); + } + + @override + Future removePersistedSession() { + return sharedPreferencesLocalStorage.removePersistedSession(); + } +} + +/// A [LocalStorage] implementation that implements Hive as the +/// storage method. +class HiveLocalStorage extends LocalStorage { + /// Creates a LocalStorage instance that implements the Hive Database + const HiveLocalStorage(); + + /// The encryption key used by Hive. If null, the box is not encrypted + /// + /// This value should not be redefined in runtime, otherwise the user may + /// not be fetched correctly + /// + /// See also: + /// + /// * + static String? encryptionKey; + + @override + Future initialize() async { + HiveCipher? encryptionCipher; + if (encryptionKey != null) { + encryptionCipher = HiveAesCipher(base64Url.decode(encryptionKey!)); + } + await Hive.initFlutter('auth'); + await Hive.openBox(_hiveBoxName, encryptionCipher: encryptionCipher) + .timeout(const Duration(seconds: 1)); + } + + @override + Future hasAccessToken() { + return Future.value( + Hive.box(_hiveBoxName).containsKey( + supabasePersistSessionKey, + ), + ); + } + + @override + Future accessToken() { + return Future.value( + Hive.box(_hiveBoxName).get(supabasePersistSessionKey) as String?, + ); + } + + @override + Future removePersistedSession() { + return Hive.box(_hiveBoxName).delete(supabasePersistSessionKey); + } + + @override + Future persistSession(String persistSessionString) { + // Flush after X amount of writes + return Hive.box(_hiveBoxName) + .put(supabasePersistSessionKey, persistSessionString); + } +} +``` + +You can then initialize Supabase with `MigrationLocalStorage` and it will automatically migrate the sessino from Hive to SharedPreferences. + +```dart +Supabase.initialize( + // ... + authOptions: FlutterAuthClientOptions( + localStorage: const MigrationLocalStorage( + persistSessionKey: + "sb-${Uri.parse(url).host.split(".").first}-auth-token", + ), + ), +); +``` + --- ## Contributing diff --git a/packages/supabase_flutter/lib/src/local_storage.dart b/packages/supabase_flutter/lib/src/local_storage.dart index 30271e9f..607d4a15 100644 --- a/packages/supabase_flutter/lib/src/local_storage.dart +++ b/packages/supabase_flutter/lib/src/local_storage.dart @@ -1,17 +1,13 @@ import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; -import 'package:hive_flutter/hive_flutter.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import './local_storage_stub.dart' if (dart.library.html) './local_storage_web.dart' as web; -const _hiveBoxName = 'supabase_authentication'; const supabasePersistSessionKey = 'SUPABASE_PERSIST_SESSION_KEY'; /// LocalStorage is used to persist the user session in the device. @@ -64,62 +60,6 @@ class EmptyLocalStorage extends LocalStorage { Future persistSession(persistSessionString) async {} } -/// A [LocalStorage] implementation that implements Hive as the -/// storage method. -class HiveLocalStorage extends LocalStorage { - /// Creates a LocalStorage instance that implements the Hive Database - const HiveLocalStorage(); - - /// The encryption key used by Hive. If null, the box is not encrypted - /// - /// This value should not be redefined in runtime, otherwise the user may - /// not be fetched correctly - /// - /// See also: - /// - /// * - static String? encryptionKey; - - @override - Future initialize() async { - HiveCipher? encryptionCipher; - if (encryptionKey != null) { - encryptionCipher = HiveAesCipher(base64Url.decode(encryptionKey!)); - } - await Hive.initFlutter('auth'); - await Hive.openBox(_hiveBoxName, encryptionCipher: encryptionCipher) - .timeout(const Duration(seconds: 1)); - } - - @override - Future hasAccessToken() { - return Future.value( - Hive.box(_hiveBoxName).containsKey( - supabasePersistSessionKey, - ), - ); - } - - @override - Future accessToken() { - return Future.value( - Hive.box(_hiveBoxName).get(supabasePersistSessionKey) as String?, - ); - } - - @override - Future removePersistedSession() { - return Hive.box(_hiveBoxName).delete(supabasePersistSessionKey); - } - - @override - Future persistSession(String persistSessionString) { - // Flush after X amount of writes - return Hive.box(_hiveBoxName) - .put(supabasePersistSessionKey, persistSessionString); - } -} - /// A [LocalStorage] implementation that implements SharedPreferences as the /// storage method. class SharedPreferencesLocalStorage extends LocalStorage { @@ -173,82 +113,6 @@ class SharedPreferencesLocalStorage extends LocalStorage { } } -class MigrationLocalStorage extends LocalStorage { - final SharedPreferencesLocalStorage sharedPreferencesLocalStorage; - late final HiveLocalStorage hiveLocalStorage; - - MigrationLocalStorage({required String persistSessionKey}) - : sharedPreferencesLocalStorage = - SharedPreferencesLocalStorage(persistSessionKey: persistSessionKey); - - @override - Future initialize() async { - await Hive.initFlutter('auth'); - hiveLocalStorage = const HiveLocalStorage(); - await sharedPreferencesLocalStorage.initialize(); - try { - await migrate(); - } on TimeoutException { - // Ignore TimeoutException thrown by Hive methods - // https://github.com/supabase/supabase-flutter/issues/794 - } - } - - @visibleForTesting - Future migrate() async { - // Migrate from Hive to SharedPreferences - if (await Hive.boxExists(_hiveBoxName)) { - await hiveLocalStorage.initialize(); - - final hasHive = await hiveLocalStorage.hasAccessToken(); - if (hasHive) { - final accessToken = await hiveLocalStorage.accessToken(); - final session = - Session.fromJson(jsonDecode(accessToken!)['currentSession']); - if (session == null) { - return; - } - await sharedPreferencesLocalStorage - .persistSession(jsonEncode(session.toJson())); - await hiveLocalStorage.removePersistedSession(); - } - if (Hive.box(_hiveBoxName).isEmpty) { - final boxPath = Hive.box(_hiveBoxName).path; - await Hive.deleteBoxFromDisk(_hiveBoxName); - - //Delete `auth` folder if it's empty - if (!kIsWeb && boxPath != null) { - final boxDir = File(boxPath).parent; - final dirIsEmpty = await boxDir.list().length == 0; - if (dirIsEmpty) { - await boxDir.delete(); - } - } - } - } - } - - @override - Future accessToken() { - return sharedPreferencesLocalStorage.accessToken(); - } - - @override - Future hasAccessToken() { - return sharedPreferencesLocalStorage.hasAccessToken(); - } - - @override - Future persistSession(String persistSessionString) { - return sharedPreferencesLocalStorage.persistSession(persistSessionString); - } - - @override - Future removePersistedSession() { - return sharedPreferencesLocalStorage.removePersistedSession(); - } -} - /// local storage to store pkce flow code verifier. class SharedPreferencesGotrueAsyncStorage extends GotrueAsyncStorage { SharedPreferencesGotrueAsyncStorage() { diff --git a/packages/supabase_flutter/lib/src/supabase.dart b/packages/supabase_flutter/lib/src/supabase.dart index c6ec30f4..af2c2540 100644 --- a/packages/supabase_flutter/lib/src/supabase.dart +++ b/packages/supabase_flutter/lib/src/supabase.dart @@ -88,7 +88,7 @@ class Supabase { } if (authOptions.localStorage == null) { authOptions = authOptions.copyWith( - localStorage: MigrationLocalStorage( + localStorage: SharedPreferencesLocalStorage( persistSessionKey: "sb-${Uri.parse(url).host.split(".").first}-auth-token", ), diff --git a/packages/supabase_flutter/pubspec.yaml b/packages/supabase_flutter/pubspec.yaml index d53360f8..885eb975 100644 --- a/packages/supabase_flutter/pubspec.yaml +++ b/packages/supabase_flutter/pubspec.yaml @@ -15,8 +15,6 @@ dependencies: crypto: ^3.0.2 flutter: sdk: flutter - hive: ^2.2.1 - hive_flutter: ^1.1.0 http: '>=0.13.4 <2.0.0' meta: ^1.7.0 supabase: ^2.0.7 diff --git a/packages/supabase_flutter/test/widget_test.dart b/packages/supabase_flutter/test/widget_test.dart index 5d04f89d..37d770b3 100644 --- a/packages/supabase_flutter/test/widget_test.dart +++ b/packages/supabase_flutter/test/widget_test.dart @@ -1,12 +1,7 @@ -import 'dart:convert'; -import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:path/path.dart' as path; import 'package:supabase_flutter/supabase_flutter.dart'; -import 'utils.dart'; import 'widget_test_stubs.dart'; void main() { @@ -33,28 +28,4 @@ void main() { await tester.pump(); expect(find.text('You have signed out'), findsOneWidget); }); - - test( - "Migrates from Hive to SharedPreferences", - () async { - final hiveLocalStorage = TestHiveLocalStorage(); - await hiveLocalStorage.initialize(); - final (:accessToken, :sessionString) = getSessionData(DateTime.now()); - await hiveLocalStorage - .persistSession('{"currentSession":$sessionString}'); - final boxFile = - File("${path.current}/auth_test/supabase_authentication.hive"); - expect(await boxFile.exists(), true); - - final migrationLocalStorage = TestMigrationLocalStorage(); - await migrationLocalStorage.initialize(); - - final migratedSessionString = await migrationLocalStorage.accessToken(); - final migratedSession = - Session.fromJson(jsonDecode(migratedSessionString!)); - expect(await boxFile.exists(), false); - expect(accessToken, migratedSession!.accessToken); - expect(await boxFile.parent.exists(), false); - }, - ); } diff --git a/packages/supabase_flutter/test/widget_test_stubs.dart b/packages/supabase_flutter/test/widget_test_stubs.dart index a6b2cab1..df2cdd66 100644 --- a/packages/supabase_flutter/test/widget_test_stubs.dart +++ b/packages/supabase_flutter/test/widget_test_stubs.dart @@ -4,10 +4,7 @@ import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:hive_flutter/hive_flutter.dart'; import 'package:http/http.dart'; -import 'package:path/path.dart' as path; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'utils.dart'; @@ -84,28 +81,6 @@ class MockLocalStorage extends LocalStorage { Future removePersistedSession() async {} } -class TestHiveLocalStorage extends HiveLocalStorage { - @override - Future initialize() async { - Hive.init("${path.current}/auth_test"); - await Hive.openBox("supabase_authentication"); - } -} - -class TestMigrationLocalStorage extends MigrationLocalStorage { - TestMigrationLocalStorage() - : super(persistSessionKey: "SUPABASE_PERSIST_SESSION_KEY"); - - @override - Future initialize() async { - Hive.init("${path.current}/auth_test"); - hiveLocalStorage = TestHiveLocalStorage(); - SharedPreferences.setMockInitialValues({}); - await sharedPreferencesLocalStorage.initialize(); - await migrate(); - } -} - class MockEmptyLocalStorage extends LocalStorage { @override Future initialize() async {} From 076e6599f6c112a33a0449acbdfdcc8f5245c143 Mon Sep 17 00:00:00 2001 From: Tyler Date: Fri, 2 Feb 2024 11:46:05 +0900 Subject: [PATCH 2/6] Update packages/supabase_flutter/README.md Co-authored-by: Vinzent --- packages/supabase_flutter/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/supabase_flutter/README.md b/packages/supabase_flutter/README.md index 48c3bb6b..a1e8e727 100644 --- a/packages/supabase_flutter/README.md +++ b/packages/supabase_flutter/README.md @@ -567,7 +567,7 @@ class HiveLocalStorage extends LocalStorage { } ``` -You can then initialize Supabase with `MigrationLocalStorage` and it will automatically migrate the sessino from Hive to SharedPreferences. +You can then initialize Supabase with `MigrationLocalStorage` and it will automatically migrate the session from Hive to SharedPreferences. ```dart Supabase.initialize( From 6b0e7893b292af6745a156a5ede0929f1e4a965b Mon Sep 17 00:00:00 2001 From: Tyler Date: Fri, 2 Feb 2024 11:57:46 +0900 Subject: [PATCH 3/6] Update packages/supabase_flutter/README.md --- packages/supabase_flutter/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/supabase_flutter/README.md b/packages/supabase_flutter/README.md index a1e8e727..12b0a447 100644 --- a/packages/supabase_flutter/README.md +++ b/packages/supabase_flutter/README.md @@ -429,7 +429,7 @@ Supabase.initialize( ### Persisting the user session from supabase_flutter v1 -supabase_flutter v1 used hive to persist the user session. In the current versino of supabase_flutter it uses shared_preferences. If you are updating your app from v1 to v2, you can use the following custom `LocalStorage` implementation to automatically migrate the user session from hive to shared_preferences. +supabase_flutter v1 used hive to persist the user session. In the current version of supabase_flutter it uses shared_preferences. If you are updating your app from v1 to v2, you can use the following custom `LocalStorage` implementation to automatically migrate the user session from hive to shared_preferences. ```dart const _hiveBoxName = 'supabase_authentication'; From 3d2f8946b570b12a6b2196317663112870787138 Mon Sep 17 00:00:00 2001 From: dshukertjr Date: Fri, 2 Feb 2024 13:01:19 +0900 Subject: [PATCH 4/6] add link to hive and shared preferences --- packages/supabase_flutter/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/supabase_flutter/README.md b/packages/supabase_flutter/README.md index 12b0a447..43c78af2 100644 --- a/packages/supabase_flutter/README.md +++ b/packages/supabase_flutter/README.md @@ -429,7 +429,7 @@ Supabase.initialize( ### Persisting the user session from supabase_flutter v1 -supabase_flutter v1 used hive to persist the user session. In the current version of supabase_flutter it uses shared_preferences. If you are updating your app from v1 to v2, you can use the following custom `LocalStorage` implementation to automatically migrate the user session from hive to shared_preferences. +supabase_flutter v1 used hive to persist the user session. In the current version of supabase_flutter it uses shared_preferences. If you are updating your app from v1 to v2, you can use the following custom `LocalStorage` implementation to automatically migrate the user session from [hive](https://pub.dev/packages/hive) to [shared_preferences](https://pub.dev/packages/shared_preferences). ```dart const _hiveBoxName = 'supabase_authentication'; From f890d70addc17cc258c21de9f3d0a8cfeacc31ff Mon Sep 17 00:00:00 2001 From: dshukertjr Date: Sat, 3 Feb 2024 22:12:51 +0900 Subject: [PATCH 5/6] Add migration guide link --- packages/supabase_flutter/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/supabase_flutter/README.md b/packages/supabase_flutter/README.md index 43c78af2..b1c04c8d 100644 --- a/packages/supabase_flutter/README.md +++ b/packages/supabase_flutter/README.md @@ -583,6 +583,11 @@ Supabase.initialize( --- +## Migrating Guide + +You can find the migration guide to migrate from v1 to v2 here: +https://supabase.com/docs/reference/dart/upgrade-guide + ## Contributing - Fork the repo on [GitHub](https://github.com/supabase/supabase-flutter) From 82f23e8b5346263557511b13c86dc40452dee10b Mon Sep 17 00:00:00 2001 From: dshukertjr Date: Mon, 5 Feb 2024 22:12:12 +0900 Subject: [PATCH 6/6] Add upgrade guide on changelog --- packages/supabase_flutter/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/supabase_flutter/CHANGELOG.md b/packages/supabase_flutter/CHANGELOG.md index 8cde7597..e8b6259d 100644 --- a/packages/supabase_flutter/CHANGELOG.md +++ b/packages/supabase_flutter/CHANGELOG.md @@ -31,7 +31,7 @@ ## 2.0.0 - - Graduate package to a stable release. See pre-releases prior to this version for changelog entries. + - Graduate package to a stable release. See pre-releases prior to this version for changelog entries. Upgrade guide can be found [here](https://supabase.com/docs/reference/dart/upgrade-guide). ## 2.0.0-dev.4