diff --git a/packages/storage_client/lib/src/storage_client.dart b/packages/storage_client/lib/src/storage_client.dart index 89b7db955..1665777b8 100644 --- a/packages/storage_client/lib/src/storage_client.dart +++ b/packages/storage_client/lib/src/storage_client.dart @@ -42,7 +42,7 @@ class SupabaseStorageClient extends StorageBucketApi { ), _defaultRetryAttempts = retryAttempts, super( - url, + _transformStorageUrl(url), {...Constants.defaultHeaders, ...headers}, httpClient: httpClient, ) { @@ -51,6 +51,39 @@ class SupabaseStorageClient extends StorageBucketApi { _log.finest('Initialize with headers: $headers'); } + /// Transforms legacy storage URLs to use the dedicated storage host. + /// + /// If legacy URI is used, replace with new storage host (disables request buffering to allow > 50GB uploads). + /// "project-ref.supabase.co/storage/v1" becomes "project-ref.storage.supabase.co/v1" + static String _transformStorageUrl(String url) { + final uri = Uri.parse(url); + final hostname = uri.host; + + // Check if it's a Supabase host (supabase.co, supabase.in, or supabase.red) + final isSupabaseHost = RegExp(r'supabase\.(co|in|red)$').hasMatch(hostname); + + // If it's a legacy storage URL, transform it + const legacyStoragePrefix = '/storage'; + if (isSupabaseHost && + !hostname.contains('storage.supabase.') && + uri.path.startsWith(legacyStoragePrefix)) { + // Remove /storage from pathname + final newPath = uri.path.substring(legacyStoragePrefix.length); + // Replace supabase. with storage.supabase. in hostname + final newHostname = hostname.replaceAll('supabase.', 'storage.supabase.'); + + // Reconstruct the URI + return uri + .replace( + host: newHostname, + path: newPath, + ) + .toString(); + } + + return url; + } + /// Perform file operation in a bucket. /// /// [id] The bucket id to operate on. diff --git a/packages/storage_client/test/basic_test.dart b/packages/storage_client/test/basic_test.dart index 62080dc69..b530e22c7 100644 --- a/packages/storage_client/test/basic_test.dart +++ b/packages/storage_client/test/basic_test.dart @@ -287,4 +287,60 @@ void main() { expect(client.headers['X-Client-Info'], 'supabase-dart/0.0.0'); }); }); + + 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', + }); + 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', + }); + 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', + }); + 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', + }); + 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', + }); + 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', + }); + expect(client.url, expectedUrl); + }); + }); }