Skip to content

Latest commit

 

History

History
246 lines (192 loc) · 8.75 KB

refactor.MD

File metadata and controls

246 lines (192 loc) · 8.75 KB

Road to official / next major version

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:

  1. Auth changes from #1291 (because these changes are basically done) & figure out the namespace
  2. Potential auto refresh changes
  3. Better exceptions
  4. Other small changes & improvements
  5. ABI validation (see #1397 as an example)

Core

Official namespace / package name

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

Future proof API methods

Move all optional parameters to DSL builders (which make it easier to add new parameters in the future)

Separate ID value classes

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.

Find a better way for (rest) error/exception handling; also leaking exceptions

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:

  1. Keep exceptions but make the hierarchy more explicit and easier to understand.
  2. Use a JS style approach with val (result, error) = table.select(...), where result is null if there is an error
  3. 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.

Unify method/class naming and also DSL approach

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

Immutable client config

Currently, after creating the client, you can still modify each plugin's config, which shouldn't be done and can lead to unexpected behavior.

Auth

Reconsider auto refresh

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

  1. Lifecycle-driven session management / system aware work schedulers: Makes it possible to better refresh the session in the background
  2. 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

New syntax (more aligned with the other SDKs)

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()
}

Revisit event system

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 / iOS OAuth related changes

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" 
}

PostgREST

More idiomatic column specification

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

  1. #761
  2. #769

Storage

More functional downloads/uploads

Currently, the uploading and downloading methods are very restricted. E.g. you can listen to the progress of the upload/download.