diff --git a/packages/storage_client/lib/src/storage_client.dart b/packages/storage_client/lib/src/storage_client.dart index 3e25a70ab..825baff36 100644 --- a/packages/storage_client/lib/src/storage_client.dart +++ b/packages/storage_client/lib/src/storage_client.dart @@ -31,18 +31,25 @@ class SupabaseStorageClient extends StorageBucketApi { /// 8. 30000 ms +/- 25% /// /// Anything beyond the 8th try will have 30 second delay. + /// + /// [useNewHostname] controls whether legacy storage URLs are rewritten to use + /// the dedicated storage host (`.storage.supabase.co`). Set to `true` + /// only if your project has the dedicated storage host enabled; otherwise + /// every storage request will fail with an `Invalid Storage request` error. + /// Defaults to `false` (opt-in). SupabaseStorageClient( String url, Map headers, { Client? httpClient, int retryAttempts = 0, + bool useNewHostname = false, }) : assert( retryAttempts >= 0, 'retryAttempts has to be greater than or equal to 0', ), _defaultRetryAttempts = retryAttempts, super( - _transformStorageUrl(url), + useNewHostname ? _transformStorageUrl(url) : url, {...Constants.defaultHeaders, ...headers}, httpClient: httpClient, ) { diff --git a/packages/storage_client/test/basic_test.dart b/packages/storage_client/test/basic_test.dart index 254ea1d40..168606ede 100644 --- a/packages/storage_client/test/basic_test.dart +++ b/packages/storage_client/test/basic_test.dart @@ -308,58 +308,128 @@ void main() { }); group('URL Construction', () { - test('should update legacy prod host to new host', () { - const inputUrl = 'https://blah.supabase.co/storage/v1'; - const expectedUrl = 'https://blah.storage.supabase.co/v1'; - client = SupabaseStorageClient(inputUrl, { - 'Authorization': 'Bearer $supabaseKey', + group('default behavior (useNewHostname: false)', () { + test('should NOT transform legacy prod host by default', () { + const inputUrl = 'https://blah.supabase.co/storage/v1'; + client = SupabaseStorageClient(inputUrl, { + 'Authorization': 'Bearer $supabaseKey', + }); + expect(client.url, inputUrl); }); - expect(client.url, expectedUrl); - }); - test('should update legacy staging host to new host', () { - const inputUrl = 'https://blah.supabase.red/storage/v1'; - const expectedUrl = 'https://blah.storage.supabase.red/v1'; - client = SupabaseStorageClient(inputUrl, { - 'Authorization': 'Bearer $supabaseKey', + test('should NOT transform legacy staging host by default', () { + const inputUrl = 'https://blah.supabase.red/storage/v1'; + client = SupabaseStorageClient(inputUrl, { + 'Authorization': 'Bearer $supabaseKey', + }); + expect(client.url, inputUrl); }); - expect(client.url, expectedUrl); - }); - test('should accept new host without modification', () { - const inputUrl = 'https://blah.storage.supabase.co/v1'; - const expectedUrl = 'https://blah.storage.supabase.co/v1'; - client = SupabaseStorageClient(inputUrl, { - 'Authorization': 'Bearer $supabaseKey', + test('should NOT transform legacy supabase.in host by default', () { + const inputUrl = 'https://blah.supabase.in/storage/v1'; + client = SupabaseStorageClient(inputUrl, { + 'Authorization': 'Bearer $supabaseKey', + }); + expect(client.url, inputUrl); }); - expect(client.url, expectedUrl); - }); - test('should not modify non-platform hosts', () { - const inputUrl = 'https://blah.supabase.co.example.com/storage/v1'; - const expectedUrl = 'https://blah.supabase.co.example.com/storage/v1'; - client = SupabaseStorageClient(inputUrl, { - 'Authorization': 'Bearer $supabaseKey', + test('should accept new host without modification', () { + const inputUrl = 'https://blah.storage.supabase.co/v1'; + client = SupabaseStorageClient(inputUrl, { + 'Authorization': 'Bearer $supabaseKey', + }); + expect(client.url, inputUrl); }); - expect(client.url, expectedUrl); - }); - test('should support local host with port without modification', () { - const inputUrl = 'http://localhost:1234/storage/v1'; - const expectedUrl = 'http://localhost:1234/storage/v1'; - client = SupabaseStorageClient(inputUrl, { - 'Authorization': 'Bearer $supabaseKey', + test('should not modify non-platform hosts', () { + const inputUrl = 'https://blah.supabase.co.example.com/storage/v1'; + client = SupabaseStorageClient(inputUrl, { + 'Authorization': 'Bearer $supabaseKey', + }); + expect(client.url, inputUrl); + }); + + test('should support local host with port without modification', () { + const inputUrl = 'http://localhost:1234/storage/v1'; + client = SupabaseStorageClient(inputUrl, { + 'Authorization': 'Bearer $supabaseKey', + }); + expect(client.url, inputUrl); }); - expect(client.url, expectedUrl); }); - test('should update legacy supabase.in host to new host', () { - const inputUrl = 'https://blah.supabase.in/storage/v1'; - const expectedUrl = 'https://blah.storage.supabase.in/v1'; - client = SupabaseStorageClient(inputUrl, { - 'Authorization': 'Bearer $supabaseKey', + group('opt-in behavior (useNewHostname: true)', () { + test('should update legacy prod host to new host', () { + const inputUrl = 'https://blah.supabase.co/storage/v1'; + const expectedUrl = 'https://blah.storage.supabase.co/v1'; + client = SupabaseStorageClient( + inputUrl, + { + 'Authorization': 'Bearer $supabaseKey', + }, + useNewHostname: true); + expect(client.url, expectedUrl); + }); + + test('should update legacy staging host to new host', () { + const inputUrl = 'https://blah.supabase.red/storage/v1'; + const expectedUrl = 'https://blah.storage.supabase.red/v1'; + client = SupabaseStorageClient( + inputUrl, + { + 'Authorization': 'Bearer $supabaseKey', + }, + useNewHostname: true); + expect(client.url, expectedUrl); + }); + + test('should accept new host without modification', () { + const inputUrl = 'https://blah.storage.supabase.co/v1'; + const expectedUrl = 'https://blah.storage.supabase.co/v1'; + client = SupabaseStorageClient( + inputUrl, + { + 'Authorization': 'Bearer $supabaseKey', + }, + useNewHostname: true); + expect(client.url, expectedUrl); + }); + + test('should not modify non-platform hosts', () { + const inputUrl = 'https://blah.supabase.co.example.com/storage/v1'; + const expectedUrl = 'https://blah.supabase.co.example.com/storage/v1'; + client = SupabaseStorageClient( + inputUrl, + { + 'Authorization': 'Bearer $supabaseKey', + }, + useNewHostname: true); + expect(client.url, expectedUrl); + }); + + test('should support local host with port without modification', () { + const inputUrl = 'http://localhost:1234/storage/v1'; + const expectedUrl = 'http://localhost:1234/storage/v1'; + client = SupabaseStorageClient( + inputUrl, + { + 'Authorization': 'Bearer $supabaseKey', + }, + useNewHostname: true); + expect(client.url, expectedUrl); + }); + + test('should update legacy supabase.in host to new host', () { + const inputUrl = 'https://blah.supabase.in/storage/v1'; + const expectedUrl = 'https://blah.storage.supabase.in/v1'; + client = SupabaseStorageClient( + inputUrl, + { + 'Authorization': 'Bearer $supabaseKey', + }, + useNewHostname: true); + expect(client.url, expectedUrl); }); - expect(client.url, expectedUrl); }); }); } diff --git a/packages/supabase/lib/src/supabase_client.dart b/packages/supabase/lib/src/supabase_client.dart index 2bd00aac5..f9f124820 100644 --- a/packages/supabase/lib/src/supabase_client.dart +++ b/packages/supabase/lib/src/supabase_client.dart @@ -149,7 +149,8 @@ class SupabaseClient { AuthHttpClient(_supabaseKey, httpClient ?? Client(), _getAccessToken); rest = _initRestClient(); functions = _initFunctionsClient(); - storage = _initStorageClient(storageOptions.retryAttempts); + storage = _initStorageClient( + storageOptions.retryAttempts, storageOptions.useNewHostname); realtime = _initRealtimeClient(options: realtimeClientOptions); if (accessToken == null) { _log.config( @@ -316,12 +317,14 @@ class SupabaseClient { ); } - SupabaseStorageClient _initStorageClient(int storageRetryAttempts) { + SupabaseStorageClient _initStorageClient( + int storageRetryAttempts, bool useNewHostname) { return SupabaseStorageClient( _storageUrl, {...headers}, httpClient: _authHttpClient, retryAttempts: storageRetryAttempts, + useNewHostname: useNewHostname, ); } diff --git a/packages/supabase/lib/src/supabase_client_options.dart b/packages/supabase/lib/src/supabase_client_options.dart index 0c0c404fb..12045bed6 100644 --- a/packages/supabase/lib/src/supabase_client_options.dart +++ b/packages/supabase/lib/src/supabase_client_options.dart @@ -21,7 +21,17 @@ class AuthClientOptions { class StorageClientOptions { final int retryAttempts; - const StorageClientOptions({this.retryAttempts = 0}); + /// Whether to rewrite legacy storage URLs to use the dedicated storage host + /// (`.storage.supabase.co`). Enables uploads larger than 50 GB by + /// bypassing proxy buffering limits. + /// + /// Set to `true` only if your project has the dedicated storage host + /// enabled; otherwise every storage request will fail with an + /// `Invalid Storage request` error. Defaults to `false` (opt-in). + final bool useNewHostname; + + const StorageClientOptions( + {this.retryAttempts = 0, this.useNewHostname = false}); } class FunctionsClientOptions {