feat(supabase_flutter)!: use a system web auth session for OAuth, SSO and identity linking#1403
Draft
spydon wants to merge 77 commits into
Draft
feat(supabase_flutter)!: use a system web auth session for OAuth, SSO and identity linking#1403spydon wants to merge 77 commits into
spydon wants to merge 77 commits into
Conversation
… and identity linking Route signInWithOAuth, signInWithSSO and linkIdentity through flutter_web_auth_2 on native and desktop platforms, so the auth screen runs in ASWebAuthenticationSession on Apple platforms and Custom Tabs on Android. The session captures the redirect itself and closes automatically, fixing the blank in-app browser that was left open after a successful sign in (#1174). On web the current tab is redirected as before. url_launcher is dropped as a direct dependency and the LaunchMode export is removed. The authScreenLaunchMode/launchMode parameters are replaced by a preferEphemeral option that maps to the web auth session. BREAKING CHANGE: signInWithOAuth, signInWithSSO and linkIdentity no longer accept authScreenLaunchMode/launchMode, and the LaunchMode export is removed. Android apps must register the flutter_web_auth_2 CallbackActivity for their redirect scheme. The OAuth callback no longer arrives through the app_links deep link handler.
…1536) > Stacked on #1535. Review/merge that one first. ## What kind of change does this PR introduce? Bug fix ## What is the current behavior? `getAuthorizationDetails` crashed with a `FormatException` when the OAuth 2.1 server short-circuited the consent flow. This happens when a user who already approved a client starts a new authorization for it: the server returns a redirect-only body (`{"redirect_url": "...?code=..."}`) with no user or client, which the parser rejected because it required a user object. ## What is the new behavior? `getAuthorizationDetails` now checks whether a `redirect_url` is present returning `OAuthAuthorizationRedirectResponse` when it is present. When the key isn't present the old `OAuthAuthorizationDetailsResponse` object is returned. Callers now switch on the result and forward the redirect instead of losing it to an exception. Genuinely malformed detail bodies (no redirect_url and no user) still throw, so validation isn't weakened. ## Additional context BREAKING CHANGE: `getAuthorizationDetails` now returns the sealed `OAuthAuthorizationResponse` instead of `OAuthAuthorizationDetailsResponse`. Callers must switch/cast to access client, user, scope and redirectUri.
## What
The `update-version` melos script (run as a `preCommit` hook during
`melos version`) iterated over every `packages/*/` and redirected output
into `$d/lib/src/version.dart`. This failed for `supabase_lints`, which
is a lints-only package with no `lib/src/` directory:
```
ERROR: /bin/sh: line 4: packages/supabase_lints//lib/src/version.dart: No such file or directory
```
The package was added after the script was written and was never
accounted for.
## Fix
Only update packages that already have a `version.dart` file:
```sh
[ -f "${d}lib/src/version.dart" ] || continue
```
This naturally skips `supabase_lints` (no `lib/src/version.dart`) and
`yet_another_json_isolate` (intentionally keeps no tracked
`version.dart`), which also lets us drop the fragile `rm
packages/yet_another_json_isolate/lib/src/version.dart` line.
## Verification
Dry-run of the updated loop writes `version.dart` for the seven packages
that have one and skips both `supabase_lints` and
`yet_another_json_isolate`.
- gotrue@2.26.0 - realtime_client@2.11.0 - supabase@2.14.0 - supabase_flutter@2.16.0
## What Housekeeping pass over every package to make the published pub.dev metadata and supporting files consistent and correct. ### pubspec.yaml (all packages) - Add `topics` (pub.dev tags) to every package. - Add `issue_tracker` pointing to the monorepo issues. - Fix `yet_another_json_isolate` metadata that pointed at the stale `supabase-community/json-isolate-dart` repo (`homepage`/`repository`/`issue_tracker`). - Fix the broken `documentation` link in `storage_client` (`storage-createbucket` → `file-buckets-createbucket`). ### READMEs - Standardize every package README to the same header format as `supabase_flutter` (logo, title, description, `Guides · Reference Docs`, badges). - Fix stale badge URLs that pointed at archived per-package repos (`gotrue-dart`, `postgrest-dart`, `realtime-dart`, `storage-dart`, `supabase-dart`) to the monorepo. - Use the Supabase wordmark logo (`logo-preview.jpg`). A single `<img>` is used instead of a `<picture>` element because pub.dev does not honor `<picture>` / `prefers-color-scheme`, which would leave a white-text logo invisible on pub.dev's light background. See dart-lang/pub-dev#6363. The `logo-preview.jpg` asset has a dark card baked in, so it stays visible on both light and dark backgrounds. - Add missing `License` sections to `yet_another_json_isolate` and `supabase_lints`. ### Examples - Replace the empty `functions_client` example with a real invoke example. - Fix the misspelled `annonToken` variable and align naming in the `gotrue` example. - Update stale `supabase.io` comment URLs to `supabase.com`. - Fix a 404 link in the `supabase` example README. ### Not touched - CHANGELOG files (auto-generated by release tooling). - LICENSE files (verified, all MIT). ## Notes - Related upstream issue: dart-lang/pub-dev#6363 (pub.dev support for `<picture>` / `prefers-color-scheme`). Resolves SDK-1246
## What Fixes the WASM incompatibility that lowers `storage_client`'s pub.dev platform support score (partial 10/20 with the note "Package not compatible with runtime wasm"). ## Why Two conditional imports were gated on `dart.library.js`: ```dart import 'file_io.dart' if (dart.library.js) './file_stub.dart'; ``` `dart.library.js` refers to the **legacy** JS library, which is **not** defined under WASM (WASM exposes `dart.library.js_interop`). So a WASM build evaluates the condition to `false` and falls through to the default `file_io.dart`, which does `export 'dart:io' show File;`. `dart:io` is unavailable on web, so WASM compilation fails. ## How Invert the conditional to the WASM-safe pattern already used by the other packages (`platform_stub` / `dart.library.io`): default to the stub, and only import `dart:io` when it actually exists. ```dart import 'file_stub.dart' if (dart.library.io) './file_io.dart'; ``` - WASM / web: `dart.library.io` is false → uses `file_stub.dart` (no `dart:io`) - Native (VM, mobile, desktop): `dart.library.io` is true → uses `file_io.dart` `storage_client` is a transitive dependency of `supabase` and `supabase_flutter`, so this clears the WASM incompatibility for those too. ## Verification - `dart analyze` passes clean - `pana` now reports: **WASM-ready: This package is compatible with runtime `wasm`.**
## What Adds a `Release Pana` workflow that runs [pana](https://pub.dev/packages/pana) on every publishable package, but only on release PRs (title starts with `chore(release):`, created by the melos versioning action). ## Why `storage_client` recently shipped a WASM incompatibility that dropped its pub.dev platform-support score to a partial 10/20 (see #1543). Nothing in CI caught it before publishing. Release PRs are the last gate before packages go to pub.dev, so that's where a pana check belongs. ## How - **Trigger:** `pull_request` (`opened`, `synchronize`, `reopened`, `edited`), gated by a job-level `if: startsWith(github.event.pull_request.title, 'chore(release):')`. It is skipped (cheaply) on all non-release PRs. - **Setup:** Flutter + Melos (so both Dart and Flutter packages resolve), then `dart pub global activate pana`. - **Matrix:** one job per publishable runtime package (`functions_client`, `gotrue`, `postgrest`, `realtime_client`, `storage_client`, `supabase`, `supabase_flutter`, `yet_another_json_isolate`). `supabase_lints` is excluded since platform/WASM support isn't meaningful for a lint package. - **Report:** the full pana score table (per section) and tags are written to the job summary for every package. - **Hard fail:** if a package reports `platform:web` but not `is:wasm-ready`. This is the exact regression class from #1543, and keying off these tags avoids false positives on native-only or reduced-platform packages. The jq/tag logic was verified against real `pana --json` output locally.
## What Bumps the `melos` dev_dependency from `^8.1.0` to `^8.2.0` and updates `pubspec.lock` accordingly. ## Why The "Detect affected packages" CI job runs `dart pub global activate melos` (now 8.2.0) on a Dart-only runner. When the committed lock pinned 8.1.0, the version mismatch made cli_launcher relaunch the pinned melos via `dart run`, whose implicit `dart pub get` fails on this Flutter pub-workspace: ``` Because passkeys_example requires the Flutter SDK, version solving failed. Flutter users should use `flutter pub` instead of `dart pub`. ``` Keeping the pin in sync with the released version means global == local, so no relaunch happens and the Dart-only job resolves cleanly. The underlying launcher issue is fixed separately in cli_launcher (relaunch via `flutter pub run` for Flutter workspaces).
…on/FunctionsHttpException subtypes (#1545) ## What Introduces three error subtypes as subclasses of the existing `FunctionException`, so consumers can match on the failure mode: - **`FunctionsFetchException`** — network/transport failure reaching the function, before any response is received. The originating error is available in `details`; `status` is `0`. - **`FunctionsRelayException`** — error returned by the relay, detected via the `x-relay-error: true` response header. - **`FunctionsHttpException`** — non-2xx response from the function itself. `invoke()` now throws the appropriate subtype depending on the failure mode. ## Naming The subtypes use the `Exception` suffix (not `Error`) to follow Dart's `Error`/`Exception` distinction: they are meant to be caught, extend `FunctionException` (which implements `Exception`), and wrap an underlying `ClientException`. This also keeps them consistent with the base class name. ## Why non-breaking Existing `catch` blocks that catch `FunctionException` continue to work unchanged; the new subtypes only refine the type consumers can optionally match on. No existing signature or default behavior changes. `toString()` now uses `\$runtimeType` so subtypes print their own name. A follow-up for making `FunctionException` a `sealed` class (breaking, enabling exhaustive `switch`) is tracked in #1550 for v3. ## Tests Added `network-error` and `relay-error` fixtures to the custom HTTP client, plus cases asserting each subtype is thrown, that `status`/`details`/`reasonPhrase` are populated, and that the subtypes remain catchable as `FunctionException`. Closes SDK-1258
…dpoint (#1551) ## Summary An empty or default `TransformOptions()` was routing `download()` and `getPublicUrl()` through the `/render/image/` endpoint unnecessarily. Previously the decision was based on `transform != null`, so any `TransformOptions` instance, even one with all-null fields, switched to the render path. Now routing only switches to the render endpoint when the transform options actually produce query parameters. **Outcome:** implemented (bug fix) ## Behavior - `download(path)` without transform uses `/object/` endpoint - `download(path, transform: TransformOptions())` with empty transform uses `/object/` endpoint - `download(path, transform: TransformOptions(width: 200))` uses `/render/image/authenticated/` endpoint - Same logic for `getPublicUrl()` ## Tests Added unit tests in `packages/storage_client/test/basic_test.dart` covering empty vs actual transform routing for both `download()` and `getPublicUrl()`. All storage_client unit tests pass. ## Reference - Linear: SDK-878 - supabase-js PR: supabase/supabase-js#2219 (commit `993bb5fc`) ## Compliance matrix `storage.file_buckets.download_with_transform` and `storage.file_buckets.get_public_url` remain `implemented`; this fix brings their routing behavior in line with supabase-js, so no status change was required.
## Summary Adds an optional `currentPassword` field to `UserAttributes`. When the auth server has `GOTRUE_SECURITY_UPDATE_PASSWORD_REQUIRE_CURRENT_PASSWORD` enabled, changing a password requires the current password to be sent for verification. The field is serialized as `current_password`, which is the key the auth server reads (`json:"current_password"`). Note that supabase-js currently sends the raw camelCase `currentPassword`, which the server does not read; a separate fix has been opened against supabase-js. ## Changes - `packages/gotrue/lib/src/types/user_attributes.dart` — add nullable `currentPassword` field, include it in `toJson()` as `current_password`, and in `==`/`hashCode`. - `packages/gotrue/test/src/types/user_attributes_test.dart` — tests for serialization, null-omission, and equality. ## Outcome Implemented. ## References - Linear: SDK-753 - supabase-js: PR #2131 / commit f681484 - auth server: supabase/auth PR #2364 ## Compliance matrix `auth.session.update_user` already tracked as `partially_implemented`; no status change.
#1555) ## Summary `OAuthProvider` already accepts arbitrary identifiers, so a custom OIDC provider passed as `OAuthProvider('custom:my-oidc-provider')` already worked at runtime and was forwarded to the GoTrue API unchanged, matching supabase-js's `custom:` prefix behavior. This PR does not change that runtime behavior. It cleans up the internal serialization and adds coverage and documentation for it. ## What changed - Switched provider serialization in `signInWithIdToken` and `getOAuthSignInUrl` from the deprecated `OAuthProvider.snakeCase` getter to `OAuthProvider.name`. Both return the identifier verbatim, so this is behavior-neutral. - Added unit tests confirming custom OIDC provider sign-in URL generation, with and without options. - Updated the compliance matrix note under `auth.sign_in.sign_in_with_oauth` to record that custom OIDC providers are supported. ## Notes - No normalization is applied to the provider name. The value is forwarded to the server as-is, so built-in providers keep working unchanged and there are no breaking changes. - The `snakeCase` to `name` change does not affect built-in providers, because `snakeCase` already returned `name` verbatim. ## Reference - supabase-js PR: supabase/supabase-js#2134 - Linear: SDK-752
…token (#1565) ## What Removes the `jwt_decode` dependency from `gotrue`. Its two remaining uses were both unauthenticated payload decodes, which are now handled by the existing `decodeJwt` helper (built on `dart_jsonwebtoken`, already a dependency). ## Changes - `session.dart` — `Jwt.parseJwt(accessToken)['exp']` → `decodeJwt(accessToken).payload.exp` - `gotrue_mfa_api.dart` — `Jwt.parseJwt(session.accessToken)` → `decodeJwt(session.accessToken).payload.claims` (keeps `aal`/`amr` lookups working) - `gotrue_client.dart` — dropped the now-unused `jwt_decode` import - `client_test.dart` — updated assertions to use `decodeJwt(...).payload.exp` - `pubspec.yaml` — removed `jwt_decode: ^0.3.1` Signature verification via `dart_jsonwebtoken`'s `JWT.verify` is untouched. Behavior is preserved: both replaced uses were plain payload decodes without verification. `dart pub get` resolves cleanly (no `jwt_decode` in the lock) and `dart analyze lib test` reports no issues. Closes SDK-475.
…lushing (#1553) ## Summary When `subscribe()` runs before an asynchronous access token has resolved (common when the token is read from async storage, e.g. React Native / Expo, or a third-party `accessToken` callback), the buffered `phx_join` payload captured at subscribe time carries no `access_token`. When the connection opens the token-less join is flushed to the server, which accepts the join but cannot evaluate RLS, so `postgres_changes` events are silently filtered out. This ports the supabase-js fix to the Dart realtime client: on connection open, the access token is now resolved before the send buffer is flushed. Once auth has settled, buffered channel join payloads are patched with the resolved token, the stale buffered joins are dropped, and the join is re-sent for any channel still joining. ## Outcome **implemented** (bug fix). No public API changes; the fix is internal to the connect flow in `RealtimeClient`. ## Changes - `packages/realtime_client/lib/src/realtime_client.dart` — `_onConnOpen` now delegates to a new async `_resolveAccessTokenAndFlush` that resolves the token via `setAuth`, patches each channel's join payload, clears the stale send buffer, and re-sends joins for channels still joining before flushing. - `packages/realtime_client/test/socket_test.dart` — new test verifying a buffered join is re-sent with the resolved token patched into its payload. - `sdk-compliance.yaml` — added `realtime.client.join_payload_access_token` as `implemented`. ## Testing - `packages/realtime_client` unit tests (`socket_test.dart`, `channel_test.dart`) pass. - Realtime integration tests (protocol 1.0.0 and 2.0.0) pass against the local backend. ## References - Linear: SDK-754 - supabase-js: PR #2136, commit `c35fba4` - Compliance matrix: `realtime.client.join_payload_access_token` → `implemented`
## What Adds an optional `channel` parameter (`sms` / `whatsapp`) to `GoTrueMFAApi.challenge()` for phone factors, matching supabase-js. ```dart await supabase.auth.mfa.challenge( factorId: factorId, channel: OtpChannel.whatsapp, ); ``` ## Why Parity gap tracked in `sdk-compliance.yaml` (`auth.mfa.challenge` was `partially_implemented`): there was no way to pick the messaging channel for phone MFA challenges, so it always used SMS. ## How - Reuses the existing `OtpChannel` enum (already used by `signInWithOtp`, `signUp`, `resend`) instead of introducing a new type. - The parameter is nullable and only added to the request body when provided, so when omitted the server's existing SMS default applies. This keeps the change non-breaking; existing callers send the exact same request as before. - Updated `sdk-compliance.yaml` to mark `auth.mfa.challenge` as `implemented`. ## Tests Added `packages/gotrue/test/mfa_challenge_mock_test.dart` covering the default (no channel), `whatsapp`, and `sms` cases via a mock HTTP client that asserts the forwarded request body. Resolves SDK-1260.
## What Marks `functions.invocation.timeout` as `not_applicable` in `sdk-compliance.yaml` instead of implementing it. ## Why This started as an implementation of a per-invocation `timeout` on `FunctionsClient.invoke()`. As raised in review, wrapping the returned `Future` in `Future.timeout` does **not** abort the in-flight request. So a caller could still see the function succeed after the timeout fires, and the behavior is identical to the caller applying `.timeout()` on the result themselves. Exposing it as an `invoke()` option therefore adds no real capability and would be misleading, so the implementation is dropped and the capability is recorded as `not_applicable` with an explanatory note. A genuinely useful version would require request cancellation (tracked separately as `functions.invocation.request_cancellation`). ## Changes - Added a `not_applicable` status to the legend in `sdk-compliance.yaml`. - Set `functions.invocation.timeout` to `not_applicable` with a note explaining the no-op wrapper reasoning. Closes SDK-1262.
…auth parity implemented (#1548) ## What Marks `auth.sign_in.sign_in_with_oauth` as `implemented` in the SDK compliance matrix and documents the URL-without-launch path. - `sdk-compliance.yaml`: `auth.sign_in.sign_in_with_oauth` moves from `partially_implemented` to `implemented`. - `signInWithOAuth` doc comment now points callers to `getOAuthSignInUrl` when they want the OAuth URL without launching a browser. ## Why The compliance gap was "the `skipBrowserRedirect` option is not publicly exposed." In supabase-js `skipBrowserRedirect` does two things: (1) suppresses the automatic `window.location.assign(url)` browser navigation, and (2) adds a `skip_http_redirect` server hint. In Flutter, half (1) is already the default behavior of `getOAuthSignInUrl`, which builds and returns the OAuth `OAuthResponse` (including the URL) without ever launching a browser. That is the meaningful Flutter equivalent of "skip the browser redirect and hand me the URL." Adding an explicit `skipBrowserRedirect` flag was considered and dropped: - On `signInWithOAuth` it would only build the URL and discard it, since that method returns `Future<bool>` (launch success), not the URL. - On `getOAuthSignInUrl` it would only toggle the `skip_http_redirect` server hint, which is a niche flag that makes the server return JSON instead of a 302, undesirable for the common flow of opening the URL in a browser/webview, and oddly named on a method that never touches a browser. So the capability is satisfied by the existing API; this PR reclassifies it and adds a doc pointer. ## Non-breaking Documentation and compliance-metadata only; no API or behavior change. Closes SDK-1261
…extension (#1562) ## What Four OAuth client enums in `packages/gotrue/lib/src/types/types.dart` hardcoded a `value` field that was just the snake_case of the entry name. This drops that duplication and derives the wire value from the existing `Enum.snakeCase` extension instead. ## Changes `types.dart`: Convert `OAuthClientGrantType`, `OAuthClientResponseType`, `OAuthClientType`, and `OAuthClientRegistrationType` to plain enums (drop the `value` field and constructors) and replace every `.value` use with `.snakeCase` across `fromString`, `OAuthClient.fromJson`, and the `toJson` methods. Enums with values that deviate from a clean snake_case conversion (`ErrorCode`'s `...Disabled` → `..._not_enabled`, `AMRMethod`'s slash-separated values, `AuthChangeEvent`'s SCREAMING_SNAKE) were intentionally left untouched. ## Testing - `dart analyze lib` clean - `dart test test/src/gotrue_oauth_api_test.dart` passes (8/8)
## What Removes the `rxdart` dependency from `gotrue`. It was used in exactly one place: the two `BehaviorSubject<AuthState>` controllers backing `onAuthStateChange` and `onAuthStateChangeSync`. They are replaced with a small, private `_ReplaySubject<T>` built on top of the standard `dart:async` primitives (`StreamController.broadcast` + `Stream.multi`). ## Why `rxdart` was pulling in a whole reactive-extensions library for a single feature: latest-value replay for late subscribers. ## Preserved behavior - **Latest-value replay** — a new subscriber immediately receives the current `AuthState` on `.listen()`. - **`sync: true`** delivery for `onAuthStateChangeSync` (synchronous event ordering). - **Error propagation** — network errors are still emitted as stream errors (and the last error is replayed to late subscribers). - **Broadcast semantics** — multiple simultaneous listeners. The forwarding of live events is done synchronously through the per-listener `Stream.multi` controller so the underlying broadcast controller's scheduling is the only hop. This keeps delivery latency identical to `BehaviorSubject` (a naive double-controller would add an extra microtask, which broke an ordering-sensitive test). ## Testing - Full `gotrue` unit suite passes; the remaining failures are pre-existing integration tests that require a running GoTrue server (confirmed identical failures on `main`). - Stream behavior (replay, broadcast, sync delivery, error replay/propagation, done-on-close) validated against `BehaviorSubject` semantics. Closes SDK-1278.
## What Marks `database.configuration.request_timeout` as `not_applicable` in `sdk-compliance.yaml`, and adds the `not_applicable` status to the legend. ## Why The canonical capability requires cancelling in-flight requests once the deadline is reached. Over Dart's `http.Client` transport there is no cancellation primitive, so the only Dart-native option is `Future.timeout`, which lets the request keep running. That is functionally identical to a caller applying `.timeout()` on the result themselves (the PostgREST builder's existing `.timeout()` already behaves this way), so exposing it as a construction-time option adds no real capability and would be misleading. This is the same reasoning applied to `functions.invocation.timeout` in #1546. A genuinely useful version would require `database.using_modifiers.request_cancellation`, which stays tracked as `not_implemented`. ## Changes - Added the `not_applicable` status to the legend in `sdk-compliance.yaml`. - Set `database.configuration.request_timeout` to `not_applicable` with an explanatory note.
## Summary SDK-701 tracks a supabase-js bug fix (PR supabase/supabase-js#1763, commit f99a58c) in the WebAuthn MFA enrollment flow: `mfa.webauthn.enroll` runs the WebAuthn ceremony inline in the browser and patches the credential creation options' `user.name` before calling `navigator.credentials.create()`. The fix corrects the fallback naming so a provided `friendlyName` is used, falling back to user data only when it is absent. ## Outcome: not_applicable The Dart SDK does not perform the client-side WebAuthn ceremony inline. It: - exposes only the server side of the ceremony through the `passkey` API in `gotrue` (get options / verify credential), which never touches `user.name`; - delegates the device ceremony to a platform passkey plugin, with `supabase_flutter`'s `passkey_options_mapper.dart` handing the raw server options to the plugin; - rejects `webauthn` in `mfa.enroll` (already documented on `auth.mfa.enroll`), routing WebAuthn through the passkey API instead; - has no `friendlyName` parameter on passkey registration, as the server generates the friendly name. There is therefore no client-side option-patching step where the SDK-701 bug could exist, and the `friendlyName` fallback cannot be reproduced faithfully in the passkey idiom. This is a JS/browser-ceremony concern with no meaningful Dart equivalent. ## Compliance matrix Added `auth.mfa.webauthn_enroll` → `not_applicable` with a note explaining the divergence, and documented the `not_applicable` status in the header comment block. Ref: SDK-701
## What Modernizes dependency and `dev_dependency` constraints across the whole workspace to the newest versions that still resolve on the declared minimum Dart SDK of `>=3.9.0`, as tracked in SDK-1279. The SDK constraint itself is unchanged. ## Dependency bumps - Raised floors on direct dependencies: `http` `^1.6.0`, `logging` `^1.3.0`, `collection` `^1.19.0`, `crypto` `^3.0.7`, `meta` `^1.16.0`, `retry` `^3.1.2`, `rxdart` `^0.28.0`, `mime` `>=2.0.0`, `http_parser` `^4.1.0`, `web` `>=1.0.0`, `web_socket_channel` `>=3.0.0`, `dart_jsonwebtoken` `>=3.0.0`, `url_launcher` `^6.3.2`, `shared_preferences` `^2.5.5`. - Development tooling: `lints` and `flutter_lints` `^4.0.0` to `^6.0.0`, `mocktail` `^1.0.5`, `postgres` `^3.5.0`, `dotenv` `^4.2.0`, `otp` `^3.2.0`, `mason_logger` `^0.3.5`. - `app_links` floor raised to `>=6.4.1` while keeping the `<8.0.0` upper bound. Version 7.x requires Dart `>=3.10`, so the floor stays resolvable on the 3.9.0 minimum while consumers on newer SDKs still get 7.x. ## Staying resolvable on the minimum Flutter 3.35 SDK Local development runs on a newer Dart, so a few packages had to be held below their latest release to remain resolvable on Flutter 3.35 (Dart 3.9.0), which is the minimum this repository supports: - `test` is capped at `^1.25.0`. On Flutter 3.35, `flutter_test` pins `test_api 0.7.6`, and `test >=1.31.0` requires a newer `test_api` (and Dart `>=3.10`). This floor resolves to the 1.26.x line on the minimum SDK and to the latest elsewhere. - `async` `^2.12.0` and `path` `^1.9.0` stay at or below the versions vendored by Dart 3.9.0. ## Consolidating on supabase_lints - Every example now depends on `supabase_lints` instead of `flutter_lints` or `lints` directly, and includes the shared analysis options. ## Follow-on lint and analyzer fixes - `lints` 6 introduces `unnecessary_underscores`, so `(_, __, ___)` wildcard parameters in `realtime_client` were collapsed to `(_, _, _)`. - `gotrue`: use a null-aware map element (available since Dart 3.8) and drop inferrable type arguments to satisfy DCM. - `supabase_lints`: disable `prefer-shorthands-with-constructors`, which requires the still-experimental dot shorthands, alongside the other shorthand rules already disabled for the 3.9.0 floor. - `supabase_flutter` example: switching it to `supabase_lints` surfaced real DCM findings, now fixed in code (guard `setState` behind `mounted` after awaits, extract async button handlers so callbacks stay synchronous, and cancel the auth stream subscription). Only `prefer-single-widget-per-file` is relaxed, since keeping a single-file demo in one file is intentional. ## Verification - `melos bootstrap`, `melos analyze` (`--fatal-infos`), `melos format --set-exit-if-changed`, and `dcm analyze packages` all pass across the 13 packages. - `dart pub outdated` no longer reports any dependency held back by our own constraints. The remaining entries are pinned by the Flutter and Dart SDK environment. - Mock and unit test suites pass: `yet_another_json_isolate`, `postgrest` (172, run serially), `functions_client` (41), `supabase_flutter` (59), and the `realtime_client` channel tests (54). The server-backed integration suites (gotrue, storage, realtime socket, supabase) require the docker test infrastructure with per-run database reset and are validated in CI.
…flag (#1558) ## Summary Implements the two `partially_implemented` client initialization options tracked by SDK-1272: - **`FlutterAuthClientOptions.detectSessionInUriPredicate`** — an optional `bool Function(Uri)` predicate that decides whether an incoming deep link should be treated as an auth callback and exchanged for a session. When null, the existing default heuristic (checks for `access_token`, `code`, `error`, `error_code`, `error_description` in the query or fragment) is used. This lets apps disambiguate redirects when they use those same parameters for other purposes. - **`FlutterAuthClientOptions.persistSession`** — a real flag (default `true`). When `false` and no `localStorage` is supplied, sessions are kept in memory only (`EmptyLocalStorage` is selected automatically), so opting out of persistence no longer requires manually wiring up an `EmptyLocalStorage` backend. Supplying a custom `localStorage` still takes precedence. Both are new optional initialization options, so existing `Supabase.initialize` calls are unaffected. ## Outcome implemented ## Reference Derived from the compliance matrix (`sdk-compliance.yaml`); mirrors supabase-js `detectSessionInUrl` / `persistSession` GoTrue client options. ## Tests Added to `packages/supabase_flutter/test/deep_link_test.dart`: - predicate returning `false` suppresses detection of an otherwise valid auth callback - predicate governs detection based on the incoming URI (and receives the URI) - `persistSession: true` persists to the default storage - `persistSession: false` keeps the session in memory only All 63 `supabase_flutter` tests pass; `melos format` and `dart analyze` clean. ## Compliance matrix - `client.authentication_integration.session_url_detection` → `implemented` - `client.session_management.persist_session` → `implemented` SDK-1272
## Summary Implements the postgrest parity ticket SDK-1269, bundling three additive query-builder capabilities from the SDK compliance matrix. - **`dryRun()`** — new `PostgrestTransformBuilder.dryRun()` modifier that appends `Prefer: tx=rollback`, so the query runs but the transaction is rolled back and no changes are persisted (supabase-js `rollback()`). - **`stripNulls()`** — new `PostgrestTransformBuilder.stripNulls()` modifier that appends `;nulls=stripped` to the `Accept` header, omitting null-valued properties from the response (requires PostgREST 11.2+). - **rpc `head`/`count`** — no code change. In Dart these are already available through the type-safe `.head()` / `.count()` chaining, which is the idiomatic builder pattern. Inline call options cannot preserve the three distinct return types (`PostgrestFilterBuilder<T>`, `PostgrestBuilder<void>`, `ResponsePostgrestBuilder<PostgrestResponse<T>>`) since Dart has no method overloading, so the matrix entry is reconciled to `implemented` with a note. ## Outcome implemented ## Reference Canonical capability registry: `supabase/sdk` `capabilities/database.yaml` (`dry_run`, `strip_nulls`). supabase-js `rollback()` in `postgrest-js` `PostgrestTransformBuilder.ts`. ## Compliance matrix - `database.using_modifiers.dry_run` → `implemented` - `database.using_modifiers.strip_nulls` → `implemented` - `database.query.rpc` → `implemented` (was `partially_implemented`) ## Tests Added header-assertion and backend-behavior tests in `packages/postgrest/test/transforms_test.dart`. Full postgrest suite passes (176 tests) against a local `supabase start` backend. SDK-1269
## What Removes the `rxdart` dependency from the `supabase` package. It was used in exactly one place: `SupabaseStreamBuilder`, which backed its stream controllers with rxdart's `BehaviorSubject`. They are replaced with a small, private `_BehaviorSubject<T>` built on top of the standard `dart:async` primitives (`StreamController.broadcast` + `Stream.multi`). ## Why `rxdart` was pulling in a whole reactive-extensions library for a single feature: latest-value replay for late subscribers. This mirrors what #1566 already did for `gotrue`, and removes `rxdart` from the monorepo entirely. Note: #1566 only dropped rxdart from `gotrue`; the `supabase` package used it independently, and #1567 merely bumped its version constraint. ## Preserved behavior - **Latest-value replay** — a new subscriber immediately receives the last emitted event on `.listen()`. - **Error propagation** — errors are still emitted as stream errors (and the last error is replayed to late subscribers). - **Broadcast semantics** — multiple simultaneous listeners. - `onListen` / `onCancel` lifecycle hooks used to lazily start/tear down the realtime + postgrest fetch. ## Testing `dart analyze` clean and all `supabase` package tests pass (mock_test.dart, client_test.dart).
## What
Implements the Storage **Vector Buckets** subsystem in `storage_client`,
closing all 14 `storage.vector_buckets.*` gaps in the compliance matrix.
Reachable through `supabase.storage.vectors`, mirroring the supabase-js
`StorageVectorsClient` structure while staying idiomatic to the Dart
storage client (operations return parsed data directly and throw
`StorageException` on failure instead of a `{data, error}` envelope).
```dart
final vectors = supabase.storage.vectors;
await vectors.createBucket('embeddings');
final bucket = vectors.from('embeddings');
await bucket.createIndex(
name: 'documents',
dimension: 1536,
distanceMetric: DistanceMetric.cosine,
);
final index = bucket.index('documents');
await index.putVectors([
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);
```
## Capabilities
Bucket & index management (`SupabaseVectorsClient`,
`StorageVectorBucketApi`):
- create / list / delete vector bucket, get bucket metadata
- scope to a bucket via `.from(name)` and to an index via `.index(name)`
- create / get / list / delete index
Vector data operations (`StorageVectorIndexApi`):
- put / get / list / query / delete vectors
- parallel scan via `listVectors`' `segmentCount` / `segmentIndex`
## Idiomatic Dart choices
- `dataType` and `distanceMetric` are enums (`VectorDataType`,
`DistanceMetric`), parsed leniently (unknown values decode to `null` for
forward compatibility).
- `metadataConfiguration` is flattened to a `nonFilterableMetadataKeys`
parameter.
- Batch sizes (put/delete 1-500) and parallel-scan segments (1-16) are
validated client-side with `ArgumentError`.
## Tests
- 20 self-contained unit tests using the mock HTTP client, verifying
request URLs/bodies and response parsing for every operation, plus the
validation paths.
## Compliance matrix
- All 14 `storage.vector_buckets.*` features set to `implemented` with
every new public symbol registered. The symbol check and compliance-file
validator both pass locally.
Closes SDK-1307.
## Summary
Adds `signInWithWeb3` support to `gotrue`, wrapping the GoTrue `POST
/token?grant_type=web3` endpoint used for Web3 wallet authentication
(Sign-In with Ethereum and Sign-In with Solana, both derived from
EIP-4361).
- `GoTrueClient.signInWithWeb3({required Web3Chain chain, required
String message, required String signature, String? captchaToken})`
exchanges a signed message for a session, saves it, and emits
`AuthChangeEvent.signedIn`.
- New `Web3Chain` enum (`ethereum`, `solana`).
- Exposed through `supabase` / `supabase_flutter` automatically via the
existing `auth` getter.
Message signing and wallet interaction are delegated to the caller
(using `web3dart`, `walletconnect_dart`, or a Solana wallet library).
This matches the platform-agnostic path in supabase-js:
`window.ethereum` / `window.solana` auto-detection has no Flutter
equivalent, so the SDK does not build the message or auto-detect a
browser wallet.
## Outcome
**implemented** — for `Web3Chain.ethereum` the signature is a hex
string, for `Web3Chain.solana` it is a base64url string, matching the
wire format supabase-js sends.
## Tests
`packages/gotrue/test/web3_auth_test.dart` (mocked, 6 tests, all
passing): Ethereum happy path, Solana happy path, captcha token, bad
signature, expired nonce, and network error.
## Reference
supabase-js / auth-js `GoTrueClient.signInWithWeb3` (`POST
/token?grant_type=web3`).
## Compliance matrix
`auth.sign_in.sign_in_with_web3`: `not_implemented` → `implemented`
(symbols: `GoTrueClient.signInWithWeb3`, `Web3Chain`).
Linear: SDK-836
…mes (#1604) ## Summary Removes the explicit `<String, String>` type arguments from the `_platformNames` const map in `packages/supabase_common/lib/src/client_info.dart`. The type is inferred identically from the string literals, so this is a no-op at runtime. ## Why The DCM check (`CQLabs/setup-dcm` installs the latest DCM at runtime) now flags this as `avoid-inferrable-type-arguments`. Because the DCM version is not pinned, the rule surfaces intermittently and can turn the DCM job red on unrelated PRs. This removes the finding at the source. No behavior change.
…#1599) ## What Reconciles `sdk-compliance.yaml` by marking several auth and realtime features as `implemented` where the Dart implementation is functionally equivalent to supabase-js, with idiomatic differences aside. This is a matrix-only change; no library code is touched. ## Changed entries - `auth.sign_in.sign_in_with_password` (note dropped): weak passwords surface as a thrown `AuthWeakPasswordException`, the idiomatic Dart error-handling shape, rather than a `weakPassword` field on the response. - `auth.sign_in.sign_in_with_sso` (note kept): exposed as `getSSOSignInUrl` returning `Future<String>` (the URL) rather than a structured `{url}` response. Functionally equivalent. - `auth.sign_in.reauthenticate` (note dropped): neither SDK returns a session here. The `/reauthenticate` endpoint only sends a reauthentication OTP and returns no session, and supabase-js's `reauthenticate` returns an `AuthResponse` whose `user` and `session` are always null, so the `Future<void>` return is equivalent. - `auth.session.sign_out` (note dropped): marked implemented. - `auth.identities.link_identity` (note kept): split into `getLinkIdentityUrl` (OAuth) and `linkIdentityWithIdToken` (ID token) rather than a single overloaded method. - `auth.identities.list_identities` (note dropped): `getUserIdentities` returns a `List<UserIdentity>` directly, the idiomatic Dart shape, rather than a `{data, error}` object. - `realtime.channel.broadcast_http` (note kept): `httpSend` delivers broadcasts over REST and is functionally equivalent. ## Follow-up (v3) Two entries above keep a note describing an idiomatic difference that would be a breaking change to fully align, each tracked for v3: - Aligning the SSO API name and return shape with supabase-js: #1598. - Removing the deprecated automatic REST fallback in `RealtimeChannel.send()`: #1600. Both are added to the v3 umbrella #1278.
…es (#1595) ## What Fills genuine, server-free unit coverage gaps found by measuring actual line coverage (`dart test --coverage` + `format_coverage`) across the self-contained packages. ### `supabase_common` → now 100% line coverage on every file - `test/supabase_common_test.dart`: - `FetchOptions` group (defaults + provided values, previously 0%). - `platform info` group covering the `dart:io` getters (`platform_io.dart`, previously 0%). - Extra `ReplaySubject` cases: async error replay, sync value replay, setter-assigned `onListen`/`onCancel`, and the `onPause`/`onResume` no-ops. - `test/retry_test.dart`: covers the `onRetry` callback (the one uncovered line in `retry.dart`). ### `storage_client/lib/src/types.dart` → 86.8% to ~99% New `test/types_test.dart` (29 tests): `Bucket`/`FileObject`/`FileObjectV2`/`PaginatedListResult` `fromJson` (including the `FormatException` path and defaults), the `toMap`/`toQueryParameters` serializers, `SignedUrl` equality/`hashCode`/`copyWith`/`toString`, exhaustive `switch` matching over the sealed `SignedUrlResult`, `StorageException`, `TransformOptions.toQueryParams`, `DownloadBehavior` and `StorageRetryController`. All new and modified tests pass and are formatted. No production code changed. ## Issues surfaced while writing these tests 1. **Dead defensive branch in `storage_client/lib/src/fetch.dart`** — `_handleError`'s `else` branch (handling a non-`http.Response` error) is unreachable; both call sites only ever pass an `http.Response`. Left as-is here; flagging for follow-up. 2. **`ReplaySubject` sync-replay quirk** — a `sync: true` subject delivers live events synchronously but replays the latest buffered event to a late subscriber asynchronously (because `Stream.multi` schedules its generator). Tests await accordingly. 3. **Pre-existing integration failures (unrelated to this PR)** — `storage_client/test/client_test.dart` and 62 postgrest "Default http client" tests require a running/clean local server; the additions here are all offline/mock-based.
…passkey enrollment (#1603) ## Summary Implements the WebAuthn MFA enrollment `friendlyName` and `user.name` fallback from supabase-js in the Dart passkey API, reopening SDK-701 (previously resolved as not_applicable in #1556). `GoTruePasskeyApi.startRegistration()` now accepts an optional `friendlyName`. Between fetching the registration options and the platform ceremony, it backfills `options['user']['name']` (and `displayName`) when the server omits it, using `friendlyName` if given or a generic default (`Passkey`) otherwise. This mirrors supabase-js's pre-`navigator.credentials.create()` patch, expressed idiomatically without the `${user.id}:` concatenation. `GoTrueClientPasskey.registerPasskey()` in `supabase_flutter` threads `friendlyName` through to the full ceremony. ## Verify first (server behavior) Confirmed against `supabase/auth`: `webAuthnUser.WebAuthnName()` returns the user's email or phone, and an empty string when the user has neither. The server therefore *can* omit a meaningful `user.name`, so the fallback is load-bearing, not merely defensive. ## No passkeys library change needed The `passkeys` plugin's `UserType` requires non-null `name`/`displayName`. Because the fallback runs in `gotrue` before the options reach the plugin, the plugin always receives a valid name and needs no change. ## Outcome: implemented ## Tests - `packages/gotrue/test/passkey_test.dart`: enrollment with `friendlyName`, without `friendlyName` (default), and the server-omits-`user.name` case; plus a guard that a server-provided `user.name` is preserved. All 20 passkey tests pass. ## Compliance matrix `auth.mfa.enroll` (partially_implemented): dropped the "WebAuthn has no Dart equivalent" caveat; the note now points to `auth.passkey.register_passkey`, which accepts a `friendlyName` and applies the same `user.name` fallback. No new public symbols (optional named parameters only). ## Reference supabase-js PR #1763, commit f99a58c. Ref: SDK-701
#1601) ## Summary Under the PKCE flow, `updateUser({ email })` now generates a PKCE code verifier and sends the corresponding `code_challenge` / `code_challenge_method` in the request body, mirroring what `resend()` and `getSSOSignInUrl` already do. This lets an email change started under PKCE complete via `exchangeCodeForSession()`. The fields are only added when an email is being changed, so no public API surface or return type changes. Non-PKCE flows are unaffected (the challenge is `null`). ## Outcome implemented ## Reference - supabase-js parity, following the already-shipped Dart `resend()` PKCE work (SDK-1023). ## Compliance matrix - `auth.session.update_user` → `implemented` (was `partially_implemented`). ## Tests - Added mock tests in `packages/gotrue/test/otp_mock_test.dart`: PKCE email change includes the code challenge, non-email updates omit it, and implicit flow omits it. Closes SDK-1318
…1593) ## Summary Adds request cancellation to `FunctionsClient.invoke`, closing the gap versus the canonical compliance matrix. `invoke` now accepts an optional `abortSignal` Future; completing it cancels the in-flight HTTP request and throws `RequestAbortedException`, mirroring how the database client exposes cancellation through `PostgrestBuilder.abortSignal`. This is done idiomatically with the `http` package's `AbortableRequest`/`AbortableMultipartRequest` (wiring `abortTrigger`), so it works for both regular and multipart invocations. `RequestAbortedException` is re-exported from the package so callers can catch it. A per-invocation timeout is now expressible as `invoke(..., abortSignal: Future.delayed(duration))`, so the previously `not_applicable` `functions.invocation.timeout` note is updated accordingly. ## Outcome **implemented** ## Reference Mirrors the existing Dart `database.using_modifiers.request_cancellation` idiom (`PostgrestBuilder.abortSignal`). ## Compliance matrix - `functions.invocation.request_cancellation` → **implemented** (symbol `FunctionsClient.invoke` registered) ## Tests - Aborting an in-flight request throws `RequestAbortedException` - Aborting a multipart request throws `RequestAbortedException` - A never-firing signal lets the request complete normally SDK-1310
## Summary Adds support for analytics (Apache Iceberg) buckets to `storage_client`, covering the bucket lifecycle: create, list, and delete. These map to the storage server's Iceberg bucket endpoints under `/iceberg/bucket`. New public API on the storage bucket client: - `createAnalyticsBucket(String id)` → `AnalyticsBucket` — `POST /iceberg/bucket` - `listAnalyticsBuckets([ListBucketsOptions options])` → `List<AnalyticsBucket>` — `GET /iceberg/bucket` - `deleteAnalyticsBucket(String id)` → `String` — `DELETE /iceberg/bucket/:name` Listing reuses the existing `ListBucketsOptions` (limit/offset/search/sortColumn/sortOrder), matching the file bucket listing API. A new `AnalyticsBucket` type models the response (`id`, `name`, `createdAt`, `updatedAt`). ## Scope This PR covers the analytics **bucket** capabilities only. The Iceberg REST Catalog operations (namespaces and tables) are a much larger surface (a standalone `iceberg-js`-equivalent client) and are tracked as a follow-up in SDK-1309. ## Outcome `implemented` for: - `storage.analytics.create_analytics_bucket` - `storage.analytics.list_analytics_buckets` - `storage.analytics.delete_analytics_bucket` ## Tests Added unit tests in `basic_test.dart` (mock HTTP client) verifying request method/path/body and response parsing for all three methods. Full `storage_client` mock test suite passes. ## Reference Supabase Storage server Iceberg bucket routes (`src/http/routes/iceberg/bucket.ts`). In supabase-js the analytics bucket API is not yet in the storage client; the Iceberg catalog lives in the standalone `supabase/iceberg-js` package. ## Compliance matrix `sdk-compliance.yaml`: the three capabilities above → `implemented`, with new public symbols registered. Part of SDK-1308.
Prepared all packages to be released to pub.dev Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
## What Adds `pull-requests: write` to the `SDK Compliance` workflow (both the top-level `permissions` and the `validate` job) so the reusable workflow it calls can run. ## Why Every PR run of `SDK Compliance` has failed with `startup_failure` since 2026-07-15. It is not caused by any individual PR. The workflow calls the `@main`-pinned reusable workflow `supabase/sdk/.github/workflows/validate-sdk-compliance-dart.yml`. In supabase/sdk #60 (commit `45dba43`), the reusable workflow's `check` job started requesting `pull-requests: write` so it can post the capability matrix drift comment. A called reusable workflow's `GITHUB_TOKEN` permissions can only be equal to or more restrictive than the caller's. This caller only granted `contents: read`, so the reusable workflow requesting a scope the caller does not have makes GitHub reject the run at compile time, before any job starts. Granting `pull-requests: write` in the caller lets the token be passed down so the drift comment step can post. ## Notes - The same fix is needed in the other SDK repos that consume this reusable workflow (e.g. supabase-js), which are failing the same way. - The drift check posts/updates a sticky PR comment; it is best-effort and swallows 403s on fork PRs.
…ell values (#1612) ## What kind of change does this PR introduce? Bug fix (internal exception noise; no observable behavior change). ## What is the current behavior? `convertCell`'s doc comment says *"If the value of the cell is `null`, returns null"* — but the code never checks for null. A null value in an `int2/int4/int8/oid` (or `float4/float8/numeric`) column falls through to `toInt`/`toDouble`, which do: ```dart try { return int.parse(value.toString()); // null → "null" → FormatException } catch (_) { return null; } ``` So every `postgres_changes` payload containing a null numeric column throws (and immediately catches) `FormatException: Invalid radix-10 number (at character 1) null` inside the library. The converted result is correct, but the exception-as-control-flow fires on **every CDC frame** for any subscribed table with nullable numeric columns. In practice this means anyone debugging a realtime Flutter app with "break on all exceptions" enabled gets stopped in `integers_patch.dart` constantly, with no way to silence it from app code. For reference, realtime-js doesn't have this problem: its `toNumber` only parses `typeof value === 'string'` and passes null through untouched. ## What is the new behavior? - `convertCell` short-circuits `null` → `null`, matching its own documented behavior and realtime-js - `toInt`/`toDouble` use `tryParse` instead of `parse` inside try/catch, so unparseable strings also no longer throw internally Output is unchanged for every input — existing tests already pin `convertCell('int8', null) => null` and `convertCell('float8', null) => null`, and they still pass. Added direct unit tests for `toInt`/`toDouble` covering null, empty, and non-numeric inputs. ## Additional context Repro: subscribe to `postgres_changes` on any table with a nullable `integer` column, update a row while the column is null, run with a debugger set to break on all exceptions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ject (#1607) ## Summary Adds `StorageFileApi.purgeCache(path, {transformations})` to invalidate the CDN cache for a single object in a bucket. Issues `DELETE /cdn/{bucket}/{path}` against the storage base URL and returns the server's success message. When `transformations` is `true`, only the resized/formatted variants are purged, leaving the original cached object intact; otherwise the object cache is purged. Requires the service-role key and the tenant `purgeCache` feature enabled on the storage server. **Outcome:** implemented **Reference:** supabase-js PR [#2429](supabase/supabase-js#2429) (`storage.file_buckets.purge_cache`) ## Compliance matrix - `storage.file_buckets.purge_cache` → `implemented` (symbol `StorageFileApi.purgeCache`) ## Tests Added unit tests in `packages/storage_client/test/basic_test.dart` covering the DELETE endpoint/URL and the `transformations` query parameter. SDK-1331
#1613) ## What Adds the backing public symbol `GoTrueClient.getSSOSignInUrl` to the `auth.sign_in.sign_in_with_sso` entry in `sdk-compliance.yaml`. ## Why SSO sign-in is already functionally complete in the Dart SDK: the redirect URL is obtained via the public `getSSOSignInUrl`, which is re-exported all the way up so end users can call `supabase.auth.getSSOSignInUrl(...)` directly. The matrix already documented the deviation from supabase-js (returns `Future<String>` instead of the structured `{ url }` response); this change records the concrete symbol that backs the capability, matching how other entries (e.g. `auth.session.get_session` → `GoTrueClient.getSession`) list theirs. Follow-up to the discussion on #1598, which was closed as not planned since the capability is already reachable and the name/return-shape alignment is a v3 concern.
…lities (#1614) ## What Adds a `symbols:` list to every capability in `sdk-compliance.yaml` that was marked `implemented` but had no registered symbols (156 capabilities across auth, database, storage, realtime, functions, and client). > **Stacked on #1613.** This PR targets `chore/sso-compliance-symbol` and inherits the `sign_in_with_sso` symbol registered there; review/merge #1613 first, then this. ## Why The reusable **SDK Compliance** workflow runs a capability-matrix drift check on every PR (see the drift comment on #1613). For each `implemented` capability it expects a `symbols:` list so the Dart symbol extractor can confirm the backing public API actually exists. Capabilities without a list are reported as unverifiable, which is why every PR got a ~150-line drift warning. This registers the backing symbol(s) for each remaining one, clearing the drift. ## How - Ran the same `dart_symbol_extractor` the CI uses (from `supabase/sdk`) against this repo to produce the ground-truth public symbol dump, then mapped each capability to the public Dart symbol(s) that implement it (e.g. `auth.sign_in.sign_up` → `GoTrueClient.signUp`, `database.using_filters.eq` → `PostgrestFilterBuilder.eq`). - Every registered symbol was validated to exist in the extractor output, and the drift-check logic was replicated locally: it now reports **0 findings**. - Symbols that are intentionally `@internal` (e.g. `RealtimeClient.connect`, which is not part of the public parity surface since Dart connects lazily on subscribe) were not registered; those capabilities point at the public symbol that actually drives them. ## Notes - No source or public API changed, this only touches the compliance matrix, so the "new public symbols" check is unaffected.
…cognized sb_ key subtype (#1615) ## Summary Parity with supabase-js and supabase-swift ([#1130](supabase/supabase-swift#1130)) for the new Supabase API key format (`sb_publishable_…` / `sb_secret_…`). These keys are not JWTs and must never be sent as an `Authorization: Bearer` token. - **Functions never sends a new-format key as Bearer.** The `Authorization: Bearer` header for Functions (and REST/Storage) is injected per request by the shared `AuthHttpClient`. When no user session exists it previously fell back to the API key, so a `sb_publishable_`/`sb_secret_` key was sent as `Bearer …`, which the Edge Functions runtime rejects. The Functions client now uses a dedicated `AuthHttpClient` that omits the Bearer header for a new-format key when there is no session. A genuine session JWT is still sent normally. - **Warn on unrecognized `sb_` subtypes.** `SupabaseClient` now warns once per unrecognized `sb_`-prefixed key subtype at construction. It never throws (the server, not the SDK, decides key validity) and never logs the key value. Scope matches the reference implementations: this affects Functions only. Legacy JWT keys, REST, Storage, Auth, and Realtime are unchanged. ## Implementation notes Unlike supabase-swift/js, supabase-flutter injects the Bearer token through a **shared** `AuthHttpClient` used by REST, Functions, and Storage, and `FunctionsClient.setAuth` is never wired up from `SupabaseClient`. To keep the fix scoped to Functions, a separate `AuthHttpClient` instance (`omitNewApiKeyAsBearer: true`) is created for the Functions client instead of changing auth-state handling. ## Changes - `packages/supabase/lib/src/api_key.dart` (new): `isNewApiKey` classification and `warnOnUnrecognizedApiKey` warn-once helper. - `packages/supabase/lib/src/auth_http_client.dart`: `omitNewApiKeyAsBearer` flag suppressing the Bearer header for new-format keys with no session. - `packages/supabase/lib/src/supabase_client.dart`: dedicated Functions `AuthHttpClient`; warn at construction. - `packages/supabase/test/api_key_test.dart` (new): classification, warn-once/no-key-leak, and all Bearer-suppression cases. ## Test plan - [x] `dart analyze` — clean - [x] `dart test` (supabase package) — all 91 tests pass, including the 8 new ones - [x] `dart format` run
## What
Adds a runnable **Database CRUD with PostgREST** example under
`examples/database_crud`, plus the shared schema and seed data it runs
against. Closes SDK-1065.
The example is a small task manager that covers everything
`supabase.from(...)` offers for CRUD:
- **Select** tasks joined to their project (`select('*,
projects(name)')`)
- **Filters**: `eq` (project, completion), `ilike` (title search)
- **Ordering**: `order('priority', ascending:
false).order('created_at')`
- **Insert** returning the created joined row
(`insert().select().single()`)
- **Update** title and completion state (`update().eq()`)
- **Delete** (`delete().eq()`)
All database access lives in `lib/tasks_repository.dart`, kept separate
from the UI so the queries are easy to read and can later be driven from
a full end-to-end integration test.
## Shared config
- `examples/supabase/migrations/20240601000000_crud_example.sql` —
`projects` 1:N `tasks` with a foreign key and index, RLS enabled with
permissive demo policies, and grants so the publishable (anon) key can
read projects and manage tasks without signing in.
- `examples/supabase/seed.sql` — sample projects and tasks.
## Wiring
- Registered `examples/database_crud` in the root workspace
`pubspec.yaml` and the `examples/README.md` table. The launcher
auto-discovers it.
## Verification
- `flutter analyze examples` → no issues; `dart format` clean.
- `supabase db reset` against `examples/` applies the migration and seed
cleanly.
- Exercised the full flow over PostgREST with the publishable key:
joined + ordered + `ilike` select, insert returning the joined row,
update, filtered select, and delete (204) with confirmation.
## What Adds an end-to-end integration test for the `passkeys` example, matching the ones added for `realtime_room` and `database_crud`. It drives the actual app widgets against the local Supabase stack. ## The test `integration_test/passkeys_test.dart` uses `testWidgets` to: 1. Pump `PasskeyExampleApp`, create an account and land on the passkey management screen (a fresh account shows "No passkeys yet." and the "Register a passkey" button). 2. Sign out and assert the sign-in screen returns. 3. Attempt a wrong password and assert an error surfaces instead of signing in. 4. Sign in with the correct password and assert the signed-in screen returns. ### Scope The WebAuthn ceremony itself (`registerPasskey` / `signInWithPasskey`) drives a platform authenticator prompt (Face ID, Windows Hello, a security key, ...) that cannot be automated headlessly, so it is exercised manually per the README rather than in this test. The test covers everything around the ceremony: account creation, the passkey-list screen, session management and password sign-in error handling. A small `_pumpUntil` helper polls the widget tree (auth calls are async over the network, so the UI can't be `pumpAndSettle`d). Each run uses a unique email so sign up never collides with an existing user. Also adds the `integration_test` dev dependency and an "Integration test" section to the example README. ## Testing - `flutter analyze`: no issues; `dart format`: clean. - Ran end to end against a local stack, twice, both green. Integration tests need a device, so run it with `-d macos`, an emulator or a real device.
) ## What Adds a full end-to-end integration test for the `database_crud` example, matching the one added for `realtime_room`. It drives the actual app widgets against the local Supabase stack rather than calling the repository directly. ## The test `integration_test/tasks_test.dart` uses `testWidgets` to: 1. Pump `CrudExampleApp` and wait for the seeded tasks to load (select). 2. **Filter** by title through the search field and assert the list narrows, then clears (ilike). 3. **Create** a task through the new-task dialog and assert it appears (insert). 4. **Complete** it by ticking the checkbox in its tile and assert the checkbox becomes checked (update). 5. **Rename** it through the edit dialog and assert the new title replaces the old (update). 6. **Delete** it and assert it leaves the list (delete). Because reads and writes go over the network, the UI can't be settled with `pumpAndSettle`; a small `_pumpUntil` helper polls the widget tree with a timeout instead. The test uses a unique task title and a `tearDown` that removes any `E2E %` rows, so it runs repeatably and leaves the seed data untouched. Also adds the `integration_test` dev dependency and an "Integration test" section to the example README. ## Testing - `flutter analyze`: no issues; `dart format`: clean. - Ran end to end against a local stack, twice, both green. Integration tests need a device, so run it with `-d macos`, an emulator or a real device.
) ## What Adds a weekly workflow that keeps `sdk-compliance.yaml` in sync with new capability IDs added to the canonical spec at https://github.com/supabase/sdk. The sync logic lives centrally in `supabase/sdk` as a reusable workflow (see supabase/sdk#65), mirroring the existing `validate-sdk-compliance-dart.yml` pattern. This repo just adds a thin caller: - **`.github/workflows/sync-compliance.yml`** — runs Mondays 06:00 UTC (and on manual dispatch), calls `supabase/sdk/.github/workflows/sync-sdk-compliance.yml@main`, passing this repo's `APP_ID`/`PRIVATE_KEY` app-token secrets so the sync PR is opened under our own token. The reusable workflow diffs the canonical feature IDs against the keys already in `sdk-compliance.yaml`, inserts any missing ones as `not_implemented` next to their `area.group.` siblings (correct section, no guessing), and opens/updates a PR here. ## Why centralized The diff/insert logic is language-agnostic, so it belongs in `supabase/sdk` where every SDK can share one implementation. Each SDK repo adds the same thin caller and runs on its own token, so no cross-repo permissions are needed. ## Notes - Depends on supabase/sdk#65 merging first (the `@main` reference resolves once it lands). - Only adds new IDs as `not_implemented`; real status and symbols are triaged by a human / `/parity`. - Opens a PR rather than committing directly.
#1617) ## Summary Blocks adding a `postgres_changes` listener after `subscribe()` has been called, matching the behavior introduced in supabase-js. Calling `onPostgresChanges(...)` while the channel is `joining` or `joined` now throws a clear error instead of silently registering a binding that would never be applied to the join payload. **Outcome:** implemented ## Details - `RealtimeChannel.onEvents` now throws when a `postgres_changes` binding is added while the channel is joining or joined, with the message `cannot add \`postgres_changes\` callbacks for <topic> after \`subscribe()\`.` - Presence listeners are intentionally **not** blocked: the Dart SDK deliberately supports adding presence callbacks after join and resubscribes with presence enabled (`_handlePresenceUpdate`). Blocking presence would regress that established Dart idiom, so this differs from supabase-js by design. ## Reference - supabase-js commit `219c0c16`, PR supabase/supabase-js#2201 ## Compliance matrix `realtime.subscriptions.postgres_changes` was already `implemented`; this change makes the behavior equivalent for the post-subscribe case. No new public symbols, so no matrix edit was required. Linear: SDK-827
) ## Summary The binary broadcast serializer measured header field lengths with `String.length` (UTF-16 code units) and wrote one byte per code unit. Any multi-byte character (accents, emoji, non-Latin scripts) in the topic, ref, join-ref, event name, or metadata corrupted the length prefixes and desynchronized the binary frame. Each header field is now encoded to UTF-8 bytes first, and the byte length is used for both the length prefixes and the write offsets, matching the `utf8.decode` on the decode side. **Outcome:** implemented (bug fix) ## Changes - `packages/realtime_client/lib/src/serializer.dart` — UTF-8 encode `topic`, `ref`, `joinRef`, `userEvent`, and `metadata` before measuring/writing; replaced the code-unit `_writeString` helper with a byte-copying `_writeBytes`. - `packages/realtime_client/test/serializer_test.dart` — added a test covering multi-byte header fields (accented text and emoji) round-tripping correctly. ## Reference supabase-js PR #2516 / commit `b4ddbd5` (fix(realtime): encode broadcast header fields as UTF-8) ## Compliance matrix `realtime.configuration.binary_protocol` remains `implemented` (no new public symbols; the change is internal to the non-exported serializer). Closes SDK-1322
…#1624) ## What Adds a new `supabase_flutter` example under `examples/storage_transforms` that demonstrates Supabase Storage: uploading images, listing them, serving them through image transformations, downloading transformed bytes and deleting objects. Closes SDK-1067. ## The example A small image gallery: - **Upload** generated PNG bytes to a public bucket (`uploadBinary`). - **List** the objects, newest first (`list`). - **Transform** images on the fly with `TransformOptions` (width, height, resize mode, quality) via `getPublicUrl`, so the grid loads a small cropped thumbnail and the detail view renders several resized variants. - **Download** transformed bytes (`download`). - **Delete** an object (`remove`). All Storage access lives in `lib/storage_repository.dart`, kept separate from the UI so it is easy to read and to drive from an integration test. Sample images are drawn in memory in `lib/sample_image.dart`, so the example uploads real image bytes without bundling asset files or depending on an image picker. ## Backend wiring - New migration `examples/supabase/migrations/20240603000000_storage_transforms_example.sql` creates a public `images` bucket with row level security policies allowing the demo to view, upload, overwrite and delete objects with the publishable key. - `examples/supabase/config.toml` enables `[storage.image_transformation]`. - Registered `examples/storage_transforms` in the root workspace and the `examples/README.md` table. ## Testing Verified end to end against the local stack (`supabase db reset`, then upload/list/transform-render/authenticated-transformed-download/delete over HTTP): uploads succeed, the public render endpoint returns a resized image, and deletes clean up. `dart format` and `flutter analyze` are clean. The end-to-end integration test is a follow-up stacked PR, matching the other examples.
…xample (#1625) Stacked on #1624. Adds `examples/storage_transforms/integration_test/storage_test.dart`, an end-to-end test that runs against the local Supabase stack, matching the other examples' e2e tests. It covers: - **Repository flow:** upload generated PNG bytes, confirm the object appears in `list`, build a transformed public URL and check it targets the render endpoint with the transform query parameters, download the original and a transformed variant and decode both to assert the transform actually resized the image (640×640 → 100×100), then delete and confirm it is gone. - **Widget flow:** drive the app to upload an image through the FAB, open the detail view and see the transformation variants by name, then delete from the detail view and land back on an empty gallery. Also adds the `integration_test` dev dependency and a "Integration test" section to the example README. ## Running With the local stack up: ```bash cd examples/storage_transforms flutter test integration_test/storage_test.dart -d macos \ --dart-define=SUPABASE_URL=http://localhost:54321 \ --dart-define=SUPABASE_PUBLISHABLE_KEY=YOUR_LOCAL_PUBLISHABLE_KEY ```
Pins the `sync-compliance.yml` caller's reference to the reusable workflow at supabase/sdk from `@main` to the current main SHA (`faba359a57d270bbbf68fd6b583db0604ddce8af`), matching the pinning being applied across the other SDK caller repos (supabase-js #2534, supabase-py #1541, supabase-swift #1128). The original caller workflow was merged in #1606 with a floating `@main` reference, so this follow-up pins it. The `# main` trailing comment keeps the tracked branch visible for future bumps.
## What Adds a repo-level `.coderabbit.yaml` that disables CodeRabbit's failing commit status: ```yaml reviews: commit_status: true fail_commit_status: false ``` ## Why Open PRs are currently showing a red `CodeRabbit` check with the reason **"Review rate limited"** (e.g. #1630, #1629, #1627), even though nothing is actually wrong with the change. A completed review shows `pass` (e.g. #1626). - `commit_status: true` keeps the useful passing check for completed reviews. - `fail_commit_status: false` stops CodeRabbit from setting a `failure` status when it can't review (which is what the rate-limit case triggers). Committing the config overrides CodeRabbit's cloud/org defaults. ## Note If a rate-limited PR still goes red after this lands, the rate-limit failure path ignores this flag and the only remaining lever is `commit_status: false` (disables the check entirely, losing the green pass too).
## Summary
`RealtimeChannel.track()` only forwarded `timeout` to the underlying
`send()`, rebuilding the opts map as `{'timeout': opts['timeout'] ??
_timeout}` and silently dropping any other caller-supplied options. It
now forwards the full `opts` map, matching `untrack()` and the
supabase-js behavior. The timeout fallback is preserved because `send()`
already resolves `opts['timeout'] ?? _timeout`.
**Outcome:** implemented (bug-fix)
## Tests
Added a `track opts forwarding` group in `channel_test.dart` verifying
that a custom non-timeout opt, a custom timeout opt, and the empty
default all reach `send()` unchanged. Full `channel_test.dart` passes
(57 tests).
## References
- Linear: SDK-1321
- supabase-js: PR #2490 / commit `7fd2bc39`
## Compliance matrix
`realtime.presence.track` was already `implemented` with symbol
`RealtimeChannel.track`; this fix corrects its behavior without adding
new public symbols, so no matrix change was required.
## What
Adds an `edge_functions` example under `examples/`, following the same
structure as `database_crud` and `storage_transforms`. It demonstrates
invoking Supabase Edge Functions with `supabase.functions.invoke(...)`:
- **JSON body over POST**, reading the JSON response back
(`invoke('greet', body: {...})`).
- **GET with query parameters** instead of a body (`method:
HttpMethod.get, queryParameters: {...}`).
- **Custom headers** sent to the function and echoed back (`headers:
{...}`).
- **Plain text in and out**: a `String` body is sent as `text/plain` and
the `text/plain` response arrives as a `String` (`invoke('shout', body:
text)`).
- **Error handling**: a non-2xx response throws a `FunctionException`
whose `details` carry the response body (`invoke('word-count', ...)`).
## How it's structured
- All Edge Function access lives in `lib/functions_repository.dart`
(`FunctionsRepository`), kept separate from the UI so the calls are easy
to read and to drive from tests. Models with `factory fromJson` live in
`lib/models.dart`. The Material 3 UI in `lib/main.dart` stays thin.
- Three demo functions ship under `examples/supabase/functions/`:
`greet`, `shout` and `word-count`.
- The Edge Runtime is enabled in `examples/supabase/config.toml`;
`supabase start` serves the functions automatically. The demo functions
set `verify_jwt = false` so the example can call them with just the
publishable key, matching the other examples which run unauthenticated.
- The new package is registered in the workspace `pubspec.yaml` and
listed in `examples/README.md`.
## Testing
- `dart format .` and `flutter analyze` are clean.
- `integration_test/functions_test.dart` was run against the local stack
(`supabase start`) on web (Chrome 150) via `flutter drive`: **all tests
passed**. It covers the repository flow (JSON greeting over POST and
GET, plain-text transform, and a 400 validation error surfacing as a
`FunctionException`) plus a UI smoke test.
A stacked PR (#1633) adds a full end-to-end suite covering the entire
`functions.invoke` surface.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **New Features**
- Added a Flutter example demonstrating Supabase Edge Functions.
- Added greeting, plain-text transformation, and word-count function
examples.
- Added support for POST requests, query parameters, custom headers, and
error handling.
- Enabled the example to run in the browser and against the local stack.
- **Documentation**
- Added setup, usage, project structure, and integration testing
instructions.
- **Tests**
- Added end-to-end coverage for function responses, validation errors,
and UI interactions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
…ession # Conflicts: # packages/supabase_flutter/README.md # packages/supabase_flutter/lib/src/supabase_auth.dart # packages/supabase_flutter/pubspec.yaml
Contributor
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Move
signInWithOAuth,signInWithSSOandlinkIdentityoffurl_launcherand onto a native system web authentication session viaflutter_web_auth_2:ASWebAuthenticationSessionwebpackage)The session captures the redirect to the
redirectToscheme, closes itself, and returns the callback URL, which is exchanged withgetSessionFromUrl.Addresses #1402. Fixes #1174.
Why
url_launcher's in-app browser does not dismiss itself when the OAuth redirect returns to the app, leaving the user on a blank page after a successful sign in (#1174). A system web auth session is OS owned, auto-dismisses, and hands the callback back to the caller, so it fixes the dismissal on every platform and lets the call resolve on completion.Changes
flutter_web_auth_2; removeurl_launcheras a direct dependency and theLaunchModeexport.authScreenLaunchMode/launchModewith apreferEphemeraloption.redirectTo; forward host and path forhttpsuniversal links.package:web.oauth_test.dartcovering the native flow,preferEphemeral,httpsuniversal links, and the missing-redirectToerror.Breaking changes
authScreenLaunchMode/launchModeremoved;LaunchModeno longer exported.flutter_web_auth_2CallbackActivityfor their redirect scheme.app_linksdeep link handler (magic links, email confirmation and password recovery still do).Status
Draft.
flutter analyzeandflutter testpass, but the native and desktop flows have not been verified on device yet. See the open questions in #1402 (forcing the external browser, the web popup vs redirect choice, dropping the Google on Android workaround, desktop callback model).