diff --git a/examples/authentication/integration_test/authentication_test.dart b/examples/authentication/integration_test/authentication_test.dart index a18638bd2..e08619e6c 100644 --- a/examples/authentication/integration_test/authentication_test.dart +++ b/examples/authentication/integration_test/authentication_test.dart @@ -30,7 +30,8 @@ const _skipOnWeb = kIsWeb; /// Supabase stack, one sign in method per test: /// /// * email & password (sign up, sign out, sign in) -/// * a full password reset (the recovery code is read back from the mail server) +/// * a full password reset (the recovery code is read back from the mail +/// server) /// * passwordless email OTP (the code is read back from the local mail server) /// * phone SMS OTP (using the configured test OTP) /// * anonymous sign in and upgrading it to a permanent account @@ -389,7 +390,8 @@ List _base32Decode(String input) { return output; } -/// Pumps frames until [finder] matches at least one widget or [timeout] elapses. +/// Pumps frames until [finder] matches at least one widget or [timeout] +/// elapses. /// /// Auth calls go over the network, so the UI can't be settled with /// `pumpAndSettle`; this polls the widget tree instead. diff --git a/examples/authentication/lib/auth_repository.dart b/examples/authentication/lib/auth_repository.dart index dae0b4557..39fdbfdee 100644 --- a/examples/authentication/lib/auth_repository.dart +++ b/examples/authentication/lib/auth_repository.dart @@ -1,8 +1,9 @@ import 'package:supabase_flutter/supabase_flutter.dart'; -/// Every `supabase.auth.*` call for the example lives here, so the UI stays thin -/// and each authentication flow is easy to read and to drive from an integration -/// test. The methods are grouped by the sign in method they belong to. +/// Every `supabase.auth.*` call for the example lives here, so the UI stays +/// thin and each authentication flow is easy to read and to drive from an +/// integration test. The methods are grouped by the sign in method they belong +/// to. class AuthRepository { AuthRepository(this._client); diff --git a/examples/authentication/lib/main.dart b/examples/authentication/lib/main.dart index 6e585ee46..a325cc788 100644 --- a/examples/authentication/lib/main.dart +++ b/examples/authentication/lib/main.dart @@ -170,8 +170,8 @@ class _PasswordFormState extends State<_PasswordForm> { ), ); - /// Sends the reset email, then reveals the fields to finish the reset with the - /// code from that email. + /// Sends the reset email, then reveals the fields to finish the reset with + /// the code from that email. Future _startReset() => _run(() async { await _auth.sendPasswordReset( _email.text.trim(), @@ -250,8 +250,8 @@ class _PasswordFormState extends State<_PasswordForm> { } } -/// Passwordless sign in with `signInWithOtp` (email) then `verifyOTP`. The email -/// carries both a magic link and the code entered here. +/// Passwordless sign in with `signInWithOtp` (email) then `verifyOTP`. The +/// email carries both a magic link and the code entered here. class _EmailOtpForm extends StatefulWidget { const _EmailOtpForm(); @@ -518,8 +518,8 @@ class _AnonymousFormState extends State<_AnonymousForm> { } } -/// The account screen: shows who is signed in, lets an anonymous user upgrade to -/// a permanent account, manages MFA factors and signs out. +/// The account screen: shows who is signed in, lets an anonymous user upgrade +/// to a permanent account, manages MFA factors and signs out. class _SignedInView extends StatelessWidget { const _SignedInView({required this.user}); diff --git a/examples/authentication/lib/models.dart b/examples/authentication/lib/models.dart index dcfb945d7..a20577f9c 100644 --- a/examples/authentication/lib/models.dart +++ b/examples/authentication/lib/models.dart @@ -1,5 +1,5 @@ -/// The sign in methods the example offers on its signed-out screen. Each maps to -/// one or two calls on [AuthRepository]; the label is what the method picker +/// The sign in methods the example offers on its signed-out screen. Each maps +/// to one or two calls on [AuthRepository]; the label is what the method picker /// shows. enum AuthMethod { password('Email & password'), diff --git a/examples/database_crud/integration_test/tasks_test.dart b/examples/database_crud/integration_test/tasks_test.dart index 3c6762943..505cf27e9 100644 --- a/examples/database_crud/integration_test/tasks_test.dart +++ b/examples/database_crud/integration_test/tasks_test.dart @@ -70,11 +70,11 @@ void main() { createdTitle, ); await tester.tap(find.widgetWithText(FilledButton, 'Create')); - // Searches for the new task rather than looking for it in the full list: the - // dialog leaves the keyboard up, and on a phone that leaves room for only a - // couple of tiles, so it would otherwise end up below the fold. Waits for the - // tile rather than the title text, which also matches the text field of the - // dialog that is still animating away. + // Searches for the new task rather than looking for it in the full list: + // the dialog leaves the keyboard up, and on a phone that leaves room for + // only a couple of tiles, so it would otherwise end up below the fold. + // Waits for the tile rather than the title text, which also matches the + // text field of the dialog that is still animating away. await _searchFor(tester, createdTitle); await _pumpUntil(tester, _tile(createdTitle)); @@ -122,7 +122,8 @@ Finder _inTile(String title, Finder target) => Future _searchFor(WidgetTester tester, String query) => tester.enterText(find.widgetWithText(TextField, 'Search title'), query); -/// Pumps frames until [finder] matches at least one widget or [timeout] elapses. +/// Pumps frames until [finder] matches at least one widget or [timeout] +/// elapses. /// /// Reads and writes go over the network, so the UI can't be settled with /// `pumpAndSettle`; this polls the widget tree instead. @@ -157,9 +158,8 @@ String _screen() { .byType(Checkbox) .evaluate() .map((element) => '${(element.widget as Checkbox).value}'); - return 'Labels: ${labels.join(' | ')}\n' - 'Text fields: ${fields.join(' | ')}\n' - 'Checkboxes: ${boxes.join(' | ')}'; + return 'Labels: ${labels.join(' | ')}\nText fields: ' + '${fields.join(' | ')}\nCheckboxes: ${boxes.join(' | ')}'; } /// The inverse of [_pumpUntil]: pumps until [finder] matches nothing. diff --git a/examples/database_crud/lib/main.dart b/examples/database_crud/lib/main.dart index 4d50b82fa..0d2756867 100644 --- a/examples/database_crud/lib/main.dart +++ b/examples/database_crud/lib/main.dart @@ -58,8 +58,8 @@ class _TasksPageState extends State { bool _mutating = false; Timer? _debounce; - /// Bumped on every task reload so a slower earlier request can't overwrite the - /// results of a later one. + /// Bumped on every task reload so a slower earlier request can't overwrite + /// the results of a later one. int _requestId = 0; @override @@ -87,8 +87,8 @@ class _TasksPageState extends State { } /// Reloads the task list for the current filters. Leaves the previous list on - /// screen while it runs, so changing a filter or toggling a task doesn't flash - /// a spinner over the whole list. + /// screen while it runs, so changing a filter or toggling a task doesn't + /// flash a spinner over the whole list. Future _loadTasks() async { final requestId = ++_requestId; // Ignore a response if a newer reload started or the widget went away while diff --git a/examples/database_crud/lib/models.dart b/examples/database_crud/lib/models.dart index 73f60b67f..ffb6aa438 100644 --- a/examples/database_crud/lib/models.dart +++ b/examples/database_crud/lib/models.dart @@ -60,7 +60,7 @@ class Task { final Priority priority; final DateTime createdAt; - /// Name of the task's project, populated from the embedded `projects` row when - /// the task is fetched with a join. + /// Name of the task's project, populated from the embedded `projects` row + /// when the task is fetched with a join. final String? projectName; } diff --git a/examples/database_crud/lib/tasks_repository.dart b/examples/database_crud/lib/tasks_repository.dart index 512c3bac9..45f1277eb 100644 --- a/examples/database_crud/lib/tasks_repository.dart +++ b/examples/database_crud/lib/tasks_repository.dart @@ -2,8 +2,8 @@ import 'package:supabase_flutter/supabase_flutter.dart'; import 'models.dart'; -/// All database access for the CRUD example lives here, so the UI stays thin and -/// every `supabase.from(...)` call is easy to read and to exercise from an +/// All database access for the CRUD example lives here, so the UI stays thin +/// and every `supabase.from(...)` call is easy to read and to exercise from an /// integration test. class TasksRepository { TasksRepository(this._client); diff --git a/examples/edge_functions/integration_test/functions_test.dart b/examples/edge_functions/integration_test/functions_test.dart index dbc170a01..ec6a321fa 100644 --- a/examples/edge_functions/integration_test/functions_test.dart +++ b/examples/edge_functions/integration_test/functions_test.dart @@ -15,8 +15,8 @@ const supabasePublishableKey = String.fromEnvironment( /// /// The first test exercises the core flow through the repository (a JSON /// greeting over POST and GET, a plain-text transform, and a validation error), -/// asserting on what each function returns. The second drives the app widgets to -/// confirm the greeting card is wired to the function. Edge Functions are +/// asserting on what each function returns. The second drives the app widgets +/// to confirm the greeting card is wired to the function. Edge Functions are /// stateless, so there is nothing to clean up between runs. void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -85,7 +85,8 @@ void main() { }); } -/// Pumps frames until [finder] matches at least one widget or [timeout] elapses. +/// Pumps frames until [finder] matches at least one widget or [timeout] +/// elapses. /// /// Invoking a function goes over the network, so the UI can't be settled with /// `pumpAndSettle`; this polls the widget tree instead. diff --git a/examples/edge_functions/integration_test/invoke_test.dart b/examples/edge_functions/integration_test/invoke_test.dart index 75a7bd3b4..e9ea9dd7a 100644 --- a/examples/edge_functions/integration_test/invoke_test.dart +++ b/examples/edge_functions/integration_test/invoke_test.dart @@ -205,7 +205,8 @@ void main() { _, ) async { // A client pointed at a closed port can never connect, so the request - // fails before any response, surfacing as a fetch exception with status 0. + // fails before any response, surfacing as a fetch exception with status + // 0. final unreachable = FunctionsClient( 'http://127.0.0.1:1/functions/v1', const {'apikey': supabasePublishableKey}, diff --git a/examples/edge_functions/lib/functions_repository.dart b/examples/edge_functions/lib/functions_repository.dart index 2c99c3aca..e80fe3499 100644 --- a/examples/edge_functions/lib/functions_repository.dart +++ b/examples/edge_functions/lib/functions_repository.dart @@ -2,9 +2,9 @@ import 'package:supabase_flutter/supabase_flutter.dart'; import 'models.dart'; -/// All Edge Function access for the example lives here, so the UI stays thin and -/// every `supabase.functions.invoke(...)` call is easy to read and to exercise -/// from an integration test. +/// All Edge Function access for the example lives here, so the UI stays thin +/// and every `supabase.functions.invoke(...)` call is easy to read and to +/// exercise from an integration test. class FunctionsRepository { FunctionsRepository(this._client); @@ -17,7 +17,8 @@ class FunctionsRepository { /// /// The custom `x-greeting-source` header is echoed back in the response, so /// the example can show that headers set here reach the function. A JSON body - /// comes back decoded as a `Map`, which [Greeting.fromJson] turns into a model. + /// comes back decoded as a `Map`, which [Greeting.fromJson] turns into a + /// model. Future greet({required String name, bool excited = false}) async { final response = await _functions.invoke( 'greet', @@ -42,7 +43,8 @@ class FunctionsRepository { /// responds with. /// /// A `String` body is sent as `text/plain`, and the function replies with - /// `text/plain` too, so `response.data` is a `String` rather than decoded JSON. + /// `text/plain` too, so `response.data` is a `String` rather than decoded + /// JSON. Future shout(String text) async { final response = await _functions.invoke('shout', body: text); return response.data as String; @@ -50,9 +52,9 @@ class FunctionsRepository { /// Invokes the `word-count` function, which validates its input. /// - /// When [text] is empty the function replies with a 400 and a JSON error body, - /// which surfaces here as a [FunctionException] whose `details` hold that body. - /// The caller is expected to handle that exception. + /// When [text] is empty the function replies with a 400 and a JSON error + /// body, which surfaces here as a [FunctionException] whose `details` hold + /// that body. The caller is expected to handle that exception. Future countWords(String text) async { final response = await _functions.invoke( 'word-count', diff --git a/examples/edge_functions/lib/models.dart b/examples/edge_functions/lib/models.dart index 47a2ad6c0..d5bafe65d 100644 --- a/examples/edge_functions/lib/models.dart +++ b/examples/edge_functions/lib/models.dart @@ -8,8 +8,8 @@ class Greeting { factory Greeting.fromJson(Map json) => Greeting( message: json['message'] as String, - // How the function was invoked (`GET` or `POST`), echoed back so the app can - // show that the same function was reached two different ways. + // How the function was invoked (`GET` or `POST`), echoed back so the app + // can show that the same function was reached two different ways. method: json['method'] as String, // The `x-greeting-source` header the app sent, echoed back to show that // custom headers reach the function. diff --git a/examples/launcher/lib/launcher.dart b/examples/launcher/lib/launcher.dart index f075d1d1e..1e5dcee1c 100644 --- a/examples/launcher/lib/launcher.dart +++ b/examples/launcher/lib/launcher.dart @@ -7,8 +7,8 @@ const _ok = 0; const _failure = 1; const _noTerminalMessage = - 'The launcher needs an interactive terminal to pick an example. ' - 'Run it directly in your terminal.'; + 'The launcher needs an interactive terminal to pick an example. Run it ' + 'directly in your terminal.'; final _logger = Logger(); @@ -90,8 +90,8 @@ Future run(List args) async { _logger ..info('') ..info( - '${styleBold.wrap('Running')} ${cyan.wrap(selected.name)} ' - 'against ${cyan.wrap(url)}', + '${styleBold.wrap('Running')} ${cyan.wrap(selected.name)} against ' + '${cyan.wrap(url)}', ); // Serve on a fixed origin so it matches the WebAuthn rp_origins configured @@ -119,7 +119,8 @@ Future run(List args) async { ); // Forward Ctrl-C to flutter so it shuts down and control returns here, - // letting the cleanup below stop Supabase, rather than killing the launcher. + // letting the cleanup below stop Supabase, rather than killing the + // launcher. final sigint = ProcessSignal.sigint.watch().listen( (_) => process.kill(ProcessSignal.sigint), ); diff --git a/examples/passkeys/integration_test/passkeys_test.dart b/examples/passkeys/integration_test/passkeys_test.dart index 8a88ce295..54f828571 100644 --- a/examples/passkeys/integration_test/passkeys_test.dart +++ b/examples/passkeys/integration_test/passkeys_test.dart @@ -13,10 +13,11 @@ const supabasePublishableKey = String.fromEnvironment( /// Supabase stack. /// /// It covers everything around the WebAuthn ceremony: creating an account, -/// landing on the passkey management screen, signing out, a failed sign in and a -/// successful password sign in. The ceremony itself (`registerPasskey` / -/// `signInWithPasskey`) drives a platform authenticator prompt (Face ID, Windows -/// Hello, a security key, ...) that can't be automated headlessly, so it is +/// landing on the passkey management screen, signing out, a failed sign in and +/// a successful password sign in. The ceremony itself (`registerPasskey` / +/// `signInWithPasskey`) drives an authenticator prompt, whether from a platform +/// authenticator such as Face ID or Windows Hello or from a roaming one such as +/// a security key. That prompt can't be automated headlessly, so it is /// exercised manually per the README rather than here. void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -93,7 +94,8 @@ void main() { }); } -/// Pumps frames until [finder] matches at least one widget or [timeout] elapses. +/// Pumps frames until [finder] matches at least one widget or [timeout] +/// elapses. /// /// Auth calls go over the network, so the UI can't be settled with /// `pumpAndSettle`; this polls the widget tree instead. diff --git a/examples/realtime_room/integration_test/room_test.dart b/examples/realtime_room/integration_test/room_test.dart index 8b9226cb6..268c82f9e 100644 --- a/examples/realtime_room/integration_test/room_test.dart +++ b/examples/realtime_room/integration_test/room_test.dart @@ -12,8 +12,8 @@ const supabasePublishableKey = String.fromEnvironment( /// End-to-end test that drives the real app widgets against the local Supabase /// stack, exercising all three realtime features through the UI: Postgres -/// Changes (a message round-tripping through the database), Presence (the online -/// roster) and Broadcast (the typing indicator). +/// Changes (a message round-tripping through the database), Presence (the +/// online roster) and Broadcast (the typing indicator). void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -47,9 +47,9 @@ void main() { await tester.tap(find.text('Join room')); await tester.pumpAndSettle(); - // Wait for the loading spinner to clear. The room only finishes loading once - // Postgres Changes replication is live, so a message sent after this is - // guaranteed to stream back rather than being missed during setup. + // Wait for the loading spinner to clear. The room only finishes loading + // once Postgres Changes replication is live, so a message sent after this + // is guaranteed to stream back rather than being missed during setup. await _pumpUntilGone(tester, find.byType(CircularProgressIndicator)); // Presence: once subscribed, our own name appears in the roster. @@ -100,7 +100,8 @@ void main() { }); } -/// Pumps frames until [finder] matches at least one widget or [timeout] elapses. +/// Pumps frames until [finder] matches at least one widget or [timeout] +/// elapses. /// /// Realtime updates arrive asynchronously over the network, so the UI can't be /// settled with `pumpAndSettle`; this polls the widget tree instead. diff --git a/examples/realtime_room/lib/main.dart b/examples/realtime_room/lib/main.dart index defb3f189..25fe41663 100644 --- a/examples/realtime_room/lib/main.dart +++ b/examples/realtime_room/lib/main.dart @@ -38,8 +38,8 @@ class RealtimeRoomApp extends StatelessWidget { } } -/// Asks for a display name before joining the room. Open the example in a second -/// window with a different name to see the realtime features in action. +/// Asks for a display name before joining the room. Open the example in a +/// second window with a different name to see the realtime features in action. class JoinPage extends StatefulWidget { const JoinPage({super.key}); @@ -193,8 +193,8 @@ class _RoomPageState extends State { setState(() => _messages.removeWhere((message) => message.id == id)); } - /// Shows ` is typing` for a short while, resetting the timer each time a - /// fresh ping arrives from that user. + /// Shows ` is typing` for a short while, resetting the timer each time + /// a fresh ping arrives from that user. void _showTyping(String name) { _typingTimers[name]?.cancel(); setState(() => _typingUsers.add(name)); @@ -203,8 +203,8 @@ class _RoomPageState extends State { }); } - /// Throttles typing pings so a burst of keystrokes sends at most one broadcast - /// per second. + /// Throttles typing pings so a burst of keystrokes sends at most one + /// broadcast per second. void _onInputChanged(String _) { if (_typingThrottle?.isActive ?? false) return; _typingThrottle = Timer(const Duration(seconds: 1), () {}); @@ -230,8 +230,8 @@ class _RoomPageState extends State { } } - /// Deletes a message and reports any failure, like [_send]. The row leaves the - /// list through the Postgres Changes delete stream, so there's nothing to + /// Deletes a message and reports any failure, like [_send]. The row leaves + /// the list through the Postgres Changes delete stream, so there's nothing to /// remove here on success. Future _delete(String id) async { try { diff --git a/examples/realtime_room/lib/models.dart b/examples/realtime_room/lib/models.dart index b85fdabe6..2c4af005b 100644 --- a/examples/realtime_room/lib/models.dart +++ b/examples/realtime_room/lib/models.dart @@ -1,5 +1,5 @@ -/// A chat message stored in the `messages` table. New rows are streamed to every -/// client in the room through realtime Postgres Changes. +/// A chat message stored in the `messages` table. New rows are streamed to +/// every client in the room through realtime Postgres Changes. class Message { const Message({ required this.id, diff --git a/examples/realtime_room/lib/room_channel.dart b/examples/realtime_room/lib/room_channel.dart index 98b3e6f66..d3c9de304 100644 --- a/examples/realtime_room/lib/room_channel.dart +++ b/examples/realtime_room/lib/room_channel.dart @@ -7,15 +7,15 @@ import 'models.dart'; /// A single realtime channel for the room, wrapping the three realtime features /// this example demonstrates: /// -/// * **Postgres Changes** stream inserts and deletes on the `messages` table, so -/// the chat log stays in sync without re-fetching. -/// * **Broadcast** relays ephemeral "typing" pings that are never written to the -/// database, only forwarded to the other connected clients. +/// * **Postgres Changes** stream inserts and deletes on the `messages` table, +/// so the chat log stays in sync without re-fetching. +/// * **Broadcast** relays ephemeral "typing" pings that are never written to +/// the database, only forwarded to the other connected clients. /// * **Presence** tracks who is currently in the room and exposes the live /// roster. /// -/// The channel is created but not connected in the constructor; call [subscribe] -/// to join and [dispose] to leave and release the streams. +/// The channel is created but not connected in the constructor; call +/// [subscribe] to join and [dispose] to leave and release the streams. class RoomChannel { RoomChannel({ required SupabaseClient client, @@ -24,8 +24,8 @@ class RoomChannel { }) : _client = client, _channel = client.channel( roomName, - // `self: true` echoes our own broadcast and presence events back to us, - // so this client also shows up in its own roster. + // `self: true` echoes our own broadcast and presence events back to + // us, so this client also shows up in its own roster. // // `replicationReady: true` asks the server to emit a system event once // the replication backing Postgres Changes is live. Without it, a row @@ -49,10 +49,12 @@ class RoomChannel { final _typing = StreamController.broadcast(); final _onlineUsers = StreamController>.broadcast(); - /// A message someone added to the room (from a Postgres Changes insert event). + /// A message someone added to the room (from a Postgres Changes insert + /// event). Stream get onMessageInserted => _messageInserted.stream; - /// The id of a message someone removed (from a Postgres Changes delete event). + /// The id of a message someone removed (from a Postgres Changes delete + /// event). Stream get onMessageDeleted => _messageDeleted.stream; /// The username of another client that is currently typing (from a broadcast @@ -65,8 +67,9 @@ class RoomChannel { static const _typingEvent = 'typing'; /// Registers the realtime listeners and joins the channel. Completes once the - /// server confirms both the subscription and that Postgres Changes replication - /// is live, so a message sent right afterwards is guaranteed to stream back. + /// server confirms both the subscription and that Postgres Changes + /// replication is live, so a message sent right afterwards is guaranteed to + /// stream back. Future subscribe() { final ready = Completer(); @@ -79,8 +82,8 @@ class RoomChannel { callback: (payload) => _messageInserted.add(Message.fromJson(payload.newRecord)), ) - // Postgres Changes: deleted rows. The delete payload carries the removed - // row under `oldRecord`. + // Postgres Changes: deleted rows. The delete payload carries the + // removed row under `oldRecord`. .onPostgresChanges( event: PostgresChangeEvent.delete, schema: 'public', @@ -116,7 +119,8 @@ class RoomChannel { .subscribe((status, error) { if (status == RealtimeSubscribeStatus.subscribed) { // Announce ourselves to the room now that we're connected. The - // payload is arbitrary JSON the other clients read back as presence. + // payload is arbitrary JSON the other clients read back as + // presence. unawaited( _channel.track({ 'username': username, diff --git a/examples/realtime_room/lib/room_repository.dart b/examples/realtime_room/lib/room_repository.dart index 769b8f423..200af6c00 100644 --- a/examples/realtime_room/lib/room_repository.dart +++ b/examples/realtime_room/lib/room_repository.dart @@ -24,8 +24,9 @@ class RoomRepository { /// INSERT a message and return the stored row. /// /// The insert is all that's needed to update every other client: the - /// `messages` table is in the realtime publication, so the server streams this - /// row to every subscribed [RoomChannel] as a Postgres Changes insert event. + /// `messages` table is in the realtime publication, so the server streams + /// this row to every subscribed [RoomChannel] as a Postgres Changes insert + /// event. Future sendMessage({ required String username, required String content, diff --git a/examples/storage_transforms/integration_test/storage_test.dart b/examples/storage_transforms/integration_test/storage_test.dart index d4098a440..49d4077b6 100644 --- a/examples/storage_transforms/integration_test/storage_test.dart +++ b/examples/storage_transforms/integration_test/storage_test.dart @@ -16,10 +16,10 @@ const supabasePublishableKey = String.fromEnvironment( /// End-to-end tests that drive the Storage example against the local stack. /// -/// The first test exercises the whole flow through the repository (upload, list, -/// transformed download and delete), asserting on the returned bytes. The second -/// drives the app widgets to confirm the gallery, detail view and delete button -/// are wired to those calls. +/// The first test exercises the whole flow through the repository (upload, +/// list, transformed download and delete), asserting on the returned bytes. The +/// second drives the app widgets to confirm the gallery, detail view and delete +/// button are wired to those calls. void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -130,7 +130,8 @@ Future _decodeSize(Uint8List bytes) async { return size; } -/// Pumps frames until [finder] matches at least one widget or [timeout] elapses. +/// Pumps frames until [finder] matches at least one widget or [timeout] +/// elapses. /// /// Storage calls go over the network, so the UI can't be settled with /// `pumpAndSettle`; this polls the widget tree instead. diff --git a/examples/storage_transforms/lib/main.dart b/examples/storage_transforms/lib/main.dart index 452d8a596..036407fce 100644 --- a/examples/storage_transforms/lib/main.dart +++ b/examples/storage_transforms/lib/main.dart @@ -83,8 +83,9 @@ class _GalleryPageState extends State { } } - /// Generates a fresh PNG and uploads it, then reloads the gallery. Ignores the - /// call while another upload is in flight so a double tap can't fire twice. + /// Generates a fresh PNG and uploads it, then reloads the gallery. Ignores + /// the call while another upload is in flight so a double tap can't fire + /// twice. Future _upload() async { if (_mutating) return; setState(() => _mutating = true); @@ -137,8 +138,8 @@ class _GalleryPageState extends State { final image = _images[index]; return _GalleryTile( // A downscaled, cropped thumbnail keeps the grid light: the - // resize happens server-side, so the app never downloads the - // full-size image here. + // resize happens server-side, so the app never downloads + // the full-size image here. url: _repository.imageUrl( image.path, transform: TransformPreset.thumbnail.options, diff --git a/examples/storage_transforms/lib/storage_repository.dart b/examples/storage_transforms/lib/storage_repository.dart index 1dacfd9ff..02926c91e 100644 --- a/examples/storage_transforms/lib/storage_repository.dart +++ b/examples/storage_transforms/lib/storage_repository.dart @@ -4,9 +4,9 @@ import 'package:supabase_flutter/supabase_flutter.dart'; import 'models.dart'; -/// All Storage access for the example lives here, so the UI stays thin and every -/// `supabase.storage` call is easy to read and to exercise from an integration -/// test. +/// All Storage access for the example lives here, so the UI stays thin and +/// every `supabase.storage` call is easy to read and to exercise from an +/// integration test. class StorageRepository { StorageRepository(this._client); @@ -35,8 +35,8 @@ class StorageRepository { /// Lists the images in the bucket, newest first. /// - /// `list()` also returns folder placeholders, which have no `id`, so those are - /// filtered out before mapping to [StoredImage]. + /// `list()` also returns folder placeholders, which have no `id`, so those + /// are filtered out before mapping to [StoredImage]. Future> listImages() async { final files = await _files.list( searchOptions: const SearchOptions( diff --git a/packages/functions_client/lib/src/functions_client.dart b/packages/functions_client/lib/src/functions_client.dart index ebefa6cab..245d702f5 100644 --- a/packages/functions_client/lib/src/functions_client.dart +++ b/packages/functions_client/lib/src/functions_client.dart @@ -19,7 +19,8 @@ class FunctionsClient { final String? _region; final _log = Logger("supabase.functions"); - /// In case you don't provide your own isolate, call [dispose] when you're done + /// In case you don't provide your own isolate, call [dispose] when you're + /// done FunctionsClient( String url, Map headers, { @@ -33,7 +34,8 @@ class FunctionsClient { _httpClient = httpClient, _region = region { _log.config( - "Initialize FunctionsClient v$version with url '$url' and region '$region'", + "Initialize FunctionsClient v$version with url '$url' and region " + "'$region'", ); _log.finest("Initialize with headers: $headers"); } @@ -63,8 +65,9 @@ class FunctionsClient { /// /// [files] to send in a `MultipartRequest`. [body] is used for the fields. /// - /// [region] optionally specify the region to invoke the function in. - /// When specified, adds both `x-region` header and `forceFunctionRegion` query parameter. + /// [region] optionally specify the region to invoke the function in. When + /// specified, adds both `x-region` header and `forceFunctionRegion` query + /// parameter. /// /// [abortSignal] cancels the in-flight request when the provided [Future] /// completes. It must not complete with an error. On abort, a @@ -105,8 +108,8 @@ class FunctionsClient { /// print(val); /// }); /// ``` - /// To stream SSE on the web, you can use a custom HTTP client that is - /// able to handle SSE such as [fetch_client](https://pub.dev/packages/fetch_client). + /// To stream SSE on the web, you can use a custom HTTP client that is able to + /// handle SSE such as [fetch_client](https://pub.dev/packages/fetch_client). /// ```dart /// final fetchClient = FetchClient(mode: RequestMode.cors); /// await Supabase.initialize( diff --git a/packages/functions_client/lib/src/types.dart b/packages/functions_client/lib/src/types.dart index 01d57912a..e0e1c4f5d 100644 --- a/packages/functions_client/lib/src/types.dart +++ b/packages/functions_client/lib/src/types.dart @@ -12,7 +12,8 @@ enum HttpMethod { } class FunctionResponse { - /// The data returned by the function. Type depends on the header `Content-Type`: + /// The data returned by the function. Type depends on the header + /// `Content-Type`: /// - 'text/plain': [String] /// - 'octet/stream': [Uint8List] /// - 'application/json': dynamic ([jsonDecode] is used) @@ -39,7 +40,8 @@ class FunctionException implements Exception { @override String toString() => - '$runtimeType(status: $status, details: $details, reasonPhrase: $reasonPhrase)'; + '$runtimeType(status: $status, details: $details, reasonPhrase: ' + '$reasonPhrase)'; } /// Thrown when the request to the Edge Function could not be sent, for example diff --git a/packages/functions_client/test/functions_dart_test.dart b/packages/functions_client/test/functions_dart_test.dart index 5d73f5f93..ab400cddd 100644 --- a/packages/functions_client/test/functions_dart_test.dart +++ b/packages/functions_client/test/functions_dart_test.dart @@ -85,7 +85,8 @@ void main() { 'error response with a streaming content type exposes the body', () async { // The error body must be drained and decoded into `details` rather than - // handed back as an unconsumed stream (which also leaks the connection). + // handed back as an unconsumed stream (which also leaks the + // connection). await expectLater( functionsCustomHttpClient.invoke('error-sse'), throwsA( @@ -392,7 +393,8 @@ void main() { group('Region support', () { test( - 'region parameter adds x-region header and forceFunctionRegion query param', + 'region parameter adds x-region header and forceFunctionRegion query ' + 'param', () async { await functionsCustomHttpClient.invoke( 'function', diff --git a/packages/gotrue/lib/src/broadcast_stub.dart b/packages/gotrue/lib/src/broadcast_stub.dart index 9bdaed18f..4df0dcad6 100644 --- a/packages/gotrue/lib/src/broadcast_stub.dart +++ b/packages/gotrue/lib/src/broadcast_stub.dart @@ -1,7 +1,8 @@ // coverage:ignore-file import 'package:gotrue/src/types/types.dart'; -/// Stub implementation of [BroadcastChannel] for platforms that don't support it. +/// Stub implementation of [BroadcastChannel] for platforms that don't support +/// it. BroadcastChannel getBroadcastChannel(String broadcastKey) { throw UnimplementedError(); } diff --git a/packages/gotrue/lib/src/constants.dart b/packages/gotrue/lib/src/constants.dart index 3499b0f05..638d79670 100644 --- a/packages/gotrue/lib/src/constants.dart +++ b/packages/gotrue/lib/src/constants.dart @@ -18,7 +18,8 @@ class Constants { /// Current session will be checked for refresh at this interval. static const autoRefreshTickDuration = Duration(seconds: 10); - /// A token refresh will be attempted this many ticks before the current session expires. + /// A token refresh will be attempted this many ticks before the current + /// session expires. static const autoRefreshTickThreshold = 3; /// The name of the header that contains API version. @@ -112,6 +113,8 @@ enum SignOutScope { /// Only this session will be signed out. local, - /// All other sessions except the current one will be signed out. When using others, there is no [AuthChangeEvent.signedOut] event fired on the current session! + /// All other sessions except the current one will be signed out. When using + /// others, there is no [AuthChangeEvent.signedOut] event fired on the current + /// session! others, } diff --git a/packages/gotrue/lib/src/fetch.dart b/packages/gotrue/lib/src/fetch.dart index d855f8865..28daa2f2a 100644 --- a/packages/gotrue/lib/src/fetch.dart +++ b/packages/gotrue/lib/src/fetch.dart @@ -55,11 +55,13 @@ class GotrueFetch { final dynamic data; - // Catch this case as trying to decode it will throw a misleading [FormatException] + // Catch this case as trying to decode it will throw a misleading + // [FormatException] if (response.body.isEmpty) { throw AuthUnknownException( message: - 'Received an empty response with status code ${response.statusCode}', + 'Received an empty response with status code ' + '${response.statusCode}', originalError: response, ); } diff --git a/packages/gotrue/lib/src/gotrue_admin_api.dart b/packages/gotrue/lib/src/gotrue_admin_api.dart index edb1a6caf..8cc25135e 100644 --- a/packages/gotrue/lib/src/gotrue_admin_api.dart +++ b/packages/gotrue/lib/src/gotrue_admin_api.dart @@ -79,7 +79,8 @@ class GoTrueAdminApi { /// Creates a new user. /// - /// This function should only be called on a server. Never expose your `secret` key on the client. + /// This function should only be called on a server. Never expose your + /// `secret` key on the client. /// /// Requires either an email or phone Future createUser(AdminUserAttributes attributes) async { @@ -103,7 +104,8 @@ class GoTrueAdminApi { /// record and any associated data while marking the user as deleted. It /// defaults to `false`, which permanently removes the user. /// - /// This function should only be called on a server. Never expose your `secret` key on the client. + /// This function should only be called on a server. Never expose your + /// `secret` key on the client. Future deleteUser(String id, {bool shouldSoftDelete = false}) async { validateUuid(id); final options = GotrueRequestOptions( @@ -119,9 +121,11 @@ class GoTrueAdminApi { /// Get a list of users. /// - /// This function should only be called on a server. Never expose your `secret` key on the client. + /// This function should only be called on a server. Never expose your + /// `secret` key on the client. /// - /// The result is paginated. Use the [page] and [perPage] parameters to paginate the result. + /// The result is paginated. Use the [page] and [perPage] parameters to + /// paginate the result. Future> listUsers({int? page, int? perPage}) async { final options = GotrueRequestOptions( headers: _headers, diff --git a/packages/gotrue/lib/src/gotrue_admin_oauth_api.dart b/packages/gotrue/lib/src/gotrue_admin_oauth_api.dart index 30c960e80..4c5303afd 100644 --- a/packages/gotrue/lib/src/gotrue_admin_oauth_api.dart +++ b/packages/gotrue/lib/src/gotrue_admin_oauth_api.dart @@ -65,7 +65,8 @@ class GoTrueAdminOAuthApi { /// Lists all OAuth clients with optional pagination. /// Only relevant when the OAuth 2.1 server is enabled in Supabase Auth. /// - /// This function should only be called on a server. Never expose your `secret` key in the browser. + /// This function should only be called on a server. Never expose your + /// `secret` key in the browser. Future listClients({ int? page, int? perPage, @@ -88,7 +89,8 @@ class GoTrueAdminOAuthApi { /// Creates a new OAuth client. /// Only relevant when the OAuth 2.1 server is enabled in Supabase Auth. /// - /// This function should only be called on a server. Never expose your `secret` key in the browser. + /// This function should only be called on a server. Never expose your + /// `secret` key in the browser. Future createClient( CreateOAuthClientParams params, ) async { @@ -107,7 +109,8 @@ class GoTrueAdminOAuthApi { /// Gets details of a specific OAuth client. /// Only relevant when the OAuth 2.1 server is enabled in Supabase Auth. /// - /// This function should only be called on a server. Never expose your `secret` key in the browser. + /// This function should only be called on a server. Never expose your + /// `secret` key in the browser. Future getClient(String clientId) async { validateUuid(clientId); @@ -125,7 +128,8 @@ class GoTrueAdminOAuthApi { /// Updates an existing OAuth client. /// Only relevant when the OAuth 2.1 server is enabled in Supabase Auth. /// - /// This function should only be called on a server. Never expose your `secret` key in the browser. + /// This function should only be called on a server. Never expose your + /// `secret` key in the browser. Future updateClient( String clientId, UpdateOAuthClientParams params, @@ -147,7 +151,8 @@ class GoTrueAdminOAuthApi { /// Deletes an OAuth client. /// Only relevant when the OAuth 2.1 server is enabled in Supabase Auth. /// - /// This function should only be called on a server. Never expose your `secret` key in the browser. + /// This function should only be called on a server. Never expose your + /// `secret` key in the browser. Future deleteClient(String clientId) async { validateUuid(clientId); @@ -165,7 +170,8 @@ class GoTrueAdminOAuthApi { /// Regenerates the secret for an OAuth client. /// Only relevant when the OAuth 2.1 server is enabled in Supabase Auth. /// - /// This function should only be called on a server. Never expose your `secret` key in the browser. + /// This function should only be called on a server. Never expose your + /// `secret` key in the browser. Future regenerateClientSecret(String clientId) async { validateUuid(clientId); diff --git a/packages/gotrue/lib/src/gotrue_client.dart b/packages/gotrue/lib/src/gotrue_client.dart index 0363b689a..2b050424b 100644 --- a/packages/gotrue/lib/src/gotrue_client.dart +++ b/packages/gotrue/lib/src/gotrue_client.dart @@ -39,17 +39,19 @@ class _SessionState { /// /// [url] URL of gotrue instance /// -/// [autoRefreshToken] whether to refresh the token automatically or not. Defaults to true. +/// [autoRefreshToken] whether to refresh the token automatically or not. +/// Defaults to true. /// /// [httpClient] custom http client. /// -/// [asyncStorage] local storage to store pkce code verifiers. Required when using the pkce flow. +/// [asyncStorage] local storage to store pkce code verifiers. Required when +/// using the pkce flow. /// /// Set [flowType] to [AuthFlowType.implicit] to perform old implicit auth flow. /// {@endtemplate} class GoTrueClient { - /// Namespace for the GoTrue API methods. - /// These can be used for example to get a user from a JWT in a server environment or reset a user's password. + /// Namespace for the GoTrue API methods. These can be used for example to get + /// a user from a JWT in a server environment or reset a user's password. late final GoTrueAdminApi admin; /// Namespace for the GoTrue MFA API methods. @@ -107,10 +109,10 @@ class GoTrueClient { /// crash the app. /// /// When the user is signed out because the session could not be recovered - /// (e.g. an invalid or expired refresh token), an - /// [AuthChangeEvent.signedOut] event is emitted with [AuthState.signOutReason] - /// set to the matching [SignOutReason], so you can tell it apart from an - /// explicit [signOut] without relying on the `onError` handler. + /// (e.g. an invalid or expired refresh token), an [AuthChangeEvent.signedOut] + /// event is emitted with [AuthState.signOutReason] set to the matching + /// [SignOutReason], so you can tell it apart from an explicit [signOut] + /// without relying on the `onError` handler. /// /// ```dart /// supabase.auth.onAuthStateChange.listen( @@ -140,7 +142,8 @@ class GoTrueClient { final _log = Logger('supabase.auth'); - /// Proxy to the web BroadcastChannel API. Should be null on non-web platforms. + /// Proxy to the web BroadcastChannel API. Should be null on non-web + /// platforms. BroadcastChannel? _broadcastChannel; StreamSubscription? _broadcastChannelSubscription; @@ -162,7 +165,10 @@ class GoTrueClient { final gotrueUrl = url ?? Constants.defaultGotrueUrl; _log.config( - 'Initialize GoTrueClient v$version with url: $_url, autoRefreshToken: $_autoRefreshToken, flowType: ${_flowType.name}, tickDuration: ${Constants.autoRefreshTickDuration}, tickThreshold: ${Constants.autoRefreshTickThreshold}', + 'Initialize GoTrueClient v$version with url: $_url, autoRefreshToken: ' + '$_autoRefreshToken, flowType: ${_flowType.name}, tickDuration: ' + '${Constants.autoRefreshTickDuration}, tickThreshold: ' + '${Constants.autoRefreshTickThreshold}', ); _log.finest('Initialize with headers: $_headers'); admin = GoTrueAdminApi( @@ -267,10 +273,12 @@ class GoTrueClient { /// Creates a new user. /// /// Be aware that if a user account exists in the system you may get back an - /// error message that attempts to hide this information from the user. - /// This method has support for PKCE via email signups. The PKCE flow cannot be used when autoconfirm is enabled. + /// error message that attempts to hide this information from the user. This + /// method has support for PKCE via email signups. The PKCE flow cannot be + /// used when autoconfirm is enabled. /// - /// Returns a logged-in session if the server has "autoconfirm" ON, but only a user if the server has "autoconfirm" OFF + /// Returns a logged-in session if the server has "autoconfirm" ON, but only a + /// user if the server has "autoconfirm" OFF /// /// [email] is the user's email address /// @@ -388,7 +396,8 @@ class GoTrueClient { ); } else { throw AuthException( - 'You must provide either an email, phone number, a third-party provider or OpenID Connect.', + 'You must provide either an email, phone number, a third-party ' + 'provider or OpenID Connect.', ); } @@ -488,8 +497,8 @@ class GoTrueClient { return generatePKCEChallenge(codeVerifier); } - /// Allows signing in with an ID token issued by supported providers. - /// Common supported providers include Apple, Google, Facebook, Kakao, and Keycloak. + /// Allows signing in with an ID token issued by supported providers. Common + /// supported providers include Apple, Google, Facebook, Kakao, and Keycloak. /// The [idToken] is verified for validity and a new session is established. /// /// If the ID token contains an `at_hash` claim, then [accessToken] must be @@ -585,19 +594,26 @@ class GoTrueClient { /// Log in a user using magiclink or a one-time password (OTP). /// - /// If the `{{ .ConfirmationURL }}` variable is specified in the email template, a magiclink will be sent. + /// If the `{{ .ConfirmationURL }}` variable is specified in the email + /// template, a magiclink will be sent. /// - /// If the `{{ .Token }}` variable is specified in the email template, an OTP will be sent. + /// If the `{{ .Token }}` variable is specified in the email template, an OTP + /// will be sent. /// - /// If you're using phone sign-ins, only an OTP will be sent. You won't be able to send a magiclink for phone sign-ins. + /// If you're using phone sign-ins, only an OTP will be sent. You won't be + /// able to send a magiclink for phone sign-ins. /// - /// If [shouldCreateUser] is set to false, this method will not create a new user. Defaults to true. + /// If [shouldCreateUser] is set to false, this method will not create a new + /// user. Defaults to true. /// - /// [emailRedirectTo] can be used to specify the redirect URL embedded in the email link + /// [emailRedirectTo] can be used to specify the redirect URL embedded in the + /// email link /// - /// [data] can be used to set the user's metadata, which maps to the `auth.users.user_metadata` column. + /// [data] can be used to set the user's metadata, which maps to the + /// `auth.users.user_metadata` column. /// - /// [captchaToken] Verification token received when the user completes the captcha on the site. + /// [captchaToken] Verification token received when the user completes the + /// captcha on the site. /// /// [channel] Messaging channel to use (e.g. whatsapp or sms) Future signInWithOtp({ @@ -647,7 +663,8 @@ class GoTrueClient { return; } throw AuthException( - 'You must provide either an email, phone number, a third-party provider or OpenID Connect.', + 'You must provide either an email, phone number, a third-party provider ' + 'or OpenID Connect.', ); } @@ -686,7 +703,8 @@ class GoTrueClient { // For recovery with tokenHash, email/phone should not be provided assert( email == null && phone == null, - 'For recovery type with tokenHash, only tokenHash and type should be provided.', + 'For recovery type with tokenHash, only tokenHash and type should be ' + 'provided.', ); } @@ -770,9 +788,10 @@ class GoTrueClient { return res['url'] as String; } - /// Returns a new session, regardless of expiry status. - /// Takes in an optional [refreshToken]. If not provided, then refreshSession() will attempt to retrieve it from the current session. - /// If no refresh token is available (neither provided nor in current session), an error will be thrown. + /// Returns a new session, regardless of expiry status. Takes in an optional + /// [refreshToken]. If not provided, then refreshSession() will attempt to + /// retrieve it from the current session. If no refresh token is available + /// (neither provided nor in current session), an error will be thrown. Future refreshSession([String? refreshToken]) async { _log.info('Refresh session'); @@ -808,7 +827,8 @@ class GoTrueClient { ); } - /// Resends an existing signup confirmation email, email change email, SMS OTP or phone change OTP. + /// Resends an existing signup confirmation email, email change email, SMS OTP + /// or phone change OTP. /// /// For [type] of [OtpType.signup] or [OtpType.emailChange] [email] must be /// provided, and for [type] or [OtpType.sms] or [OtpType.phoneChange], @@ -930,7 +950,8 @@ class GoTrueClient { /// Sets the session data from [refreshToken] and returns the current session. /// /// If [accessToken] is provided and not yet expired, the session is restored - /// directly from the supplied tokens, skipping the `/token` refresh round-trip. + /// directly from the supplied tokens, skipping the `/token` refresh + /// round-trip. Future setSession( String refreshToken, { String? accessToken, @@ -1071,7 +1092,8 @@ class GoTrueClient { /// /// [scope] determines which sessions should be logged out. /// - /// If using [SignOutScope.others] scope, no [AuthChangeEvent.signedOut] event is fired! + /// If using [SignOutScope.others] scope, no [AuthChangeEvent.signedOut] event + /// is fired! Future signOut({SignOutScope scope = SignOutScope.local}) => _signOut(scope: scope, reason: SignOutReason.userInitiated); @@ -1097,9 +1119,10 @@ class GoTrueClient { try { await admin.signOut(accessToken, scope: scope); } on AuthException catch (error) { - // ignore 401s since an invalid or expired JWT should sign out the current session - // ignore 403s since user might not exist anymore - // ignore 404s since user might not exist anymore + // Ignore 401s since an invalid or expired JWT should sign out the + // current session. + // Ignore 403s since the user might not exist anymore. + // Ignore 404s since the user might not exist anymore. if (error.statusCode != '401' && error.statusCode != '403' && error.statusCode != '404') { @@ -1221,7 +1244,8 @@ class GoTrueClient { /// Unlinks an identity from a user by deleting it. /// - /// The user will no longer be able to sign in with that identity once it's unlinked. + /// The user will no longer be able to sign in with that identity once it's + /// unlinked. Future unlinkIdentity(UserIdentity identity) async { await _fetch.request( '$_url/user/identities/${identity.identityId}', @@ -1326,8 +1350,9 @@ class GoTrueClient { } } - /// Starts an auto-refresh process in the background. Close to the time of expiration a process is started to - /// refresh the session. If refreshing fails it will be retried for as long as necessary. + /// Starts an auto-refresh process in the background. Close to the time of + /// expiration a process is started to refresh the session. If refreshing + /// fails it will be retried for as long as necessary. void startAutoRefresh() async { stopAutoRefresh(); @@ -1582,9 +1607,10 @@ class GoTrueClient { /// and notifies subscribers. /// /// Returns the refreshed [AuthResponse] or throws the underlying error. This - /// is the single place that emits refresh outcomes: [AuthChangeEvent.tokenRefreshed] - /// on success, [AuthChangeEvent.signedOut] when the refresh token is invalid, - /// or a stream error ([notifyException]) for a retryable/unexpected failure. + /// is the single place that emits refresh outcomes: + /// [AuthChangeEvent.tokenRefreshed] on success, [AuthChangeEvent.signedOut] + /// when the refresh token is invalid, or a stream error ([notifyException]) + /// for a retryable/unexpected failure. Future _doRefresh(String refreshToken) async { final versionBeforeRefresh = _sessionVersion; _log.fine('Refresh access token'); @@ -1615,8 +1641,8 @@ class GoTrueClient { existingSession != null && !existingSession.isExpired) { _log.fine( - 'Refresh token already used but current session is still ' - 'valid, returning it instead of signing out', + 'Refresh token already used but current session is still valid, ' + 'returning it instead of signing out', ); return AuthResponse(session: existingSession); } @@ -1702,7 +1728,8 @@ class GoTrueClient { return cachedJwk; } - // jwk isn't cached in memory so we need to fetch it from the well-known endpoint + // jwk isn't cached in memory so we need to fetch it from the well-known + // endpoint final jwksResponse = await _fetch.request( '$_url/.well-known/jwks.json', RequestMethodType.get, @@ -1729,18 +1756,20 @@ class GoTrueClient { /// sends a request to the Auth server for each JWT. /// /// If the project is not using an asymmetric JWT signing key (like ECC or - /// RSA) it always sends a request to the Auth server (similar to [getUser]) to verify the JWT. + /// RSA) it always sends a request to the Auth server (similar to [getUser]) + /// to verify the JWT. /// /// For JWTs signed with asymmetric algorithms (RS256, ES256, etc.), the JWKS - /// is fetched from the server on the first call and cached for subsequent calls. - /// The cache is refreshed automatically after 10 minutes. + /// is fetched from the server on the first call and cached for subsequent + /// calls. The cache is refreshed automatically after 10 minutes. /// /// [jwt] An optional specific JWT you wish to verify, not the one you /// can obtain from [currentSession]. /// [options] Various additional options that allow you to customize the /// behavior of this method. /// - /// Returns a [GetClaimsResponse] containing the JWT claims, or throws an [AuthException] on error. + /// Returns a [GetClaimsResponse] containing the JWT claims, or throws an + /// [AuthException] on error. Future getClaims([ String? jwt, GetClaimsOptions? options, diff --git a/packages/gotrue/lib/src/gotrue_mfa_api.dart b/packages/gotrue/lib/src/gotrue_mfa_api.dart index 23077b837..b3add0a06 100644 --- a/packages/gotrue/lib/src/gotrue_mfa_api.dart +++ b/packages/gotrue/lib/src/gotrue_mfa_api.dart @@ -10,7 +10,8 @@ class GoTrueMFAApi { /// Unenroll removes a MFA factor. /// - /// A user has to have an `aal2` authenticator level in order to unenroll a `verified` factor. + /// A user has to have an `aal2` authenticator level in order to unenroll a + /// `verified` factor. Future unenroll(String factorId) async { final session = _client.currentSession; @@ -26,15 +27,18 @@ class GoTrueMFAApi { return AuthMFAUnenrollResponse.fromJson(data); } - /// Starts the enrollment process for a new Multi-Factor Authentication (MFA) factor. - /// This method creates a new `unverified` factor. + /// Starts the enrollment process for a new Multi-Factor Authentication (MFA) + /// factor. This method creates a new `unverified` factor. /// - /// For TOTP: To verify a factor, present the QR code or secret to the user and ask them to add it to their authenticator app. - /// For Phone: The user will receive an SMS with a verification code. + /// For TOTP: To verify a factor, present the QR code or secret to the user + /// and ask them to add it to their authenticator app. For Phone: The user + /// will receive an SMS with a verification code. /// - /// The user has to enter the code from their authenticator app or SMS to verify it. + /// The user has to enter the code from their authenticator app or SMS to + /// verify it. /// - /// Upon verifying a factor, all other sessions are logged out and the current session's authenticator level is promoted to `aal2`. + /// Upon verifying a factor, all other sessions are logged out and the current + /// session's authenticator level is promoted to `aal2`. /// /// [factorType] : Type of factor being enrolled. /// @@ -69,7 +73,8 @@ class GoTrueMFAApi { body['phone'] = phone; } else { throw ArgumentError( - 'Invalid arguments, unsupported factor type for enroll: ${factorType.name}.', + 'Invalid arguments, unsupported factor type for enroll: ' + '${factorType.name}.', ); } @@ -94,7 +99,8 @@ class GoTrueMFAApi { /// Verifies a code against a [challengeId]. /// - /// The verification [code] is provided by the user by entering a code seen in their authenticator app. + /// The verification [code] is provided by the user by entering a code seen in + /// their authenticator app. Future verify({ required String factorId, required String challengeId, @@ -129,12 +135,14 @@ class GoTrueMFAApi { return response; } - /// Prepares a challenge used to verify that a user has access to a MFA factor. + /// Prepares a challenge used to verify that a user has access to a MFA + /// factor. /// - /// [factorId] System assigned identifier for authenticator device as returned by enroll + /// [factorId] System assigned identifier for authenticator device as returned + /// by enroll /// - /// [channel] Messaging channel to use for phone factors (e.g. whatsapp or sms). - /// Defaults to the server's behavior (sms) when omitted. + /// [channel] Messaging channel to use for phone factors (e.g. whatsapp or + /// sms). Defaults to the server's behavior (sms) when omitted. Future challenge({ required String factorId, OtpChannel? channel, @@ -154,9 +162,11 @@ class GoTrueMFAApi { return AuthMFAChallengeResponse.fromJson(data); } - /// Helper method which creates a challenge and immediately uses the given code to verify against it thereafter. + /// Helper method which creates a challenge and immediately uses the given + /// code to verify against it thereafter. /// - /// The verification code is provided by the user by entering a code seen in their authenticator app. + /// The verification code is provided by the user by entering a code seen in + /// their authenticator app. Future challengeAndVerify({ required String factorId, required String code, @@ -208,7 +218,8 @@ class GoTrueMFAApi { /// Returns the Authenticator Assurance Level (AAL) for the active session. /// - /// You can use this to check whether the current user needs to be shown a screen to verify their MFA factors. + /// You can use this to check whether the current user needs to be shown a + /// screen to verify their MFA factors. AuthMFAGetAuthenticatorAssuranceLevelResponse getAuthenticatorAssuranceLevel() { final session = _client.currentSession; diff --git a/packages/gotrue/lib/src/gotrue_oauth_api.dart b/packages/gotrue/lib/src/gotrue_oauth_api.dart index 02064ce7a..6cb279140 100644 --- a/packages/gotrue/lib/src/gotrue_oauth_api.dart +++ b/packages/gotrue/lib/src/gotrue_oauth_api.dart @@ -194,14 +194,19 @@ class OAuthConsentResponse { /// /// ```dart /// // 1. Extract the authorization_id from the incoming redirect URL. -/// final authorizationId = Uri.parse(currentUrl).queryParameters['authorization_id']!; +/// final authorizationId = +/// Uri.parse(currentUrl).queryParameters['authorization_id']!; /// /// // 2. Show the consent screen. -/// final details = await supabase.auth.oauth.getAuthorizationDetails(authorizationId); +/// final details = await supabase.auth.oauth.getAuthorizationDetails( +/// authorizationId, +/// ); /// print('App "${details.client.clientName}" requests: ${details.scope}'); /// /// // 3. Act on the user's decision. -/// final consent = await supabase.auth.oauth.approveAuthorization(authorizationId); +/// final consent = await supabase.auth.oauth.approveAuthorization( +/// authorizationId, +/// ); /// // Redirect the user to consent.redirectUrl. /// ``` /// diff --git a/packages/gotrue/lib/src/helper.dart b/packages/gotrue/lib/src/helper.dart index d919911f2..6b9cf41e2 100644 --- a/packages/gotrue/lib/src/helper.dart +++ b/packages/gotrue/lib/src/helper.dart @@ -10,8 +10,8 @@ export 'package:supabase_common/supabase_common.dart' /// Decodes a JWT token without performing validation /// -/// Returns a [DecodedJwt] containing the header, payload, signature, and raw parts. -/// Throws [AuthInvalidJwtException] if the JWT structure is invalid. +/// Returns a [DecodedJwt] containing the header, payload, signature, and raw +/// parts. Throws [AuthInvalidJwtException] if the JWT structure is invalid. DecodedJwt decodeJwt(String token) { final parts = token.split('.'); if (parts.length != 3) { @@ -52,7 +52,8 @@ DecodedJwt decodeJwt(String token) { } } -/// Decodes only the payload of a JWT without validating the header or signature. +/// Decodes only the payload of a JWT without validating the header or +/// signature. /// /// Useful where just the claims are needed and the token may not carry a /// well-formed header or signature. Throws [AuthInvalidJwtException] if the diff --git a/packages/gotrue/lib/src/types/auth_exception.dart b/packages/gotrue/lib/src/types/auth_exception.dart index 81fd30ebf..298d9b30b 100644 --- a/packages/gotrue/lib/src/types/auth_exception.dart +++ b/packages/gotrue/lib/src/types/auth_exception.dart @@ -68,7 +68,8 @@ class AuthApiException extends AuthException { @override String toString() => - 'AuthApiException(message: $message, statusCode: $statusCode, code: $code)'; + 'AuthApiException(message: $message, statusCode: $statusCode, code: ' + '$code)'; } class AuthUnknownException extends AuthException { @@ -87,7 +88,8 @@ class AuthUnknownException extends AuthException { @override String toString() => - 'AuthUnknownException(message: $message, originalError: $originalError, statusCode: $statusCode)'; + 'AuthUnknownException(message: $message, originalError: $originalError, ' + 'statusCode: $statusCode)'; } class AuthWeakPasswordException extends AuthException { @@ -101,7 +103,8 @@ class AuthWeakPasswordException extends AuthException { @override String toString() => - 'AuthWeakPasswordException(message: $message, statusCode: $statusCode, reasons: $reasons)'; + 'AuthWeakPasswordException(message: $message, statusCode: $statusCode, ' + 'reasons: $reasons)'; } class AuthInvalidJwtException extends AuthException { @@ -113,5 +116,6 @@ class AuthInvalidJwtException extends AuthException { @override String toString() => - 'AuthInvalidJwtException(message: $message, statusCode: $statusCode, code: $code)'; + 'AuthInvalidJwtException(message: $message, statusCode: $statusCode, ' + 'code: $code)'; } diff --git a/packages/gotrue/lib/src/types/auth_response.dart b/packages/gotrue/lib/src/types/auth_response.dart index 86515fef2..d248e5899 100644 --- a/packages/gotrue/lib/src/types/auth_response.dart +++ b/packages/gotrue/lib/src/types/auth_response.dart @@ -75,12 +75,13 @@ class GenerateLinkResponse { } class GenerateLinkProperties { - /// The email link to send to the user. - /// The action_link follows the following format: auth/v1/verify?type={verification_type}&token={hashed_token}&redirect_to={redirect_to} + /// The email link to send to the user. The action_link follows the following + /// format: + /// auth/v1/verify?type={verification_type}&token={hashed_token}&redirect_to={redirect_to} final String actionLink; - /// The raw email OTP. - /// You should send this in the email if you want your users to verify using an OTP instead of the action link. + /// The raw email OTP. You should send this in the email if you want your + /// users to verify using an OTP instead of the action link. final String emailOtp; /// The hashed token appended to the action link. diff --git a/packages/gotrue/lib/src/types/auth_state.dart b/packages/gotrue/lib/src/types/auth_state.dart index 94f6b96b8..fda6a5081 100644 --- a/packages/gotrue/lib/src/types/auth_state.dart +++ b/packages/gotrue/lib/src/types/auth_state.dart @@ -10,10 +10,10 @@ class AuthState { /// [AuthChangeEvent.signedOut]. /// /// Lets listeners tell an explicit [GoTrueClient.signOut] apart from an - /// involuntary sign out, such as an invalid or expired refresh token, directly - /// from the `signedOut` event rather than from the matching stream error. An - /// `onError` handler is still needed to catch the other exceptions emitted on - /// the stream. It is `null` for every event other than + /// involuntary sign out, such as an invalid or expired refresh token, + /// directly from the `signedOut` event rather than from the matching stream + /// error. An `onError` handler is still needed to catch the other exceptions + /// emitted on the stream. It is `null` for every event other than /// [AuthChangeEvent.signedOut] and for `signedOut` events received from /// another tab via `web.BroadcastChannel`. final SignOutReason? signOutReason; @@ -31,7 +31,7 @@ class AuthState { @override String toString() { - return 'AuthState(event: ${event.name}, session: $session, ' - 'fromBroadcast: $fromBroadcast, signOutReason: ${signOutReason?.name})'; + return 'AuthState(event: ${event.name}, session: $session, fromBroadcast: ' + '$fromBroadcast, signOutReason: ${signOutReason?.name})'; } } diff --git a/packages/gotrue/lib/src/types/jwt.dart b/packages/gotrue/lib/src/types/jwt.dart index 80f87648d..c78a6746a 100644 --- a/packages/gotrue/lib/src/types/jwt.dart +++ b/packages/gotrue/lib/src/types/jwt.dart @@ -152,8 +152,9 @@ class GetClaimsResponse { /// Options for getClaims method class GetClaimsOptions { - /// If set to `true`, the `exp` claim will not be validated against the current time. - /// This allows you to extract claims from expired JWTs without getting an error. + /// If set to `true`, the `exp` claim will not be validated against the + /// current time. This allows you to extract claims from expired JWTs without + /// getting an error. final bool allowExpired; const GetClaimsOptions({ @@ -260,10 +261,10 @@ class JWK { /// Builds the RSA public key for verifying RS256 JWTs from this JWK's /// modulus (`n`) and exponent (`e`). /// - /// The key is assembled as a PKCS#1 `RSAPublicKey` DER structure and handed to - /// [RSAPublicKey.bytes]. This avoids `JWTKey.fromJWK`, which is only available - /// in dart_jsonwebtoken 3.x and would force a dependency bump that is - /// incompatible with the minimum supported Flutter version. + /// The key is assembled as a PKCS#1 `RSAPublicKey` DER structure and handed + /// to [RSAPublicKey.bytes]. This avoids `JWTKey.fromJWK`, which is only + /// available in dart_jsonwebtoken 3.x and would force a dependency bump that + /// is incompatible with the minimum supported Flutter version. // TODO: replace this manual DER assembly with `JWTKey.fromJWK` once the // minimum Flutter is >= 3.29.0 (Dart 3.7.0), whose flutter_test bundles // clock 1.1.2 and so allows dart_jsonwebtoken 3.x. fromJWK also adds EC diff --git a/packages/gotrue/lib/src/types/mfa.dart b/packages/gotrue/lib/src/types/mfa.dart index 6b071f5ed..e2c6f12c0 100644 --- a/packages/gotrue/lib/src/types/mfa.dart +++ b/packages/gotrue/lib/src/types/mfa.dart @@ -4,7 +4,8 @@ class AuthMFAEnrollResponse { /// ID of the factor that was just enrolled (in an unverified state). final String id; - /// Type of MFA factor. Supports both `[FactorType.totp]` and `[FactorType.phone]`. + /// Type of MFA factor. Supports both `[FactorType.totp]` and + /// `[FactorType.phone]`. final FactorType type; /// TOTP enrollment information (only present when type is totp). @@ -39,17 +40,20 @@ class AuthMFAEnrollResponse { } class TOTPEnrollment { - ///Contains a QR code encoding the authenticator URI. + /// Contains a QR code encoding the authenticator URI. /// - ///You can convert it to a URL by prepending `data:image/svg+xml;utf-8,` to the value. Avoid logging this value to the console. + /// You can convert it to a URL by prepending `data:image/svg+xml;utf-8,` to + /// the value. Avoid logging this value to the console. final String qrCode; - ///The TOTP secret (also encoded in the QR code). + /// The TOTP secret (also encoded in the QR code). /// - ///Show this secret in a password-style field to the user, in case they are unable to scan the QR code. Avoid logging this value to the console. + /// Show this secret in a password-style field to the user, in case they are + /// unable to scan the QR code. Avoid logging this value to the console. final String secret; - ///The authenticator URI encoded within the QR code, should you need to use it. Avoid logging this value to the console. + /// The authenticator URI encoded within the QR code, should you need to use + /// it. Avoid logging this value to the console. final String uri; const TOTPEnrollment({ @@ -255,7 +259,8 @@ class Factor { /// ID of the factor. final String id; - /// Friendly name of the factor, useful to disambiguate between multiple factors. + /// Friendly name of the factor, useful to disambiguate between multiple + /// factors. final String? friendlyName; /// Type of factor. Supports `totp`, `phone` and `webauthn`. @@ -347,7 +352,9 @@ class Factor { @override String toString() { - return 'Factor(id: $id, friendlyName: $friendlyName, factorType: ${factorType.name}, status: ${status.name}, createdAt: $createdAt, updatedAt: $updatedAt)'; + return 'Factor(id: $id, friendlyName: $friendlyName, factorType: ' + '${factorType.name}, status: ${status.name}, createdAt: $createdAt, ' + 'updatedAt: $updatedAt)'; } } @@ -373,7 +380,8 @@ class AuthMFAGetAuthenticatorAssuranceLevelResponse { /// A list of all authentication methods attached to this session. /// - /// Use the information here to detect the last time a user verified a factor, for example if implementing a step-up scenario. + /// Use the information here to detect the last time a user verified a factor, + /// for example if implementing a step-up scenario. final List currentAuthenticationMethods; const AuthMFAGetAuthenticatorAssuranceLevelResponse({ diff --git a/packages/gotrue/lib/src/types/session.dart b/packages/gotrue/lib/src/types/session.dart index 7ef93b027..d661ef06b 100644 --- a/packages/gotrue/lib/src/types/session.dart +++ b/packages/gotrue/lib/src/types/session.dart @@ -84,7 +84,8 @@ class Session { } } - /// Returns 'true` if the token is expired or will expire in the next 10 seconds. + /// Returns `true` if the token is expired or will expire in the next 10 + /// seconds. /// /// The 10 second buffer is to account for latency issues. bool get isExpired { @@ -128,7 +129,9 @@ class Session { @override String toString() { - return 'Session(providerToken: $providerToken, providerRefreshToken: $providerRefreshToken, expiresIn: $expiresIn, tokenType: $tokenType, user: $user, accessToken: $accessToken, refreshToken: $refreshToken)'; + return 'Session(providerToken: $providerToken, providerRefreshToken: ' + '$providerRefreshToken, expiresIn: $expiresIn, tokenType: $tokenType, ' + 'user: $user, accessToken: $accessToken, refreshToken: $refreshToken)'; } @override diff --git a/packages/gotrue/lib/src/types/types.dart b/packages/gotrue/lib/src/types/types.dart index c295a7bb2..8c76dd2ca 100644 --- a/packages/gotrue/lib/src/types/types.dart +++ b/packages/gotrue/lib/src/types/types.dart @@ -112,8 +112,9 @@ enum OAuthClientGrantType { authorizationCode, refreshToken } /// Only relevant when the OAuth 2.1 server is enabled in Supabase Auth. enum OAuthClientResponseType { code } -/// OAuth client type indicating whether the client can keep credentials confidential. -/// Only relevant when the OAuth 2.1 server is enabled in Supabase Auth. +/// OAuth client type indicating whether the client can keep credentials +/// confidential. Only relevant when the OAuth 2.1 server is enabled in Supabase +/// Auth. enum OAuthClientType { public, confidential; @@ -239,7 +240,8 @@ class CreateOAuthClientParams { /// Array of allowed redirect URIs final List redirectUris; - /// Array of allowed grant types (optional, defaults to authorization_code and refresh_token) + /// Array of allowed grant types (optional, defaults to authorization_code and + /// refresh_token) final List? grantTypes; /// Array of allowed response types (optional, defaults to code) diff --git a/packages/gotrue/lib/src/types/user.dart b/packages/gotrue/lib/src/types/user.dart index 00f5a7635..2f4782714 100644 --- a/packages/gotrue/lib/src/types/user.dart +++ b/packages/gotrue/lib/src/types/user.dart @@ -121,7 +121,15 @@ class User { @override String toString() { - return 'User(id: $id, appMetadata: $appMetadata, userMetadata: $userMetadata, aud: $aud, confirmationSentAt: $confirmationSentAt, recoverySentAt: $recoverySentAt, emailChangeSentAt: $emailChangeSentAt, newEmail: $newEmail, invitedAt: $invitedAt, actionLink: $actionLink, email: $email, phone: $phone, createdAt: $createdAt, confirmedAt: $confirmedAt, emailConfirmedAt: $emailConfirmedAt, phoneConfirmedAt: $phoneConfirmedAt, lastSignInAt: $lastSignInAt, role: $role, updatedAt: $updatedAt, identities: $identities, factors: $factors, isAnonymous: $isAnonymous)'; + return 'User(id: $id, appMetadata: $appMetadata, userMetadata: ' + '$userMetadata, aud: $aud, confirmationSentAt: $confirmationSentAt, ' + 'recoverySentAt: $recoverySentAt, emailChangeSentAt: ' + '$emailChangeSentAt, newEmail: $newEmail, invitedAt: $invitedAt, ' + 'actionLink: $actionLink, email: $email, phone: $phone, createdAt: ' + '$createdAt, confirmedAt: $confirmedAt, emailConfirmedAt: ' + '$emailConfirmedAt, phoneConfirmedAt: $phoneConfirmedAt, lastSignInAt: ' + '$lastSignInAt, role: $role, updatedAt: $updatedAt, identities: ' + '$identities, factors: $factors, isAnonymous: $isAnonymous)'; } @override @@ -252,7 +260,10 @@ class UserIdentity { @override String toString() { - return 'UserIdentity(id: $id, userId: $userId, identityData: $identityData, identityId: $identityId, provider: $provider, createdAt: $createdAt, lastSignInAt: $lastSignInAt, updatedAt: $updatedAt)'; + return 'UserIdentity(id: $id, userId: $userId, identityData: ' + '$identityData, identityId: $identityId, provider: $provider, ' + 'createdAt: $createdAt, lastSignInAt: $lastSignInAt, updatedAt: ' + '$updatedAt)'; } @override diff --git a/packages/gotrue/lib/src/types/user_attributes.dart b/packages/gotrue/lib/src/types/user_attributes.dart index d5165c636..5f9d0f238 100644 --- a/packages/gotrue/lib/src/types/user_attributes.dart +++ b/packages/gotrue/lib/src/types/user_attributes.dart @@ -10,14 +10,17 @@ class UserAttributes { /// The user's password. String? password; - /// The nonce sent for reauthentication if the user's password is to be updated. + /// The nonce sent for reauthentication if the user's password is to be + /// updated. /// /// Call reauthenticate() to obtain the nonce first. String? nonce; - /// A custom data object to store the user's metadata. This maps to the `auth.users.user_metadata` column. + /// A custom data object to store the user's metadata. This maps to the + /// `auth.users.user_metadata` column. /// - /// The `data` should be a JSON object that includes user-specific info, such as their first and last name. + /// The `data` should be a JSON object that includes user-specific info, such + /// as their first and last name. Object? data; /// The user's current password. @@ -75,22 +78,26 @@ class UserAttributes { } class AdminUserAttributes extends UserAttributes { - /// A custom data object to store the user's metadata. This maps to the `auth.users.user_metadata` column. + /// A custom data object to store the user's metadata. This maps to the + /// `auth.users.user_metadata` column. /// /// Only a service role can modify. /// - /// The `user_metadata` should be a JSON object that includes user-specific info, such as their first and last name. + /// The `user_metadata` should be a JSON object that includes user-specific + /// info, such as their first and last name. /// - /// Note: When using the GoTrueAdminApi and wanting to modify a user's metadata, - /// this attribute is used instead of UserAttributes data. + /// Note: When using the GoTrueAdminApi and wanting to modify a user's + /// metadata, this attribute is used instead of UserAttributes data. final Map? userMetadata; - /// A custom data object to store the user's application specific metadata. This maps to the `auth.users.app_metadata` column. + /// A custom data object to store the user's application specific metadata. + /// This maps to the `auth.users.app_metadata` column. /// /// Only a service role can modify. /// - /// The `app_metadata` should be a JSON object that includes app-specific info, such as identity providers, roles, and other - /// access control information. + /// The `app_metadata` should be a JSON object that includes app-specific + /// info, such as identity providers, roles, and other access control + /// information. final Map? appMetadata; /// Confirms the user's email address if set to true. @@ -105,8 +112,9 @@ class AdminUserAttributes extends UserAttributes { /// Determines how long a user is banned for. /// - /// The format for the ban duration follows a strict sequence of decimal numbers with a unit suffix. - /// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". + /// The format for the ban duration follows a strict sequence of decimal + /// numbers with a unit suffix. Valid time units are "ns", "us" (or "µs"), + /// "ms", "s", "m", "h". /// /// For example, some possible durations include: '300ms', '2h45m'. /// diff --git a/packages/gotrue/test/client_test.dart b/packages/gotrue/test/client_test.dart index dc56fd3f5..dd555bb28 100644 --- a/packages/gotrue/test/client_test.dart +++ b/packages/gotrue/test/client_test.dart @@ -69,14 +69,29 @@ void main() { test('basic json parsing', () async { const body = - '{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNjExODk1MzExLCJzdWIiOiI0Njg3YjkzNi02ZDE5LTRkNmUtOGIyYi1kYmU0N2I1ZjYzOWMiLCJlbWFpbCI6InRlc3Q5QGdtYWlsLmNvbSIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6ImVtYWlsIn0sInVzZXJfbWV0YWRhdGEiOm51bGwsInJvbGUiOiJhdXRoZW50aWNhdGVkIn0.GyIokEvKGp0M8PYU8IiIpvzeTAXspoCtR5aj-jCnWys","token_type":"bearer","expires_in":3600,"refresh_token":"gnqAPZwZDj_XCYMF7U2Xtg","user":{"id":"4687b936-6d19-4d6e-8b2b-dbe47b5f639c","aud":"authenticated","role":"authenticated","email":"test9@gmail.com","confirmed_at":"2021-01-29T03:41:51.026791085Z","last_sign_in_at":"2021-01-29T03:41:51.032154484Z","app_metadata":{"provider":"email"},"user_metadata":null,"created_at":"2021-01-29T03:41:51.022787Z","updated_at":"2021-01-29T03:41:51.033826Z"}}'; + '{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdX' + 'RoZW50aWNhdGVkIiwiZXhwIjoxNjExODk1MzExLCJzdWIiOiI0Njg3YjkzNi02ZDE5LT' + 'RkNmUtOGIyYi1kYmU0N2I1ZjYzOWMiLCJlbWFpbCI6InRlc3Q5QGdtYWlsLmNvbSIsIm' + 'FwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6ImVtYWlsIn0sInVzZXJfbWV0YWRhdGEiOm' + '51bGwsInJvbGUiOiJhdXRoZW50aWNhdGVkIn0.GyIokEvKGp0M8PYU8IiIpvzeTAXspo' + 'CtR5aj-jCnWys","token_type":"bearer","expires_in":3600,"refresh_toke' + 'n":"gnqAPZwZDj_XCYMF7U2Xtg","user":{"id":"4687b936-6d19-4d6e-8b2b-db' + 'e47b5f639c","aud":"authenticated","role":"authenticated","email":"te' + 'st9@gmail.com","confirmed_at":"2021-01-29T03:41:51.026791085Z","last' + '_sign_in_at":"2021-01-29T03:41:51.032154484Z","app_metadata":{"provi' + 'der":"email"},"user_metadata":null,"created_at":"2021-01-29T03:41:51' + '.022787Z","updated_at":"2021-01-29T03:41:51.033826Z"}}'; final bodyJson = json.decode(body); final session = Session.fromJson(bodyJson as Map); expect(session, isNotNull); expect( session!.accessToken, - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNjExODk1MzExLCJzdWIiOiI0Njg3YjkzNi02ZDE5LTRkNmUtOGIyYi1kYmU0N2I1ZjYzOWMiLCJlbWFpbCI6InRlc3Q5QGdtYWlsLmNvbSIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6ImVtYWlsIn0sInVzZXJfbWV0YWRhdGEiOm51bGwsInJvbGUiOiJhdXRoZW50aWNhdGVkIn0.GyIokEvKGp0M8PYU8IiIpvzeTAXspoCtR5aj-jCnWys', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZ' + 'XhwIjoxNjExODk1MzExLCJzdWIiOiI0Njg3YjkzNi02ZDE5LTRkNmUtOGIyYi1kYmU0N2I' + '1ZjYzOWMiLCJlbWFpbCI6InRlc3Q5QGdtYWlsLmNvbSIsImFwcF9tZXRhZGF0YSI6eyJwc' + 'm92aWRlciI6ImVtYWlsIn0sInVzZXJfbWV0YWRhdGEiOm51bGwsInJvbGUiOiJhdXRoZW5' + '0aWNhdGVkIn0.GyIokEvKGp0M8PYU8IiIpvzeTAXspoCtR5aj-jCnWys', ); }); @@ -133,7 +148,8 @@ void main() { test('Parsing an error URL should throw', () async { const errorMessage = - 'Unverified email with spotify. A confirmation email has been sent to your spotify email'; + 'Unverified email with spotify. A confirmation email has been sent ' + 'to your spotify email'; final urlWithoutAccessToken = Uri.parse( 'http://my-callback-url.com/#error=unauthorized_client&error_code=401&error_description=${Uri.encodeComponent(errorMessage)}', @@ -274,7 +290,8 @@ void main() { }); test( - 'Set session with an empty refresh token throws AuthSessionMissingException', + 'Set session with an empty refresh token throws ' + 'AuthSessionMissingException', () async { await expectLater( client.setSession(''), @@ -284,7 +301,8 @@ void main() { ); test( - 'Set session with both access token and refresh token skips network refresh', + 'Set session with both access token and refresh token skips network ' + 'refresh', () async { await client.signInWithPassword(email: email1, password: password); @@ -333,9 +351,8 @@ void main() { // Payload: {"sub":"user","exp":1} (epoch second 1 = Jan 1, 1970) // Signature: 3 zero bytes as valid base64url ("AAAA") const expiredAccessToken = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' - '.eyJzdWIiOiJ1c2VyIiwiZXhwIjoxfQ' - '.AAAA'; + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyIiwiZXhwIjoxf' + 'Q.AAAA'; final newClient = GoTrueClient( url: gotrueUrl, @@ -541,9 +558,26 @@ void main() { httpClient: httpClient, ); final session = - '{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2ODAzNDE3MDUsInN1YiI6IjRkMjU4M2RhLThkZTQtNDlkMy05Y2QxLTM3YTlhNzRmNTViZCIsImVtYWlsIjoiZmFrZTE2ODAzMzgxMDVAZW1haWwuY29tIiwicGhvbmUiOiIiLCJhcHBfbWV0YWRhdGEiOnsicHJvdmlkZXIiOiJlbWFpbCIsInByb3ZpZGVycyI6WyJlbWFpbCJdfSwidXNlcl9tZXRhZGF0YSI6eyJIZWxsbyI6IldvcmxkIn0sInJvbGUiOiIiLCJhYWwiOiJhYWwxIiwiYW1yIjpbeyJtZXRob2QiOiJwYXNzd29yZCIsInRpbWVzdGFtcCI6MTY4MDMzODEwNX1dLCJzZXNzaW9uX2lkIjoiYzhiOTg2Y2UtZWJkZC00ZGUxLWI4MjAtZjIyOWYyNjg1OGIwIn0.0x1rFlPKbIU1rZPY1SH_FNSZaXerfkFA1Y-EOlhuzUs","expires_in":3600,"refresh_token":"-yeS4omysFs9tpUYBws9Rg","token_type":"bearer","provider_token":null,"provider_refresh_token":null,"user":{"id":"4d2583da-8de4-49d3-9cd1-37a9a74f55bd","app_metadata":{"provider":"email","providers":["email"]},"user_metadata":{"Hello":"World"},"aud":"","email":"fake1680338105@email.com","phone":"","created_at":"2023-04-01T08:35:05.208586Z","confirmed_at":null,"email_confirmed_at":"2023-04-01T08:35:05.220096086Z","phone_confirmed_at":null,"last_sign_in_at":"2023-04-01T08:35:05.222755878Z","role":"","updated_at":"2023-04-01T08:35:05.226938Z"},"expiresAt":1680341705}'; - - ///These 3 are bundled and in sum 1 refresh token requests is made, because the first 3 fail in [RetryTestHttpClient] + '{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OD' + 'AzNDE3MDUsInN1YiI6IjRkMjU4M2RhLThkZTQtNDlkMy05Y2QxLTM3YTlhNzRmNTViZC' + 'IsImVtYWlsIjoiZmFrZTE2ODAzMzgxMDVAZW1haWwuY29tIiwicGhvbmUiOiIiLCJhcH' + 'BfbWV0YWRhdGEiOnsicHJvdmlkZXIiOiJlbWFpbCIsInByb3ZpZGVycyI6WyJlbWFpbC' + 'JdfSwidXNlcl9tZXRhZGF0YSI6eyJIZWxsbyI6IldvcmxkIn0sInJvbGUiOiIiLCJhYW' + 'wiOiJhYWwxIiwiYW1yIjpbeyJtZXRob2QiOiJwYXNzd29yZCIsInRpbWVzdGFtcCI6MT' + 'Y4MDMzODEwNX1dLCJzZXNzaW9uX2lkIjoiYzhiOTg2Y2UtZWJkZC00ZGUxLWI4MjAtZj' + 'IyOWYyNjg1OGIwIn0.0x1rFlPKbIU1rZPY1SH_FNSZaXerfkFA1Y-EOlhuzUs","expi' + 'res_in":3600,"refresh_token":"-yeS4omysFs9tpUYBws9Rg","token_type":"' + 'bearer","provider_token":null,"provider_refresh_token":null,"user":{' + '"id":"4d2583da-8de4-49d3-9cd1-37a9a74f55bd","app_metadata":{"provide' + 'r":"email","providers":["email"]},"user_metadata":{"Hello":"World"},' + '"aud":"","email":"fake1680338105@email.com","phone":"","created_at":' + '"2023-04-01T08:35:05.208586Z","confirmed_at":null,"email_confirmed_a' + 't":"2023-04-01T08:35:05.220096086Z","phone_confirmed_at":null,"last_' + 'sign_in_at":"2023-04-01T08:35:05.222755878Z","role":"","updated_at":' + '"2023-04-01T08:35:05.226938Z"},"expiresAt":1680341705}'; + + // These 3 are bundled and in sum 1 refresh token requests is made, + // because the first 3 fail in [RetryTestHttpClient] final responses = await Future.wait([ bundledClient.recoverSession(session), bundledClient.recoverSession(session), @@ -645,7 +679,23 @@ void main() { test('Session recovery succeeds after retries', () async { try { await client.recoverSession( - '{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2ODAzNDE3MDUsInN1YiI6IjRkMjU4M2RhLThkZTQtNDlkMy05Y2QxLTM3YTlhNzRmNTViZCIsImVtYWlsIjoiZmFrZTE2ODAzMzgxMDVAZW1haWwuY29tIiwicGhvbmUiOiIiLCJhcHBfbWV0YWRhdGEiOnsicHJvdmlkZXIiOiJlbWFpbCIsInByb3ZpZGVycyI6WyJlbWFpbCJdfSwidXNlcl9tZXRhZGF0YSI6eyJIZWxsbyI6IldvcmxkIn0sInJvbGUiOiIiLCJhYWwiOiJhYWwxIiwiYW1yIjpbeyJtZXRob2QiOiJwYXNzd29yZCIsInRpbWVzdGFtcCI6MTY4MDMzODEwNX1dLCJzZXNzaW9uX2lkIjoiYzhiOTg2Y2UtZWJkZC00ZGUxLWI4MjAtZjIyOWYyNjg1OGIwIn0.0x1rFlPKbIU1rZPY1SH_FNSZaXerfkFA1Y-EOlhuzUs","expires_in":3600,"refresh_token":"-yeS4omysFs9tpUYBws9Rg","token_type":"bearer","provider_token":null,"provider_refresh_token":null,"user":{"id":"4d2583da-8de4-49d3-9cd1-37a9a74f55bd","app_metadata":{"provider":"email","providers":["email"]},"user_metadata":{"Hello":"World"},"aud":"","email":"fake1680338105@email.com","phone":"","created_at":"2023-04-01T08:35:05.208586Z","confirmed_at":null,"email_confirmed_at":"2023-04-01T08:35:05.220096086Z","phone_confirmed_at":null,"last_sign_in_at":"2023-04-01T08:35:05.222755878Z","role":"","updated_at":"2023-04-01T08:35:05.226938Z"},"expiresAt":1680341705}', + '{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OD' + 'AzNDE3MDUsInN1YiI6IjRkMjU4M2RhLThkZTQtNDlkMy05Y2QxLTM3YTlhNzRmNTViZC' + 'IsImVtYWlsIjoiZmFrZTE2ODAzMzgxMDVAZW1haWwuY29tIiwicGhvbmUiOiIiLCJhcH' + 'BfbWV0YWRhdGEiOnsicHJvdmlkZXIiOiJlbWFpbCIsInByb3ZpZGVycyI6WyJlbWFpbC' + 'JdfSwidXNlcl9tZXRhZGF0YSI6eyJIZWxsbyI6IldvcmxkIn0sInJvbGUiOiIiLCJhYW' + 'wiOiJhYWwxIiwiYW1yIjpbeyJtZXRob2QiOiJwYXNzd29yZCIsInRpbWVzdGFtcCI6MT' + 'Y4MDMzODEwNX1dLCJzZXNzaW9uX2lkIjoiYzhiOTg2Y2UtZWJkZC00ZGUxLWI4MjAtZj' + 'IyOWYyNjg1OGIwIn0.0x1rFlPKbIU1rZPY1SH_FNSZaXerfkFA1Y-EOlhuzUs","expi' + 'res_in":3600,"refresh_token":"-yeS4omysFs9tpUYBws9Rg","token_type":"' + 'bearer","provider_token":null,"provider_refresh_token":null,"user":{' + '"id":"4d2583da-8de4-49d3-9cd1-37a9a74f55bd","app_metadata":{"provide' + 'r":"email","providers":["email"]},"user_metadata":{"Hello":"World"},' + '"aud":"","email":"fake1680338105@email.com","phone":"","created_at":' + '"2023-04-01T08:35:05.208586Z","confirmed_at":null,"email_confirmed_a' + 't":"2023-04-01T08:35:05.220096086Z","phone_confirmed_at":null,"last_' + 'sign_in_at":"2023-04-01T08:35:05.222755878Z","role":"","updated_at":' + '"2023-04-01T08:35:05.226938Z"},"expiresAt":1680341705}', ); } on ClientException { // the method should throw @@ -684,7 +734,8 @@ void main() { test('Parsing an error URL should throw', () async { const errorMessage = - 'Unverified email with spotify. A confirmation email has been sent to your spotify email'; + 'Unverified email with spotify. A confirmation email has been sent ' + 'to your spotify email'; // Supabase Auth returns a URL with `#` even when using pkce flow. final urlWithoutAccessToken = Uri.parse( @@ -725,9 +776,9 @@ void main() { ); final url = Uri.parse( - 'http://my-callback-url.com/#access_token=my-access-token' - '&expires_in=3600&refresh_token=my-refresh-token' - '&token_type=bearer&type=email_change', + 'http://my-callback-url.com/#access_token=my-access-token&expires_in=' + '3600&refresh_token=my-refresh-token&token_type=bearer&type=email_cha' + 'nge', ); final emittedEvent = pkceClient.onAuthStateChange @@ -808,7 +859,8 @@ void main() { ); }); - // Regression test for https://github.com/supabase/supabase-flutter/issues/1158 + // Regression test for + // https://github.com/supabase/supabase-flutter/issues/1158 // // On cold start both `recoverSession` and the auto-refresh tick (fired when // the app resumes) can try to refresh the same persisted, expired session. @@ -817,41 +869,45 @@ void main() { // the server respond with `refresh_token_already_used`, signing the user // out. `recoverSession` must instead detect the already valid in-memory // session and return it. - test('does not reuse a stale refresh token after another refresh', () async { - final expiredSessionString = getSessionData( - DateTime.now().subtract(const Duration(hours: 1)), - ).sessionString; - - // First recovery refreshes the expired session, advancing the in-memory - // session onto a brand new refresh token. - final first = await client.recoverSession(expiredSessionString); - expect(first.session, isNotNull); - expect(first.session!.isExpired, isFalse); - expect(httpClient.refreshCount, 1); - - var signedOut = false; - final subscription = client.onAuthStateChange.listen( - (state) { - if (state.event == AuthChangeEvent.signedOut) signedOut = true; - }, - onError: (_) {}, - ); - - // Second recovery uses the same (now stale) persisted session, as happens - // when a second code path recovers the session it read before the first - // refresh completed. It must not resend the already-used refresh token. - final second = await client.recoverSession(expiredSessionString); - expect(second.session, isNotNull); - expect(second.session!.isExpired, isFalse); - - // No second refresh request was made and the user stays signed in. - expect(httpClient.refreshCount, 1); - await pumpEventQueue(); - expect(signedOut, isFalse); - expect(client.currentSession, isNotNull); + test( + 'does not reuse a stale refresh token after another refresh', + () async { + final expiredSessionString = getSessionData( + DateTime.now().subtract(const Duration(hours: 1)), + ).sessionString; + + // First recovery refreshes the expired session, advancing the in-memory + // session onto a brand new refresh token. + final first = await client.recoverSession(expiredSessionString); + expect(first.session, isNotNull); + expect(first.session!.isExpired, isFalse); + expect(httpClient.refreshCount, 1); + + var signedOut = false; + final subscription = client.onAuthStateChange.listen( + (state) { + if (state.event == AuthChangeEvent.signedOut) signedOut = true; + }, + onError: (_) {}, + ); - await subscription.cancel(); - }); + // Second recovery uses the same (now stale) persisted session, as + // happens when a second code path recovers the session it read before + // the first refresh completed. It must not resend the already-used + // refresh token. + final second = await client.recoverSession(expiredSessionString); + expect(second.session, isNotNull); + expect(second.session!.isExpired, isFalse); + + // No second refresh request was made and the user stays signed in. + expect(httpClient.refreshCount, 1); + await pumpEventQueue(); + expect(signedOut, isFalse); + expect(client.currentSession, isNotNull); + + await subscription.cancel(); + }, + ); }); } diff --git a/packages/gotrue/test/custom_http_client.dart b/packages/gotrue/test/custom_http_client.dart index e02015b1f..837a81ae5 100644 --- a/packages/gotrue/test/custom_http_client.dart +++ b/packages/gotrue/test/custom_http_client.dart @@ -78,8 +78,9 @@ class RetryTestHttpClient extends BaseClient { final jwt = JWT( { 'exp': (DateTime.now().millisecondsSinceEpoch / 1000).round() + 60, - 'retry_count': - retryCount, // Add retryCount so that tokens issued on different retries are different. + // Add retryCount so that tokens issued on different retries are + // different. + 'retry_count': retryCount, }, subject: userId1, ); diff --git a/packages/gotrue/test/custom_oauth_provider_test.dart b/packages/gotrue/test/custom_oauth_provider_test.dart index 0aad4b2b0..2a7ad75b7 100644 --- a/packages/gotrue/test/custom_oauth_provider_test.dart +++ b/packages/gotrue/test/custom_oauth_provider_test.dart @@ -1,8 +1,9 @@ // Regression test for https://github.com/supabase/supabase-flutter/issues/1337 // -// OAuthProvider was a plain Dart enum, making OAuthProvider('custom:my-provider') -// a compile-time error. It has been converted to a final class so arbitrary -// provider strings are supported, as the docs show. +// OAuthProvider was a plain Dart enum, making +// OAuthProvider('custom:my-provider') a compile-time error. It has been +// converted to a final class so arbitrary provider strings are supported, as +// the docs show. import 'dart:io'; diff --git a/packages/gotrue/test/get_claims_test.dart b/packages/gotrue/test/get_claims_test.dart index bbde0687a..79df2acfb 100644 --- a/packages/gotrue/test/get_claims_test.dart +++ b/packages/gotrue/test/get_claims_test.dart @@ -110,7 +110,8 @@ void main() { test('getClaims() throws with expired JWT', () async { // This is an expired JWT token (exp is in the past) const expiredJwt = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjoxNTE2MjM5MDIyfQ.4Adcj0vVzr2Nzz_KKAKrVZsLZyTBGv9-Ey8SN0p7Kzs'; + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXh' + 'wIjoxNTE2MjM5MDIyfQ.4Adcj0vVzr2Nzz_KKAKrVZsLZyTBGv9-Ey8SN0p7Kzs'; expect( () => client.getClaims(expiredJwt), @@ -121,11 +122,12 @@ void main() { test('getClaims() with allowExpired option allows expired JWT', () async { // This is an expired JWT token (exp is in the past) const expiredJwt = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjoxNTE2MjM5MDIyfQ.4Adcj0vVzr2Nzz_KKAKrVZsLZyTBGv9-Ey8SN0p7Kzs'; + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXh' + 'wIjoxNTE2MjM5MDIyfQ.4Adcj0vVzr2Nzz_KKAKrVZsLZyTBGv9-Ey8SN0p7Kzs'; - // With allowExpired, we should be able to decode the JWT - // Note: This will still fail at getUser() because the token is invalid on the server - // but the expiration check should pass + // With allowExpired, we should be able to decode the JWT Note: This will + // still fail at getUser() because the token is invalid on the server but + // the expiration check should pass try { await client.getClaims( expiredJwt, @@ -235,13 +237,19 @@ void main() { test( 'getClaims() with RS256 JWT on first call should not crash (SDK-627)', () async { - // This test reproduces the bug reported in SDK-627 - // A JWT with RS256 algorithm and kid in header - // Header: {"alg":"RS256","typ":"JWT","kid":"test-key-id"} - // Payload: {"sub":"1234567890","aud":"authenticated","exp":9999999999,"iat":1516239022,"email":"test@example.com","role":"authenticated"} - // Signature: dummy base64url encoded signature (not cryptographically valid, but structurally valid) + // This test reproduces the bug reported in SDK-627: a JWT signed with + // RS256 that carries a `kid` in its header. + // + // The header declares `alg` RS256, `typ` JWT and `kid` test-key-id, + // while the payload carries the usual `sub`, `aud`, `exp`, `iat`, + // `email` and `role` claims. The signature is base64url encoded and + // structurally valid, but not cryptographically valid. const rs256Jwt = - 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InRlc3Qta2V5LWlkIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiYXV0aGVudGljYXRlZCIsImV4cCI6OTk5OTk5OTk5OSwiaWF0IjoxNTE2MjM5MDIyLCJlbWFpbCI6InRlc3RAZXhhbXBsZS5jb20iLCJyb2xlIjoiYXV0aGVudGljYXRlZCJ9.SW52YWxpZFNpZ25hdHVyZURhdGFIZXJlVGhhdElzTm90UmVhbEJ1dFZhbGlkQmFzZTY0VXJs'; + 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InRlc3Qta2V5LWlkIn0.ey' + 'JzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiYXV0aGVudGljYXRlZCIsImV4cCI6OTk5' + 'OTk5OTk5OSwiaWF0IjoxNTE2MjM5MDIyLCJlbWFpbCI6InRlc3RAZXhhbXBsZS5jb2' + '0iLCJyb2xlIjoiYXV0aGVudGljYXRlZCJ9.SW52YWxpZFNpZ25hdHVyZURhdGFIZXJ' + 'lVGhhdElzTm90UmVhbEJ1dFZhbGlkQmFzZTY0VXJs'; // Before the fix, this would crash with: // "Null check operator used on a null value" @@ -252,7 +260,8 @@ void main() { // but NOT crash with null error try { await client.getClaims(rs256Jwt); - // If we get here, the server responded successfully (unlikely in test env) + // If we get here, the server responded successfully (unlikely in test + // env) } catch (error) { // The important part is that it should NOT crash with null error // It may fail with network error, invalid signature, etc. @@ -269,7 +278,9 @@ void main() { test('decodeJwt() successfully decodes valid JWT', () { // A sample JWT with known values final jwt = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InRlc3Qta2lkIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjk5OTk5OTk5OTl9.XyI0rWcOYLpz3R8G8qHWmg7U-tWMHJqzN_e1oDQKzgc'; + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InRlc3Qta2lkIn0.eyJzdWIi' + 'OiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJl' + 'eHAiOjk5OTk5OTk5OTl9.XyI0rWcOYLpz3R8G8qHWmg7U-tWMHJqzN_e1oDQKzgc'; final decoded = decodeJwt(jwt); @@ -312,7 +323,9 @@ void main() { test('decodeJwtPayload() successfully decodes valid JWT', () { final jwt = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InRlc3Qta2lkIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjk5OTk5OTk5OTl9.XyI0rWcOYLpz3R8G8qHWmg7U-tWMHJqzN_e1oDQKzgc'; + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InRlc3Qta2lkIn0.eyJzdWIi' + 'OiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJl' + 'eHAiOjk5OTk5OTk5OTl9.XyI0rWcOYLpz3R8G8qHWmg7U-tWMHJqzN_e1oDQKzgc'; final payload = decodeJwtPayload(jwt); diff --git a/packages/gotrue/test/jwk_test.dart b/packages/gotrue/test/jwk_test.dart index 93c75964f..d8360e13f 100644 --- a/packages/gotrue/test/jwk_test.dart +++ b/packages/gotrue/test/jwk_test.dart @@ -15,7 +15,12 @@ void main() { 'kid': 'rsa-test', 'use': 'sig', 'n': - 't0XB0gQ32Obq7f-L1rZiBTnJvIGfDV4TqGif43rC6Y0hvGFfEPlWnz6M0jbLEK-v0tTXDGbG-EMS3r_bCtm-ZuF4eyfZvWw9DRjQG7D4MPoRmjyKZ8xgpkzgEJLQB7dCuI8xvm1Hh38eiRk1Kb_tSsaZ9Yd7ppibJpcxu_lI_FaKE7RT6CjW8u6nvolrNXlhL_4qPeoy_sRg7uIC7LgOXVwh73-0lq4DVtDMVkJG-WJ0v4ljAzyt_Sl2c7ag1HKhCWxo5HBdp0gzeWnuotOT0zPAwR_5cJuW7VWHjecwfnWbgDXZNb_BMGOnT64dwzClCeh2VcZDYHa0o4w5FHClUw', + 't0XB0gQ32Obq7f-L1rZiBTnJvIGfDV4TqGif43rC6Y0hvGFfEPlWnz6M0jbLEK-v0t' + 'TXDGbG-EMS3r_bCtm-ZuF4eyfZvWw9DRjQG7D4MPoRmjyKZ8xgpkzgEJLQB7dCuI8x' + 'vm1Hh38eiRk1Kb_tSsaZ9Yd7ppibJpcxu_lI_FaKE7RT6CjW8u6nvolrNXlhL_4qPe' + 'oy_sRg7uIC7LgOXVwh73-0lq4DVtDMVkJG-WJ0v4ljAzyt_Sl2c7ag1HKhCWxo5HBd' + 'p0gzeWnuotOT0zPAwR_5cJuW7VWHjecwfnWbgDXZNb_BMGOnT64dwzClCeh2VcZDYH' + 'a0o4w5FHClUw', 'e': 'AQAB', }); diff --git a/packages/gotrue/test/otp_mock_test.dart b/packages/gotrue/test/otp_mock_test.dart index 1b6c722e9..2d5c78ad5 100644 --- a/packages/gotrue/test/otp_mock_test.dart +++ b/packages/gotrue/test/otp_mock_test.dart @@ -169,7 +169,8 @@ void main() { (e) => e.toString(), 'toString()', contains( - 'For recovery type with tokenHash, only tokenHash and type should be provided', + 'For recovery type with tokenHash, only tokenHash and type ' + 'should be provided', ), ), ), @@ -616,26 +617,29 @@ void main() { ); }); - test('response with null session returns the intermediate response', () async { - final client = GoTrueClient( - url: 'https://example.com', - httpClient: NullSessionClient(), - asyncStorage: TestAsyncStorage(), - ); + test( + 'response with null session returns the intermediate response', + () async { + final client = GoTrueClient( + url: 'https://example.com', + httpClient: NullSessionClient(), + asyncStorage: TestAsyncStorage(), + ); - // Verifying the first OTP of a secure email change returns a `{msg, code}` - // payload with neither a user nor a session. This should not throw, the - // intermediate response is returned so the second OTP can subsequently be - // verified. - final response = await client.verifyOTP( - email: testEmail, - token: '123456', - type: OtpType.emailChange, - ); + // Verifying the first OTP of a secure email change returns a `{msg, + // code}` payload with neither a user nor a session. This should not + // throw, the intermediate response is returned so the second OTP can + // subsequently be verified. + final response = await client.verifyOTP( + email: testEmail, + token: '123456', + type: OtpType.emailChange, + ); - expect(response.session, isNull); - expect(response.user, isNull); - }); + expect(response.session, isNull); + expect(response.user, isNull); + }, + ); }); group('Channel Types Tests', () { diff --git a/packages/gotrue/test/refresh_token_race_test.dart b/packages/gotrue/test/refresh_token_race_test.dart index 8a194b633..c566bfab3 100644 --- a/packages/gotrue/test/refresh_token_race_test.dart +++ b/packages/gotrue/test/refresh_token_race_test.dart @@ -11,9 +11,11 @@ import 'utils.dart'; /// HTTP client that simulates server-side refresh token consumption. /// /// - First use of a refresh token succeeds and returns new tokens -/// - Second use of the SAME refresh token returns 400 "refresh_token_already_used" +/// - Second use of the SAME refresh token returns 400 +/// "refresh_token_already_used" /// -/// This simulates real GoTrue server behavior where refresh tokens are single-use. +/// This simulates real GoTrue server behavior where refresh tokens are +/// single-use. class RefreshTokenTrackingHttpClient extends BaseClient { final Set _usedRefreshTokens = {}; final List requestLog = []; @@ -162,7 +164,14 @@ String createExpiredSessionForUser1() { ), ); final accessToken = 'any.$accessTokenMid.any'; - return '{"access_token":"$accessToken","expires_in":-3600,"refresh_token":"-yeS4omysFs9tpUYBws9Rg","token_type":"bearer","provider_token":null,"provider_refresh_token":null,"user":{"id":"$userId1","app_metadata":{"provider":"email","providers":["email"]},"user_metadata":{},"aud":"","email":"test@example.com","phone":"","created_at":"2023-04-01T08:35:05.208586Z","confirmed_at":null,"email_confirmed_at":"2023-04-01T08:35:05.220096086Z","phone_confirmed_at":null,"last_sign_in_at":"2023-04-01T08:35:05.222755878Z","role":"","updated_at":"2023-04-01T08:35:05.226938Z"}}'; + return '{"access_token":"$accessToken","expires_in":-3600,"refresh_token":"-y' + 'eS4omysFs9tpUYBws9Rg","token_type":"bearer","provider_token":null,"provi' + 'der_refresh_token":null,"user":{"id":"$userId1","app_metadata":{"provide' + 'r":"email","providers":["email"]},"user_metadata":{},"aud":"","email":"t' + 'est@example.com","phone":"","created_at":"2023-04-01T08:35:05.208586Z","' + 'confirmed_at":null,"email_confirmed_at":"2023-04-01T08:35:05.220096086Z"' + ',"phone_confirmed_at":null,"last_sign_in_at":"2023-04-01T08:35:05.222755' + '878Z","role":"","updated_at":"2023-04-01T08:35:05.226938Z"}}'; } void main() { @@ -205,7 +214,8 @@ void main() { ); test( - 'FIXED: sequential recoverSession calls with same user return current session', + 'FIXED: sequential recoverSession calls with same user return current ' + 'session', () async { final httpClient = RefreshTokenTrackingHttpClient(); final client = GoTrueClient( @@ -233,12 +243,14 @@ void main() { expect(result2.session, isNotNull); // Should return the CURRENT valid session expect(result2.session?.refreshToken, newRefreshToken); - // Should NOT have made another HTTP request (early return in recoverSession) + // Should NOT have made another HTTP request (early return in + // recoverSession) expect( httpClient.requestCount, 1, reason: - 'Should not make request if session already refreshed for same user', + 'Should not make request if session already refreshed for same ' + 'user', ); }, ); @@ -267,7 +279,8 @@ void main() { // Give time for the request to start await Future.delayed(Duration(milliseconds: 10)); - // Now start auto-refresh tick (simulates didChangeAppLifecycleState(resumed)) + // Now start auto-refresh tick (simulates + // didChangeAppLifecycleState(resumed)) client.startAutoRefresh(); // Release the held request @@ -327,7 +340,8 @@ void main() { ); test( - 'FIXED: "refresh_token_already_used" error is handled gracefully when session is valid', + 'FIXED: "refresh_token_already_used" error is handled gracefully when ' + 'session is valid', () async { final httpClient = RefreshTokenTrackingHttpClient(); @@ -356,8 +370,9 @@ void main() { expect(client.currentSession?.isExpired, isFalse); // 4. Manually mark the current token as "already used" on the server - // This simulates a race condition where another request (e.g., auto-refresh) - // already consumed the token before our next refresh attempt + // This simulates a race condition where another request (e.g., + // auto-refresh) already consumed the token before our next refresh + // attempt httpClient.markTokenAsUsed(newToken!); // 5. Attempt refresh - this will get "already_used" error from server @@ -365,7 +380,8 @@ void main() { final response = await client.refreshSession(); expect(response.session, isNotNull); - // Session should still be valid (the error handler returned current session) + // Session should still be valid (the error handler returned current + // session) expect(client.currentSession, isNotNull); expect(client.currentSession?.isExpired, isFalse); }, @@ -399,7 +415,8 @@ void main() { onError: (_) {}, // Ignore stream errors ); - // Second call with stale token (same user) - should return current session + // Second call with stale token (same user) - should return current + // session final result2 = await client.recoverSession(expiredSession); // Should succeed @@ -423,7 +440,8 @@ void main() { ); test( - 'FIXED: concurrent recoverSession and autoRefreshTick both succeed with same result', + 'FIXED: concurrent recoverSession and autoRefreshTick both succeed with ' + 'same result', () async { final httpClient = RefreshTokenTrackingHttpClient( responseDelay: Duration(milliseconds: 50), @@ -468,7 +486,8 @@ void main() { ); test( - 'FIXED: recoverSession returns current session for same user when already valid', + 'FIXED: recoverSession returns current session for same user when ' + 'already valid', () async { final httpClient = RefreshTokenTrackingHttpClient(); final client = GoTrueClient( @@ -494,7 +513,8 @@ void main() { httpClient.requestCount, 1, reason: - 'Should not attempt refresh when current session is valid for same user', + 'Should not attempt refresh when current session is valid for ' + 'same user', ); }, ); @@ -580,7 +600,8 @@ void main() { 'recoverSession stays in the stack trace when the refresh fails', () async { final httpClient = RefreshTokenTrackingHttpClient(); - // Force the refresh to fail by pre-consuming the persisted refresh token. + // Force the refresh to fail by pre-consuming the persisted refresh + // token. httpClient.markTokenAsUsed('-yeS4omysFs9tpUYBws9Rg'); final client = GoTrueClient( url: gotrueUrl, @@ -606,31 +627,34 @@ void main() { }, ); - test('recoverSession emits a single error for an expired session', () async { - final client = GoTrueClient( - url: gotrueUrl, - asyncStorage: TestAsyncStorage(), - autoRefreshToken: false, - httpClient: RefreshTokenTrackingHttpClient(), - ); + test( + 'recoverSession emits a single error for an expired session', + () async { + final client = GoTrueClient( + url: gotrueUrl, + asyncStorage: TestAsyncStorage(), + autoRefreshToken: false, + httpClient: RefreshTokenTrackingHttpClient(), + ); - var errorCount = 0; - final subscription = client.onAuthStateChange.listen( - (_) {}, - onError: (_) => errorCount++, - ); + var errorCount = 0; + final subscription = client.onAuthStateChange.listen( + (_) {}, + onError: (_) => errorCount++, + ); - await expectLater( - client.recoverSession(createExpiredSessionForUser1()), - throwsA(isA()), - ); - await pumpEventQueue(); + await expectLater( + client.recoverSession(createExpiredSessionForUser1()), + throwsA(isA()), + ); + await pumpEventQueue(); - // The error must reach the stream exactly once, not be re-notified by the - // surrounding catch on top of the explicit notification. - expect(errorCount, 1); + // The error must reach the stream exactly once, not be re-notified by + // the surrounding catch on top of the explicit notification. + expect(errorCount, 1); - await subscription.cancel(); - }); + await subscription.cancel(); + }, + ); }); } diff --git a/packages/gotrue/test/src/set_session_test.dart b/packages/gotrue/test/src/set_session_test.dart index 38342cb78..c4ffc8441 100644 --- a/packages/gotrue/test/src/set_session_test.dart +++ b/packages/gotrue/test/src/set_session_test.dart @@ -129,7 +129,8 @@ void main() { ); expect(response.session, isNotNull); - // The returned token must be the freshly refreshed one, not our near-expired JWT. + // The returned token must be the freshly refreshed one, not our + // near-expired JWT. expect(response.session?.accessToken, isNot(equals(accessToken))); expect(mockClient.userCallCount, 0); // /user was NOT called }); diff --git a/packages/gotrue/test/utils.dart b/packages/gotrue/test/utils.dart index 5dfaac4bb..2775c393f 100644 --- a/packages/gotrue/test/utils.dart +++ b/packages/gotrue/test/utils.dart @@ -68,7 +68,16 @@ const sessionDataUserId = '4d2583da-8de4-49d3-9cd1-37a9a74f55bd'; ); final accessToken = 'any.$accessTokenMid.any'; final sessionString = - '{"access_token":"$accessToken","expires_in":${expireDateTime.difference(DateTime.now()).inSeconds},"refresh_token":"-yeS4omysFs9tpUYBws9Rg","token_type":"bearer","provider_token":null,"provider_refresh_token":null,"user":{"id":"$sessionDataUserId","app_metadata":{"provider":"email","providers":["email"]},"user_metadata":{"Hello":"World"},"aud":"","email":"fake1680338105@email.com","phone":"","created_at":"2023-04-01T08:35:05.208586Z","confirmed_at":null,"email_confirmed_at":"2023-04-01T08:35:05.220096086Z","phone_confirmed_at":null,"last_sign_in_at":"2023-04-01T08:35:05.222755878Z","role":"","updated_at":"2023-04-01T08:35:05.226938Z"}}'; + '{"access_token":"$accessToken","expires_in":' + '${expireDateTime.difference(DateTime.now()).inSeconds},"refresh_token":"' + '-yeS4omysFs9tpUYBws9Rg","token_type":"bearer","provider_token":null,"pro' + 'vider_refresh_token":null,"user":{"id":"$sessionDataUserId","app_metadat' + 'a":{"provider":"email","providers":["email"]},"user_metadata":{"Hello":"' + 'World"},"aud":"","email":"fake1680338105@email.com","phone":"","created_' + 'at":"2023-04-01T08:35:05.208586Z","confirmed_at":null,"email_confirmed_a' + 't":"2023-04-01T08:35:05.220096086Z","phone_confirmed_at":null,"last_sign' + '_in_at":"2023-04-01T08:35:05.222755878Z","role":"","updated_at":"2023-04' + '-01T08:35:05.226938Z"}}'; return (accessToken: accessToken, sessionString: sessionString); } diff --git a/packages/postgrest/lib/src/postgrest.dart b/packages/postgrest/lib/src/postgrest.dart index 7905c331f..fcdb2df12 100644 --- a/packages/postgrest/lib/src/postgrest.dart +++ b/packages/postgrest/lib/src/postgrest.dart @@ -5,7 +5,8 @@ import 'package:postgrest/postgrest.dart'; import 'package:postgrest/src/constants.dart'; import 'package:yet_another_json_isolate/yet_another_json_isolate.dart'; -/// A PostgREST api client written in Dartlang. The goal of this library is to make an "ORM-like" restful interface. +/// A PostgREST api client written in Dartlang. The goal of this library is to +/// make an "ORM-like" restful interface. class PostgrestClient { /// HTTP status codes that trigger an automatic retry by default. static const Set defaultRetryableStatusCodes = {503, 520}; @@ -33,11 +34,13 @@ class PostgrestClient { /// /// [httpClient] is optional and can be used to provide a custom http client /// - /// [isolate] is optional and can be used to provide a custom isolate, which is used for heavy json computation + /// [isolate] is optional and can be used to provide a custom isolate, which + /// is used for heavy json computation /// - /// [retryEnabled] controls whether automatic retries are performed for GET and - /// HEAD requests that fail with a retryable status code or a network error. - /// Defaults to `true`. Use [PostgrestBuilder.retry] to override this per request. + /// [retryEnabled] controls whether automatic retries are performed for GET + /// and HEAD requests that fail with a retryable status code or a network + /// error. Defaults to `true`. Use [PostgrestBuilder.retry] to override this + /// per request. /// /// [retryCount] is the number of retry attempts made for a retryable request /// before giving up. Defaults to `3`. diff --git a/packages/postgrest/lib/src/postgrest_builder.dart b/packages/postgrest/lib/src/postgrest_builder.dart index 7b689447b..6fb010f27 100644 --- a/packages/postgrest/lib/src/postgrest_builder.dart +++ b/packages/postgrest/lib/src/postgrest_builder.dart @@ -725,8 +725,9 @@ class PostgrestBuilder implements Future { throw ArgumentError.value( onError, "onError", - "Error handler must accept one Object or one Object and a StackTrace " - "as arguments, and return a value of the returned future's type", + "Error handler must accept one Object or one Object and a " + "StackTrace as arguments, and return a value of the returned " + "future's type", ); } try { diff --git a/packages/postgrest/lib/src/postgrest_filter_builder.dart b/packages/postgrest/lib/src/postgrest_filter_builder.dart index 6c922b0a5..352a43732 100644 --- a/packages/postgrest/lib/src/postgrest_filter_builder.dart +++ b/packages/postgrest/lib/src/postgrest_filter_builder.dart @@ -49,7 +49,8 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { return copyWithUrl(url); } - /// Finds all rows whose value on the stated [column] exactly matches the specified [value]. + /// Finds all rows whose value on the stated [column] exactly matches the + /// specified [value]. /// /// ```dart /// await supabase @@ -69,7 +70,8 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { return copyWithUrl(url); } - /// Finds all rows whose value on the stated [column] doesn't match the specified [value]. + /// Finds all rows whose value on the stated [column] doesn't match the + /// specified [value]. /// /// ```dart /// await supabase @@ -87,7 +89,8 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { return copyWithUrl(url); } - /// Finds all rows whose value on the stated [column] is greater than the specified [value]. + /// Finds all rows whose value on the stated [column] is greater than the + /// specified [value]. /// /// ```dart /// await supabase @@ -99,7 +102,8 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { return copyWithUrl(appendSearchParams(column, 'gt.$value')); } - /// Finds all rows whose value on the stated [column] is greater than or equal to the specified [value]. + /// Finds all rows whose value on the stated [column] is greater than or equal + /// to the specified [value]. /// /// ```dart /// await supabase @@ -111,7 +115,8 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { return copyWithUrl(appendSearchParams(column, 'gte.$value')); } - /// Finds all rows whose value on the stated [column] is less than the specified [value]. + /// Finds all rows whose value on the stated [column] is less than the + /// specified [value]. /// /// ```dart /// await supabase @@ -123,7 +128,8 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { return copyWithUrl(appendSearchParams(column, 'lt.$value')); } - /// Finds all rows whose value on the stated [column] is less than or equal to the specified [value]. + /// Finds all rows whose value on the stated [column] is less than or equal to + /// the specified [value]. /// /// ```dart /// await supabase @@ -135,7 +141,8 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { return copyWithUrl(appendSearchParams(column, 'lte.$value')); } - /// Finds all rows whose value in the stated [column] matches the supplied [pattern] (case sensitive). + /// Finds all rows whose value in the stated [column] matches the supplied + /// [pattern] (case sensitive). /// /// ```dart /// await supabase @@ -175,7 +182,8 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { ); } - /// Finds all rows whose value in the stated [column] matches the supplied [pattern] (case insensitive). + /// Finds all rows whose value in the stated [column] matches the supplied + /// [pattern] (case insensitive). /// /// ```dart /// await supabase @@ -187,7 +195,8 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { return copyWithUrl(appendSearchParams(column, 'ilike.$pattern')); } - /// Match only rows where [column] matches all of [patterns] case-insensitively. + /// Match only rows where [column] matches all of [patterns] + /// case-insensitively. /// /// ```dart /// await supabase @@ -201,7 +210,8 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { ); } - /// Match only rows where [column] matches any of [patterns] case-insensitively. + /// Match only rows where [column] matches any of [patterns] + /// case-insensitively. /// /// ```dart /// await supabase @@ -217,7 +227,8 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { /// A check for exact equality (null, true, false) /// - /// Finds all rows whose value on the stated [column] exactly match the specified [value]. + /// Finds all rows whose value on the stated [column] exactly match the + /// specified [value]. /// ```dart /// await supabase /// .from('users') @@ -228,7 +239,8 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { return copyWithUrl(appendSearchParams(column, 'is.$value')); } - /// Finds all rows whose value on the stated [column] is found on the specified [values]. + /// Finds all rows whose value on the stated [column] is found on the + /// specified [values]. /// /// ```dart /// await supabase @@ -242,7 +254,8 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { ); } - /// Finds all rows whose json, array, or range value on the stated [column] contains the values specified in [value]. + /// Finds all rows whose json, array, or range value on the stated [column] + /// contains the values specified in [value]. /// /// Pass an array or use brackets in a string for an inclusive range and /// use parenthesis in a string for an exclusive range: @@ -283,7 +296,8 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { return copyWithUrl(url); } - /// Finds all rows whose json, array, or range value on the stated [column] is contained by the specified [value]. + /// Finds all rows whose json, array, or range value on the stated [column] is + /// contained by the specified [value]. /// /// Pass an array or use brackets in a string for an inclusive range and /// use parenthesis in a string for an exclusive range @@ -324,7 +338,8 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { return copyWithUrl(url); } - /// Finds all rows whose range value on the stated [column] is strictly to the left of the specified [range]. + /// Finds all rows whose range value on the stated [column] is strictly to the + /// left of the specified [range]. /// /// ```dart /// await supabase @@ -336,7 +351,8 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { return copyWithUrl(appendSearchParams(column, 'sl.$range')); } - /// Finds all rows whose range value on the stated [column] is strictly to the right of the specified [range]. + /// Finds all rows whose range value on the stated [column] is strictly to the + /// right of the specified [range]. /// /// ```dart /// await supabase @@ -348,7 +364,8 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { return copyWithUrl(appendSearchParams(column, 'sr.$range')); } - /// Finds all rows whose range value on the stated [column] does not extend to the left of the specified [range]. + /// Finds all rows whose range value on the stated [column] does not extend to + /// the left of the specified [range]. /// /// ```dart /// await supabase @@ -360,7 +377,8 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { return copyWithUrl(appendSearchParams(column, 'nxl.$range')); } - /// Finds all rows whose range value on the stated [column] does not extend to the right of the specified [range]. + /// Finds all rows whose range value on the stated [column] does not extend to + /// the right of the specified [range]. /// /// ```dart /// await supabase @@ -372,7 +390,8 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { return copyWithUrl(appendSearchParams(column, 'nxr.$range')); } - /// Finds all rows whose range value on the stated [column] is adjacent to the specified [range]. + /// Finds all rows whose range value on the stated [column] is adjacent to the + /// specified [range]. /// /// ```dart /// await supabase @@ -384,7 +403,8 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { return copyWithUrl(appendSearchParams(column, 'adj.$range')); } - /// Finds all rows whose array or range value on the stated [column] overlaps (has a value in common) with the specified [value]. + /// Finds all rows whose array or range value on the stated [column] overlaps + /// (has a value in common) with the specified [value]. /// /// ```dart /// await supabase @@ -405,7 +425,8 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { return copyWithUrl(url); } - /// Finds all rows whose text or tsvector value on the stated [column] matches the tsquery in [query]. + /// Finds all rows whose text or tsvector value on the stated [column] matches + /// the tsquery in [query]. /// /// ```dart /// await supabase @@ -484,7 +505,8 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { return copyWithUrl(url); } - /// Finds all rows whose value in the stated [column] matches the supplied [pattern] using PostgreSQL regular expression (case sensitive). + /// Finds all rows whose value in the stated [column] matches the supplied + /// [pattern] using PostgreSQL regular expression (case sensitive). /// /// ```dart /// await supabase @@ -496,7 +518,8 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { return copyWithUrl(appendSearchParams(column, 'match.$pattern')); } - /// Finds all rows whose value in the stated [column] matches the supplied [pattern] using PostgreSQL regular expression (case insensitive). + /// Finds all rows whose value in the stated [column] matches the supplied + /// [pattern] using PostgreSQL regular expression (case insensitive). /// /// ```dart /// await supabase @@ -508,7 +531,8 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { return copyWithUrl(appendSearchParams(column, 'imatch.$pattern')); } - /// Finds all rows whose value on the stated [column] is not equal to the specified [value], treating `NULL` as a comparable value. + /// Finds all rows whose value on the stated [column] is not equal to the + /// specified [value], treating `NULL` as a comparable value. /// /// This is different from [neq] which treats `NULL` specially. /// diff --git a/packages/postgrest/lib/src/postgrest_query_builder.dart b/packages/postgrest/lib/src/postgrest_query_builder.dart index b8019f0f8..d8fae1be1 100644 --- a/packages/postgrest/lib/src/postgrest_query_builder.dart +++ b/packages/postgrest/lib/src/postgrest_query_builder.dart @@ -1,7 +1,8 @@ part of 'postgrest_builder.dart'; /// {@template postgrest_query_builder} -/// The query builder class provides a convenient interface to creating request queries. +/// The query builder class provides a convenient interface to creating request +/// queries. /// /// Allows the user to stack the filter functions before they call any of /// * select() - "get" @@ -53,7 +54,8 @@ class PostgrestQueryBuilder extends RawPostgrestBuilder { /// ```dart /// supabase.from('users').select('id, messages').count(CountOption.exact); /// ``` - /// By appending [count] the return type is [PostgrestResponse]. Otherwise it's the data directly without the wrapper. + /// By appending [count] the return type is [PostgrestResponse]. Otherwise + /// it's the data directly without the wrapper. PostgrestFilterBuilder select([String columns = '*']) { // Remove whitespaces except when quoted var quoted = false; @@ -81,11 +83,13 @@ class PostgrestQueryBuilder extends RawPostgrestBuilder { /// /// By default no data is returned. Use a trailing [select] to return data. /// - /// When inserting multiple rows in bulk, [defaultToNull] is used to set the values of fields missing in a proper subset of rows - /// to be either `NULL` or the default value of these columns. - /// Fields missing in all rows always use the default value of these columns. + /// When inserting multiple rows in bulk, [defaultToNull] is used to set the + /// values of fields missing in a proper subset of rows to be either `NULL` or + /// the default value of these columns. Fields missing in all rows always use + /// the default value of these columns. /// - /// For single row insertions, missing fields will be set to default values when applicable. + /// For single row insertions, missing fields will be set to default values + /// when applicable. /// /// Default (not returning data): /// ```dart @@ -129,16 +133,19 @@ class PostgrestQueryBuilder extends RawPostgrestBuilder { /// Perform an UPSERT on the table or view. /// - /// By specifying the [onConflict] parameter, you can make UPSERT work on a column(s) that has a UNIQUE constraint. - /// [ignoreDuplicates] Specifies if duplicate rows should be ignored and not inserted. + /// By specifying the [onConflict] parameter, you can make UPSERT work on a + /// column(s) that has a UNIQUE constraint. [ignoreDuplicates] Specifies if + /// duplicate rows should be ignored and not inserted. /// /// By default no data is returned. Use a trailing `select` to return data. /// - /// When inserting multiple rows in bulk, [defaultToNull] is used to set the values of fields missing in a proper subset of rows - /// to be either `NULL` or the default value of these columns. - /// Fields missing in all rows always use the default value of these columns. + /// When inserting multiple rows in bulk, [defaultToNull] is used to set the + /// values of fields missing in a proper subset of rows to be either `NULL` or + /// the default value of these columns. Fields missing in all rows always use + /// the default value of these columns. /// - /// For single row insertions, missing fields will be set to default values when applicable. + /// For single row insertions, missing fields will be set to default values + /// when applicable. /// /// Default (not returning data): /// ```dart diff --git a/packages/postgrest/lib/src/postgrest_transform_builder.dart b/packages/postgrest/lib/src/postgrest_transform_builder.dart index 9e6c6b2ab..10d0a4f25 100644 --- a/packages/postgrest/lib/src/postgrest_transform_builder.dart +++ b/packages/postgrest/lib/src/postgrest_transform_builder.dart @@ -33,10 +33,15 @@ class PostgrestTransformBuilder extends RawPostgrestBuilder { /// supabase.from('users').insert().select('id, messages'); /// ``` /// ```dart - /// supabase.from('users').insert().select('id, messages').count(CountOption.exact); + /// supabase + /// .from('users') + /// .insert() + /// .select('id, messages') + /// .count(CountOption.exact); /// ``` /// - /// By appending [count] the return type is [PostgrestResponse]. Otherwise it's the data directly without the wrapper. + /// By appending [count] the return type is [PostgrestResponse]. Otherwise + /// it's the data directly without the wrapper. PostgrestTransformBuilder select([String columns = '*']) { // Remove whitespaces except when quoted var quoted = false; @@ -108,8 +113,9 @@ class PostgrestTransformBuilder extends RawPostgrestBuilder { final key = referencedTable == null ? 'order' : '$referencedTable.order'; final existingOrder = _url.queryParameters[key]; final value = - '${existingOrder == null ? '' : '$existingOrder,'}' - '$column.${ascending ? 'asc' : 'desc'}.${nullsFirst ? 'nullsfirst' : 'nullslast'}'; + '${existingOrder == null ? '' : '$existingOrder,'}$column.' + '${ascending ? 'asc' : 'desc'}.' + '${nullsFirst ? 'nullsfirst' : 'nullslast'}'; final url = overrideSearchParams(key, value); return PostgrestTransformBuilder(copyWithUrl(url)); } @@ -171,7 +177,8 @@ class PostgrestTransformBuilder extends RawPostgrestBuilder { /// Retrieves only one row from the result. /// - /// Result must be one row (e.g. using `limit`), otherwise this will result in an error. + /// Result must be one row (e.g. using `limit`), otherwise this will result in + /// an error. /// ```dart /// final data = await supabase /// .from('users') @@ -271,7 +278,8 @@ class PostgrestTransformBuilder extends RawPostgrestBuilder { /// query. The value for count respects any filters (e.g. eq, gt), but ignores /// modifiers (e.g. limit, range). /// - /// This changes the return type from the data only to a [PostgrestResponse] with the data and the count. + /// This changes the return type from the data only to a [PostgrestResponse] + /// with the data and the count. /// /// ```dart /// final res = await postgrest @@ -322,11 +330,15 @@ class PostgrestTransformBuilder extends RawPostgrestBuilder { /// Sets the maximum number of rows that can be affected by the query. /// - /// Only available with PATCH and DELETE operations. Requires PostgREST v13 or higher. - /// When the limit is exceeded, the query will fail with an error. + /// Only available with PATCH and DELETE operations. Requires PostgREST v13 or + /// higher. When the limit is exceeded, the query will fail with an error. /// /// ```dart - /// supabase.from('users').update({'active': false}).eq('status', 'inactive').maxAffected(5); + /// supabase + /// .from('users') + /// .update({'active': false}) + /// .eq('status', 'inactive') + /// .maxAffected(5); /// ``` /// /// ```dart @@ -363,11 +375,14 @@ class PostgrestTransformBuilder extends RawPostgrestBuilder { /// /// https://supabase.com/docs/guides/api/rest/debugging-performance#enabling-explain /// - /// [analyze] If `true`, the query will be executed and the actual run time will be displayed. + /// [analyze] If `true`, the query will be executed and the actual run time + /// will be displayed. /// - /// [verbose] If `true`, the query identifier will be displayed and the result will include the output columns of the query. + /// [verbose] If `true`, the query identifier will be displayed and the result + /// will include the output columns of the query. /// - /// [settings] If `true`, include information on configuration parameters that affect query planning. + /// [settings] If `true`, include information on configuration parameters that + /// affect query planning. /// /// [buffers] If `true`, include information on buffer usage. /// @@ -392,7 +407,8 @@ class PostgrestTransformBuilder extends RawPostgrestBuilder { if (wal) 'wal', ].join('|'); - // An Accept header can carry multiple media types but postgrest-js always sends one + // An Accept header can carry multiple media types but postgrest-js always + // sends one final forMediatype = _headers['Accept'] ?? 'application/json'; final newHeaders = {..._headers}; newHeaders['Accept'] = diff --git a/packages/postgrest/lib/src/raw_postgrest_builder.dart b/packages/postgrest/lib/src/raw_postgrest_builder.dart index 8d64003ca..d8a568a70 100644 --- a/packages/postgrest/lib/src/raw_postgrest_builder.dart +++ b/packages/postgrest/lib/src/raw_postgrest_builder.dart @@ -1,11 +1,14 @@ part of 'postgrest_builder.dart'; -/// Needed as a wrapper around [PostgrestBuilder] to allow for the different return type of [withConverter] than in [ResponsePostgrestBuilder.withConverter]. +/// Needed as a wrapper around [PostgrestBuilder] to allow for the different +/// return type of [withConverter] than in +/// [ResponsePostgrestBuilder.withConverter]. class RawPostgrestBuilder extends PostgrestBuilder { RawPostgrestBuilder(PostgrestBuilder builder) : super._(config: builder._config, converter: builder._converter); - /// Very similar to [_copyWith], but allows changing the generics, therefore [_converter] is omitted + /// Very similar to [_copyWith], but allows changing the generics, therefore + /// [_converter] is omitted RawPostgrestBuilder _copyWithType({ Uri? url, // ignore: avoid-unnecessary-nullable-parameters @@ -43,7 +46,8 @@ class RawPostgrestBuilder extends PostgrestBuilder { ); } - /// Converts any response that comes from the server into a type-safe response. + /// Converts any response that comes from the server into a type-safe + /// response. /// /// ```dart /// List users = await postgrest diff --git a/packages/postgrest/lib/src/response_postgrest_builder.dart b/packages/postgrest/lib/src/response_postgrest_builder.dart index 88deaa18f..17b09857b 100644 --- a/packages/postgrest/lib/src/response_postgrest_builder.dart +++ b/packages/postgrest/lib/src/response_postgrest_builder.dart @@ -1,6 +1,7 @@ part of 'postgrest_builder.dart'; -/// Needed as a wrapper around [PostgrestBuilder] to allow for the different return type of [withConverter] than in [RawPostgrestBuilder.withConverter]. +/// Needed as a wrapper around [PostgrestBuilder] to allow for the different +/// return type of [withConverter] than in [RawPostgrestBuilder.withConverter]. class ResponsePostgrestBuilder extends PostgrestBuilder { ResponsePostgrestBuilder(PostgrestBuilder builder) : super._(config: builder._config, converter: builder._converter); @@ -12,7 +13,8 @@ class ResponsePostgrestBuilder extends PostgrestBuilder { ); } - /// Converts any response that comes from the server into a type-safe response. + /// Converts any response that comes from the server into a type-safe + /// response. /// /// ```dart /// final res = await postgrest diff --git a/packages/postgrest/lib/src/types.dart b/packages/postgrest/lib/src/types.dart index f91c93e40..96aa538b6 100644 --- a/packages/postgrest/lib/src/types.dart +++ b/packages/postgrest/lib/src/types.dart @@ -44,7 +44,8 @@ class PostgrestException implements Exception { @override String toString() { - return 'PostgrestException(message: $message, code: $code, details: $details, hint: $hint)'; + return 'PostgrestException(message: $message, code: $code, details: ' + '$details, hint: $hint)'; } } @@ -89,7 +90,8 @@ enum CountOption { /// Exact but slow count algorithm. Performs a `COUNT(*)` under the hood. exact, - /// Approximated but fast count algorithm. Uses the Postgres statistics under the hood. + /// Approximated but fast count algorithm. Uses the Postgres statistics under + /// the hood. planned, /// Uses exact count for low numbers and planned count for high numbers. @@ -119,7 +121,8 @@ enum TextSearchType { /// Uses PostgreSQL's phraseto_tsquery function. phrase, - /// Uses PostgreSQL's websearch_to_tsquery function. - /// This function will never raise syntax errors, which makes it possible to use raw user-supplied input for search, and can be used with advanced operators. + /// Uses PostgreSQL's websearch_to_tsquery function. This function will never + /// raise syntax errors, which makes it possible to use raw user-supplied + /// input for search, and can be used with advanced operators. websearch, } diff --git a/packages/postgrest/test/reset_helper.dart b/packages/postgrest/test/reset_helper.dart index 30626f215..345a869bc 100644 --- a/packages/postgrest/test/reset_helper.dart +++ b/packages/postgrest/test/reset_helper.dart @@ -32,7 +32,8 @@ class ResetHelper { final insertedUsers = await _postgrest.from('users').select(); - // Somehow the order of the users is sometimes not correct. Adding the delay should solve this. + // Somehow the order of the users is sometimes not correct. Adding the + // delay should solve this. if (!DeepCollectionEquality().equals(insertedUsers, _users)) { return await reset(delay + 500); } diff --git a/packages/postgrest/test/retry_test.dart b/packages/postgrest/test/retry_test.dart index 25649a559..ab2be004f 100644 --- a/packages/postgrest/test/retry_test.dart +++ b/packages/postgrest/test/retry_test.dart @@ -472,7 +472,8 @@ void main() { test('.retry(requestTimeout:) overrides the timeout per request', () async { // The client has no timeout, but the per-request override adds one that - // is shorter than every attempt, so each attempt times out and is retried. + // is shorter than every attempt, so each attempt times out and is + // retried. final mock = _MockRetryClient([_ok(), _ok()]); final client = PostgrestClient( 'http://localhost:3000', diff --git a/packages/postgrest/test/stack_trace_test.dart b/packages/postgrest/test/stack_trace_test.dart index 9b84fc34f..c5f64eda5 100644 --- a/packages/postgrest/test/stack_trace_test.dart +++ b/packages/postgrest/test/stack_trace_test.dart @@ -119,7 +119,8 @@ void main() { capturedTrace?.toString(), contains('singleArgCallerFunction'), reason: - 'Outer catch should include the caller frame even with a single-arg onError', + 'Outer catch should include the caller frame even with a ' + 'single-arg onError', ); }, ); diff --git a/packages/postgrest/test/transforms_test.dart b/packages/postgrest/test/transforms_test.dart index fe2abbd39..981babf7d 100644 --- a/packages/postgrest/test/transforms_test.dart +++ b/packages/postgrest/test/transforms_test.dart @@ -409,7 +409,8 @@ void main() { }); test( - 'maybeSingle followed by another transformer preserves the maybeSingle status', + 'maybeSingle followed by another transformer preserves the maybeSingle ' + 'status', () async { await expectLater( () => postgrest.from('channels').select().maybeSingle().limit(2), @@ -550,7 +551,8 @@ void main() { }); test( - 'maxAffected works with select operations (sets headers but likely ineffective)', + 'maxAffected works with select operations (sets headers but likely ' + 'ineffective)', () async { try { await postgrestCustomHttpClient.from('users').select().maxAffected(2); diff --git a/packages/postgrest/test/upsert_test.dart b/packages/postgrest/test/upsert_test.dart index aabc76152..c95dcfffc 100644 --- a/packages/postgrest/test/upsert_test.dart +++ b/packages/postgrest/test/upsert_test.dart @@ -26,7 +26,8 @@ void main() { // Clean up the imported_data table before starting the test await postgrest.from('imported_data').delete().neq('id', 0); - // Test data - 3 rows with unique constraint on external_id + source_system + // Test data - 3 rows with unique constraint on external_id + + // source_system final testData = [ { 'external_id': 'ext_001', @@ -59,7 +60,8 @@ void main() { expect(insertResult[1]['external_id'], 'ext_002'); expect(insertResult[2]['external_id'], 'ext_003'); - // Step 2: UPSERT with first row from test data (without onConflict) - should fail + // Step 2: UPSERT with first row from test data (without onConflict) - + // should fail final duplicateData = [ { 'external_id': 'ext_001', @@ -76,7 +78,8 @@ void main() { ), ); - // Step 3: UPSERT with first row from test data (with onConflict) - should succeed + // Step 3: UPSERT with first row from test data (with onConflict) - should + // succeed final updatedData = [ { 'external_id': 'ext_001', diff --git a/packages/realtime_client/lib/realtime_client.dart b/packages/realtime_client/lib/realtime_client.dart index e8cf0341c..c32a86192 100644 --- a/packages/realtime_client/lib/realtime_client.dart +++ b/packages/realtime_client/lib/realtime_client.dart @@ -1,4 +1,5 @@ -/// Listens to changes in a PostgreSQL database via websockets using Supabase Realtime. +/// Listens to changes in a PostgreSQL database via websockets using Supabase +/// Realtime. library; export 'src/constants.dart' diff --git a/packages/realtime_client/lib/src/realtime_channel.dart b/packages/realtime_client/lib/src/realtime_channel.dart index d33d8aa9b..e6163ad44 100644 --- a/packages/realtime_client/lib/src/realtime_channel.dart +++ b/packages/realtime_client/lib/src/realtime_channel.dart @@ -34,7 +34,8 @@ class RealtimeChannel { @internal final RealtimeClient socket; - /// Defines if the channel is private or not and if RLS policies will be used to check data + /// Defines if the channel is private or not and if RLS policies will be used + /// to check data late final bool _private; RealtimeChannel( @@ -137,7 +138,8 @@ class RealtimeChannel { /// /// Pass a [callback] to react to different status changes. /// - /// [timeout] parameter can be used to override the default timeout set on [RealtimeClient]. + /// [timeout] parameter can be used to override the default timeout set on + /// [RealtimeClient]. RealtimeChannel subscribe([ void Function(RealtimeSubscribeStatus status, Object? error)? callback, Duration? timeout, @@ -146,7 +148,8 @@ class RealtimeChannel { unawaited(socket.connect()); } if (joinedOnce == true) { - throw "tried to subscribe multiple times. 'subscribe' can only be called a single time per channel instance"; + throw "tried to subscribe multiple times. 'subscribe' can only be " + "called a single time per channel instance"; } final broadcast = params['config']['broadcast']; final presenceConfig = params['config']['presence']; @@ -274,10 +277,10 @@ class RealtimeChannel { final filter = clientPostgresBinding.filter['filter']; final serverPostgresFilter = serverPostgresFilters[i]; - // NOTE: `select` is intentionally not part of this equality check (mirroring - // supabase-js), so a server that echoes `select` back in a slightly - // different shape does not force a spurious unsubscribe. The client - // binding keeps its own `select` regardless. + // NOTE: `select` is intentionally not part of this equality check + // (mirroring supabase-js), so a server that echoes `select` back in a + // slightly different shape does not force a spurious unsubscribe. The + // client binding keeps its own `select` regardless. if (serverPostgresFilter != null && serverPostgresFilter['event'] == event && serverPostgresFilter['schema'] == schema && @@ -294,7 +297,8 @@ class RealtimeChannel { callback( RealtimeSubscribeStatus.channelError, Exception( - 'mismatch between server and client bindings for postgres changes', + 'mismatch between server and client bindings for postgres ' + 'changes', ), ); } @@ -356,7 +360,8 @@ class RealtimeChannel { ); } - /// Registers a callback that will be executed when the channel encounters an error. + /// Registers a callback that will be executed when the channel encounters an + /// error. void _onError(Function callback) { onEvents( ChannelEvent.error.eventName(), @@ -367,22 +372,25 @@ class RealtimeChannel { /// Sets up a listener on your Supabase database. /// - /// [event] determines whether you listen to `insert`, `update`, `delete`, or all of the events. + /// [event] determines whether you listen to `insert`, `update`, `delete`, or + /// all of the events. /// /// [schema] is the schema of the database on which to set up the listener. - /// The listener will return all changes from every listenable schema if omitted. + /// The listener will return all changes from every listenable schema if + /// omitted. /// - /// [table] is the table of the database on which to setup the listener. - /// The listener will return all changes from every listenable table if omitted. + /// [table] is the table of the database on which to setup the listener. The + /// listener will return all changes from every listenable table if omitted. /// - /// [filter] can be used to further control which rows to listen to within the given [schema] and [table]. + /// [filter] can be used to further control which rows to listen to within the + /// given [schema] and [table]. /// /// [filters] combines multiple [PostgresChangeFilter]s with an `AND`. Provide /// either [filter] or [filters], not both. /// - /// [select] restricts the change payload to a subset of columns instead of the - /// full row (reducing payload size). The listed columns must be selectable by - /// the subscribing role. + /// [select] restricts the change payload to a subset of columns instead of + /// the full row (reducing payload size). The listed columns must be + /// selectable by the subscribing role. /// /// ```dart /// supabase.channel('my_channel').onPostgresChanges( @@ -637,7 +645,8 @@ class RealtimeChannel { Duration? timeout, ]) { if (!joinedOnce) { - throw "tried to push '${event.eventName()}' to '$topic' before joining. Use channel.subscribe() before pushing events"; + throw "tried to push '${event.eventName()}' to '$topic' before joining. " + "Use channel.subscribe() before pushing events"; } final pushEvent = Push(this, event, payload, timeout ?? _timeout); if (canPush) { @@ -652,8 +661,9 @@ class RealtimeChannel { /// Sends a broadcast message explicitly via REST API. /// - /// This method always uses the REST API endpoint regardless of WebSocket connection state. - /// Useful when you want to guarantee REST delivery or when gradually migrating from implicit REST fallback. + /// This method always uses the REST API endpoint regardless of WebSocket + /// connection state. Useful when you want to guarantee REST delivery or when + /// gradually migrating from implicit REST fallback. /// /// [payload] must be either a `Map`, which is JSON-encoded, /// or binary data ([TypedData] such as [Uint8List], or [ByteBuffer]), which @@ -700,10 +710,8 @@ class RealtimeChannel { }; final url = Uri.parse( - '$broadcastEndpointURL' - '/${Uri.encodeComponent(subTopic)}' - '/events/${Uri.encodeComponent(event)}' - '${_private ? '?private=true' : ''}', + '$broadcastEndpointURL/${Uri.encodeComponent(subTopic)}/events/' + '${Uri.encodeComponent(event)}${_private ? '?private=true' : ''}', ); final body = isBinary ? _asBytes(payload) : json.encode(payload); @@ -725,8 +733,8 @@ class RealtimeChannel { if (response.statusCode == 404) { throw Exception( 'httpSend() requires Realtime server v2.97.0 or newer; the endpoint ' - 'returned 404. Update your Supabase CLI to a recent version, or upgrade ' - 'the Realtime server in your self-hosted setup.', + 'returned 404. Update your Supabase CLI to a recent version, or ' + 'upgrade the Realtime server in your self-hosted setup.', ); } @@ -793,9 +801,9 @@ class RealtimeChannel { if (!canPush && type == RealtimeListenType.broadcast) { socket.log( 'channel', - 'send() is automatically falling back to REST API. ' - 'This behavior will be deprecated in the future. ' - 'Please use httpSend() explicitly for REST delivery.', + 'send() is automatically falling back to REST API. This behavior will ' + 'be deprecated in the future. Please use httpSend() explicitly ' + 'for REST delivery.', ); try { @@ -877,10 +885,11 @@ class RealtimeChannel { /// Leaves the channel /// - /// Unsubscribes from server events, and instructs channel to terminate on server. - /// Triggers onClose() hooks. + /// Unsubscribes from server events, and instructs channel to terminate on + /// server. Triggers onClose() hooks. /// - /// To receive leave acknowledgements, use the a `receive` hook to bind to the server ack, + /// To receive leave acknowledgements, use a `receive` hook to bind to the + /// server ack, /// ```dart /// channel.unsubscribe().receive("ok", (_){print("left!");} ); /// ``` @@ -929,8 +938,8 @@ class RealtimeChannel { /// Overridable message hook /// - /// Receives all events for specialized message handling before dispatching to the channel callbacks. - /// Must return the payload, modified or unmodified. + /// Receives all events for specialized message handling before dispatching to + /// the channel callbacks. Must return the payload, modified or unmodified. @internal dynamic onMessage(String event, dynamic payload, [String? ref]) { return payload; @@ -987,7 +996,8 @@ class RealtimeChannel { var handledPayload = onMessage(typeLower, payload, ref); if (payload != null && handledPayload == null) { - throw 'channel onMessage callbacks must return the payload, modified or unmodified'; + throw 'channel onMessage callbacks must return the payload, modified or ' + 'unmodified'; } if (['insert', 'update', 'delete'].contains(typeLower)) { diff --git a/packages/realtime_client/lib/src/realtime_client.dart b/packages/realtime_client/lib/src/realtime_client.dart index fb9b7e337..894b439dc 100644 --- a/packages/realtime_client/lib/src/realtime_client.dart +++ b/packages/realtime_client/lib/src/realtime_client.dart @@ -53,7 +53,8 @@ class RealtimeCloseEvent { } } -/// The lifecycle status of a heartbeat reported to [RealtimeClient.onHeartbeat]. +/// The lifecycle status of a heartbeat reported to +/// [RealtimeClient.onHeartbeat]. enum RealtimeHeartbeatStatus { sent, ok, @@ -64,9 +65,9 @@ enum RealtimeHeartbeatStatus { /// Manages a persistent WebSocket connection to the Supabase Realtime server. /// /// [RealtimeClient] is the central hub for all real-time communication. It owns -/// the WebSocket lifecycle — opening, closing, and reconnecting with exponential -/// backoff — and multiplexes multiple [RealtimeChannel] subscriptions over a -/// single connection. +/// the WebSocket lifecycle — opening, closing, and reconnecting with +/// exponential backoff — and multiplexes multiple [RealtimeChannel] +/// subscriptions over a single connection. /// /// **Responsibilities:** /// - Establishes and maintains the WebSocket connection to [endPoint]. @@ -160,7 +161,8 @@ class RealtimeClient { /// Initializes the Socket /// - /// [endPoint] The string WebSocket endpoint, ie, "ws://example.com/socket", "wss://example.com", "/socket" (inherited host & protocol + /// [endPoint] The string WebSocket endpoint, ie, "ws://example.com/socket", + /// "wss://example.com", or "/socket" (which inherits the host and protocol). /// /// [transport] The Websocket Transport, for example WebSocket. /// @@ -181,7 +183,11 @@ class RealtimeClient { /// reused. Pass [Duration.zero] to disconnect immediately. Defaults to twice /// the heartbeat interval. /// - /// [logger] The optional function for specialized logging, ie: logger: (kind, message, data) => { console.log(`$kind: $message`, data) } + /// [logger] The optional function for specialized logging, ie: + /// + /// ```dart + /// logger: (kind, message, data) => print('$kind: $message $data') + /// ``` /// /// [encode] Overrides how outgoing messages are serialized, for example to /// use a faster JSON implementation. Defaults to the codec for [version]. @@ -189,7 +195,9 @@ class RealtimeClient { /// [decode] Overrides how incoming frames are deserialized. Defaults to the /// codec for [version]. /// - /// [reconnectAfterMs] The optional function that returns the millisec reconnect interval. Defaults to stepped backoff off. + /// [reconnectAfterMs] The optional function that returns the millisec + /// reconnect interval. Defaults to the stepped backoff of + /// [RetryTimer.createRetryFunction]. /// /// [logLevel] Specifies the log level for the connection on the server. /// @@ -237,7 +245,8 @@ class RealtimeClient { ? _decodeLegacy : _serializer.decode) { _log.config( - 'Initialize RealtimeClient with endpoint: $endPoint, timeout: $timeout, heartbeatIntervalMs: $heartbeatIntervalMs, logLevel: ${logLevel?.name}', + 'Initialize RealtimeClient with endpoint: $endPoint, timeout: $timeout, ' + 'heartbeatIntervalMs: $heartbeatIntervalMs, logLevel: ${logLevel?.name}', ); _log.finest('Initialize with headers: $headers, params: $params'); final customJWT = this.headers['Authorization']?.split(' ').last; @@ -332,7 +341,8 @@ class RealtimeClient { final shouldCloseSink = oldState == SocketState.open || oldState == SocketState.connecting; if (shouldCloseSink) { - // Don't set the state to `disconnecting` if the connection is already closed. + // Don't set the state to `disconnecting` if the connection is already + // closed. connectionState = SocketState.disconnecting; log('transport', 'disconnecting', { 'code': code, @@ -358,8 +368,9 @@ class RealtimeClient { if (code != null) { // Add a timeout to close the sink to avoid hanging in case something - // is wrong with the connection. - // The Dart SDK has a timeout of 5 seconds for closing the IO WebSocket connection, so we set a timeout of 6 seconds here to avoid hanging indefinitely. + // is wrong with the connection. The Dart SDK has a timeout of 5 + // seconds for closing the IO WebSocket connection, so we set a + // timeout of 6 seconds here to avoid hanging indefinitely. await connection.sink .close(code, reason ?? '') .timeout(connectionCloseTimeout, onTimeout: onTimeout); @@ -373,10 +384,10 @@ class RealtimeClient { log('transport', 'disconnected', null, Level.FINE); } - // Cancel any reconnect scheduled by `_onConnectionClose`. When the socket has - // already dropped (`connectionState == closed`) the block above is skipped, so - // without this an armed backoff timer would fire after the user - // explicitly disconnected and silently reopen the connection. + // Cancel any reconnect scheduled by `_onConnectionClose`. When the socket + // has already dropped (`connectionState == closed`) the block above is + // skipped, so without this an armed backoff timer would fire after the + // user explicitly disconnected and silently reopen the connection. reconnectTimer.cancel(); this.connection = null; @@ -512,7 +523,8 @@ class RealtimeClient { /// Push out a message if the socket is connected. /// - /// If the socket is not connected, the message gets enqueued within a local buffer, and sent out when a connection is next established. + /// If the socket is not connected, the message gets enqueued within a local + /// buffer, and sent out when a connection is next established. // ignore: function-always-returns-null String? push(Message message) { void callback() { @@ -601,7 +613,8 @@ class RealtimeClient { return ref.toString(); } - /// Sets the JWT access token used for channel subscription authorization and Realtime RLS. + /// Sets the JWT access token used for channel subscription authorization and + /// Realtime RLS. /// /// `token` A JWT strings. Future setAuth(String? token) async { diff --git a/packages/realtime_client/lib/src/realtime_presence.dart b/packages/realtime_client/lib/src/realtime_presence.dart index 5a567cdfe..26b2e8f9c 100644 --- a/packages/realtime_client/lib/src/realtime_presence.dart +++ b/packages/realtime_client/lib/src/realtime_presence.dart @@ -73,7 +73,8 @@ class RealtimePresence { /// /// `channel` - The RealtimeChannel /// - /// `opts` - The options, for example `PresenceOpts(events: PresenceEvents(state: 'state', diff: 'diff'))` + /// `opts` - The options, for example `PresenceOpts(events: + /// PresenceEvents(state: 'state', diff: 'diff'))` RealtimePresence(this.channel, [PresenceOpts? opts]) { final events = opts?.events ?? diff --git a/packages/realtime_client/lib/src/retry_timer.dart b/packages/realtime_client/lib/src/retry_timer.dart index 946e092cf..d672d3c41 100644 --- a/packages/realtime_client/lib/src/retry_timer.dart +++ b/packages/realtime_client/lib/src/retry_timer.dart @@ -5,7 +5,8 @@ import 'package:meta/meta.dart'; typedef TimerCallback = void Function(); typedef TimerCalculation = int Function(int tries); -// Need to limit doubling to avoid overflow, this limit gives 1 million times the first delay +// Need to limit doubling to avoid overflow, this limit gives 1 million times +// the first delay const maxShift = 20; /// Creates a timer that accepts a `timerCalc` function to perform @@ -16,7 +17,10 @@ const maxShift = 20; /// return [1000, 5000, 10000][tries - 1] ?? 10000; /// } /// -/// let reconnectTimer = new RetryTimer(() => this.connect(), calculateRetryDuration) +/// final reconnectTimer = RetryTimer( +/// () => connect(), +/// calculateRetryDuration, +/// ); /// /// reconnectTimer.scheduleTimeout() // fires after 1000 /// reconnectTimer.scheduleTimeout() // fires after 5000 diff --git a/packages/realtime_client/lib/src/transformers.dart b/packages/realtime_client/lib/src/transformers.dart index 6abd0544d..39b0e9610 100644 --- a/packages/realtime_client/lib/src/transformers.dart +++ b/packages/realtime_client/lib/src/transformers.dart @@ -1,5 +1,6 @@ // Adapted from epgsql (src/epgsql_binary.erl), this module licensed under -// 3-clause BSD found here: https://raw.githubusercontent.com/epgsql/epgsql/devel/LICENSE +// 3-clause BSD found here: +// https://raw.githubusercontent.com/epgsql/epgsql/devel/LICENSE import 'dart:convert'; @@ -53,15 +54,19 @@ class PostgresColumn { }); } -/// Takes an array of columns and an object of string values then converts each string value -/// to its mapped type. +/// Takes an array of columns and an object of string values then converts each +/// string value to its mapped type. /// /// `columns` All of the columns /// `record` The map of string values /// `skipTypes` The array of types that should not be converted /// /// ```dart -/// convertChangeData([{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], {'first_name': 'Paul', 'age':'33'}, {}) +/// convertChangeData( +/// [{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], +/// {'first_name': 'Paul', 'age':'33'}, +/// {}, +/// ) /// => { 'first_name': 'Paul', 'age': 33 } /// ``` Map convertChangeData( @@ -94,9 +99,19 @@ Map convertChangeData( /// `skipTypes` An array of types that should not be converted /// /// ```dart -/// convertColumn('age', [{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], ['Paul', '33'], []) +/// convertColumn( +/// 'age', +/// [{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], +/// ['Paul', '33'], +/// [], +/// ) /// => 33 -/// convertColumn('age', [{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], ['Paul', '33'], ['int4']) +/// convertColumn( +/// 'age', +/// [{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], +/// ['Paul', '33'], +/// ['int4'], +/// ) /// => "33" /// ``` dynamic convertColumn( @@ -231,7 +246,8 @@ dynamic toJson(dynamic value) { /// Converts a Postgres Array into a native Dart array /// ///``` dart -/// @example toArray('{"[2021-01-01,2021-12-31)","(2021-01-01,2021-12-32]"}', 'daterange') +/// @example toArray('{"[2021-01-01,2021-12-31)","(2021-01-01,2021-12-32]"}', +/// 'daterange') /// //=> ['[2021-01-01,2021-12-31)', '(2021-01-01,2021-12-32]'] /// @example toArray([1,2,3,4], 'int4') /// //=> [1,2,3,4] @@ -265,8 +281,8 @@ dynamic toArray(dynamic value, String type) { return value; } -/// Fixes timestamp to be ISO-8601. Swaps the space between the date and time for a 'T' -/// See https://github.com/supabase/supabase/issues/18 +/// Fixes timestamp to be ISO-8601. Swaps the space between the date and time +/// for a 'T' See https://github.com/supabase/supabase/issues/18 /// ///```dart /// @example toTimestampString('2019-09-10 00:00:00') diff --git a/packages/realtime_client/lib/src/types.dart b/packages/realtime_client/lib/src/types.dart index 2d87d93db..ab1076361 100644 --- a/packages/realtime_client/lib/src/types.dart +++ b/packages/realtime_client/lib/src/types.dart @@ -60,7 +60,8 @@ enum PostgresChangeEvent { 'UPDATE' => PostgresChangeEvent.update, 'DELETE' => PostgresChangeEvent.delete, _ => throw ArgumentError( - 'Only "INSERT", "UPDATE", or "DELETE" can be can be passed to `fromString()` method.', + 'Only "INSERT", "UPDATE", or "DELETE" can be passed to the ' + '`fromString()` method.', ), }; } @@ -111,7 +112,8 @@ enum ChannelResponse { ok, timedOut, @Deprecated( - 'Client side rate limiting has been removed, and this enum value will never be returned.', + 'Client side rate limiting has been removed, and this enum value will ' + 'never be returned.', ) rateLimited, error, @@ -145,7 +147,8 @@ enum PresenceEvent { } } throw ArgumentError( - 'Only "sync", "join", or "leave" can be can be passed to `fromString()` method.', + 'Only "sync", "join", or "leave" can be passed to the `fromString()` ' + 'method.', ); } } @@ -176,13 +179,17 @@ class ReplayOption { } class RealtimeChannelConfig { - /// [ack] option instructs server to acknowledge that broadcast message was received + /// [ack] option instructs server to acknowledge that broadcast message was + /// received final bool ack; /// [self] option enables client to receive message it broadcasted final bool self; - /// [replay] enables **private** channels to access messages that were sent earlier. Only messages published via [Broadcast From the Database](https://supabase.com/docs/guides/realtime/broadcast#trigger-broadcast-messages-from-your-database) are available for replay. + /// [replay] enables **private** channels to access messages that were sent + /// earlier. Only messages published via [Broadcast From the + /// Database](https://supabase.com/docs/guides/realtime/broadcast#trigger-broadcast-messages-from-your-database) + /// are available for replay. final ReplayOption? replay; /// [key] option is used to track presence payload across clients @@ -191,7 +198,8 @@ class RealtimeChannelConfig { /// Enables presence even without presence bindings final bool enabled; - /// Defines if the channel is private or not and if RLS policies will be used to check data + /// Defines if the channel is private or not and if RLS policies will be used + /// to check data final bool private; /// [replicationReady] instructs the server to emit a `system` event once the @@ -278,7 +286,8 @@ class RealtimeSystemPayload { @override String toString() => - 'RealtimeSystemPayload(extension: $extension, status: $status, message: $message, channel: $channel)'; + 'RealtimeSystemPayload(extension: $extension, status: $status, message: ' + '$message, channel: $channel)'; } /// Data class that contains the Postgres change event payload. @@ -300,7 +309,8 @@ class PostgresChangePayload { required this.errors, }); - /// Creates a PostgresChangePayload instance from the enriched postgres change payload + /// Creates a PostgresChangePayload instance from the enriched postgres change + /// payload factory PostgresChangePayload.fromPayload(Map payload) { final commitTimestampStr = payload['commit_timestamp'] as String?; DateTime commitTimestamp; @@ -330,7 +340,10 @@ class PostgresChangePayload { @override String toString() { - return 'PostgresChangePayload(schema: $schema, table: $table, commitTimestamp: $commitTimestamp, eventType: ${eventType.name}, newRow: $newRecord, oldRow: $oldRecord, errors: $errors)'; + return 'PostgresChangePayload(schema: $schema, table: $table, ' + 'commitTimestamp: $commitTimestamp, eventType: ${eventType.name}, ' + 'newRow: ' + '$newRecord, oldRow: $oldRecord, errors: $errors)'; } @override @@ -359,46 +372,57 @@ class PostgresChangePayload { } } -/// Specifies the type of filter to be applied on realtime Postgres Change listener. +/// Specifies the type of filter to be applied on realtime Postgres Change +/// listener. /// /// These mirror the PostgREST operator surface that the Realtime server /// evaluates for Postgres Changes. Any operator can be negated with the `not.` /// prefix via [PostgresChangeFilter.negate]. enum PostgresChangeFilterType { - /// Listens to changes where a column's value in a table equals a client-specified value. + /// Listens to changes where a column's value in a table equals a + /// client-specified value. eq, - /// Listens to changes where a column's value in a table does not equal a value specified. + /// Listens to changes where a column's value in a table does not equal a + /// value specified. neq, - /// Listen to changes where a column's value in a table is less than a value specified. + /// Listen to changes where a column's value in a table is less than a value + /// specified. lt, - /// Listens to changes where a column's value in a table is less than or equal to a value specified. + /// Listens to changes where a column's value in a table is less than or equal + /// to a value specified. lte, - /// Listens to changes where a column's value in a table is greater than a value specified. + /// Listens to changes where a column's value in a table is greater than a + /// value specified. gt, - /// Listens to changes where a column's value in a table is greater than or equal to a value specified. + /// Listens to changes where a column's value in a table is greater than or + /// equal to a value specified. gte, - /// Listen to changes when a column's value in a table equals any of the values specified. + /// Listen to changes when a column's value in a table equals any of the + /// values specified. inFilter, - /// Listens to changes where a column matches a case-sensitive pattern (`LIKE`). + /// Listens to changes where a column matches a case-sensitive pattern + /// (`LIKE`). /// /// Use `%` and `_` as wildcards, e.g. `title=like.%foo%`. like, - /// Listens to changes where a column matches a case-insensitive pattern (`ILIKE`). + /// Listens to changes where a column matches a case-insensitive pattern + /// (`ILIKE`). ilike, /// Listens to changes where a column `IS` a given value (`null`, `true`, /// `false` or `unknown`), e.g. `deleted_at=is.null`. isFilter, - /// Listens to changes where a column matches a POSIX regular expression (`~`). + /// Listens to changes where a column matches a POSIX regular expression + /// (`~`). match, /// Listens to changes where a column matches a case-insensitive POSIX regular @@ -544,7 +568,8 @@ class RealtimePresenceJoinPayload extends RealtimePresencePayload { @override String toString() => - 'PresenceJoinPayload(key: $key, newPresences: $newPresences, currentPresences: $currentPresences)'; + 'PresenceJoinPayload(key: $key, newPresences: $newPresences, ' + 'currentPresences: $currentPresences)'; } /// Payload for [PresenceEvent.leave] callback. @@ -578,7 +603,8 @@ class RealtimePresenceLeavePayload extends RealtimePresencePayload { @override String toString() => - 'PresenceLeavePayload(key: $key, leftPresences: $leftPresences, currentPresences: $currentPresences)'; + 'PresenceLeavePayload(key: $key, leftPresences: $leftPresences, ' + 'currentPresences: $currentPresences)'; } /// A single client connected through presence. diff --git a/packages/realtime_client/test/channel_test.dart b/packages/realtime_client/test/channel_test.dart index 26a2ff1b4..ca7e5b68e 100644 --- a/packages/realtime_client/test/channel_test.dart +++ b/packages/realtime_client/test/channel_test.dart @@ -155,8 +155,8 @@ void main() { status, RealtimeSubscribeStatus.subscribed, reason: - "If the catch is missing the 'ok' callback aborts at setAuth " - "and the subscribed status is never emitted.", + "If the catch is missing the 'ok' callback aborts at setAuth and " + "the subscribed status is never emitted.", ); }); @@ -190,8 +190,8 @@ void main() { status, isNull, reason: - 'A non-InvalidJWTToken FormatException should propagate out of ' - 'the callback before subscribed is emitted.', + 'A non-InvalidJWTToken FormatException should propagate out of the ' + 'callback before subscribed is emitted.', ); }); }); @@ -767,7 +767,8 @@ void main() { }); test( - 'send message via http request to Broadcast endpoint when not subscribed to channel', + 'send message via http request to Broadcast endpoint when not subscribed ' + 'to channel', () async { final requestFuture = mockServer.first; final sendFuture = channel.send( @@ -886,7 +887,8 @@ void main() { }); test( - 'should enable presence when config.presence.enabled is true even without bindings', + 'should enable presence when config.presence.enabled is true even ' + 'without bindings', () { channel = RealtimeChannel( 'topic', @@ -916,7 +918,8 @@ void main() { }); test( - 'should enable presence when both bindings exist and config.presence.enabled is true', + 'should enable presence when both bindings exist and ' + 'config.presence.enabled is true', () { channel = RealtimeChannel( 'topic', @@ -933,7 +936,8 @@ void main() { ); test( - 'should not enable presence when neither bindings exist nor config.presence.enabled is true', + 'should not enable presence when neither bindings exist nor ' + 'config.presence.enabled is true', () { channel = RealtimeChannel( 'topic', @@ -983,7 +987,8 @@ void main() { }); test( - 'should resubscribe when presence callback added to subscribed channel without initial presence', + 'should resubscribe when presence callback added to subscribed channel ' + 'without initial presence', () { channel = RealtimeChannel( 'topic', @@ -1002,7 +1007,8 @@ void main() { ); test( - 'should not resubscribe when presence callback added to channel with existing presence', + 'should not resubscribe when presence callback added to channel with ' + 'existing presence', () { channel = RealtimeChannel( 'topic', @@ -1047,7 +1053,8 @@ void main() { ); test( - 'should not resubscribe when presence callback added to unsubscribed channel', + 'should not resubscribe when presence callback added to unsubscribed ' + 'channel', () { channel = RealtimeChannel( 'topic', @@ -1064,7 +1071,8 @@ void main() { ); test( - 'should receive presence events after resubscription triggered by adding callback', + 'should receive presence events after resubscription triggered by adding ' + 'callback', () { channel = RealtimeChannel( 'topic', diff --git a/packages/realtime_client/test/mock_test.dart b/packages/realtime_client/test/mock_test.dart index f599c455e..d3e16635f 100644 --- a/packages/realtime_client/test/mock_test.dart +++ b/packages/realtime_client/test/mock_test.dart @@ -34,9 +34,9 @@ void main() { /// Protocol 2.0.0 text frames are positional arrays: /// [join_ref, ref, topic, event, payload]. /// - /// `filter` might be there or not depending on whether is a filter set - /// to the realtime subscription, so include the filter if the request - /// includes a filter. + /// `filter` might be there or not depending on whether is a filter + /// set to the realtime subscription, so include the filter if the + /// request includes a filter. final requestJson = jsonDecode(message as String) as List; final requestPayload = requestJson[4] as Map; final String? postgresFilter = @@ -337,9 +337,9 @@ void main() { /// Protocol 2.0.0 text frames are positional arrays: /// [join_ref, ref, topic, event, payload]. /// - /// `filter` might be there or not depending on whether is a filter set - /// to the realtime subscription, so include the filter if the request - /// includes a filter. + /// `filter` might be there or not depending on whether is a filter + /// set to the realtime subscription, so include the filter if the + /// request includes a filter. final requestJson = jsonDecode(message as String) as List; final requestPayload = requestJson[4] as Map; diff --git a/packages/realtime_client/test/realtime_integration_test.dart b/packages/realtime_client/test/realtime_integration_test.dart index fa5da5d73..f4eac9c98 100644 --- a/packages/realtime_client/test/realtime_integration_test.dart +++ b/packages/realtime_client/test/realtime_integration_test.dart @@ -258,7 +258,8 @@ void main() { expect(insert.newRecord['task'], 'write tests'); await database.execute( - "UPDATE public.todos SET is_complete = true WHERE task = 'write tests'", + "UPDATE public.todos SET is_complete = true WHERE task = 'write " + "tests'", ); final update = await updates.future.timeout( const Duration(seconds: 20), @@ -299,7 +300,8 @@ void main() { await Future.delayed(const Duration(seconds: 2)); await database.execute( - "INSERT INTO public.todos (task, is_complete) VALUES ('ignored', false)", + "INSERT INTO public.todos (task, is_complete) VALUES ('ignored', " + "false)", ); await Future.delayed(const Duration(seconds: 3)); @@ -310,7 +312,8 @@ void main() { ); await database.execute( - "INSERT INTO public.todos (task, is_complete) VALUES ('matched', true)", + "INSERT INTO public.todos (task, is_complete) VALUES ('matched', " + "true)", ); final payload = await matched.future.timeout( diff --git a/packages/realtime_client/test/serializer_test.dart b/packages/realtime_client/test/serializer_test.dart index 6a2b583bb..0a2c94cd4 100644 --- a/packages/realtime_client/test/serializer_test.dart +++ b/packages/realtime_client/test/serializer_test.dart @@ -258,7 +258,8 @@ void main() { final userEventBytes = utf8.encode(userEvent); final metadataBytes = utf8.encode(jsonEncode({'label': 'naïve'})); - // Length prefixes must be UTF-8 byte lengths, not UTF-16 code-unit counts. + // Length prefixes must be UTF-8 byte lengths, not UTF-16 code-unit + // counts. expect(bytes[1], joinRefBytes.length); expect(bytes[2], refBytes.length); expect(bytes[3], topicBytes.length); diff --git a/packages/realtime_client/test/socket_test.dart b/packages/realtime_client/test/socket_test.dart index a2006ef38..29afc8b57 100644 --- a/packages/realtime_client/test/socket_test.dart +++ b/packages/realtime_client/test/socket_test.dart @@ -366,7 +366,8 @@ void main() { // The user disconnects explicitly while the socket is already closed. await mockedSocket.disconnect(); - // Wait past the reconnect delay; the scheduled reconnect must be canceled. + // Wait past the reconnect delay; the scheduled reconnect must be + // canceled. await Future.delayed(const Duration(milliseconds: 60)); expect( connectCount, @@ -1046,7 +1047,8 @@ void main() { final pushPayload = {'access_token': token}; test( - "sets access token, updates channels' join payload, and pushes token to channels", + "sets access token, updates channels' join payload, and pushes token to " + "channels", () async { final mockedChannel1 = MockChannel(); when(() => mockedChannel1.joinedOnce).thenReturn(true); @@ -1090,7 +1092,8 @@ void main() { ); test( - "sets access token, updates channels' join payload, and pushes token to channels if is not a jwt", + "sets access token, updates channels' join payload, and pushes token to " + "channels if is not a jwt", () async { final mockedChannel1 = MockChannel(); final mockedChannel2 = MockChannel(); @@ -1292,7 +1295,8 @@ void main() { unawaited(mockedSocket.connect()); }); - //! Unimplemented Test: closes socket when heartbeat is not ack'd within heartbeat window + //! Unimplemented Test: closes socket when heartbeat is not ack'd within + //! heartbeat window test('pushes heartbeat data when connected', () async { mockedSocket.connectionState = SocketState.open; @@ -1312,7 +1316,8 @@ void main() { group('connect/disconnect race condition', () { test( - 'connect does not crash if disconnect nullifies connection during await ready', + 'connect does not crash if disconnect nullifies connection during await ' + 'ready', () async { final readyCompleter = Completer(); final mockedSocketChannel = MockIOWebSocketChannel(); @@ -1338,12 +1343,14 @@ void main() { // Start disconnect (also suspends on ready since state is connecting) final disconnectFuture = socket.disconnect(); - // Now complete the ready future — both connect and disconnect can proceed + // Now complete the ready future — both connect and disconnect can + // proceed readyCompleter.complete(); await disconnectFuture; await connectFuture; - // Should NOT have transitioned to open because disconnect nullified connection + // Should NOT have transitioned to open because disconnect nullified + // connection expect(socket.connectionState, isNot(SocketState.open)); expect(socket.connection, isNull); }, diff --git a/packages/realtime_client/test/utils/realtime_test_utils.dart b/packages/realtime_client/test/utils/realtime_test_utils.dart index e9f659aca..0e692082d 100644 --- a/packages/realtime_client/test/utils/realtime_test_utils.dart +++ b/packages/realtime_client/test/utils/realtime_test_utils.dart @@ -104,9 +104,9 @@ Future _isRealtimeHttpReachable() async { /// delivered reliably. /// /// On first use the Realtime server creates a replication slot asynchronously, -/// and any change made before the slot exists is missed. This repeatedly inserts -/// a sentinel row until a change event is observed, which proves the pipeline is -/// live, then cleans up the inserted rows. +/// and any change made before the slot exists is missed. This repeatedly +/// inserts a sentinel row until a change event is observed, which proves the +/// pipeline is live, then cleans up the inserted rows. Future primePostgresChanges({ Duration timeout = const Duration(seconds: 90), }) async { diff --git a/packages/realtime_client/test/websocket_io_test.dart b/packages/realtime_client/test/websocket_io_test.dart index 04420c554..7296d10b8 100644 --- a/packages/realtime_client/test/websocket_io_test.dart +++ b/packages/realtime_client/test/websocket_io_test.dart @@ -35,10 +35,8 @@ Future _startUnresponsiveServer() async { .bytes, ); socket.write( - 'HTTP/1.1 101 Switching Protocols\r\n' - 'Upgrade: websocket\r\n' - 'Connection: Upgrade\r\n' - 'Sec-WebSocket-Accept: $accept\r\n\r\n', + 'HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: ' + 'Upgrade\r\nSec-WebSocket-Accept: $accept\r\n\r\n', ); // From here on, deliberately ignore everything (including ping frames). subscription.onData((_) {}); @@ -61,8 +59,8 @@ void main() { ); await channel.ready; - // Without a transport level ping interval this would never complete, since - // the dead peer sends neither data nor a close frame. + // Without a transport level ping interval this would never complete, + // since the dead peer sends neither data nor a close frame. await channel.stream.drain().timeout(const Duration(seconds: 5)); }, ); diff --git a/packages/storage_client/lib/src/fetch.dart b/packages/storage_client/lib/src/fetch.dart index 55c9e388c..19d6ba45b 100644 --- a/packages/storage_client/lib/src/fetch.dart +++ b/packages/storage_client/lib/src/fetch.dart @@ -156,7 +156,8 @@ class Fetch { ) async { final headers = options?.headers ?? {}; - // Create a factory function that generates a fresh MultipartRequest for each attempt + // Create a factory function that generates a fresh MultipartRequest for + // each attempt http.MultipartRequest createRequest() { final request = http.MultipartRequest(method, Uri.parse(url)) ..headers.addAll(headers) diff --git a/packages/storage_client/lib/src/storage_client.dart b/packages/storage_client/lib/src/storage_client.dart index 3640e8128..a27fc451d 100644 --- a/packages/storage_client/lib/src/storage_client.dart +++ b/packages/storage_client/lib/src/storage_client.dart @@ -14,7 +14,8 @@ class SupabaseStorageClient extends StorageBucketApi { final Client? _httpClient; final _log = Logger('supabase.storage'); - /// To create a [SupabaseStorageClient], you need to provide an [url] and [headers]. + /// To create a [SupabaseStorageClient], you need to provide an [url] and + /// [headers]. /// /// ```dart /// SupabaseStorageClient(STORAGE_URL, {'apikey': 'foo'}); @@ -60,15 +61,17 @@ class SupabaseStorageClient extends StorageBucketApi { httpClient: httpClient, ) { _log.config( - 'Initialize SupabaseStorageClient v$version with url: $url, retryAttempts: $_defaultRetryAttempts', + 'Initialize SupabaseStorageClient v$version with url: $url, ' + 'retryAttempts: $_defaultRetryAttempts', ); _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" + /// 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; diff --git a/packages/storage_client/lib/src/storage_file_api.dart b/packages/storage_client/lib/src/storage_file_api.dart index 5b8a59c70..4de442d33 100644 --- a/packages/storage_client/lib/src/storage_file_api.dart +++ b/packages/storage_client/lib/src/storage_file_api.dart @@ -71,9 +71,11 @@ class StorageFileApi { /// /// [fileOptions] HTTP headers. For example `cacheControl` /// - /// [retryAttempts] overrides the retryAttempts parameter set across the storage client. + /// [retryAttempts] overrides the retryAttempts parameter set across the + /// storage client. /// - /// You can pass a [retryController] and call `cancel()` to cancel the retry attempts. + /// You can pass a [retryController] and call `cancel()` to cancel the retry + /// attempts. Future upload( String path, File file, { @@ -105,9 +107,11 @@ class StorageFileApi { /// /// [fileOptions] HTTP headers. For example `cacheControl` /// - /// [retryAttempts] overrides the retryAttempts parameter set across the storage client. + /// [retryAttempts] overrides the retryAttempts parameter set across the + /// storage client. /// - /// You can pass a [retryController] and call `cancel()` to cancel the retry attempts. + /// You can pass a [retryController] and call `cancel()` to cancel the retry + /// attempts. Future uploadBinary( String path, Uint8List data, { @@ -131,7 +135,9 @@ class StorageFileApi { /// Upload a file with a token generated from `createUploadSignedUrl`. /// - /// [path] The file path, including the file name. Should be of the format `folder/subfolder/filename.png`. The bucket must already exist before attempting to upload. + /// [path] The file path, including the file name. Should be of the format + /// `folder/subfolder/filename.png`. The bucket must already exist before + /// attempting to upload. /// /// [token] The token generated from `createUploadSignedUrl` /// @@ -164,7 +170,9 @@ class StorageFileApi { /// Upload a binary file with a token generated from `createUploadSignedUrl`. /// - /// [path] The file path, including the file name. Should be of the format `folder/subfolder/filename.png`. The bucket must already exist before attempting to upload. + /// [path] The file path, including the file name. Should be of the format + /// `folder/subfolder/filename.png`. The bucket must already exist before + /// attempting to upload. /// /// [token] The token generated from `createUploadSignedUrl` /// @@ -197,10 +205,11 @@ class StorageFileApi { /// Creates a signed upload URL. /// - /// Signed upload URLs can be used upload files to the bucket without further authentication. - /// They are valid for one minute. + /// Signed upload URLs can be used to upload files to the bucket without + /// further authentication. They are valid for one minute. /// - /// [path] The file path, including the current file name. For example `folder/image.png`. + /// [path] The file path, including the current file name. For example + /// `folder/image.png`. /// /// When [upsert] is `true` the signed URL allows overwriting an existing /// file at [path]. It defaults to `false`. @@ -243,9 +252,11 @@ class StorageFileApi { /// /// [fileOptions] HTTP headers. For example `cacheControl` /// - /// [retryAttempts] overrides the retryAttempts parameter set across the storage client. + /// [retryAttempts] overrides the retryAttempts parameter set across the + /// storage client. /// - /// You can pass a [retryController] and call `cancel()` to cancel the retry attempts. + /// You can pass a [retryController] and call `cancel()` to cancel the retry + /// attempts. Future update( String path, File file, { @@ -278,9 +289,11 @@ class StorageFileApi { /// /// [fileOptions] HTTP headers. For example `cacheControl` /// - /// [retryAttempts] overrides the retryAttempts parameter set across the storage client. + /// [retryAttempts] overrides the retryAttempts parameter set across the + /// storage client. /// - /// You can pass a [retryController] and call `cancel()` to cancel the retry attempts. + /// You can pass a [retryController] and call `cancel()` to cancel the retry + /// attempts. Future updateBinary( String path, Uint8List data, { @@ -309,7 +322,8 @@ class StorageFileApi { /// [toPath] is the new file path, including the new file name. For example /// `folder/image-new.png`. /// - /// When copying to a different bucket, you have to specify the [destinationBucket]. + /// When copying to a different bucket, you have to specify the + /// [destinationBucket]. Future move( String fromPath, String toPath, { @@ -337,7 +351,8 @@ class StorageFileApi { /// [toPath] is the new file path, including the new file name. For example /// `folder/image-copy.png`. /// - /// When copying to a different bucket, you have to specify the [destinationBucket]. + /// When copying to a different bucket, you have to specify the + /// [destinationBucket]. Future copy( String fromPath, String toPath, { @@ -410,10 +425,12 @@ class StorageFileApi { /// Create signed URLs to download files without requiring permissions. /// /// Items for paths that do not exist are silently omitted. Use - /// [createSignedUrlsResult] to distinguish missing paths from successful ones. + /// [createSignedUrlsResult] to distinguish missing paths from successful + /// ones. /// /// [paths] is the file paths to be downloaded, including the current file - /// names. For example: `createSignedUrls(['folder/image.png', 'folder2/image2.png'])`. + /// names. For example: `createSignedUrls(['folder/image.png', + /// 'folder2/image2.png'])`. /// /// [expiresIn] is the number of seconds until the signed URLs expire. For /// example, `60` for URLs which are valid for one minute. @@ -440,11 +457,13 @@ class StorageFileApi { /// URLs can be valid for a set number of seconds. /// /// Returns one [SignedUrlResult] per requested path. Each result is either a - /// [SignedUrlSuccess] (with a ready-to-use signed URL) or a [SignedUrlFailure] - /// (when the server could not sign that path, e.g. the file does not exist). + /// [SignedUrlSuccess] (with a ready-to-use signed URL) or a + /// [SignedUrlFailure] (when the server could not sign that path, e.g. the + /// file does not exist). /// /// [paths] is the file paths to be downloaded, including the current file - /// names. For example: `createSignedUrlsResult(['folder/image.png', 'folder2/image2.png'])`. + /// names. For example: `createSignedUrlsResult(['folder/image.png', + /// 'folder2/image2.png'])`. /// /// [expiresIn] is the number of seconds until the signed URLs expire. For /// example, `60` for URLs which are valid for one minute. @@ -496,7 +515,8 @@ class StorageFileApi { /// [path] is the file path to be downloaded, including the path and file /// name. For example `download('folder/image.png')`. /// - /// [transform] download a transformed variant of the image with the provided options + /// [transform] downloads a transformed variant of the image with the provided + /// options /// /// [queryParams] additional query parameters to be added to the URL /// @@ -558,7 +578,7 @@ class StorageFileApi { /// [path] is the file path to be downloaded, including the path and file /// name. For example `downloadStream('folder/image.png')`. /// - /// [transform] download a transformed variant of the image with the provided + /// [transform] downloads a transformed variant of the image with the provided /// options. /// /// [queryParams] additional query parameters to be added to the URL. diff --git a/packages/storage_client/lib/src/types.dart b/packages/storage_client/lib/src/types.dart index 7bb05022c..0123090dc 100644 --- a/packages/storage_client/lib/src/types.dart +++ b/packages/storage_client/lib/src/types.dart @@ -175,10 +175,12 @@ class FileObjectV2 { /// authorization token to download objects, but still require a valid token for /// all other operations. By default, buckets are private. /// -/// [fileSizeLimit] specifies the file size limit that this bucket can accept during upload. -/// It should be in a format such as `20GB`, `20MB`, `30KB`, or `3B` +/// [fileSizeLimit] specifies the file size limit that this bucket can accept +/// during upload. It should be in a format such as `20GB`, `20MB`, `30KB`, or +/// `3B` /// -/// [allowedMimeTypes] specifies the allowed mime types that this bucket can accept during upload +/// [allowedMimeTypes] specifies the allowed mime types that this bucket can +/// accept during upload class BucketOptions { final bool public; final String? fileSizeLimit; @@ -503,7 +505,8 @@ class PaginatedListResult { } class SignedUrl { - /// The file path, including the current file name. For example `folder/image.png`. + /// The file path, including the current file name. For example + /// `folder/image.png`. final String path; /// Full signed URL of the files. @@ -559,7 +562,8 @@ sealed class SignedUrlResult { const SignedUrlResult({required this.path}); } -/// A successful [SignedUrlResult]: the file was found and a signed URL was generated. +/// A successful [SignedUrlResult]: the file was found and a signed URL was +/// generated. final class SignedUrlSuccess extends SignedUrlResult { /// The signed URL ready for use. final String signedUrl; @@ -569,7 +573,8 @@ final class SignedUrlSuccess extends SignedUrlResult { String toString() => 'SignedUrlSuccess(path: $path, signedUrl: $signedUrl)'; } -/// A failed [SignedUrlResult]: the path could not be signed (e.g. the file does not exist). +/// A failed [SignedUrlResult]: the path could not be signed (e.g. the file does +/// not exist). final class SignedUrlFailure extends SignedUrlResult { /// The reason the URL could not be created. final String error; @@ -608,7 +613,8 @@ class StorageException implements Exception { @override String toString() { - return 'StorageException(message: $message, statusCode: $statusCode, error: $error)'; + return 'StorageException(message: $message, statusCode: $statusCode, ' + 'error: $error)'; } } @@ -627,10 +633,12 @@ class StorageRetryController { } /// {@template resize_mode} -/// Specifies how image cropping should be handled when performing image transformations. +/// Specifies how image cropping should be handled when performing image +/// transformations. /// {@endtemplate} enum ResizeMode { - /// Resizes the image while keeping the aspect ratio to fill a given size and crops projecting parts. + /// Resizes the image while keeping the aspect ratio to fill a given size and + /// crops projecting parts. cover, /// Resizes the image while keeping the aspect ratio to fit a given size. @@ -659,13 +667,14 @@ class TransformOptions { /// [ResizeMode.cover] will be used if no value is specified. final ResizeMode? resize; - /// Set the quality of the returned image, this is percentage based, default 80 + /// Set the quality of the returned image, this is percentage based, default + /// 80 final int? quality; /// Specify the format of the image requested. /// - /// When using 'origin' we force the format to be the same as the original image, - /// bypassing automatic browser optimization such as webp conversion + /// When using 'origin' we force the format to be the same as the original + /// image, bypassing automatic browser optimization such as webp conversion final RequestImageFormat? format; /// {@macro transform_options} diff --git a/packages/storage_client/lib/src/vector_client.dart b/packages/storage_client/lib/src/vector_client.dart index a32344d34..5413cf584 100644 --- a/packages/storage_client/lib/src/vector_client.dart +++ b/packages/storage_client/lib/src/vector_client.dart @@ -19,7 +19,10 @@ import 'package:supabase_common/supabase_common.dart'; /// Vector(key: 'doc-1', data: [0.1, 0.2, 0.3], metadata: {'title': 'Intro'}), /// ]); /// -/// final result = await index.queryVectors(queryVector: [0.1, 0.2, 0.3], topK: 5); +/// final result = await index.queryVectors( +/// queryVector: [0.1, 0.2, 0.3], +/// topK: 5, +/// ); /// ``` /// /// This API is part of a public alpha and may not be available to every diff --git a/packages/storage_client/test/basic_test.dart b/packages/storage_client/test/basic_test.dart index d1677e6e8..5d26e118a 100644 --- a/packages/storage_client/test/basic_test.dart +++ b/packages/storage_client/test/basic_test.dart @@ -765,7 +765,8 @@ void main() { '$supabaseUrl/storage/v1', {'Authorization': 'Bearer $supabaseKey'}, retryAttempts: 5, - // `RetryHttpClient` will throw `SocketException` for the first two tries + // `RetryHttpClient` will throw `SocketException` for the first two + // tries httpClient: RetryHttpClient(), ); }); diff --git a/packages/storage_client/test/client_test.dart b/packages/storage_client/test/client_test.dart index 7e77f8995..d762d066c 100644 --- a/packages/storage_client/test/client_test.dart +++ b/packages/storage_client/test/client_test.dart @@ -290,8 +290,8 @@ void main() { expect( url, - '$localStackStorageUrl/render/image/public/' - '$newBucketName/$uploadPath?width=200&height=300&quality=60', + '$localStackStorageUrl/render/image/public/$newBucketName/$uploadPath?w' + 'idth=200&height=300&quality=60', ); }); @@ -728,7 +728,8 @@ void main() { }); test( - 'setHeader on StorageFileApi does not affect other StorageFileApi instances', + 'setHeader on StorageFileApi does not affect other StorageFileApi ' + 'instances', () async { customHttpClient.response = []; customHttpClient.statusCode = 200; @@ -836,12 +837,12 @@ void main() { }); group('object keys with reserved URL characters', () { - // The SDK percent-encodes each object key segment (see _getFinalPath). These - // tests confirm the round-trip against a real server: the storage server - // percent-decodes the path back to the literal key, so upload and download - // address the same object. Without encoding a `?` or `#` in the key would be - // parsed as the start of the query string or fragment and the SDK would - // silently address the wrong object. + // The SDK percent-encodes each object key segment (see _getFinalPath). + // These tests confirm the round-trip against a real server: the storage + // server percent-decodes the path back to the literal key, so upload and + // download address the same object. Without encoding a `?` or `#` in the + // key would be parsed as the start of the query string or fragment and the + // SDK would silently address the wrong object. late String bucket; setUp(() async { diff --git a/packages/supabase/lib/src/remove_subscription_result.dart b/packages/supabase/lib/src/remove_subscription_result.dart index e72f3e2c7..63c612f9c 100644 --- a/packages/supabase/lib/src/remove_subscription_result.dart +++ b/packages/supabase/lib/src/remove_subscription_result.dart @@ -8,5 +8,6 @@ class RemoveSubscriptionResult { @override String toString() => - 'RemoveSubscriptionResult(openSubscriptions: $openSubscriptions, error: $error)'; + 'RemoveSubscriptionResult(openSubscriptions: $openSubscriptions, error: ' + '$error)'; } diff --git a/packages/supabase/lib/src/supabase_client.dart b/packages/supabase/lib/src/supabase_client.dart index f76841589..aa2a4ea58 100644 --- a/packages/supabase/lib/src/supabase_client.dart +++ b/packages/supabase/lib/src/supabase_client.dart @@ -28,12 +28,14 @@ import 'trace_http_client.dart'; /// [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`. +/// [realtimeClientOptions] specifies different options you can pass to +/// `RealtimeClient`. /// -/// [accessToken] Optional function for using a third-party authentication system with Supabase. -/// The function should return an access token or ID token (JWT) by obtaining -/// it from the third-party auth client library. Note that this function may be -/// called concurrently and many times. Use memoization and locking techniques +/// [accessToken] Optional function for using a third-party authentication +/// system with Supabase. The function should return an access token or ID token +/// (JWT) by obtaining it from the third-party auth client library. Note that +/// this function may be called concurrently and many times. Use memoization and +/// locking techniques /// if this is not supported by the client libraries. When set, the `auth` /// namespace of the Supabase client cannot be used. /// @@ -64,7 +66,8 @@ class SupabaseClient { /// Supabase Functions allows you to deploy and invoke edge functions. late final FunctionsClient functions; - /// Supabase Storage allows you to manage user-generated content, such as photos or videos. + /// Supabase Storage allows you to manage user-generated content, such as + /// photos or videos. late final SupabaseStorageClient storage; late final RealtimeClient realtime; late final PostgrestClient rest; @@ -73,7 +76,8 @@ class SupabaseClient { final bool _hasCustomIsolate; final Future Function()? accessToken; - /// Increment ID of the stream to create different realtime topic for each stream + /// Increment ID of the stream to create different realtime topic for each + /// stream final _incrementId = Counter(); final _log = Logger('supabase.supabase'); @@ -81,7 +85,8 @@ class SupabaseClient { /// Getter for the HTTP headers Map get headers => Map.unmodifiable(_headers); - /// To apply the new headers in existing realtime channels, manually unsubscribe and resubscribe these channels. + /// To apply the new headers in existing realtime channels, manually + /// unsubscribe and resubscribe these channels. set headers(Map newHeaders) { _headers.clear(); _headers.addAll({ @@ -201,7 +206,8 @@ class SupabaseClient { return _authInstance!; } throw AuthException( - 'Supabase Client is configured with the accessToken option, accessing supabase.auth is not possible.', + 'Supabase Client is configured with the accessToken option, accessing ' + 'supabase.auth is not possible.', ); } diff --git a/packages/supabase/lib/src/supabase_client_options.dart b/packages/supabase/lib/src/supabase_client_options.dart index 36b025c37..5079ecbd3 100644 --- a/packages/supabase/lib/src/supabase_client_options.dart +++ b/packages/supabase/lib/src/supabase_client_options.dart @@ -7,7 +7,8 @@ class PostgrestClientOptions { /// fail with a retryable status code or a network error. final bool retryEnabled; - /// The number of retry attempts made for a retryable request before giving up. + /// The number of retry attempts made for a retryable request before giving + /// up. final int retryCount; /// The HTTP status codes that trigger an automatic retry. diff --git a/packages/supabase/lib/src/supabase_query_schema.dart b/packages/supabase/lib/src/supabase_query_schema.dart index 64e703a8c..ef63c1969 100644 --- a/packages/supabase/lib/src/supabase_query_schema.dart +++ b/packages/supabase/lib/src/supabase_query_schema.dart @@ -4,7 +4,8 @@ import 'package:yet_another_json_isolate/yet_another_json_isolate.dart'; import 'counter.dart'; -/// Used to perform [rpc] and [from] operations with a different schema than in [SupabaseClient]. +/// Used to perform [rpc] and [from] operations with a different schema than in +/// [SupabaseClient]. class SupabaseQuerySchema { final Counter _counter; final String _restUrl; diff --git a/packages/supabase/lib/src/supabase_stream_builder.dart b/packages/supabase/lib/src/supabase_stream_builder.dart index 3dc8231d1..1efa28fc7 100644 --- a/packages/supabase/lib/src/supabase_stream_builder.dart +++ b/packages/supabase/lib/src/supabase_stream_builder.dart @@ -24,7 +24,8 @@ class RealtimeSubscribeException implements Exception { @override String toString() { - return 'RealtimeSubscribeException(status: ${status.name}, details: $details)'; + return 'RealtimeSubscribeException(status: ${status.name}, details: ' + '$details)'; } } @@ -91,7 +92,10 @@ class SupabaseStreamBuilder extends Stream { /// When `ascending` value is true, the result will be in ascending order. /// /// ```dart - /// supabase.from('users').stream(primaryKey: ['id']).order('username', ascending: false); + /// supabase + /// .from('users') + /// .stream(primaryKey: ['id']) + /// .order('username', ascending: false); /// ``` SupabaseStreamBuilder order(String column, {bool ascending = false}) { _orderBy = (column: column, ascending: ascending); @@ -130,7 +134,8 @@ class SupabaseStreamBuilder extends Stream { ); } - /// Sets up the stream controller and calls the method to get data as necessary + /// Sets up the stream controller and calls the method to get data as + /// necessary void _setupStream() { _streamController ??= ReplaySubject( onListen: () { @@ -207,8 +212,10 @@ class SupabaseStreamBuilder extends Stream { .subscribe((status, [error]) { switch (status) { case RealtimeSubscribeStatus.subscribed: - // Reload all data after a reconnect from postgrest - // First data from postgrest gets loaded before the realtime connect + // Reload all data from PostgREST after a realtime reconnect, so + // that changes missed while the socket was down are picked up. + // The first subscribe is skipped because the initial load is + // already started below, right after subscribing. if (_wasSubscribed) { unawaited(_getPostgrestData()); } diff --git a/packages/supabase/test/client_test.dart b/packages/supabase/test/client_test.dart index 8e001a427..8a62859ac 100644 --- a/packages/supabase/test/client_test.dart +++ b/packages/supabase/test/client_test.dart @@ -22,7 +22,9 @@ void main() { group('Standard Header', () { late String supabaseUrl; const supabaseKey = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im53emxkenlsb2pyemdqemloZHJrIiwicm9sZSI6ImFub24iLCJpYXQiOjE2ODQxMzI2ODAsImV4cCI6MTk5OTcwODY4MH0.MU-LVeAPic93VLcRsHktxzYtBKBUMWAQb8E-0AQETPs'; + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6I' + 'm53emxkenlsb2pyemdqemloZHJrIiwicm9sZSI6ImFub24iLCJpYXQiOjE2ODQxMzI2ODA' + 'sImV4cCI6MTk5OTcwODY4MH0.MU-LVeAPic93VLcRsHktxzYtBKBUMWAQb8E-0AQETPs'; late SupabaseClient supabase; late HttpServer mockServer; @@ -285,7 +287,8 @@ void main() { () => supabase.auth.currentUser, throwsA( AuthException( - 'Supabase Client is configured with the accessToken option, accessing supabase.auth is not possible.', + 'Supabase Client is configured with the accessToken option, ' + 'accessing supabase.auth is not possible.', ), ), ); @@ -386,7 +389,8 @@ void main() { group('Error Handling', () { test( - 'should throw AuthException when accessing auth with custom access token', + 'should throw AuthException when accessing auth with custom access ' + 'token', () { final customTokenClient = SupabaseClient( supabaseUrl, @@ -404,7 +408,8 @@ void main() { group('Shared YAJsonIsolate', () { test( - 'does not dispose an injected YAJsonIsolate so the caller retains ownership', + 'does not dispose an injected YAJsonIsolate so the caller retains ' + 'ownership', () async { final isolate = YAJsonIsolate(); await isolate.initialize(); diff --git a/packages/supabase/test/mock_test.dart b/packages/supabase/test/mock_test.dart index 6a0a70f6e..f01f0698a 100644 --- a/packages/supabase/test/mock_test.dart +++ b/packages/supabase/test/mock_test.dart @@ -19,7 +19,8 @@ void main() { bool hasListener = false; StreamSubscription? listener; - /// `testFilter` is used to test incoming realtime filter. The value should match the realtime filter set by the library. + /// `testFilter` is used to test incoming realtime filter. The value should + /// match the realtime filter set by the library. Future handleRequests( HttpServer server, { String? expectedFilter, @@ -112,9 +113,9 @@ void main() { /// Protocol 2.0.0 text frames are positional arrays: /// [join_ref, ref, topic, event, payload]. /// - /// `filter` might be there or not depending on whether is a filter set - /// to the realtime subscription, so include the filter if the request - /// includes a filter. + /// `filter` might be there or not depending on whether is a filter + /// set to the realtime subscription, so include the filter if the + /// request includes a filter. final requestJson = jsonDecode(message as String) as List; final ref = requestJson[1]; final topic = requestJson[2]; @@ -376,7 +377,8 @@ void main() { await supabase.dispose(); await customHeadersClient.dispose(); - //Manually disconnect the socket channel to avoid automatic retrying to reconnect. This caused failing in later executed tests. + // Manually disconnect the socket channel to avoid automatic retrying to + // reconnect. This caused failing in later executed tests. await supabase.removeAllChannels(); await customHeadersClient.removeAllChannels(); @@ -441,7 +443,8 @@ void main() { stream.listen(expectAsync1((event) {}, count: 5)); stream.listen(expectAsync1((event) {}, count: 5)); - // All realtime events are done emitting, so should receive the current data + // All realtime events are done emitting, so should receive the current + // data }); test("Create two stream to same table", () async { @@ -452,15 +455,19 @@ void main() { stream2.listen(expectAsync1((event) {}, count: 5)); }); - test("stream should emit the last emitted data when listened to", () async { - final stream = supabase.from('todos').stream(primaryKey: ['id']); - stream.listen(expectAsync1((event) {}, count: 5)); + test( + "stream should emit the last emitted data when listened to", + () async { + final stream = supabase.from('todos').stream(primaryKey: ['id']); + stream.listen(expectAsync1((event) {}, count: 5)); - await Future.delayed(Duration(seconds: 3)); + await Future.delayed(Duration(seconds: 3)); - // All realtime events are done emitting, so should receive the current data - stream.listen(expectAsync1((event) {}, count: 1)); - }); + // All realtime events are done emitting, so should receive the + // current data + stream.listen(expectAsync1((event) {}, count: 1)); + }, + ); test('emits data', () { final stream = supabase.from('todos').stream(primaryKey: ['id']); expect( @@ -540,7 +547,8 @@ void main() { await Future.delayed(Duration(seconds: 3)); - // All realtime events are done emitting, so should receive the current data + // All realtime events are done emitting, so should receive the current + // data stream.listen(expectAsync1((event) {}, count: 1)); }); @@ -645,7 +653,8 @@ void main() { }); group('realtime', () { - /// Constructing Supabase query within a realtime callback caused exception + /// Constructing Supabase query within a realtime callback caused + /// exception /// https://github.com/supabase-community/supabase-flutter/issues/81 test('Calling Postgrest within realtime callback', () async { supabase diff --git a/packages/supabase/test/stream_integration_test.dart b/packages/supabase/test/stream_integration_test.dart index da2af0919..0b08287a7 100644 --- a/packages/supabase/test/stream_integration_test.dart +++ b/packages/supabase/test/stream_integration_test.dart @@ -595,9 +595,9 @@ Future _expectSnapshots({ /// Waits until every channel of [_supabase] has joined. /// -/// The first emitted snapshot comes from PostgREST, which is fetched in parallel -/// with the realtime subscription, so it does not imply that changes are being -/// streamed yet. +/// The first emitted snapshot comes from PostgREST, which is fetched in +/// parallel with the realtime subscription, so it does not imply that changes +/// are being streamed yet. Future _waitUntilJoined() async { for (var attempt = 0; attempt < 200; attempt++) { final channels = _supabase.getChannels(); diff --git a/packages/supabase/test/utilities_test.dart b/packages/supabase/test/utilities_test.dart index d8ee92dc8..4b6492f4a 100644 --- a/packages/supabase/test/utilities_test.dart +++ b/packages/supabase/test/utilities_test.dart @@ -47,7 +47,8 @@ void main() { }); test( - 'should include structured platform metadata in X-Client-Info when not on web', + 'should include structured platform metadata in X-Client-Info when not ' + 'on web', () { if (!kIsWeb) { final clientInfo = Constants.defaultHeaders['X-Client-Info']!; diff --git a/packages/supabase/test/utils.dart b/packages/supabase/test/utils.dart index 8a68e281f..678d9ca81 100644 --- a/packages/supabase/test/utils.dart +++ b/packages/supabase/test/utils.dart @@ -14,6 +14,15 @@ import 'dart:convert'; ); final accessToken = "any.$accessTokenMid.any"; final sessionString = - '{"access_token":"$accessToken","expires_in":${dateTime.difference(DateTime.now()).inSeconds},"refresh_token":"-yeS4omysFs9tpUYBws9Rg","token_type":"bearer","provider_token":null,"provider_refresh_token":null,"user":{"id":"4d2583da-8de4-49d3-9cd1-37a9a74f55bd","app_metadata":{"provider":"email","providers":["email"]},"user_metadata":{"Hello":"World"},"aud":"","email":"fake1680338105@email.com","phone":"","created_at":"2023-04-01T08:35:05.208586Z","confirmed_at":null,"email_confirmed_at":"2023-04-01T08:35:05.220096086Z","phone_confirmed_at":null,"last_sign_in_at":"2023-04-01T08:35:05.222755878Z","role":"","updated_at":"2023-04-01T08:35:05.226938Z"}}'; + '{"access_token":"$accessToken","expires_in":' + '${dateTime.difference(DateTime.now()).inSeconds},"refresh_token":"-yeS4o' + 'mysFs9tpUYBws9Rg","token_type":"bearer","provider_token":null,"provider_' + 'refresh_token":null,"user":{"id":"4d2583da-8de4-49d3-9cd1-37a9a74f55bd",' + '"app_metadata":{"provider":"email","providers":["email"]},"user_metadata' + '":{"Hello":"World"},"aud":"","email":"fake1680338105@email.com","phone":' + '"","created_at":"2023-04-01T08:35:05.208586Z","confirmed_at":null,"email' + '_confirmed_at":"2023-04-01T08:35:05.220096086Z","phone_confirmed_at":nul' + 'l,"last_sign_in_at":"2023-04-01T08:35:05.222755878Z","role":"","updated_' + 'at":"2023-04-01T08:35:05.226938Z"}}'; return (accessToken: accessToken, sessionString: sessionString); } diff --git a/packages/supabase_common/lib/src/client_info.dart b/packages/supabase_common/lib/src/client_info.dart index b0645252a..2744f3a50 100644 --- a/packages/supabase_common/lib/src/client_info.dart +++ b/packages/supabase_common/lib/src/client_info.dart @@ -44,11 +44,14 @@ String buildClientInfoHeader( if (platformInfo == null) { return '$clientName/$version'; } + final rawPlatformVersion = platformInfo.platformVersion; + final platformVersion = rawPlatformVersion == null + ? null + : Uri.encodeFull(rawPlatformVersion).replaceAll('%20', ' '); return [ '$clientName/$version', if (platformInfo.platform != null) 'platform=${platformInfo.platform}', - if (platformInfo.platformVersion != null) - 'platform-version=${Uri.encodeFull(platformInfo.platformVersion!).replaceAll("%20", " ")}', + if (platformVersion != null) 'platform-version=$platformVersion', 'runtime=dart', if (platformInfo.runtimeVersion != null) 'runtime-version=${platformInfo.runtimeVersion}', diff --git a/packages/supabase_common/lib/src/testing/local_stack.dart b/packages/supabase_common/lib/src/testing/local_stack.dart index a6a15792a..00364cb93 100644 --- a/packages/supabase_common/lib/src/testing/local_stack.dart +++ b/packages/supabase_common/lib/src/testing/local_stack.dart @@ -62,14 +62,27 @@ const localStackDatabasePassword = 'postgres'; /// the JWKS the key is published as. @visibleForTesting const localStackAnonKey = - 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjNkZjU5YWIxLWI4ZWMtNDlkMy05YzkyLThiOWQ0MmNhYzFmZSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjIwOTY4OTUxOTF9.Boe4zFpmRmJRM9b6USbJkZZzg66cXTHWYHm9uGScxnVi-xCXi6jAjy_GGsyKGOgwD110lNzNcdAQtwWjBOz-iBcVfcLpOJjgtFNg80ZK7toO2V0BwhWhAMdic1XnFI3_gxe9iq--iMuNuAebP1uIxGqn-nJ2kdua1cv3g9BZ5UtG9U-I22b4lPTQhdMU7skUsFLxcIpDOb1tS7RafWL3XcobNpd5OnZV_z88fus73DDP9oFKzBsyXARNg3H89IBBd5G9JHpeO4eQdGTPPY4xkGp_zBUnyMJJWTdgXqFjbFHpGpTdD1lSb3TbyeRheAq7IqaAvdqXyaTZVhH7LrZmbw'; + 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjNkZjU5YWIxLWI4ZWMtNDlkMy05YzkyLThiOWQ0MmNhYz' + 'FmZSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJ' + 'leHAiOjIwOTY4OTUxOTF9.Boe4zFpmRmJRM9b6USbJkZZzg66cXTHWYHm9uGScxnVi-xCXi6jA' + 'jy_GGsyKGOgwD110lNzNcdAQtwWjBOz-iBcVfcLpOJjgtFNg80ZK7toO2V0BwhWhAMdic1XnFI' + '3_gxe9iq--iMuNuAebP1uIxGqn-nJ2kdua1cv3g9BZ5UtG9U-I22b4lPTQhdMU7skUsFLxcIpD' + 'Ob1tS7RafWL3XcobNpd5OnZV_z88fus73DDP9oFKzBsyXARNg3H89IBBd5G9JHpeO4eQdGTPPY' + '4xkGp_zBUnyMJJWTdgXqFjbFHpGpTdD1lSb3TbyeRheAq7IqaAvdqXyaTZVhH7LrZmbw'; /// Service role API key of the local stack, which bypasses row level security. /// /// Signed like [localStackAnonKey]. @visibleForTesting const localStackServiceRoleKey = - 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjNkZjU5YWIxLWI4ZWMtNDlkMy05YzkyLThiOWQ0MmNhYzFmZSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MjA5Njg5NTE5Mn0.jO5vwkRNFZTiVHNjFzaypvWV4aJkKm6TvFsdl0W5x9g7LttQMWMopC7HanUpeFLmg4E9gMb-v1e6f6oZ9e0PHYpsRwEdSOxKfYwKhzFI9DsDGLrX4ueArZuKgaV_bulWpwGKI3xwLugeuCp6N0hYFkXvMmUjaKx9nClWckJ33cchSpgjVQ5YxL8PGrUj2Sjhw-5IyGiwrdPfWjTQmpWnCjePoVrRf2jEMF_VGoxDAEqt72w_HGOrdXRFU5BW9-LkvpfzkrTENrj555JtYP4mkZgvUlrkXFRSh010o3n2UehN5WonfDRzwOeTC56QEbPVS6ubvWGR9luykdMNlXawZA'; + 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjNkZjU5YWIxLWI4ZWMtNDlkMy05YzkyLThiOWQ0MmNhYz' + 'FmZSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2V' + 'fcm9sZSIsImV4cCI6MjA5Njg5NTE5Mn0.jO5vwkRNFZTiVHNjFzaypvWV4aJkKm6TvFsdl0W5x' + '9g7LttQMWMopC7HanUpeFLmg4E9gMb-v1e6f6oZ9e0PHYpsRwEdSOxKfYwKhzFI9DsDGLrX4ue' + 'ArZuKgaV_bulWpwGKI3xwLugeuCp6N0hYFkXvMmUjaKx9nClWckJ33cchSpgjVQ5YxL8PGrUj2' + 'Sjhw-5IyGiwrdPfWjTQmpWnCjePoVrRf2jEMF_VGoxDAEqt72w_HGOrdXRFU5BW9-LkvpfzkrT' + 'ENrj555JtYP4mkZgvUlrkXFRSh010o3n2UehN5WonfDRzwOeTC56QEbPVS6ubvWGR9luykdMNl' + 'XawZA'; /// Secret the local stack signs and verifies HS256 tokens with. @visibleForTesting diff --git a/packages/supabase_common/test/base64url_test.dart b/packages/supabase_common/test/base64url_test.dart index 74057087d..0a6060dbc 100644 --- a/packages/supabase_common/test/base64url_test.dart +++ b/packages/supabase_common/test/base64url_test.dart @@ -60,7 +60,8 @@ void main() { test('decodes JWT payload', () { // Standard JWT payload with sub, name, iat const jwtPayload = - 'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ'; + 'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2Mj' + 'M5MDIyfQ'; final decoded = Base64Url.decodeToString(jwtPayload); final json = jsonDecode(decoded); expect(json['sub'], '1234567890'); diff --git a/packages/supabase_flutter/lib/src/hot_restart_cleanup_web.dart b/packages/supabase_flutter/lib/src/hot_restart_cleanup_web.dart index 4a270f7a3..078b33038 100644 --- a/packages/supabase_flutter/lib/src/hot_restart_cleanup_web.dart +++ b/packages/supabase_flutter/lib/src/hot_restart_cleanup_web.dart @@ -9,7 +9,8 @@ external JSFunction? supabaseFlutterClientToDispose; /// Store a function to properly dispose the previous [SupabaseClient] in /// the js context. /// -/// WebSocket connections and [BroadcastChannel] are not closed when Flutter is hot-restarted on web. +/// WebSocket connections and [BroadcastChannel] are not closed when Flutter is +/// hot-restarted on web. /// /// This causes old dart code that is still associated with those /// connections to be still running and causes unexpected behavior like type diff --git a/packages/supabase_flutter/lib/src/local_storage.dart b/packages/supabase_flutter/lib/src/local_storage.dart index 9f2a91bd3..fa8eadee0 100644 --- a/packages/supabase_flutter/lib/src/local_storage.dart +++ b/packages/supabase_flutter/lib/src/local_storage.dart @@ -19,7 +19,8 @@ const supabasePersistSessionKey = 'SUPABASE_PERSIST_SESSION_KEY'; /// * [SupabaseAuth], the instance used to manage authentication /// * [EmptyLocalStorage], used to disable session persistence /// * [HiveLocalStorage], that implements Hive as storage method -/// * [SharedPreferencesLocalStorage], that implements SharedPreferences as storage method +/// * [SharedPreferencesLocalStorage], that implements SharedPreferences as +/// storage method /// * [MigrationLocalStorage], to migrate from Hive to SharedPreferences abstract class LocalStorage { const LocalStorage(); diff --git a/packages/supabase_flutter/lib/src/supabase.dart b/packages/supabase_flutter/lib/src/supabase.dart index 0119ad77f..b39464cba 100644 --- a/packages/supabase_flutter/lib/src/supabase.dart +++ b/packages/supabase_flutter/lib/src/supabase.dart @@ -43,7 +43,8 @@ class Supabase { static Supabase get instance { assert( _instance._isInitialized, - 'You must initialize the supabase instance before calling Supabase.instance', + 'You must initialize the supabase instance before calling ' + 'Supabase.instance', ); return _instance; } @@ -70,18 +71,21 @@ class Supabase { /// to upload a file to Supabase storage when failed due to network /// interruption. /// - /// Set [authFlowType] to [AuthFlowType.implicit] to use the old implicit flow for authentication - /// involving deep links. + /// Set [authFlowType] to [AuthFlowType.implicit] to use the old implicit flow + /// for authentication involving deep links. /// - /// PKCE flow uses shared preferences for storing the code verifier by default. - /// Pass a custom storage to [pkceAsyncStorage] to override the behavior. + /// PKCE flow uses shared preferences for storing the code verifier by + /// default. Pass a custom storage to [pkceAsyncStorage] to override the + /// behavior. /// - /// If [debug] is set to `true`, debug logs will be printed in debug console. Default is `kDebugMode`. + /// If [debug] is set to `true`, debug logs will be printed in debug console. + /// Default is `kDebugMode`. static Future initialize({ required String url, String? publishableKey, @Deprecated( - 'Use publishableKey instead. anonKey will be removed in a future major version.', + 'Use publishableKey instead. anonKey will be removed in a future major ' + 'version.', ) String? anonKey, Map? headers, @@ -112,7 +116,8 @@ class Supabase { _instance._logSubscription = Logger('supabase').onRecord.listen((record) { if (record.level >= Level.INFO) { debugPrint( - '${record.loggerName}: ${record.level.name}: ${record.message} ${record.error ?? ""}', + '${record.loggerName}: ${record.level.name}: ${record.message} ' + '${record.error ?? ""}', ); } }); @@ -153,7 +158,8 @@ class Supabase { _instance._supabaseAuth = supabaseAuth; await supabaseAuth.initialize(options: authOptions); - // Wrap `recoverSession()` in a `CancelableOperation` so that it can be canceled in dispose + // Wrap `recoverSession()` in a `CancelableOperation` so that it can be + // canceled in dispose // if still in progress _instance._restoreSessionCancellableOperation = CancelableOperation.fromFuture(supabaseAuth.recoverSession()); @@ -181,7 +187,8 @@ class Supabase { bool _debugEnable = false; - /// Wraps the `recoverSession()` call so that it can be terminated when `dispose()` is called + /// Wraps the `recoverSession()` call so that it can be terminated when + /// `dispose()` is called /// /// Only set when [Supabase.initialize] is called without a custom /// `accessToken`, since session recovery is skipped for third-party auth. diff --git a/packages/supabase_flutter/lib/src/supabase_auth.dart b/packages/supabase_flutter/lib/src/supabase_auth.dart index 902ee8948..d0307f7b5 100644 --- a/packages/supabase_flutter/lib/src/supabase_auth.dart +++ b/packages/supabase_flutter/lib/src/supabase_auth.dart @@ -28,8 +28,9 @@ import 'clear_auth_url_parameters_stub.dart' /// - Observes deep links (universal links / custom URL schemes) and exchanges /// auth codes or tokens found in those links for a valid session, supporting /// both PKCE and Implicit OAuth flows. -/// - Forwards Flutter `AppLifecycleState` changes (via `WidgetsBindingObserver`) -/// to the auth client so that token refresh resumes correctly after the app +/// - Forwards Flutter `AppLifecycleState` changes (via +/// `WidgetsBindingObserver`) to the auth client so that token refresh +/// resumes correctly after the app /// returns to the foreground. /// - Emits an [AuthChangeEvent.initialSession] event at startup so that /// listeners receive a consistent first event regardless of whether a stored @@ -79,7 +80,8 @@ class SupabaseAuth with WidgetsBindingObserver { /// - Obtains session from local storage and sets it as the current session /// - Starts a deep link observer - /// - Emits an initial session if there were no session stored in local storage + /// - Emits an initial session if there were no session stored in local + /// storage /// /// Errors emitted by the auth state change stream (e.g. during token refresh /// or network failures) are logged by the underlying auth client and do not @@ -336,8 +338,8 @@ extension GoTrueClientSignInProvider on GoTrueClient { /// ``` /// /// The return value of this method is not the auth result, and whether the - /// OAuth sign-in has succeeded or not should be observed by setting a listener - /// on [auth.onAuthStateChanged]. + /// OAuth sign-in has succeeded or not should be observed by setting a + /// listener on [auth.onAuthStateChange]. /// /// To obtain the OAuth URL without launching a browser, use /// [getOAuthSignInUrl] instead. @@ -402,8 +404,9 @@ extension GoTrueClientSignInProvider on GoTrueClient { /// If you have built an organization-specific login page, you can use the /// organization's SSO Identity Provider UUID directly instead. /// - /// Returns true if the URL was launched successfully, otherwise either returns - /// false or throws a [PlatformException] depending on the launchUrl failure. + /// Returns true if the URL was launched successfully, otherwise either + /// returns false or throws a [PlatformException] depending on the launchUrl + /// failure. /// /// ```dart /// await supabase.auth.signInWithSSO( diff --git a/packages/supabase_flutter/test/auth_test.dart b/packages/supabase_flutter/test/auth_test.dart index 73e918cd0..206ffb98b 100644 --- a/packages/supabase_flutter/test/auth_test.dart +++ b/packages/supabase_flutter/test/auth_test.dart @@ -75,8 +75,9 @@ void main() { ), ); - // Trigger an error on the auth state change stream via notifyException. - // This should not throw or cause an unhandled zone error. + // Trigger an error on the auth state change stream via + // notifyException. This should not throw or cause an unhandled zone + // error. final auth = Supabase.instance.client.auth; // ignore: invalid_use_of_internal_member auth.notifyException( @@ -87,7 +88,8 @@ void main() { // Allow the stream listener to process the error. await Future.delayed(Duration.zero); - // If we reach here the error was not rethrown as an unhandled exception. + // If we reach here the error was not rethrown as an unhandled + // exception. }, ); }); diff --git a/packages/supabase_flutter/test/debug_default_test.dart b/packages/supabase_flutter/test/debug_default_test.dart index 40ce289f4..218cab362 100644 --- a/packages/supabase_flutter/test/debug_default_test.dart +++ b/packages/supabase_flutter/test/debug_default_test.dart @@ -42,8 +42,8 @@ void main() { isFalse, ); }, - // The default relies on the FLUTTER_TEST environment variable, which is only - // readable through dart:io and therefore unavailable on web. + // The default relies on the FLUTTER_TEST environment variable, which is + // only readable through dart:io and therefore unavailable on web. skip: kIsWeb, ); diff --git a/packages/supabase_flutter/test/deep_link_test.dart b/packages/supabase_flutter/test/deep_link_test.dart index d5618b341..35724e786 100644 --- a/packages/supabase_flutter/test/deep_link_test.dart +++ b/packages/supabase_flutter/test/deep_link_test.dart @@ -47,7 +47,8 @@ void main() { }); test( - 'Having `code` as the query parameter triggers `getSessionFromUrl` call on initialize', + 'Having `code` as the query parameter triggers `getSessionFromUrl` call ' + 'on initialize', () async { // Wait for the initial app link to be handled, as this is an async // process when mocking the event channel. @@ -69,9 +70,9 @@ void main() { mockMethodChannel: false, mockEventChannel: true, initialLink: - 'com.supabase://callback/#access_token=my-access-token' - '&expires_in=3600&refresh_token=my-refresh-token' - '&token_type=bearer&type=email_change', + 'com.supabase://callback/#access_token=my-access-token&expires_in=3' + '600&refresh_token=my-refresh-token&token_type=bearer&type=email_ch' + 'ange', ); await Supabase.initialize( url: supabaseUrl, @@ -261,8 +262,7 @@ void main() { mockMethodChannel: false, mockEventChannel: true, initialLink: - 'com.supabase://callback/?error=access_denied' - '&error_code=403', + 'com.supabase://callback/?error=access_denied&error_code=403', ); await Supabase.initialize( url: supabaseUrl, diff --git a/packages/supabase_flutter/test/storage_test.dart b/packages/supabase_flutter/test/storage_test.dart index 5b7dcab2b..58fe873f7 100644 --- a/packages/supabase_flutter/test/storage_test.dart +++ b/packages/supabase_flutter/test/storage_test.dart @@ -55,7 +55,8 @@ void main() { final localStorage = await createFreshLocalStorage(); await localStorage.persistSession(testSessionValue); - // Verify the session was stored by checking through localStorage's own methods + // Verify the session was stored by checking through localStorage's own + // methods final hasToken = await localStorage.hasAccessToken(); expect(hasToken, isTrue); diff --git a/packages/supabase_flutter/test/utils.dart b/packages/supabase_flutter/test/utils.dart index de8189471..6e3e822bc 100644 --- a/packages/supabase_flutter/test/utils.dart +++ b/packages/supabase_flutter/test/utils.dart @@ -17,6 +17,15 @@ import 'dart:convert'; ); final accessToken = 'any.$accessTokenMid.any'; final sessionString = - '{"access_token":"$accessToken","expires_in":${accessTokenExpireDateTime.difference(DateTime.now()).inSeconds},"refresh_token":"-yeS4omysFs9tpUYBws9Rg","token_type":"bearer","provider_token":null,"provider_refresh_token":null,"user":{"id":"4d2583da-8de4-49d3-9cd1-37a9a74f55bd","app_metadata":{"provider":"email","providers":["email"]},"user_metadata":{"Hello":"World"},"aud":"","email":"fake1680338105@email.com","phone":"","created_at":"2023-04-01T08:35:05.208586Z","confirmed_at":null,"email_confirmed_at":"2023-04-01T08:35:05.220096086Z","phone_confirmed_at":null,"last_sign_in_at":"2023-04-01T08:35:05.222755878Z","role":"","updated_at":"2023-04-01T08:35:05.226938Z"}}'; + '{"access_token":"$accessToken","expires_in":' + '${accessTokenExpireDateTime.difference(DateTime.now()).inSeconds},"refre' + 'sh_token":"-yeS4omysFs9tpUYBws9Rg","token_type":"bearer","provider_token' + '":null,"provider_refresh_token":null,"user":{"id":"4d2583da-8de4-49d3-9c' + 'd1-37a9a74f55bd","app_metadata":{"provider":"email","providers":["email"' + ']},"user_metadata":{"Hello":"World"},"aud":"","email":"fake1680338105@em' + 'ail.com","phone":"","created_at":"2023-04-01T08:35:05.208586Z","confirme' + 'd_at":null,"email_confirmed_at":"2023-04-01T08:35:05.220096086Z","phone_' + 'confirmed_at":null,"last_sign_in_at":"2023-04-01T08:35:05.222755878Z","r' + 'ole":"","updated_at":"2023-04-01T08:35:05.226938Z"}}'; return (accessToken: accessToken, sessionString: sessionString); } diff --git a/packages/supabase_lints/lib/analysis_options.yaml b/packages/supabase_lints/lib/analysis_options.yaml index 730e8cc42..daacc5064 100644 --- a/packages/supabase_lints/lib/analysis_options.yaml +++ b/packages/supabase_lints/lib/analysis_options.yaml @@ -16,6 +16,7 @@ linter: discarded_futures: true use_enums: true unnecessary_breaks: true + lines_longer_than_80_chars: true formatter: trailing_commas: preserve diff --git a/packages/yet_another_json_isolate/lib/yet_another_json_isolate.dart b/packages/yet_another_json_isolate/lib/yet_another_json_isolate.dart index 8972ca3aa..dd8c2ecb6 100644 --- a/packages/yet_another_json_isolate/lib/yet_another_json_isolate.dart +++ b/packages/yet_another_json_isolate/lib/yet_another_json_isolate.dart @@ -1,4 +1,5 @@ -/// Simplifies JSON parsing in isolates by keeping one isolate running per instance. +/// Simplifies JSON parsing in isolates by keeping one isolate running per +/// instance. library; export 'src/_isolates_io.dart'