Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework the implementation #4

Merged
merged 8 commits into from
Jul 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import 'package:supabase_flutter/supabase_flutter.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();

Supabase(
Supabase.initialize(
url: SUPABASE_URL,
anonKey: SUPABASE_ANNON_KEY,
authCallbackUrlHostname: 'login-callback', // optional
Expand All @@ -45,7 +45,7 @@ Now you can access Supabase client anywhere in your app.
```dart
import 'package:supabase_flutter/supabase_flutter.dart';

final response = await Supabase().client.auth.signIn(email: _email, password: _password);
final response = await Supabase.instance.client.auth.signIn(email: _email, password: _password);
```

#### SupabaseAuthState
Expand All @@ -67,8 +67,8 @@ For more details, take a look at the example [here](https://github.com/phamhieu/
This method will automatically launch the auth url and open a browser for user to sign in with 3rd party login.

```dart
Supabase().client.auth.signInWithProvider(
supabase.Provider.github,
Supabase.instance.client.auth.signInWithProvider(
Provider.github,
options: supabase.AuthOptions(redirectTo: ''),
);
```
Expand All @@ -93,7 +93,8 @@ final localStorage = LocalStorage(
const storage = FlutterSecureStorage();
return storage.write(key: supabasePersistSessionKey, value: value);
});
Supabase(

Supabase.initialize(
...
localStorage: localStorage,
);
Expand Down
99 changes: 52 additions & 47 deletions lib/src/supabase.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,60 +4,75 @@ import 'package:url_launcher/url_launcher.dart';

const supabasePersistSessionKey = 'SUPABASE_PERSIST_SESSION_KEY';

Future<bool> defHasAccessToken() async {
Future<bool> _defHasAccessToken() async {
final prefs = await SharedPreferences.getInstance();
final exist = prefs.containsKey(supabasePersistSessionKey);
return exist;
}

Future<String?> defAccessToken() async {
Future<String?> _defAccessToken() async {
final prefs = await SharedPreferences.getInstance();
final jsonStr = prefs.getString(supabasePersistSessionKey);
return jsonStr;
}

Future defRemovePersistedSession() async {
Future _defRemovePersistedSession() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.remove(supabasePersistSessionKey);
}

Future defPersistSession(String persistSessionString) async {
Future _defPersistSession(String persistSessionString) async {
final prefs = await SharedPreferences.getInstance();
return prefs.setString(supabasePersistSessionKey, persistSessionString);
}

class LocalStorage {
LocalStorage(
{Future<bool> Function()? hasAccessToken,
Future<String?> Function()? accessToken,
Future Function()? removePersistedSession,
Future Function(String)? persistSession}) {
this.hasAccessToken = hasAccessToken ?? defHasAccessToken;
this.accessToken = accessToken ?? defAccessToken;
this.removePersistedSession =
removePersistedSession ?? defRemovePersistedSession;
this.persistSession = persistSession ?? defPersistSession;
}

Future<bool> Function() hasAccessToken = defHasAccessToken;
Future<String?> Function() accessToken = defAccessToken;
Future Function() removePersistedSession = defRemovePersistedSession;
Future Function(String) persistSession = defPersistSession;
/// Creates a `LocalStorage` instance
const LocalStorage({
this.hasAccessToken = _defHasAccessToken,
this.accessToken = _defAccessToken,
this.removePersistedSession = _defRemovePersistedSession,
this.persistSession = _defPersistSession,
});

final Future<bool> Function() hasAccessToken;
final Future<String?> Function() accessToken;
final Future Function() removePersistedSession;
final Future Function(String) persistSession;
}

class Supabase {
factory Supabase({
/// Gets the current supabase instance.
///
/// An error is thrown if supabase isn't initialized yet
static Supabase get instance {
assert(
_instance._initialized,
'You must initialize the supabase instance before calling Supabase.instance',
);
return _instance;
}

/// Initialize the current supabase instance
///
/// This must be called only once. If called more than once, an
/// [AssertionError] is thrown
factory Supabase.initialize({
String? url,
String? anonKey,
String? authCallbackUrlHostname,
bool? debug,
LocalStorage? localStorage,
}) {
assert(
!_instance._initialized,
'This instance is already initialized',
);
if (url != null && anonKey != null) {
_instance._init(url, anonKey);
_instance._authCallbackUrlHostname = authCallbackUrlHostname;
_instance._debugEnable = debug ?? false;
_instance._localStorage = localStorage ?? LocalStorage();
_instance._localStorage = localStorage ?? const LocalStorage();
_instance.log('***** Supabase init completed $_instance');
}

Expand All @@ -67,48 +82,37 @@ class Supabase {
Supabase._privateConstructor();
static final Supabase _instance = Supabase._privateConstructor();

SupabaseClient? _client;
bool _initialized = false;

/// The supabase client for this instance
late final SupabaseClient client;
GotrueSubscription? _initialClientSubscription;
bool _initialDeeplinkIsHandled = false;
bool _debugEnable = false;

String? _authCallbackUrlHostname;
LocalStorage _localStorage = LocalStorage();
LocalStorage _localStorage = const LocalStorage();

/// Dispose the instance
void dispose() {
if (_initialClientSubscription != null) {
_initialClientSubscription!.data!.unsubscribe();
}
_initialized = false;
}

void _init(String supabaseUrl, String supabaseAnonKey) {
if (_client != null) {
throw 'Supabase client is initialized more than once $_client';
}

_client = SupabaseClient(supabaseUrl, supabaseAnonKey);
client = SupabaseClient(supabaseUrl, supabaseAnonKey);
_initialClientSubscription =
_client!.auth.onAuthStateChange(_onAuthStateChange);
}

SupabaseClient get client {
if (_client == null) {
throw 'Supabase client is not initialized';
}
return _client!;
client.auth.onAuthStateChange(_onAuthStateChange);
_initialized = true;
}

LocalStorage get localStorage {
return _localStorage;
}
LocalStorage get localStorage => _localStorage;

Future<bool> get hasAccessToken async {
return _localStorage.hasAccessToken();
}
Future<bool> get hasAccessToken => _localStorage.hasAccessToken();

Future<String?> get accessToken async {
return _localStorage.accessToken();
}
Future<String?> get accessToken => _localStorage.accessToken();

void _onAuthStateChange(AuthChangeEvent event, Session? session) {
log('**** onAuthStateChange: $event');
Expand Down Expand Up @@ -172,7 +176,8 @@ class Supabase {
}

extension GoTrueClientSignInProvider on GoTrueClient {
Future<bool> signInWithProvider(Provider? provider,
/// Signs the user in using a thrid parties providers.
Future<bool> signInWithProvider(Provider provider,
{AuthOptions? options}) async {
final res = await signIn(
provider: provider,
Expand Down
21 changes: 11 additions & 10 deletions lib/src/supabase_auth_required_state.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:supabase/supabase.dart';
import 'package:supabase_flutter/src/supabase.dart';
import 'package:supabase_flutter/src/supabase_state.dart';
Expand All @@ -10,22 +10,22 @@ abstract class SupabaseAuthRequiredState<T extends StatefulWidget>
void initState() {
super.initState();

if (Supabase().client.auth.currentSession == null) {
if (Supabase.instance.client.auth.currentSession == null) {
_recoverSupabaseSession();
} else {
onAuthenticated(Supabase().client.auth.currentSession!);
onAuthenticated(Supabase.instance.client.auth.currentSession!);
}
}

@override
void startAuthObserver() {
Supabase().log('***** SupabaseAuthRequiredState startAuthObserver');
Supabase.instance.log('***** SupabaseAuthRequiredState startAuthObserver');
WidgetsBinding.instance?.addObserver(this);
}

@override
void stopAuthObserver() {
Supabase().log('***** SupabaseAuthRequiredState stopAuthObserver');
Supabase.instance.log('***** SupabaseAuthRequiredState stopAuthObserver');
WidgetsBinding.instance?.removeObserver(this);
}

Expand All @@ -45,26 +45,27 @@ abstract class SupabaseAuthRequiredState<T extends StatefulWidget>
}

Future<bool> onResumed() async {
Supabase().log('***** SupabaseAuthRequiredState onResumed');
Supabase.instance.log('***** SupabaseAuthRequiredState onResumed');
return _recoverSupabaseSession();
}

Future<bool> _recoverSupabaseSession() async {
final bool exist = await Supabase().localStorage.hasAccessToken();
final bool exist = await Supabase.instance.localStorage.hasAccessToken();
if (!exist) {
onUnauthenticated();
return false;
}

final String? jsonStr = await Supabase().localStorage.accessToken();
final String? jsonStr = await Supabase.instance.localStorage.accessToken();
if (jsonStr == null) {
onUnauthenticated();
return false;
}

final response = await Supabase().client.auth.recoverSession(jsonStr);
final response =
await Supabase.instance.client.auth.recoverSession(jsonStr);
if (response.error != null) {
Supabase().localStorage.removePersistedSession();
Supabase.instance.localStorage.removePersistedSession();
onUnauthenticated();
return false;
} else {
Expand Down
27 changes: 14 additions & 13 deletions lib/src/supabase_auth_state.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:supabase/supabase.dart';
import 'package:supabase_flutter/src/supabase.dart';
import 'package:supabase_flutter/src/supabase_state.dart';
Expand All @@ -10,21 +10,21 @@ abstract class SupabaseAuthState<T extends StatefulWidget>
extends SupabaseState<T> with SupabaseDeepLinkingMixin {
@override
void startAuthObserver() {
Supabase().log('***** SupabaseAuthState startAuthObserver');
Supabase.instance.log('***** SupabaseAuthState startAuthObserver');
startDeeplinkObserver();
}

@override
void stopAuthObserver() {
Supabase().log('***** SupabaseAuthState stopAuthObserver');
Supabase.instance.log('***** SupabaseAuthState stopAuthObserver');
stopDeeplinkObserver();
}

@override
Future<bool> handleDeeplink(Uri uri) async {
if (!Supabase().isAuthCallbackDeeplink(uri)) return false;
if (!Supabase.instance.isAuthCallbackDeeplink(uri)) return false;

Supabase().log('***** SupabaseAuthState handleDeeplink $uri');
Supabase.instance.log('***** SupabaseAuthState handleDeeplink $uri');

// notify auth deeplink received
onReceivedAuthDeeplink(uri);
Expand All @@ -34,15 +34,15 @@ abstract class SupabaseAuthState<T extends StatefulWidget>

@override
void onErrorReceivingDeeplink(String message) {
Supabase().log('onErrorReceivingDeppLink message: $message');
Supabase.instance.log('onErrorReceivingDeppLink message: $message');
}

Future<bool> recoverSessionFromUrl(Uri uri) async {
final uriParameters = Supabase().parseUriParameters(uri);
final uriParameters = Supabase.instance.parseUriParameters(uri);
final type = uriParameters['type'] ?? '';

// recover session from deeplink
final response = await Supabase().client.auth.getSessionFromUrl(uri);
final response = await Supabase.instance.client.auth.getSessionFromUrl(uri);
if (response.error != null) {
onErrorAuthenticating(response.error!.message);
} else {
Expand All @@ -58,21 +58,22 @@ abstract class SupabaseAuthState<T extends StatefulWidget>
/// Recover/refresh session if it's available
/// e.g. called on a Splash screen when app starts.
Future<bool> recoverSupabaseSession() async {
final bool exist = await Supabase().localStorage.hasAccessToken();
final bool exist = await Supabase.instance.localStorage.hasAccessToken();
if (!exist) {
onUnauthenticated();
return false;
}

final String? jsonStr = await Supabase().localStorage.accessToken();
final String? jsonStr = await Supabase.instance.localStorage.accessToken();
if (jsonStr == null) {
onUnauthenticated();
return false;
}

final response = await Supabase().client.auth.recoverSession(jsonStr);
final response =
await Supabase.instance.client.auth.recoverSession(jsonStr);
if (response.error != null) {
Supabase().localStorage.removePersistedSession();
Supabase.instance.localStorage.removePersistedSession();
onUnauthenticated();
return false;
} else {
Expand All @@ -83,7 +84,7 @@ abstract class SupabaseAuthState<T extends StatefulWidget>

/// Callback when deeplink received and is processing. Optional
void onReceivedAuthDeeplink(Uri uri) {
Supabase().log('onReceivedAuthDeeplink uri: $uri');
Supabase.instance.log('onReceivedAuthDeeplink uri: $uri');
}

/// Callback when user is unauthenticated
Expand Down
8 changes: 4 additions & 4 deletions lib/src/supabase_deep_linking_mixin.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/services.dart';
import 'package:supabase_flutter/src/supabase.dart';
import 'package:uni_links/uni_links.dart';
Expand All @@ -10,13 +10,13 @@ mixin SupabaseDeepLinkingMixin<T extends StatefulWidget> on State<T> {
StreamSubscription? _sub;

void startDeeplinkObserver() {
Supabase().log('***** SupabaseDeepLinkingMixin startAuthObserver');
Supabase.instance.log('***** SupabaseDeepLinkingMixin startAuthObserver');
_handleIncomingLinks();
_handleInitialUri();
}

void stopDeeplinkObserver() {
Supabase().log('***** SupabaseDeepLinkingMixin stopAuthObserver');
Supabase.instance.log('***** SupabaseDeepLinkingMixin stopAuthObserver');
if (_sub != null) _sub?.cancel();
}

Expand Down Expand Up @@ -45,7 +45,7 @@ mixin SupabaseDeepLinkingMixin<T extends StatefulWidget> on State<T> {
///
/// We handle all exceptions, since it is called from initState.
Future<void> _handleInitialUri() async {
if (!Supabase().shouldHandleInitialDeeplink()) return;
if (!Supabase.instance.shouldHandleInitialDeeplink()) return;

try {
final uri = await getInitialUri();
Expand Down
Loading