@@ -87,8 +87,14 @@ class GoTrueClient {
87
87
Stream <AuthState > get onAuthStateChangeSync =>
88
88
_onAuthStateChangeControllerSync.stream;
89
89
90
+ final Completer <void > _initalizedStorage = Completer <void >();
91
+
90
92
final AuthFlowType _flowType;
91
93
94
+ final bool _persistSession;
95
+
96
+ final String _storageKey;
97
+
92
98
final _log = Logger ('supabase.auth' );
93
99
94
100
/// Proxy to the web BroadcastChannel API. Should be null on non-web platforms.
@@ -101,8 +107,10 @@ class GoTrueClient {
101
107
String ? url,
102
108
Map <String , String >? headers,
103
109
bool ? autoRefreshToken,
110
+ bool ? persistSession,
104
111
Client ? httpClient,
105
112
GotrueAsyncStorage ? asyncStorage,
113
+ String ? storageKey,
106
114
AuthFlowType flowType = AuthFlowType .pkce,
107
115
}) : _url = url ?? Constants .defaultGotrueUrl,
108
116
_headers = {
@@ -111,7 +119,9 @@ class GoTrueClient {
111
119
},
112
120
_httpClient = httpClient,
113
121
_asyncStorage = asyncStorage,
114
- _flowType = flowType {
122
+ _flowType = flowType,
123
+ _persistSession = persistSession ?? false ,
124
+ _storageKey = storageKey ?? Constants .defaultStorageKey {
115
125
_autoRefreshToken = autoRefreshToken ?? true ;
116
126
117
127
final gotrueUrl = url ?? Constants .defaultGotrueUrl;
@@ -127,10 +137,19 @@ class GoTrueClient {
127
137
client: this ,
128
138
fetch: _fetch,
129
139
);
140
+
141
+ assert (asyncStorage != null || ! _persistSession,
142
+ 'You need to provide asyncStorage to persist session.' );
143
+ if (asyncStorage != null ) {
144
+ _initalizedStorage.complete (
145
+ asyncStorage.initialize ().catchError ((e) => notifyException (e)));
146
+ }
147
+
130
148
if (_autoRefreshToken) {
131
149
startAutoRefresh ();
132
150
}
133
151
152
+ _initialize ();
134
153
_mayStartBroadcastChannel ();
135
154
}
136
155
@@ -148,6 +167,37 @@ class GoTrueClient {
148
167
/// Returns the current session, if any;
149
168
Session ? get currentSession => _currentSession;
150
169
170
+ /// This method should not throw as it is called from the constructor.
171
+ Future <void > _initialize () async {
172
+ try {
173
+ if (_persistSession && _asyncStorage != null ) {
174
+ await _initalizedStorage.future;
175
+ final jsonStr = await _asyncStorage! .getItem (key: _storageKey);
176
+ var shouldEmitInitialSession = true ;
177
+ if (jsonStr != null ) {
178
+ await setInitialSession (jsonStr);
179
+ shouldEmitInitialSession = false ;
180
+
181
+ // Only try to recover session if the session got set in [setInitialSession]
182
+ // because if not the session is missing data and already notified an
183
+ // exception.
184
+ if (currentSession != null ) {
185
+ // [notifyException] gets already called here if needed, so we can
186
+ // catch any error.
187
+ recoverSession (jsonStr).then ((_) {}, onError: (_) {});
188
+ }
189
+ }
190
+ if (shouldEmitInitialSession) {
191
+ // Emit a null session if the user did not have persisted session
192
+ notifyAllSubscribers (AuthChangeEvent .initialSession);
193
+ }
194
+ }
195
+ } catch (error, stackTrace) {
196
+ _log.warning ('Error while loading initial session' , error, stackTrace);
197
+ notifyException (error, stackTrace);
198
+ }
199
+ }
200
+
151
201
/// Creates a new anonymous user.
152
202
///
153
203
/// Returns An `AuthResponse` with a session where the `is_anonymous` claim
@@ -172,7 +222,7 @@ class GoTrueClient {
172
222
173
223
final session = authResponse.session;
174
224
if (session != null ) {
175
- _saveSession (session);
225
+ await _saveSession (session);
176
226
notifyAllSubscribers (AuthChangeEvent .signedIn);
177
227
}
178
228
@@ -217,9 +267,8 @@ class GoTrueClient {
217
267
assert (_asyncStorage != null ,
218
268
'You need to provide asyncStorage to perform pkce flow.' );
219
269
final codeVerifier = generatePKCEVerifier ();
220
- await _asyncStorage! .setItem (
221
- key: '${Constants .defaultStorageKey }-code-verifier' ,
222
- value: codeVerifier);
270
+ await _asyncStorage!
271
+ .setItem (key: '$_storageKey -code-verifier' , value: codeVerifier);
223
272
codeChallenge = generatePKCEChallenge (codeVerifier);
224
273
}
225
274
@@ -259,7 +308,7 @@ class GoTrueClient {
259
308
260
309
final session = authResponse.session;
261
310
if (session != null ) {
262
- _saveSession (session);
311
+ await _saveSession (session);
263
312
notifyAllSubscribers (AuthChangeEvent .signedIn);
264
313
}
265
314
@@ -312,7 +361,7 @@ class GoTrueClient {
312
361
final authResponse = AuthResponse .fromJson (response);
313
362
314
363
if (authResponse.session? .accessToken != null ) {
315
- _saveSession (authResponse.session! );
364
+ await _saveSession (authResponse.session! );
316
365
notifyAllSubscribers (AuthChangeEvent .signedIn);
317
366
}
318
367
return authResponse;
@@ -339,8 +388,8 @@ class GoTrueClient {
339
388
assert (_asyncStorage != null ,
340
389
'You need to provide asyncStorage to perform pkce flow.' );
341
390
342
- final codeVerifierRawString = await _asyncStorage !
343
- .getItem (key: '${ Constants . defaultStorageKey } -code-verifier' );
391
+ final codeVerifierRawString =
392
+ await _asyncStorage ! .getItem (key: '$_storageKey -code-verifier' );
344
393
if (codeVerifierRawString == null ) {
345
394
throw AuthException ('Code verifier could not be found in local storage.' );
346
395
}
@@ -363,14 +412,13 @@ class GoTrueClient {
363
412
),
364
413
);
365
414
366
- await _asyncStorage!
367
- .removeItem (key: '${Constants .defaultStorageKey }-code-verifier' );
415
+ await _asyncStorage! .removeItem (key: '$_storageKey -code-verifier' );
368
416
369
417
final authSessionUrlResponse = AuthSessionUrlResponse (
370
418
session: Session .fromJson (response)! , redirectType: redirectType? .name);
371
419
372
420
final session = authSessionUrlResponse.session;
373
- _saveSession (session);
421
+ await _saveSession (session);
374
422
if (redirectType == AuthChangeEvent .passwordRecovery) {
375
423
notifyAllSubscribers (AuthChangeEvent .passwordRecovery);
376
424
} else {
@@ -434,7 +482,7 @@ class GoTrueClient {
434
482
);
435
483
}
436
484
437
- _saveSession (authResponse.session! );
485
+ await _saveSession (authResponse.session! );
438
486
notifyAllSubscribers (AuthChangeEvent .signedIn);
439
487
440
488
return authResponse;
@@ -472,9 +520,8 @@ class GoTrueClient {
472
520
assert (_asyncStorage != null ,
473
521
'You need to provide asyncStorage to perform pkce flow.' );
474
522
final codeVerifier = generatePKCEVerifier ();
475
- await _asyncStorage! .setItem (
476
- key: '${Constants .defaultStorageKey }-code-verifier' ,
477
- value: codeVerifier);
523
+ await _asyncStorage!
524
+ .setItem (key: '$_storageKey -code-verifier' , value: codeVerifier);
478
525
codeChallenge = generatePKCEChallenge (codeVerifier);
479
526
}
480
527
await _fetch.request (
@@ -559,7 +606,7 @@ class GoTrueClient {
559
606
);
560
607
}
561
608
562
- _saveSession (authResponse.session! );
609
+ await _saveSession (authResponse.session! );
563
610
notifyAllSubscribers (type == OtpType .recovery
564
611
? AuthChangeEvent .passwordRecovery
565
612
: AuthChangeEvent .signedIn);
@@ -594,9 +641,8 @@ class GoTrueClient {
594
641
assert (_asyncStorage != null ,
595
642
'You need to provide asyncStorage to perform pkce flow.' );
596
643
final codeVerifier = generatePKCEVerifier ();
597
- await _asyncStorage! .setItem (
598
- key: '${Constants .defaultStorageKey }-code-verifier' ,
599
- value: codeVerifier);
644
+ await _asyncStorage!
645
+ .setItem (key: '$_storageKey -code-verifier' , value: codeVerifier);
600
646
codeChallenge = generatePKCEChallenge (codeVerifier);
601
647
codeChallengeMethod = codeVerifier == codeChallenge ? 'plain' : 's256' ;
602
648
}
@@ -832,7 +878,7 @@ class GoTrueClient {
832
878
final redirectType = url.queryParameters['type' ];
833
879
834
880
if (storeSession == true ) {
835
- _saveSession (session);
881
+ await _saveSession (session);
836
882
if (redirectType == 'recovery' ) {
837
883
notifyAllSubscribers (AuthChangeEvent .passwordRecovery);
838
884
} else {
@@ -855,9 +901,8 @@ class GoTrueClient {
855
901
final accessToken = currentSession? .accessToken;
856
902
857
903
if (scope != SignOutScope .others) {
858
- _removeSession ();
859
- await _asyncStorage? .removeItem (
860
- key: '${Constants .defaultStorageKey }-code-verifier' );
904
+ await _removeSession ();
905
+ await _asyncStorage? .removeItem (key: '$_storageKey -code-verifier' );
861
906
notifyAllSubscribers (AuthChangeEvent .signedOut);
862
907
}
863
908
@@ -889,7 +934,7 @@ class GoTrueClient {
889
934
'You need to provide asyncStorage to perform pkce flow.' );
890
935
final codeVerifier = generatePKCEVerifier ();
891
936
await _asyncStorage! .setItem (
892
- key: '${ Constants . defaultStorageKey } -code-verifier' ,
937
+ key: '$_storageKey -code-verifier' ,
893
938
value: '$codeVerifier /${AuthChangeEvent .passwordRecovery .name }' ,
894
939
);
895
940
codeChallenge = generatePKCEChallenge (codeVerifier);
@@ -978,9 +1023,7 @@ class GoTrueClient {
978
1023
if (session == null ) {
979
1024
_log.warning ("Can't recover session from string, session is null" );
980
1025
await signOut ();
981
- throw notifyException (
982
- AuthException ('Current session is missing data.' ),
983
- );
1026
+ throw AuthException ('Session to restore is missing data.' );
984
1027
}
985
1028
986
1029
if (session.isExpired) {
@@ -995,7 +1038,7 @@ class GoTrueClient {
995
1038
} else {
996
1039
final shouldEmitEvent = _currentSession == null ||
997
1040
_currentSession? .user.id != session.user.id;
998
- _saveSession (session);
1041
+ await _saveSession (session);
999
1042
1000
1043
if (shouldEmitEvent) {
1001
1044
notifyAllSubscribers (AuthChangeEvent .tokenRefreshed);
@@ -1126,7 +1169,7 @@ class GoTrueClient {
1126
1169
'You need to provide asyncStorage to perform pkce flow.' );
1127
1170
final codeVerifier = generatePKCEVerifier ();
1128
1171
await _asyncStorage! .setItem (
1129
- key: '${ Constants . defaultStorageKey } -code-verifier' ,
1172
+ key: '$_storageKey -code-verifier' ,
1130
1173
value: codeVerifier,
1131
1174
);
1132
1175
@@ -1146,17 +1189,36 @@ class GoTrueClient {
1146
1189
}
1147
1190
1148
1191
/// set currentSession and currentUser
1149
- void _saveSession (Session session) {
1192
+ Future < void > _saveSession (Session session) async {
1150
1193
_log.finest ('Saving session: $session ' );
1151
1194
_log.fine ('Saving session' );
1152
1195
_currentSession = session;
1153
1196
_currentUser = session.user;
1197
+
1198
+ if (_persistSession && _asyncStorage != null ) {
1199
+ if (! _initalizedStorage.isCompleted) {
1200
+ await _initalizedStorage.future;
1201
+ }
1202
+ _asyncStorage! .setItem (
1203
+ key: _storageKey,
1204
+ value: jsonEncode (session.toJson ()),
1205
+ );
1206
+ }
1154
1207
}
1155
1208
1156
- void _removeSession () {
1209
+ Future < void > _removeSession () async {
1157
1210
_log.fine ('Removing session' );
1158
1211
_currentSession = null ;
1159
1212
_currentUser = null ;
1213
+
1214
+ if (_persistSession && _asyncStorage != null ) {
1215
+ if (! _initalizedStorage.isCompleted) {
1216
+ await _initalizedStorage.future;
1217
+ }
1218
+ _asyncStorage! .removeItem (
1219
+ key: _storageKey,
1220
+ );
1221
+ }
1160
1222
}
1161
1223
1162
1224
void _mayStartBroadcastChannel () {
@@ -1170,7 +1232,7 @@ class GoTrueClient {
1170
1232
try {
1171
1233
_broadcastChannel = web.getBroadcastChannel (broadcastKey);
1172
1234
_broadcastChannelSubscription =
1173
- _broadcastChannel? .onMessage.listen ((messageEvent) {
1235
+ _broadcastChannel? .onMessage.listen ((messageEvent) async {
1174
1236
final rawEvent = messageEvent['event' ];
1175
1237
_log.finest ('Received broadcast message: $messageEvent ' );
1176
1238
_log.info ('Received broadcast event: $rawEvent ' );
@@ -1195,9 +1257,9 @@ class GoTrueClient {
1195
1257
session = Session .fromJson (messageEvent['session' ]);
1196
1258
}
1197
1259
if (session != null ) {
1198
- _saveSession (session);
1260
+ await _saveSession (session);
1199
1261
} else {
1200
- _removeSession ();
1262
+ await _removeSession ();
1201
1263
}
1202
1264
notifyAllSubscribers (event, session: session, broadcast: false );
1203
1265
}
@@ -1247,14 +1309,14 @@ class GoTrueClient {
1247
1309
throw AuthSessionMissingException ();
1248
1310
}
1249
1311
1250
- _saveSession (session);
1312
+ await _saveSession (session);
1251
1313
notifyAllSubscribers (AuthChangeEvent .tokenRefreshed);
1252
1314
1253
1315
_refreshTokenCompleter? .complete (data);
1254
1316
return data;
1255
1317
} on AuthException catch (error, stack) {
1256
1318
if (error is ! AuthRetryableFetchException ) {
1257
- _removeSession ();
1319
+ await _removeSession ();
1258
1320
notifyAllSubscribers (AuthChangeEvent .signedOut);
1259
1321
} else {
1260
1322
notifyException (error, stack);
0 commit comments