diff --git a/packages/supabase/lib/src/supabase_client.dart b/packages/supabase/lib/src/supabase_client.dart index fb77e9e5..cfbbc208 100644 --- a/packages/supabase/lib/src/supabase_client.dart +++ b/packages/supabase/lib/src/supabase_client.dart @@ -30,8 +30,7 @@ import 'auth_http_client.dart'; /// `AuthFlowType.pkce`in order to perform auth actions with pkce flow. /// {@endtemplate} class SupabaseClient { - final String supabaseUrl; - final String supabaseKey; + final String _supabaseKey; final PostgrestClientOptions _postgrestOptions; final String _restUrl; @@ -99,27 +98,10 @@ class SupabaseClient { ..addAll(_headers); } - /// Creates a Supabase client to interact with your Supabase instance. - /// - /// [supabaseUrl] and [supabaseKey] can be found on your Supabase dashboard. - /// - /// You can access none public schema by passing different [schema]. - /// - /// Default headers can be overridden by specifying [headers]. - /// - /// Custom http client can be used by passing [httpClient] parameter. - /// - /// [storageRetryAttempts] specifies how many retry attempts there should be to - /// upload a file to Supabase storage when failed due to network interruption. - /// - /// [realtimeClientOptions] specifies different options you can pass to `RealtimeClient`. - /// - /// Pass an instance of `YAJsonIsolate` to [isolate] to use your own persisted - /// isolate instance. A new instance will be created if [isolate] is omitted. /// {@macro supabase_client} SupabaseClient( - this.supabaseUrl, - this.supabaseKey, { + String supabaseUrl, + String supabaseKey, { PostgrestClientOptions postgrestOptions = const PostgrestClientOptions(), AuthClientOptions authOptions = const AuthClientOptions(), StorageClientOptions storageOptions = const StorageClientOptions(), @@ -127,7 +109,8 @@ class SupabaseClient { Map? headers, Client? httpClient, YAJsonIsolate? isolate, - }) : _restUrl = '$supabaseUrl/rest/v1', + }) : _supabaseKey = supabaseKey, + _restUrl = '$supabaseUrl/rest/v1', _realtimeUrl = '$supabaseUrl/realtime/v1'.replaceAll('http', 'ws'), _authUrl = '$supabaseUrl/auth/v1', _storageUrl = '$supabaseUrl/storage/v1', @@ -144,7 +127,8 @@ class SupabaseClient { gotrueAsyncStorage: authOptions.pkceAsyncStorage, authFlowType: authOptions.authFlowType, ); - _authHttpClient = AuthHttpClient(supabaseKey, httpClient ?? Client(), auth); + _authHttpClient = + AuthHttpClient(_supabaseKey, httpClient ?? Client(), auth); rest = _initRestClient(); functions = _initFunctionsClient(); storage = _initStorageClient(storageOptions.retryAttempts); @@ -218,8 +202,8 @@ class SupabaseClient { required AuthFlowType authFlowType, }) { final authHeaders = {...headers}; - authHeaders['apikey'] = supabaseKey; - authHeaders['Authorization'] = 'Bearer $supabaseKey'; + authHeaders['apikey'] = _supabaseKey; + authHeaders['Authorization'] = 'Bearer $_supabaseKey'; return GoTrueClient( url: _authUrl, @@ -266,7 +250,7 @@ class SupabaseClient { return RealtimeClient( _realtimeUrl, params: { - 'apikey': supabaseKey, + 'apikey': _supabaseKey, if (eventsPerSecond != null) 'eventsPerSecond': '$eventsPerSecond' }, headers: headers, @@ -275,9 +259,9 @@ class SupabaseClient { } Map _getAuthHeaders() { - final authBearer = auth.currentSession?.accessToken ?? supabaseKey; + final authBearer = auth.currentSession?.accessToken ?? _supabaseKey; final defaultHeaders = { - 'apikey': supabaseKey, + 'apikey': _supabaseKey, 'Authorization': 'Bearer $authBearer', }; final headers = {...defaultHeaders, ..._headers}; @@ -305,7 +289,7 @@ class SupabaseClient { event == AuthChangeEvent.userDeleted) { // Token is removed - realtime.setAuth(supabaseKey); + realtime.setAuth(_supabaseKey); } } } diff --git a/packages/supabase/test/client_test.dart b/packages/supabase/test/client_test.dart index 559311b6..79d53a25 100644 --- a/packages/supabase/test/client_test.dart +++ b/packages/supabase/test/client_test.dart @@ -6,23 +6,44 @@ import 'package:test/test.dart'; import 'utils.dart'; void main() { + /// Extracts a single request sent to the realtime server + Future getRealtimeRequest({ + required HttpServer server, + required SupabaseClient supabaseClient, + }) async { + supabaseClient.channel('name').subscribe(); + + return server.first; + } + group('Standard Header', () { - const supabaseUrl = 'https://nlbsnpoablmsiwndbmer.supabase.co'; + late String supabaseUrl; const supabaseKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im53emxkenlsb2pyemdqemloZHJrIiwicm9sZSI6ImFub24iLCJpYXQiOjE2ODQxMzI2ODAsImV4cCI6MTk5OTcwODY4MH0.MU-LVeAPic93VLcRsHktxzYtBKBUMWAQb8E-0AQETPs'; late SupabaseClient client; + late HttpServer mockServer; + + setUp(() async { + mockServer = await HttpServer.bind('localhost', 0); + supabaseUrl = 'http://${mockServer.address.host}:${mockServer.port}'; - setUp(() { client = SupabaseClient(supabaseUrl, supabaseKey); }); tearDown(() async { + await client.removeAllChannels(); await client.dispose(); }); - test('X-Client-Info header is set properly on realtime', () { + test('X-Client-Info header is set properly on realtime', () async { + final request = await getRealtimeRequest( + server: mockServer, + supabaseClient: client, + ); + final xClientHeaderBeforeSlash = - client.realtime.headers['X-Client-Info']!.split('/').first; + request.headers['X-Client-Info']?.first.split('/').first; + expect(xClientHeaderBeforeSlash, 'supabase-dart'); }); @@ -32,19 +53,33 @@ void main() { expect(xClientHeaderBeforeSlash, 'supabase-dart'); }); - test('realtime URL is properly being set', () { - var realtimeWebsocketURL = Uri.parse(client.realtime.endPointURL); + test('realtime URL is properly being set', () async { + final request = await getRealtimeRequest( + server: mockServer, + supabaseClient: client, + ); + + var realtimeWebsocketURL = request.uri; + expect( realtimeWebsocketURL.queryParameters, containsPair('apikey', supabaseKey), ); expect(realtimeWebsocketURL.queryParameters['log_level'], isNull); + }); + test('log_level query parameter is properly set', () async { client = SupabaseClient(supabaseUrl, supabaseKey, realtimeClientOptions: RealtimeClientOptions(logLevel: RealtimeLogLevel.info)); - realtimeWebsocketURL = Uri.parse(client.realtime.endPointURL); + final request = await getRealtimeRequest( + server: mockServer, + supabaseClient: client, + ); + + final realtimeWebsocketURL = request.uri; + expect( realtimeWebsocketURL.queryParameters, containsPair('apikey', supabaseKey), @@ -55,8 +90,13 @@ void main() { ); }); - test('realtime access token is set properly', () { - expect(client.realtime.accessToken, supabaseKey); + test('realtime access token is set properly', () async { + final request = await getRealtimeRequest( + server: mockServer, + supabaseClient: client, + ); + + expect(request.uri.queryParameters['apikey'], supabaseKey); }); }); @@ -163,9 +203,23 @@ void main() { ); }); - test('X-Client-Info header is set properly on realtime', () { - final xClientInfoHeader = client.realtime.headers['X-Client-Info']; - expect(xClientInfoHeader, 'supabase-flutter/0.0.0'); + test('X-Client-Info header is set properly on realtime', () async { + final mockServer = await HttpServer.bind('localhost', 0); + + final client = SupabaseClient( + 'http://${mockServer.address.host}:${mockServer.port}', + supabaseKey, + headers: { + 'X-Client-Info': 'supabase-flutter/0.0.0', + }, + ); + + final request = await getRealtimeRequest( + server: mockServer, + supabaseClient: client, + ); + + expect(request.headers['X-Client-Info']?.first, 'supabase-flutter/0.0.0'); }); test('X-Client-Info header is set properly on storage', () { diff --git a/packages/supabase/test/mock_test.dart b/packages/supabase/test/mock_test.dart index da65fc04..65a5a04b 100644 --- a/packages/supabase/test/mock_test.dart +++ b/packages/supabase/test/mock_test.dart @@ -357,8 +357,8 @@ void main() { await customHeadersClient.dispose(); //Manually disconnect the socket channel to avoid automatic retrying to reconnect. This caused failing in later executed tests. - client.realtime.disconnect(); - customHeadersClient.realtime.disconnect(); + await client.removeAllChannels(); + await customHeadersClient.removeAllChannels(); // Wait for the realtime updates to come through await Future.delayed(Duration(milliseconds: 100));