This document holds suggested refactors that can be done in the transition of migrating the library to an official one. A lot of them come from the community. Features which are unlikely are highlighted as such.
Suggested order:
- Auth changes from #1291 (because these changes are basically done) & figure out the namespace
- Potential auto refresh changes
- Better exceptions
- Other small changes & improvements
- ABI validation (see #1397 as an example)
Currently, the maven repository is: io.github.jan-tennert.supabase
And the package name: io.github.jan.tennert.supabase
This should be changed to an official one. There were talks about io.github.supabase
Move all optional parameters to DSL builders (which make it easier to add new parameters in the future)
Currently, all IDs (UID, bucket id, etc.) are held as a string. This makes them hard to (1) distinguish (2) ensure type safety. Also, if IDs need additional transformations (like ensuring correct URL encoding), a separate class would make this cleaner then using a helper method each time.
Currently, it can be very hard to guess which exception is thrown when the API returns an error response. Some modules like Auth and Storage have specific error codes which are the most useful for devs,
but these are often in specific RestException subtypes like AuthRestException. Also, the whole RestException structure is very unclear. Generally options would be:
- Keep exceptions but make the hierarchy more explicit and easier to understand.
- Use a JS style approach with
val (result, error) = table.select(...), where result is null if there is an error - Use a sealed-class style approach with, e.g.:
when(val result = table.select(...)) {
is Success -> println(result.data)
is RestError -> // handle
// maybe other types like network errors etc.
}Second, there have been multiple instances where exceptions like serialization errors leak through the API and are even harder to handle as a dev.
Specifically DSL builders are named sometimes very differently like Builder, Options, Parameter. This should be revised and maybe unified.
Also, a lot of them use a different approach like some builders are serializable classes, while some have a distinct build() method for serialization. Im
Currently, after creating the client, you can still modify each plugin's config, which shouldn't be done and can lead to unexpected behavior.
Currently, the auto refresh works via coroutines and delay. This works but is not really ideal especially for lifecycle driven platforms like Android, iOS where it would be better to use alternatives.
Also, this approach currently has rare bugs which can cause the session to expire anyway. Alternatives might be
- Lifecycle-driven session management / system aware work schedulers: Makes it possible to better refresh the session in the background
- Check the expiration dynamically before a request. This is kinda done but only if the session expired. Then the client forces a refresh.
Separate auth and auth-native modules. This text is copied from #1291. This approach has been favorised by the community.
auth-kt really only provides:
(1) Methods to call the Supabase API (now with split auth.signInWithPassword(), auth.signInWithOtp(), auth.signInWithIdToken(), etc for SDK parity)
(2) Handle session management, if not disabled (this PR also makes all API related methods return a UserSession and also an option to disable the session importing into memory, so it is even easier for server-side applications)
auth-native provides:
- Seamless platform specific implementations for OAuth, Native Auth, Passkeys, Lifecycle Management, Deeplinks (of depending on the platform)
The only thing you have to do:
install(Auth.withNative())Note that the base auth-kt doesn't have any of these methods, since they would need platform specific dependencies.
If users decide to make their own implementation they can just use auth.getOAuthUrl(...) and auth.passkeys
Sign in [With Email() or Phone()]
val session = supabase.auth.signInWithPassword(Email("myEmail@example.com"), password = "123345") {
// ...
}Sign in with OTP
supabase.auth.signInWithOtp(Email("myEmail@example.com")) {
// ...
}Sign up [with Email() or Phone()]
val response = supabase.auth.signUp(Email("myEmail@example.com"), password = "12345") {
// ...
}
println(response.session)
println(response.user)Sign in with ID Token
val session = supabase.auth.signInWithIdToken(OAuthProviders.GOOGLE, token = "12343") {
// ...
}Native-Auth module: Sign in with OAuth
supabase.auth.signInWithOAuth(OAuthProviders.GOOGLE) {
// ...
}Linking identity
supabase.auth.linkIdentity(OAuthProviders.GOOGLE) {
// ...
}Verify OTP
val result = supabase.auth.verifyOtp(OtpType.Email.EMAIL_CHANGE, Email("myEmail@email.com"), token) {
// optional config
}
val result = supabase.auth.verifyOtp(OtpType.Email.EMAIL_CHANGE, TokenHash("tokenHash"), token) {
// optional config
}
supabase.auth.verifyOtp(OtpType.Phone.PHONE_CHANGE, Phone("+49 12345678"), token) {
// optional config
}Reset password
supabase.auth.resetPasswordForEmail("myEmail@example.com") {
// optional config
}Sign in anonymously
supabase.auth.signInAnonymously {
data(myData)
// ..
}Resend email/phone confirmation
supabase.auth.resend(OtpType.Email.EMAIL_CHANGE, Email("myEmail@example.com")) {
// ..
}
supabase.auth.resend(OtpType.Phone.PHONE_CHANGE, Phone("+49 123456789")) {
// ..
}Sign in with SSO
supabase.auth.signInWithSSO(SSODomain("domain"))
supabase.auth.signInWithSSO(SSOProvider("providerId"))Native Google Auth (or OAuth as a fallback)
// required for Android:
install(Auth.withNative()) {
nativeAuth {
googleClientId = "..."
}
}
val result = supabase.auth.signWithGoogle()
when(result) {
GoogleSignInResult.Cancelled -> TODO()
GoogleSignInResult.OAuthInitiated -> TODO()
is GoogleSignInResult.Success -> TODO()
}Native Apple Auth (or OAuth as a fallback)
val result = supabase.auth.signWithApple()
when(result) {
AppleSignInResult.Cancelled -> TODO()
AppleSignInResult.OAuthInitiated -> TODO()
is AppleSignInResult.Success -> TODO()
}Currently, auth-kt has sessionStatus and events. The former is a flow representing the state of the session (NotAuthenticated, Authentication, SessionExpired, Initializing),
while the latter represents events outside the session lifecycle (e.g. error during OTP verification, refresh error).
This approach is confusing and a unified way might be better (also e.g. sign-outs are hard to detect). Though: We have to consider how to represent the session lifecycle, which is crucial e.g. for distinguishing
expired session vs non-existing vs not yet loaded session.
Android
On Android, the only thing you have to do now is set the app scheme and optionally the host in the build configuration:
android {
defaultConfig {
manifestPlaceholders["supabaseAuthScheme"] = "myScheme"
// optional:
manifestPlaceholders["supabaseAuthHost"] = "myHost"
}
}The library will take care of everything else. No more handleDeeplinks() or something similar. The library will also catch magic links.
iOS
On iOS, ASWebAuthenticationSession is now used for OAuth, fixing #1253 and also making the process more iOS idiomatic. For magic links / deeplinks from outside the app, you still have to use handleDeeplinks(), but with a new method for better visbility in Swift (SupabaseDeeplinkHandlerKt.handleDeeplinks(). The scheme entry in PList.info is still required as is setting the scheme in the config:
install(Auth) {
appScheme = "myScheme"
}
Currently, you have to provide a raw string (e.g. "id,name,age") which is prone to errors especially if using a more complex expression. Solutions might be
- #761
- #769
Currently, the uploading and downloading methods are very restricted. E.g. you can listen to the progress of the upload/download.