diff --git a/.github/labeler.yml b/.github/labeler.yml index f596b56f3..09f5ba210 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -16,7 +16,7 @@ postgrest: auth: - changed-files: - - any-glob-to-any-file: GoTrue/src/** + - any-glob-to-any-file: Auth/src/** storage: - changed-files: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c29ae48f3..d1441205d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,9 +21,9 @@ jobs: java-version: '17' distribution: 'temurin' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3.1.0 + uses: gradle/actions/setup-gradle@v4.0.1 with: cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} cache-read-only: false - name: Build supabase-kt - run: ./gradlew -DLibrariesOnly=true build --stacktrace --configuration-cache --scan \ No newline at end of file + run: ./gradlew -DLibrariesOnly=true build -x test --stacktrace --configuration-cache --scan diff --git a/.github/workflows/cache.yml b/.github/workflows/cache.yml new file mode 100644 index 000000000..02e144952 --- /dev/null +++ b/.github/workflows/cache.yml @@ -0,0 +1,16 @@ +name: Clear all Github actions caches +on: + workflow_dispatch: + +permissions: + actions: write + +jobs: + clear-cache: + name: Delete all caches + runs-on: ubuntu-20.04 + steps: + - name: Clear caches + uses: easimon/wipe-cache@main + with: + github-token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/detekt.yml b/.github/workflows/detekt.yml index e8150ca08..9026982e7 100644 --- a/.github/workflows/detekt.yml +++ b/.github/workflows/detekt.yml @@ -20,9 +20,14 @@ jobs: java-version: '17' distribution: 'temurin' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3.1.0 + uses: gradle/actions/setup-gradle@v4.0.1 with: cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} cache-read-only: ${{ github.ref != 'refs/heads/master' }} - name: Analyze code using detekt - run: ./gradlew -DLibrariesOnly=true detektAll --stacktrace --configuration-cache \ No newline at end of file + run: ./gradlew -DLibrariesOnly=true detektAll --stacktrace --configuration-cache + - uses: github/codeql-action/upload-sarif@v3 + if: success() || failure() + with: + sarif_file: build/reports/detekt/merge.sarif + category: lint \ No newline at end of file diff --git a/.github/workflows/dokka.yml b/.github/workflows/dokka.yml index 88d9bea5f..dd02a0ad0 100644 --- a/.github/workflows/dokka.yml +++ b/.github/workflows/dokka.yml @@ -38,7 +38,7 @@ jobs: java-version: '17' distribution: 'temurin' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3.1.0 + uses: gradle/actions/setup-gradle@v4.0.1 with: cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} cache-read-only: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fdc44c5b4..ebc2c76dd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: java-version: '17' distribution: 'temurin' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3.1.0 + uses: gradle/actions/setup-gradle@v4.0.1 with: cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} cache-read-only: true diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index 8b291ee9b..1af0f2f09 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -27,7 +27,7 @@ jobs: java-version: '17' distribution: 'temurin' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3.1.0 + uses: gradle/actions/setup-gradle@v4.0.1 with: cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} cache-read-only: ${{ github.ref != 'refs/heads/master' }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9f7fb3e09..3c4a06d67 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: matrix: command: [ 'jvmTest testDebugUnitTest testReleaseUnitTest', - 'jsTest', + 'jsTest wasmJsTest', 'iosX64Test iosSimulatorArm64Test', 'macosArm64Test macosX64Test', 'tvosX64Test tvosSimulatorArm64Test', @@ -31,7 +31,7 @@ jobs: java-version: '17' distribution: 'temurin' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3.1.0 + uses: gradle/actions/setup-gradle@v4.0.1 with: cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} cache-read-only: ${{ github.ref != 'refs/heads/master' }} diff --git a/.gitignore b/.gitignore index b00cb7e52..419017108 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Project exclude paths -/.gradle/ +**/.gradle/** **/build/** /test/ .idea diff --git a/Auth/README.md b/Auth/README.md new file mode 100644 index 000000000..b6dcd8120 --- /dev/null +++ b/Auth/README.md @@ -0,0 +1,70 @@ +# Supabase-kt Auth + +**Only available for versions 3.0.0 and above. For versions below 3.0.0, checkout the [old README](/GoTrue)** + +Extends Supabase-kt with a multiplatform Auth client. + +Supported targets: + +| Target | **JVM** | **Android** | **JS** | **Wasm** | **Apple** | **Windows** | **Linux** | +|--------|---------|-------------|--------|----------|-----------|-------------|-----------| +| Status | ✅ | ✅ | ✅ | ✅ | ☑️* | ☑️ | ☑️ | + +> ☑️ = No built-in OAuth support. Linux has no support for persistent session storage. + +\* **iOS and macOS are fully supported** + +
+ +In-depth Kotlin targets + +**JS**: Browser, NodeJS + +**Wasm**: wasm-js + +**Apple:** + +- iOS: iosArm64, iosSimulatorArm64, iosX64 + +- tvOS: tvosArm64, tvosX64, tvosSimulatorArm64 + +- watchOS: watchosArm64, watchosX64, watchosSimulatorArm64 + +- MacOS: macosX64, macosArm64 + +**Windows**: mingwX64 + +**Linux**: linuxX64 + +
+ +# Installation + +Newest version: [![](https://img.shields.io/github/release/supabase-community/supabase-kt?label=)](https://github.com/supabase-community/supabase-kt/releases) + +```kotlin +dependencies { + implementation("io.github.jan-tennert.supabase:auth-kt:VERSION") +} +``` + +Install the plugin in your SupabaseClient. See the [documentation](https://supabase.com/docs/reference/kotlin/initializing) for more information + +```kotlin +val supabase = createSupabaseClient( + supabaseUrl = "https://id.supabase.co", + supabaseKey = "apikey" +) { + + //... + + install(Auth) { + // settings + } + +} +``` + +# Usage + +See [Auth documentation](https://supabase.com/docs/reference/kotlin/auth-signup) for usage diff --git a/GoTrue/build.gradle.kts b/Auth/build.gradle.kts similarity index 98% rename from GoTrue/build.gradle.kts rename to Auth/build.gradle.kts index 354dea6e3..971f4c78a 100644 --- a/GoTrue/build.gradle.kts +++ b/Auth/build.gradle.kts @@ -29,6 +29,7 @@ kotlin { withWatchos() withMingw() withJs() + withWasmJs() } } } diff --git a/GoTrue/src/androidMain/AndroidManifest.xml b/Auth/src/androidMain/AndroidManifest.xml similarity index 86% rename from GoTrue/src/androidMain/AndroidManifest.xml rename to Auth/src/androidMain/AndroidManifest.xml index bc8472a06..088c82e26 100644 --- a/GoTrue/src/androidMain/AndroidManifest.xml +++ b/Auth/src/androidMain/AndroidManifest.xml @@ -9,7 +9,7 @@ android:exported="false" tools:node="merge"> diff --git a/GoTrue/src/androidMain/kotlin/io/github/jan/supabase/gotrue/Android.kt b/Auth/src/androidMain/kotlin/io/github/jan/supabase/auth/Android.kt similarity index 95% rename from GoTrue/src/androidMain/kotlin/io/github/jan/supabase/gotrue/Android.kt rename to Auth/src/androidMain/kotlin/io/github/jan/supabase/auth/Android.kt index 004e0b729..c1378f8ab 100644 --- a/GoTrue/src/androidMain/kotlin/io/github/jan/supabase/gotrue/Android.kt +++ b/Auth/src/androidMain/kotlin/io/github/jan/supabase/auth/Android.kt @@ -1,11 +1,11 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import android.content.Intent import android.net.Uri import androidx.browser.customtabs.CustomTabsIntent import io.github.jan.supabase.SupabaseClient import io.github.jan.supabase.annotations.SupabaseInternal -import io.github.jan.supabase.gotrue.user.UserSession +import io.github.jan.supabase.auth.user.UserSession import kotlinx.coroutines.launch internal fun openUrl(uri: Uri, action: ExternalAuthAction) { diff --git a/GoTrue/src/androidMain/kotlin/io/github/jan/supabase/gotrue/AuthConfig.kt b/Auth/src/androidMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt similarity index 64% rename from GoTrue/src/androidMain/kotlin/io/github/jan/supabase/gotrue/AuthConfig.kt rename to Auth/src/androidMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt index 67d9fb384..e20e9fec3 100644 --- a/GoTrue/src/androidMain/kotlin/io/github/jan/supabase/gotrue/AuthConfig.kt +++ b/Auth/src/androidMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt @@ -1,9 +1,7 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import androidx.browser.customtabs.CustomTabsIntent -import io.github.jan.supabase.gotrue.ExternalAuthAction.Companion.CUSTOM_TABS -import io.github.jan.supabase.gotrue.ExternalAuthAction.Companion.EXTERNAL_BROWSER -import io.github.jan.supabase.gotrue.providers.ExternalAuthConfig +import io.github.jan.supabase.auth.providers.ExternalAuthConfig import io.github.jan.supabase.plugins.CustomSerializationConfig /** @@ -38,22 +36,12 @@ sealed interface ExternalAuthAction { data class CustomTabs(val intentBuilder: CustomTabsIntent.Builder.() -> Unit = {}) : ExternalAuthAction companion object { + /** * The default action to use for the OAuth flow */ val DEFAULT: ExternalAuthAction = ExternalBrowser - /** - * External browser action - */ - @Deprecated("Use ExternalBrowser object instead", ReplaceWith("ExternalAuthAction.ExternalBrowser")) - val EXTERNAL_BROWSER: ExternalAuthAction = ExternalBrowser - - /** - * Custom tabs action - */ - @Deprecated("Use CustomTabs class instead", ReplaceWith("ExternalAuthAction.CustomTabs()")) - val CUSTOM_TABS: ExternalAuthAction = CustomTabs() } } diff --git a/GoTrue/src/androidMain/kotlin/io/github/jan/supabase/gotrue/RedirectUrl.android.kt b/Auth/src/androidMain/kotlin/io/github/jan/supabase/auth/RedirectUrl.android.kt similarity index 81% rename from GoTrue/src/androidMain/kotlin/io/github/jan/supabase/gotrue/RedirectUrl.android.kt rename to Auth/src/androidMain/kotlin/io/github/jan/supabase/auth/RedirectUrl.android.kt index 9ddc4717e..2c8887fef 100644 --- a/GoTrue/src/androidMain/kotlin/io/github/jan/supabase/gotrue/RedirectUrl.android.kt +++ b/Auth/src/androidMain/kotlin/io/github/jan/supabase/auth/RedirectUrl.android.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.annotations.SupabaseInternal diff --git a/GoTrue/src/androidMain/kotlin/io/github/jan/supabase/gotrue/Utils.android.kt b/Auth/src/androidMain/kotlin/io/github/jan/supabase/auth/Utils.android.kt similarity index 84% rename from GoTrue/src/androidMain/kotlin/io/github/jan/supabase/gotrue/Utils.android.kt rename to Auth/src/androidMain/kotlin/io/github/jan/supabase/auth/Utils.android.kt index 0a1b89cde..fa42ca949 100644 --- a/GoTrue/src/androidMain/kotlin/io/github/jan/supabase/gotrue/Utils.android.kt +++ b/Auth/src/androidMain/kotlin/io/github/jan/supabase/auth/Utils.android.kt @@ -1,9 +1,9 @@ @file:Suppress("RedundantSuspendModifier") -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import android.net.Uri import io.github.jan.supabase.SupabaseClient -import io.github.jan.supabase.gotrue.user.UserSession +import io.github.jan.supabase.auth.user.UserSession internal actual suspend fun SupabaseClient.openExternalUrl(url: String) { openUrl(Uri.parse(url), auth.config.defaultExternalAuthAction) diff --git a/GoTrue/src/jvmMain/kotlin/io/github/jan/supabase/gotrue/providers/ExternalAuthConfig.kt b/Auth/src/androidMain/kotlin/io/github/jan/supabase/auth/providers/ExternalAuthConfig.kt similarity index 76% rename from GoTrue/src/jvmMain/kotlin/io/github/jan/supabase/gotrue/providers/ExternalAuthConfig.kt rename to Auth/src/androidMain/kotlin/io/github/jan/supabase/auth/providers/ExternalAuthConfig.kt index de8a14b38..267e6bf68 100644 --- a/GoTrue/src/jvmMain/kotlin/io/github/jan/supabase/gotrue/providers/ExternalAuthConfig.kt +++ b/Auth/src/androidMain/kotlin/io/github/jan/supabase/auth/providers/ExternalAuthConfig.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue.providers +package io.github.jan.supabase.auth.providers /** * Configuration for external authentication providers like Google, Twitter, etc. diff --git a/GoTrue/src/androidMain/kotlin/io/github/jan/supabase/gotrue/setupPlatform.kt b/Auth/src/androidMain/kotlin/io/github/jan/supabase/auth/setupPlatform.kt similarity index 98% rename from GoTrue/src/androidMain/kotlin/io/github/jan/supabase/gotrue/setupPlatform.kt rename to Auth/src/androidMain/kotlin/io/github/jan/supabase/auth/setupPlatform.kt index e14b0c8d4..458a00b36 100644 --- a/GoTrue/src/androidMain/kotlin/io/github/jan/supabase/gotrue/setupPlatform.kt +++ b/Auth/src/androidMain/kotlin/io/github/jan/supabase/auth/setupPlatform.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import android.content.Context import androidx.lifecycle.DefaultLifecycleObserver diff --git a/GoTrue/src/androidUnitTest/kotlin/platformSettings.kt b/Auth/src/androidUnitTest/kotlin/platformSettings.kt similarity index 63% rename from GoTrue/src/androidUnitTest/kotlin/platformSettings.kt rename to Auth/src/androidUnitTest/kotlin/platformSettings.kt index 2386095b9..687460611 100644 --- a/GoTrue/src/androidUnitTest/kotlin/platformSettings.kt +++ b/Auth/src/androidUnitTest/kotlin/platformSettings.kt @@ -1,4 +1,4 @@ -import io.github.jan.supabase.gotrue.AuthConfig +import io.github.jan.supabase.auth.AuthConfig actual fun AuthConfig.platformSettings() { enableLifecycleCallbacks = false diff --git a/GoTrue/src/appleMain/kotlin/io/github/jan/supabase/gotrue/Apple.kt b/Auth/src/appleMain/kotlin/io/github/jan/supabase/auth/Apple.kt similarity index 94% rename from GoTrue/src/appleMain/kotlin/io/github/jan/supabase/gotrue/Apple.kt rename to Auth/src/appleMain/kotlin/io/github/jan/supabase/auth/Apple.kt index 063aad94e..c83e19a43 100644 --- a/GoTrue/src/appleMain/kotlin/io/github/jan/supabase/gotrue/Apple.kt +++ b/Auth/src/appleMain/kotlin/io/github/jan/supabase/auth/Apple.kt @@ -1,7 +1,7 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.SupabaseClient -import io.github.jan.supabase.gotrue.user.UserSession +import io.github.jan.supabase.auth.user.UserSession import io.github.jan.supabase.logging.d import kotlinx.coroutines.launch import platform.Foundation.NSURL diff --git a/GoTrue/src/appleMain/kotlin/io/github/jan/supabase/gotrue/RedirectUrl.apple.kt b/Auth/src/appleMain/kotlin/io/github/jan/supabase/auth/RedirectUrl.apple.kt similarity index 81% rename from GoTrue/src/appleMain/kotlin/io/github/jan/supabase/gotrue/RedirectUrl.apple.kt rename to Auth/src/appleMain/kotlin/io/github/jan/supabase/auth/RedirectUrl.apple.kt index 9ddc4717e..2c8887fef 100644 --- a/GoTrue/src/appleMain/kotlin/io/github/jan/supabase/gotrue/RedirectUrl.apple.kt +++ b/Auth/src/appleMain/kotlin/io/github/jan/supabase/auth/RedirectUrl.apple.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.annotations.SupabaseInternal diff --git a/GoTrue/src/appleMain/kotlin/io/github/jan/supabase/gotrue/Utils.apple.kt b/Auth/src/appleMain/kotlin/io/github/jan/supabase/auth/Utils.apple.kt similarity index 66% rename from GoTrue/src/appleMain/kotlin/io/github/jan/supabase/gotrue/Utils.apple.kt rename to Auth/src/appleMain/kotlin/io/github/jan/supabase/auth/Utils.apple.kt index af0b81ecd..38015ada0 100644 --- a/GoTrue/src/appleMain/kotlin/io/github/jan/supabase/gotrue/Utils.apple.kt +++ b/Auth/src/appleMain/kotlin/io/github/jan/supabase/auth/Utils.apple.kt @@ -1,7 +1,7 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.SupabaseClient -import io.github.jan.supabase.gotrue.providers.openUrl +import io.github.jan.supabase.auth.providers.openUrl import platform.Foundation.NSURL internal actual suspend fun SupabaseClient.openExternalUrl(url: String) { diff --git a/GoTrue/src/linuxMain/kotlin/io/github/jan/supabase/gotrue/providers/ExternalAuthConfig.kt b/Auth/src/appleMain/kotlin/io/github/jan/supabase/auth/providers/ExternalAuthConfig.kt similarity index 76% rename from GoTrue/src/linuxMain/kotlin/io/github/jan/supabase/gotrue/providers/ExternalAuthConfig.kt rename to Auth/src/appleMain/kotlin/io/github/jan/supabase/auth/providers/ExternalAuthConfig.kt index de8a14b38..267e6bf68 100644 --- a/GoTrue/src/linuxMain/kotlin/io/github/jan/supabase/gotrue/providers/ExternalAuthConfig.kt +++ b/Auth/src/appleMain/kotlin/io/github/jan/supabase/auth/providers/ExternalAuthConfig.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue.providers +package io.github.jan.supabase.auth.providers /** * Configuration for external authentication providers like Google, Twitter, etc. diff --git a/GoTrue/src/appleMain/kotlin/io/github/jan/supabase/gotrue/providers/OAuthProvider.kt b/Auth/src/appleMain/kotlin/io/github/jan/supabase/auth/providers/OAuthProvider.kt similarity index 60% rename from GoTrue/src/appleMain/kotlin/io/github/jan/supabase/gotrue/providers/OAuthProvider.kt rename to Auth/src/appleMain/kotlin/io/github/jan/supabase/auth/providers/OAuthProvider.kt index cabdc4940..a6f9c1424 100644 --- a/GoTrue/src/appleMain/kotlin/io/github/jan/supabase/gotrue/providers/OAuthProvider.kt +++ b/Auth/src/appleMain/kotlin/io/github/jan/supabase/auth/providers/OAuthProvider.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue.providers +package io.github.jan.supabase.auth.providers import platform.Foundation.NSURL diff --git a/GoTrue/src/jvmMain/kotlin/io/github/jan/supabase/gotrue/setupPlatform.kt b/Auth/src/appleMain/kotlin/io/github/jan/supabase/auth/setupPlatform.kt similarity index 75% rename from GoTrue/src/jvmMain/kotlin/io/github/jan/supabase/gotrue/setupPlatform.kt rename to Auth/src/appleMain/kotlin/io/github/jan/supabase/auth/setupPlatform.kt index 95b106316..a1b155557 100644 --- a/GoTrue/src/jvmMain/kotlin/io/github/jan/supabase/gotrue/setupPlatform.kt +++ b/Auth/src/appleMain/kotlin/io/github/jan/supabase/auth/setupPlatform.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.annotations.SupabaseInternal diff --git a/GoTrue/src/jvmTest/kotlin/platformSettings.kt b/Auth/src/appleTest/kotlin/platformSettings.kt similarity index 50% rename from GoTrue/src/jvmTest/kotlin/platformSettings.kt rename to Auth/src/appleTest/kotlin/platformSettings.kt index c97cb2bd9..f6fa8ceb8 100644 --- a/GoTrue/src/jvmTest/kotlin/platformSettings.kt +++ b/Auth/src/appleTest/kotlin/platformSettings.kt @@ -1,3 +1,3 @@ -import io.github.jan.supabase.gotrue.AuthConfig +import io.github.jan.supabase.auth.AuthConfig actual fun AuthConfig.platformSettings() = Unit \ No newline at end of file diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/AccessToken.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/AccessToken.kt similarity index 97% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/AccessToken.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/AccessToken.kt index 0a3a4a537..ed2a5e6ce 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/AccessToken.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/AccessToken.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.SupabaseClient import io.github.jan.supabase.annotations.SupabaseInternal diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/Auth.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/Auth.kt similarity index 92% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/Auth.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/Auth.kt index 3ed62db8b..d7c1ad003 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/Auth.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/Auth.kt @@ -1,22 +1,22 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.SupabaseClient +import io.github.jan.supabase.auth.admin.AdminApi +import io.github.jan.supabase.auth.exception.AuthRestException +import io.github.jan.supabase.auth.exception.AuthWeakPasswordException +import io.github.jan.supabase.auth.mfa.MfaApi +import io.github.jan.supabase.auth.providers.AuthProvider +import io.github.jan.supabase.auth.providers.ExternalAuthConfigDefaults +import io.github.jan.supabase.auth.providers.Google +import io.github.jan.supabase.auth.providers.OAuthProvider +import io.github.jan.supabase.auth.providers.builtin.Email +import io.github.jan.supabase.auth.providers.builtin.Phone +import io.github.jan.supabase.auth.providers.builtin.SSO +import io.github.jan.supabase.auth.user.UserInfo +import io.github.jan.supabase.auth.user.UserSession +import io.github.jan.supabase.auth.user.UserUpdateBuilder import io.github.jan.supabase.exceptions.HttpRequestException import io.github.jan.supabase.exceptions.RestException -import io.github.jan.supabase.gotrue.admin.AdminApi -import io.github.jan.supabase.gotrue.exception.AuthRestException -import io.github.jan.supabase.gotrue.exception.AuthWeakPasswordException -import io.github.jan.supabase.gotrue.mfa.MfaApi -import io.github.jan.supabase.gotrue.providers.AuthProvider -import io.github.jan.supabase.gotrue.providers.ExternalAuthConfigDefaults -import io.github.jan.supabase.gotrue.providers.Google -import io.github.jan.supabase.gotrue.providers.OAuthProvider -import io.github.jan.supabase.gotrue.providers.builtin.Email -import io.github.jan.supabase.gotrue.providers.builtin.Phone -import io.github.jan.supabase.gotrue.providers.builtin.SSO -import io.github.jan.supabase.gotrue.user.UserInfo -import io.github.jan.supabase.gotrue.user.UserSession -import io.github.jan.supabase.gotrue.user.UserUpdateBuilder import io.github.jan.supabase.logging.SupabaseLogger import io.github.jan.supabase.logging.e import io.github.jan.supabase.plugins.CustomSerializationPlugin @@ -202,21 +202,6 @@ sealed interface Auth : MainPlugin, CustomSerializationPlugin { config: UserUpdateBuilder.() -> Unit ): UserInfo - /** - * Modifies the current user - * @param updateCurrentUser Whether to update the current user in the [SupabaseClient] - * @param config The configuration to use - * @throws RestException or one of its subclasses if receiving an error response. If the error response contains a error code, an [AuthRestException] will be thrown which can be used to easier identify the problem. - * @throws HttpRequestTimeoutException if the request timed out - * @throws HttpRequestException on network related issues - */ - @Deprecated("Use updateUser instead") - suspend fun modifyUser( - updateCurrentUser: Boolean = true, - redirectUrl: String? = defaultRedirectUrl(), - config: UserUpdateBuilder.() -> Unit - ): UserInfo = updateUser(updateCurrentUser, redirectUrl, config) - /** * Resends an existing signup confirmation email, email change email * @param type The email otp type diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/AuthConfig.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt similarity index 98% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/AuthConfig.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt index f812e85f1..f0b3c6b61 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/AuthConfig.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.SupabaseClientBuilder import io.github.jan.supabase.SupabaseSerializer @@ -147,6 +147,7 @@ val AuthConfig.deepLinkOrNull: String? * @param enableLifecycleCallbacks Whether to stop auto-refresh on focus loss, and resume it on focus again. Currently only supported on Android. * @see AuthConfigDefaults */ +@Suppress("LongParameterList") fun AuthConfigDefaults.minimalSettings( alwaysAutoRefresh: Boolean = false, autoLoadFromStorage: Boolean = false, diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/AuthExtensions.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/AuthExtensions.kt similarity index 96% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/AuthExtensions.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/AuthExtensions.kt index 91f89af50..fb589bd75 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/AuthExtensions.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/AuthExtensions.kt @@ -1,7 +1,7 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth +import io.github.jan.supabase.auth.user.UserSession import io.github.jan.supabase.encodeToJsonElement -import io.github.jan.supabase.gotrue.user.UserSession import io.github.jan.supabase.logging.d import kotlinx.serialization.json.jsonObject diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/AuthImpl.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/AuthImpl.kt similarity index 96% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/AuthImpl.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/AuthImpl.kt index f8cecbdcd..b7fa0ec53 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/AuthImpl.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/AuthImpl.kt @@ -1,28 +1,28 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.SupabaseClient import io.github.jan.supabase.annotations.SupabaseExperimental import io.github.jan.supabase.annotations.SupabaseInternal +import io.github.jan.supabase.auth.admin.AdminApi +import io.github.jan.supabase.auth.admin.AdminApiImpl +import io.github.jan.supabase.auth.exception.AuthRestException +import io.github.jan.supabase.auth.exception.AuthSessionMissingException +import io.github.jan.supabase.auth.exception.AuthWeakPasswordException +import io.github.jan.supabase.auth.mfa.MfaApi +import io.github.jan.supabase.auth.mfa.MfaApiImpl +import io.github.jan.supabase.auth.providers.AuthProvider +import io.github.jan.supabase.auth.providers.ExternalAuthConfigDefaults +import io.github.jan.supabase.auth.providers.OAuthProvider +import io.github.jan.supabase.auth.providers.builtin.OTP +import io.github.jan.supabase.auth.providers.builtin.SSO +import io.github.jan.supabase.auth.user.UserInfo +import io.github.jan.supabase.auth.user.UserSession +import io.github.jan.supabase.auth.user.UserUpdateBuilder import io.github.jan.supabase.bodyOrNull import io.github.jan.supabase.exceptions.BadRequestRestException import io.github.jan.supabase.exceptions.RestException import io.github.jan.supabase.exceptions.UnauthorizedRestException import io.github.jan.supabase.exceptions.UnknownRestException -import io.github.jan.supabase.gotrue.admin.AdminApi -import io.github.jan.supabase.gotrue.admin.AdminApiImpl -import io.github.jan.supabase.gotrue.exception.AuthRestException -import io.github.jan.supabase.gotrue.exception.AuthSessionMissingException -import io.github.jan.supabase.gotrue.exception.AuthWeakPasswordException -import io.github.jan.supabase.gotrue.mfa.MfaApi -import io.github.jan.supabase.gotrue.mfa.MfaApiImpl -import io.github.jan.supabase.gotrue.providers.AuthProvider -import io.github.jan.supabase.gotrue.providers.ExternalAuthConfigDefaults -import io.github.jan.supabase.gotrue.providers.OAuthProvider -import io.github.jan.supabase.gotrue.providers.builtin.OTP -import io.github.jan.supabase.gotrue.providers.builtin.SSO -import io.github.jan.supabase.gotrue.user.UserInfo -import io.github.jan.supabase.gotrue.user.UserSession -import io.github.jan.supabase.gotrue.user.UserUpdateBuilder import io.github.jan.supabase.logging.d import io.github.jan.supabase.logging.e import io.github.jan.supabase.logging.i diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/AuthenticatedSupabaseApi.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/AuthenticatedSupabaseApi.kt similarity index 98% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/AuthenticatedSupabaseApi.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/AuthenticatedSupabaseApi.kt index 424afc341..aca2fd113 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/AuthenticatedSupabaseApi.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/AuthenticatedSupabaseApi.kt @@ -1,5 +1,5 @@ @file:Suppress("UndocumentedPublicClass", "UndocumentedPublicFunction") -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.SupabaseClient import io.github.jan.supabase.annotations.SupabaseInternal diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/CodeVerifierCache.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/CodeVerifierCache.kt similarity index 96% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/CodeVerifierCache.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/CodeVerifierCache.kt index 8f1d32278..8a1cbff6e 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/CodeVerifierCache.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/CodeVerifierCache.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import kotlinx.atomicfu.AtomicRef import kotlinx.atomicfu.atomic diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/GoTrueErrorResponse.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/GoTrueErrorResponse.kt similarity index 98% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/GoTrueErrorResponse.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/GoTrueErrorResponse.kt index 101d8034f..83d8381da 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/GoTrueErrorResponse.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/GoTrueErrorResponse.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/JsonUtils.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/JsonUtils.kt similarity index 92% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/JsonUtils.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/JsonUtils.kt index 588c84a31..82a4b12d3 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/JsonUtils.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/JsonUtils.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import kotlinx.serialization.json.JsonObjectBuilder import kotlinx.serialization.json.put diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/OtpType.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/OtpType.kt similarity index 90% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/OtpType.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/OtpType.kt index c59bf64f1..dbda5b060 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/OtpType.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/OtpType.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth /** * OTPs (One Time Passwords) are used to authenticate users via email or phone. @@ -54,7 +54,14 @@ sealed interface OtpType { * Phone OTP types */ enum class Phone(override val type: String): OtpType { + /** + * OTP type for sms messages + */ SMS("sms"), + + /** + * OTP type for phone change flows + */ PHONE_CHANGE("phone_change") } diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/PKCE.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/PKCE.kt similarity index 95% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/PKCE.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/PKCE.kt index 3ef700bd4..76b138fbf 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/PKCE.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/PKCE.kt @@ -1,5 +1,5 @@ @file:Suppress("MatchingDeclarationName") -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import korlibs.crypto.SHA256 import korlibs.crypto.SecureRandom diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/PostgrestFilterDSL.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/PostgrestFilterDSL.kt similarity index 82% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/PostgrestFilterDSL.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/PostgrestFilterDSL.kt index 8f40ed9ce..981352837 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/PostgrestFilterDSL.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/PostgrestFilterDSL.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth /** * Used to mark Postgrest filter DSL functions diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/RedirectUrl.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/RedirectUrl.kt similarity index 87% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/RedirectUrl.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/RedirectUrl.kt index 40b794cef..80ac1c1d7 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/RedirectUrl.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/RedirectUrl.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.annotations.SupabaseInternal diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/SessionManager.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/SessionManager.kt similarity index 91% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/SessionManager.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/SessionManager.kt index 743543853..14703dccc 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/SessionManager.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/SessionManager.kt @@ -1,6 +1,6 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth -import io.github.jan.supabase.gotrue.user.UserSession +import io.github.jan.supabase.auth.user.UserSession import kotlinx.atomicfu.AtomicRef import kotlinx.atomicfu.atomic diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/SessionStatus.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/SessionStatus.kt similarity index 95% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/SessionStatus.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/SessionStatus.kt index 0e2e2dd6e..73e04f6dd 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/SessionStatus.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/SessionStatus.kt @@ -1,7 +1,7 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth -import io.github.jan.supabase.gotrue.providers.AuthProvider -import io.github.jan.supabase.gotrue.user.UserSession +import io.github.jan.supabase.auth.providers.AuthProvider +import io.github.jan.supabase.auth.user.UserSession /** * Represents the status of the current session in [Auth] diff --git a/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/SignOutScope.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/SignOutScope.kt new file mode 100644 index 000000000..5992199e2 --- /dev/null +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/SignOutScope.kt @@ -0,0 +1,23 @@ +package io.github.jan.supabase.auth + +/** + * Represents the scope of a sign-out action. + * + * The sign-out scope determines the scope of the sign-out action being performed. + */ +enum class SignOutScope { + /** + * Sign-out action applies to all sessions across the entire system. + */ + GLOBAL, + + /** + * Sign-out action applies only to the current session. + */ + LOCAL, + + /** + * Sign-out action applies to other sessions, excluding the current session. + */ + OTHERS +} \ No newline at end of file diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/Utils.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/Utils.kt similarity index 92% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/Utils.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/Utils.kt index 803e19979..bb3f4ce30 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/Utils.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/Utils.kt @@ -1,8 +1,8 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.SupabaseClient import io.github.jan.supabase.annotations.SupabaseInternal -import io.github.jan.supabase.gotrue.user.UserSession +import io.github.jan.supabase.auth.user.UserSession import io.github.jan.supabase.logging.d import io.ktor.client.request.HttpRequestBuilder import kotlinx.coroutines.launch diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/admin/AdminApi.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/admin/AdminApi.kt similarity index 95% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/admin/AdminApi.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/admin/AdminApi.kt index 6b7d946e6..0eb69881b 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/admin/AdminApi.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/admin/AdminApi.kt @@ -1,11 +1,11 @@ -package io.github.jan.supabase.gotrue.admin +package io.github.jan.supabase.auth.admin import io.github.jan.supabase.annotations.SupabaseInternal -import io.github.jan.supabase.gotrue.Auth -import io.github.jan.supabase.gotrue.AuthImpl -import io.github.jan.supabase.gotrue.SignOutScope -import io.github.jan.supabase.gotrue.user.UserInfo -import io.github.jan.supabase.gotrue.user.UserMfaFactor +import io.github.jan.supabase.auth.Auth +import io.github.jan.supabase.auth.AuthImpl +import io.github.jan.supabase.auth.SignOutScope +import io.github.jan.supabase.auth.user.UserInfo +import io.github.jan.supabase.auth.user.UserMfaFactor import io.github.jan.supabase.putJsonObject import io.github.jan.supabase.safeBody import io.github.jan.supabase.supabaseJson diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/admin/AdminUserBuilder.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/admin/AdminUserBuilder.kt similarity index 98% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/admin/AdminUserBuilder.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/admin/AdminUserBuilder.kt index df16e5818..d7e1b9812 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/admin/AdminUserBuilder.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/admin/AdminUserBuilder.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue.admin +package io.github.jan.supabase.auth.admin import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/admin/AdminUserUpdateBuilder.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/admin/AdminUserUpdateBuilder.kt similarity index 97% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/admin/AdminUserUpdateBuilder.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/admin/AdminUserUpdateBuilder.kt index b227c1950..e15152ef5 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/admin/AdminUserUpdateBuilder.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/admin/AdminUserUpdateBuilder.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue.admin +package io.github.jan.supabase.auth.admin import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/admin/LinkType.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/admin/LinkType.kt similarity index 98% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/admin/LinkType.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/admin/LinkType.kt index d37fda7df..843c67e54 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/admin/LinkType.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/admin/LinkType.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue.admin +package io.github.jan.supabase.auth.admin import io.github.jan.supabase.annotations.SupabaseInternal import kotlinx.serialization.SerialName diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/exception/AuthErrorCode.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/exception/AuthErrorCode.kt similarity index 97% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/exception/AuthErrorCode.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/exception/AuthErrorCode.kt index 53d5ddfb3..7d3097684 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/exception/AuthErrorCode.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/exception/AuthErrorCode.kt @@ -1,4 +1,5 @@ -package io.github.jan.supabase.gotrue.exception +@file:Suppress("UndocumentedPublicProperty") +package io.github.jan.supabase.auth.exception /** * Enum class for error codes returned by the Auth API. diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/exception/AuthRestException.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/exception/AuthRestException.kt similarity index 94% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/exception/AuthRestException.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/exception/AuthRestException.kt index 6dbd18a96..f5c123a10 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/exception/AuthRestException.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/exception/AuthRestException.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue.exception +package io.github.jan.supabase.auth.exception import io.github.jan.supabase.exceptions.RestException diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/exception/AuthSessionMissingException.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/exception/AuthSessionMissingException.kt similarity index 88% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/exception/AuthSessionMissingException.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/exception/AuthSessionMissingException.kt index 74379c06a..f5a180f10 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/exception/AuthSessionMissingException.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/exception/AuthSessionMissingException.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue.exception +package io.github.jan.supabase.auth.exception /** * Exception thrown when a session is not found. diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/exception/AuthWeakPasswordException.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/exception/AuthWeakPasswordException.kt similarity index 90% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/exception/AuthWeakPasswordException.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/exception/AuthWeakPasswordException.kt index 9b5b2e4c8..d9d1a6dc3 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/exception/AuthWeakPasswordException.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/exception/AuthWeakPasswordException.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue.exception +package io.github.jan.supabase.auth.exception /** * Exception thrown on sign-up if the password is too weak diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/mfa/AuthenticatorAssuranceLevel.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/mfa/AuthenticatorAssuranceLevel.kt similarity index 96% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/mfa/AuthenticatorAssuranceLevel.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/mfa/AuthenticatorAssuranceLevel.kt index d223a8cea..2dff23a4c 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/mfa/AuthenticatorAssuranceLevel.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/mfa/AuthenticatorAssuranceLevel.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue.mfa +package io.github.jan.supabase.auth.mfa /** * The assurance level of a session diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/mfa/FactorType.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/mfa/FactorType.kt similarity index 98% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/mfa/FactorType.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/mfa/FactorType.kt index 93bcd11b3..c5b7072b6 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/mfa/FactorType.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/mfa/FactorType.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue.mfa +package io.github.jan.supabase.auth.mfa import io.github.jan.supabase.annotations.SupabaseInternal import io.github.jan.supabase.supabaseJson diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/mfa/MfaApi.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/mfa/MfaApi.kt similarity index 77% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/mfa/MfaApi.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/mfa/MfaApi.kt index 22b2fc9aa..b7ba3ab16 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/mfa/MfaApi.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/mfa/MfaApi.kt @@ -1,11 +1,11 @@ -package io.github.jan.supabase.gotrue.mfa - -import io.github.jan.supabase.gotrue.Auth -import io.github.jan.supabase.gotrue.AuthImpl -import io.github.jan.supabase.gotrue.SessionStatus -import io.github.jan.supabase.gotrue.providers.builtin.Phone -import io.github.jan.supabase.gotrue.user.UserMfaFactor -import io.github.jan.supabase.gotrue.user.UserSession +package io.github.jan.supabase.auth.mfa + +import io.github.jan.supabase.auth.Auth +import io.github.jan.supabase.auth.AuthImpl +import io.github.jan.supabase.auth.SessionStatus +import io.github.jan.supabase.auth.providers.builtin.Phone +import io.github.jan.supabase.auth.user.UserMfaFactor +import io.github.jan.supabase.auth.user.UserSession import io.github.jan.supabase.putJsonObject import io.github.jan.supabase.safeBody import io.ktor.client.call.body @@ -37,43 +37,12 @@ sealed interface MfaApi { */ val statusFlow: Flow - /** - * Checks whether the current session was authenticated with MFA or the user has a verified MFA factor. Note that if the user has verified a factor on another device and the user hasn't been updated on this device, this will return false. - * You can use [Auth.retrieveUserForCurrentSession] to update the user and this property will update. - */ - @Deprecated("Use statusFlow instead", ReplaceWith("statusFlow")) - val isMfaEnabledFlow: Flow - - /** - * Checks whether the user has a verified MFA factor. Note that if the user has verified a factor on another device and the user hasn't been updated on this device, this will return false. - * You can use [Auth.retrieveUserForCurrentSession] to update the user and this property will update. - */ - @Deprecated("Use status.enabled instead", ReplaceWith("status.enabled")) - val isMfaEnabled: Boolean - get() { - val mfaLevel = getAuthenticatorAssuranceLevel() - return mfaLevel.next == AuthenticatorAssuranceLevel.AAL2 - } - /** * Returns all verified factors from the current user session. If the user has no verified factors or there is no session, an empty list is returned. * To fetch up-to-date factors, use [retrieveFactorsForCurrentUser]. */ val verifiedFactors: List - /** - * Checks whether the current session is authenticated with MFA - */ - @Deprecated("Use status.active instead", ReplaceWith("status.active")) - val loggedInUsingMfa: Boolean - get() = getAuthenticatorAssuranceLevel().current == AuthenticatorAssuranceLevel.AAL2 - - /** - * Checks whether the current session is authenticated with MFA - */ - @Deprecated("Use statusFlow instead", ReplaceWith("statusFlow")) - val loggedInUsingMfaFlow: Flow - /** * @param factorType The type of MFA factor to enroll. Currently only supports TOTP. * @param friendlyName Human-readable name assigned to a device @@ -156,10 +125,6 @@ internal class MfaApiImpl( } } - @Deprecated("Use statusFlow instead", replaceWith = ReplaceWith("statusFlow")) - override val isMfaEnabledFlow: Flow = statusFlow.map { it.enabled } - @Deprecated("Use statusFlow instead", replaceWith = ReplaceWith("statusFlow")) - override val loggedInUsingMfaFlow: Flow = statusFlow.map { it.active } override val verifiedFactors: List get() = auth.currentUserOrNull()?.factors?.filter(UserMfaFactor::isVerified) ?: emptyList() diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/mfa/MfaChallenge.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/mfa/MfaChallenge.kt similarity index 93% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/mfa/MfaChallenge.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/mfa/MfaChallenge.kt index e72739ada..15128f8f1 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/mfa/MfaChallenge.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/mfa/MfaChallenge.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue.mfa +package io.github.jan.supabase.auth.mfa import kotlinx.datetime.Instant import kotlinx.serialization.SerialName diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/mfa/MfaFactor.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/mfa/MfaFactor.kt similarity index 86% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/mfa/MfaFactor.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/mfa/MfaFactor.kt index f9c5c52cb..d1d66998e 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/mfa/MfaFactor.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/mfa/MfaFactor.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue.mfa +package io.github.jan.supabase.auth.mfa /** * Represents an enrolled MFA Factor diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/mfa/MfaStatus.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/mfa/MfaStatus.kt similarity index 89% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/mfa/MfaStatus.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/mfa/MfaStatus.kt index 0b89a6df2..f92bfcdd2 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/mfa/MfaStatus.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/mfa/MfaStatus.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue.mfa +package io.github.jan.supabase.auth.mfa /** * Represents the MFA status of a user. diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/AuthProvider.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/AuthProvider.kt similarity index 91% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/AuthProvider.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/AuthProvider.kt index 881a7641b..f53468584 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/AuthProvider.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/AuthProvider.kt @@ -1,7 +1,7 @@ -package io.github.jan.supabase.gotrue.providers +package io.github.jan.supabase.auth.providers import io.github.jan.supabase.SupabaseClient -import io.github.jan.supabase.gotrue.user.UserSession +import io.github.jan.supabase.auth.user.UserSession /** * An authentication provider diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/ExternalAuthConfig.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/ExternalAuthConfig.kt similarity index 87% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/ExternalAuthConfig.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/ExternalAuthConfig.kt index 7fffe5f25..d25fb73e6 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/ExternalAuthConfig.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/ExternalAuthConfig.kt @@ -1,6 +1,6 @@ -package io.github.jan.supabase.gotrue.providers +package io.github.jan.supabase.auth.providers -import io.github.jan.supabase.gotrue.Auth +import io.github.jan.supabase.auth.Auth /** * Configuration for external authentication providers like Google, Twitter, etc. diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/IDTokenProvider.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/IDTokenProvider.kt similarity index 94% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/IDTokenProvider.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/IDTokenProvider.kt index a15eebb5c..6dd56a252 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/IDTokenProvider.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/IDTokenProvider.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue.providers +package io.github.jan.supabase.auth.providers import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/OAuthProvider.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/OAuthProvider.kt similarity index 86% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/OAuthProvider.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/OAuthProvider.kt index 1b8a1f84f..f50d0f7fe 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/OAuthProvider.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/OAuthProvider.kt @@ -1,9 +1,9 @@ -package io.github.jan.supabase.gotrue.providers +package io.github.jan.supabase.auth.providers import io.github.jan.supabase.SupabaseClient -import io.github.jan.supabase.gotrue.auth -import io.github.jan.supabase.gotrue.startExternalAuth -import io.github.jan.supabase.gotrue.user.UserSession +import io.github.jan.supabase.auth.auth +import io.github.jan.supabase.auth.startExternalAuth +import io.github.jan.supabase.auth.user.UserSession /** * Represents an OAuth provider. diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/Providers.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/Providers.kt similarity index 97% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/Providers.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/Providers.kt index 54f19f1bf..698532e31 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/Providers.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/Providers.kt @@ -1,5 +1,5 @@ @file:Suppress("UndocumentedPublicClass") -package io.github.jan.supabase.gotrue.providers +package io.github.jan.supabase.auth.providers data object Google : IDTokenProvider() { diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/builtin/CaptchaTokenSerializer.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/builtin/CaptchaTokenSerializer.kt similarity index 95% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/builtin/CaptchaTokenSerializer.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/builtin/CaptchaTokenSerializer.kt index b6eca4b6e..d5e6014dd 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/builtin/CaptchaTokenSerializer.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/builtin/CaptchaTokenSerializer.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue.providers.builtin +package io.github.jan.supabase.auth.providers.builtin import io.github.jan.supabase.annotations.SupabaseInternal import kotlinx.serialization.KSerializer diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/builtin/DefaultAuthProvider.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/builtin/DefaultAuthProvider.kt similarity index 86% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/builtin/DefaultAuthProvider.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/builtin/DefaultAuthProvider.kt index 7dff8128d..b328cb296 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/builtin/DefaultAuthProvider.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/builtin/DefaultAuthProvider.kt @@ -1,17 +1,17 @@ -package io.github.jan.supabase.gotrue.providers.builtin +package io.github.jan.supabase.auth.providers.builtin import io.github.jan.supabase.SupabaseClient import io.github.jan.supabase.annotations.SupabaseExperimental import io.github.jan.supabase.annotations.SupabaseInternal -import io.github.jan.supabase.gotrue.AuthImpl -import io.github.jan.supabase.gotrue.FlowType -import io.github.jan.supabase.gotrue.auth -import io.github.jan.supabase.gotrue.generateCodeChallenge -import io.github.jan.supabase.gotrue.generateCodeVerifier -import io.github.jan.supabase.gotrue.providers.AuthProvider -import io.github.jan.supabase.gotrue.putCodeChallenge -import io.github.jan.supabase.gotrue.redirectTo -import io.github.jan.supabase.gotrue.user.UserSession +import io.github.jan.supabase.auth.AuthImpl +import io.github.jan.supabase.auth.FlowType +import io.github.jan.supabase.auth.auth +import io.github.jan.supabase.auth.generateCodeChallenge +import io.github.jan.supabase.auth.generateCodeVerifier +import io.github.jan.supabase.auth.providers.AuthProvider +import io.github.jan.supabase.auth.putCodeChallenge +import io.github.jan.supabase.auth.redirectTo +import io.github.jan.supabase.auth.user.UserSession import io.github.jan.supabase.putJsonObject import io.github.jan.supabase.supabaseJson import io.ktor.client.call.body diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/builtin/Email.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/builtin/Email.kt similarity index 92% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/builtin/Email.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/builtin/Email.kt index 8d217c896..5fb777aab 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/builtin/Email.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/builtin/Email.kt @@ -1,7 +1,7 @@ -package io.github.jan.supabase.gotrue.providers.builtin +package io.github.jan.supabase.auth.providers.builtin +import io.github.jan.supabase.auth.user.UserInfo import io.github.jan.supabase.exceptions.SupabaseEncodingException -import io.github.jan.supabase.gotrue.user.UserInfo import io.github.jan.supabase.supabaseJson import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.MissingFieldException diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/builtin/IDToken.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/builtin/IDToken.kt similarity index 86% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/builtin/IDToken.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/builtin/IDToken.kt index 655158cd6..877fd5a4d 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/builtin/IDToken.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/builtin/IDToken.kt @@ -1,12 +1,12 @@ -package io.github.jan.supabase.gotrue.providers.builtin +package io.github.jan.supabase.auth.providers.builtin +import io.github.jan.supabase.auth.providers.Apple +import io.github.jan.supabase.auth.providers.Azure +import io.github.jan.supabase.auth.providers.Facebook +import io.github.jan.supabase.auth.providers.Google +import io.github.jan.supabase.auth.providers.IDTokenProvider +import io.github.jan.supabase.auth.user.UserInfo import io.github.jan.supabase.exceptions.SupabaseEncodingException -import io.github.jan.supabase.gotrue.providers.Apple -import io.github.jan.supabase.gotrue.providers.Azure -import io.github.jan.supabase.gotrue.providers.Facebook -import io.github.jan.supabase.gotrue.providers.Google -import io.github.jan.supabase.gotrue.providers.IDTokenProvider -import io.github.jan.supabase.gotrue.user.UserInfo import io.github.jan.supabase.supabaseJson import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.MissingFieldException diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/builtin/OTP.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/builtin/OTP.kt similarity index 86% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/builtin/OTP.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/builtin/OTP.kt index eb434953e..3875f0d62 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/builtin/OTP.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/builtin/OTP.kt @@ -1,17 +1,16 @@ -package io.github.jan.supabase.gotrue.providers.builtin +package io.github.jan.supabase.auth.providers.builtin import io.github.jan.supabase.SupabaseClient import io.github.jan.supabase.SupabaseSerializer +import io.github.jan.supabase.auth.AuthImpl +import io.github.jan.supabase.auth.FlowType +import io.github.jan.supabase.auth.auth +import io.github.jan.supabase.auth.generateCodeChallenge +import io.github.jan.supabase.auth.generateCodeVerifier +import io.github.jan.supabase.auth.providers.AuthProvider +import io.github.jan.supabase.auth.putCaptchaToken +import io.github.jan.supabase.auth.user.UserSession import io.github.jan.supabase.encodeToJsonElement -import io.github.jan.supabase.gotrue.Auth -import io.github.jan.supabase.gotrue.AuthImpl -import io.github.jan.supabase.gotrue.FlowType -import io.github.jan.supabase.gotrue.auth -import io.github.jan.supabase.gotrue.generateCodeChallenge -import io.github.jan.supabase.gotrue.generateCodeVerifier -import io.github.jan.supabase.gotrue.providers.AuthProvider -import io.github.jan.supabase.gotrue.putCaptchaToken -import io.github.jan.supabase.gotrue.user.UserSession import io.github.jan.supabase.putJsonObject import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.buildJsonObject diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/builtin/Phone.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/builtin/Phone.kt similarity index 96% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/builtin/Phone.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/builtin/Phone.kt index e8e6f798a..0718dacbe 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/builtin/Phone.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/builtin/Phone.kt @@ -1,7 +1,7 @@ -package io.github.jan.supabase.gotrue.providers.builtin +package io.github.jan.supabase.auth.providers.builtin +import io.github.jan.supabase.auth.user.UserInfo import io.github.jan.supabase.exceptions.SupabaseEncodingException -import io.github.jan.supabase.gotrue.user.UserInfo import io.github.jan.supabase.supabaseJson import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/builtin/SSO.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/builtin/SSO.kt similarity index 87% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/builtin/SSO.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/builtin/SSO.kt index f4c4361ad..783e75ce1 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/providers/builtin/SSO.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/providers/builtin/SSO.kt @@ -1,10 +1,10 @@ -package io.github.jan.supabase.gotrue.providers.builtin +package io.github.jan.supabase.auth.providers.builtin import io.github.jan.supabase.SupabaseClient -import io.github.jan.supabase.gotrue.auth -import io.github.jan.supabase.gotrue.providers.AuthProvider -import io.github.jan.supabase.gotrue.startExternalAuth -import io.github.jan.supabase.gotrue.user.UserSession +import io.github.jan.supabase.auth.auth +import io.github.jan.supabase.auth.providers.AuthProvider +import io.github.jan.supabase.auth.startExternalAuth +import io.github.jan.supabase.auth.user.UserSession import kotlinx.serialization.Serializable /** diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/user/Identity.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/user/Identity.kt similarity index 94% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/user/Identity.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/user/Identity.kt index d6f1d71ba..46219e370 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/user/Identity.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/user/Identity.kt @@ -1,5 +1,5 @@ @file:Suppress("UndocumentedPublicClass", "UndocumentedPublicFunction", "UndocumentedPublicProperty") -package io.github.jan.supabase.gotrue.user +package io.github.jan.supabase.auth.user import kotlinx.serialization.SerialName diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/user/UserInfo.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/user/UserInfo.kt similarity index 98% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/user/UserInfo.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/user/UserInfo.kt index dca8f948d..8bb59ed7a 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/user/UserInfo.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/user/UserInfo.kt @@ -1,5 +1,5 @@ @file:Suppress("UndocumentedPublicClass", "UndocumentedPublicFunction", "UndocumentedPublicProperty") -package io.github.jan.supabase.gotrue.user +package io.github.jan.supabase.auth.user import kotlinx.datetime.Instant import kotlinx.serialization.SerialName diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/user/UserSession.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/user/UserSession.kt similarity index 95% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/user/UserSession.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/user/UserSession.kt index 33fb742fb..12ef9f9e2 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/user/UserSession.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/user/UserSession.kt @@ -1,5 +1,5 @@ @file:Suppress("UndocumentedPublicClass", "UndocumentedPublicFunction", "UndocumentedPublicProperty") -package io.github.jan.supabase.gotrue.user +package io.github.jan.supabase.auth.user import kotlinx.datetime.Clock import kotlinx.datetime.Instant diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/user/UserUpdateBuilder.kt b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/user/UserUpdateBuilder.kt similarity index 94% rename from GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/user/UserUpdateBuilder.kt rename to Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/user/UserUpdateBuilder.kt index 07e484ac2..a4fef6c71 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/user/UserUpdateBuilder.kt +++ b/Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/user/UserUpdateBuilder.kt @@ -1,8 +1,8 @@ -package io.github.jan.supabase.gotrue.user +package io.github.jan.supabase.auth.user import io.github.jan.supabase.SupabaseSerializer +import io.github.jan.supabase.auth.Auth import io.github.jan.supabase.encodeToJsonElement -import io.github.jan.supabase.gotrue.Auth import io.github.jan.supabase.serializer.KotlinXSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/GoTrue/src/commonTest/kotlin/AccessTokenTest.kt b/Auth/src/commonTest/kotlin/AccessTokenTest.kt similarity index 91% rename from GoTrue/src/commonTest/kotlin/AccessTokenTest.kt rename to Auth/src/commonTest/kotlin/AccessTokenTest.kt index 2c962fa7f..ad6647495 100644 --- a/GoTrue/src/commonTest/kotlin/AccessTokenTest.kt +++ b/Auth/src/commonTest/kotlin/AccessTokenTest.kt @@ -1,7 +1,7 @@ -import io.github.jan.supabase.gotrue.Auth -import io.github.jan.supabase.gotrue.auth -import io.github.jan.supabase.gotrue.minimalSettings -import io.github.jan.supabase.gotrue.resolveAccessToken +import io.github.jan.supabase.auth.Auth +import io.github.jan.supabase.auth.auth +import io.github.jan.supabase.auth.minimalSettings +import io.github.jan.supabase.auth.resolveAccessToken import io.github.jan.supabase.testing.createMockedSupabaseClient import kotlinx.coroutines.test.runTest import kotlin.test.Test diff --git a/GoTrue/src/commonTest/kotlin/AdminApiTest.kt b/Auth/src/commonTest/kotlin/AdminApiTest.kt similarity index 97% rename from GoTrue/src/commonTest/kotlin/AdminApiTest.kt rename to Auth/src/commonTest/kotlin/AdminApiTest.kt index 21ecfbf19..1e899e027 100644 --- a/GoTrue/src/commonTest/kotlin/AdminApiTest.kt +++ b/Auth/src/commonTest/kotlin/AdminApiTest.kt @@ -1,12 +1,12 @@ import io.github.jan.supabase.SupabaseClientBuilder -import io.github.jan.supabase.gotrue.Auth -import io.github.jan.supabase.gotrue.SignOutScope -import io.github.jan.supabase.gotrue.admin.LinkType -import io.github.jan.supabase.gotrue.admin.generateLinkFor -import io.github.jan.supabase.gotrue.auth -import io.github.jan.supabase.gotrue.minimalSettings -import io.github.jan.supabase.gotrue.user.UserInfo -import io.github.jan.supabase.gotrue.user.UserMfaFactor +import io.github.jan.supabase.auth.Auth +import io.github.jan.supabase.auth.SignOutScope +import io.github.jan.supabase.auth.admin.LinkType +import io.github.jan.supabase.auth.admin.generateLinkFor +import io.github.jan.supabase.auth.auth +import io.github.jan.supabase.auth.minimalSettings +import io.github.jan.supabase.auth.user.UserInfo +import io.github.jan.supabase.auth.user.UserMfaFactor import io.github.jan.supabase.testing.assertMethodIs import io.github.jan.supabase.testing.assertPathIs import io.github.jan.supabase.testing.createMockedSupabaseClient diff --git a/GoTrue/src/commonTest/kotlin/AuthApiTest.kt b/Auth/src/commonTest/kotlin/AuthApiTest.kt similarity index 97% rename from GoTrue/src/commonTest/kotlin/AuthApiTest.kt rename to Auth/src/commonTest/kotlin/AuthApiTest.kt index 5f6c6ea0b..364ffdb31 100644 --- a/GoTrue/src/commonTest/kotlin/AuthApiTest.kt +++ b/Auth/src/commonTest/kotlin/AuthApiTest.kt @@ -1,18 +1,18 @@ import io.github.jan.supabase.SupabaseClientBuilder -import io.github.jan.supabase.gotrue.Auth -import io.github.jan.supabase.gotrue.AuthConfig -import io.github.jan.supabase.gotrue.FlowType -import io.github.jan.supabase.gotrue.OtpType -import io.github.jan.supabase.gotrue.PKCEConstants -import io.github.jan.supabase.gotrue.SessionSource -import io.github.jan.supabase.gotrue.SignOutScope -import io.github.jan.supabase.gotrue.auth -import io.github.jan.supabase.gotrue.minimalSettings -import io.github.jan.supabase.gotrue.providers.Google -import io.github.jan.supabase.gotrue.providers.builtin.Email -import io.github.jan.supabase.gotrue.providers.builtin.IDToken -import io.github.jan.supabase.gotrue.providers.builtin.OTP -import io.github.jan.supabase.gotrue.providers.builtin.Phone +import io.github.jan.supabase.auth.Auth +import io.github.jan.supabase.auth.AuthConfig +import io.github.jan.supabase.auth.FlowType +import io.github.jan.supabase.auth.OtpType +import io.github.jan.supabase.auth.PKCEConstants +import io.github.jan.supabase.auth.SessionSource +import io.github.jan.supabase.auth.SignOutScope +import io.github.jan.supabase.auth.auth +import io.github.jan.supabase.auth.minimalSettings +import io.github.jan.supabase.auth.providers.Google +import io.github.jan.supabase.auth.providers.builtin.Email +import io.github.jan.supabase.auth.providers.builtin.IDToken +import io.github.jan.supabase.auth.providers.builtin.OTP +import io.github.jan.supabase.auth.providers.builtin.Phone import io.github.jan.supabase.testing.assertMethodIs import io.github.jan.supabase.testing.assertPathIs import io.github.jan.supabase.testing.createMockedSupabaseClient diff --git a/GoTrue/src/commonTest/kotlin/AuthRestExceptionTest.kt b/Auth/src/commonTest/kotlin/AuthRestExceptionTest.kt similarity index 91% rename from GoTrue/src/commonTest/kotlin/AuthRestExceptionTest.kt rename to Auth/src/commonTest/kotlin/AuthRestExceptionTest.kt index a9fff77a6..39db4412b 100644 --- a/GoTrue/src/commonTest/kotlin/AuthRestExceptionTest.kt +++ b/Auth/src/commonTest/kotlin/AuthRestExceptionTest.kt @@ -1,11 +1,11 @@ import io.github.jan.supabase.SupabaseClientBuilder +import io.github.jan.supabase.auth.Auth +import io.github.jan.supabase.auth.auth +import io.github.jan.supabase.auth.exception.AuthRestException +import io.github.jan.supabase.auth.exception.AuthWeakPasswordException +import io.github.jan.supabase.auth.minimalSettings +import io.github.jan.supabase.auth.providers.builtin.Email import io.github.jan.supabase.exceptions.BadRequestRestException -import io.github.jan.supabase.gotrue.Auth -import io.github.jan.supabase.gotrue.auth -import io.github.jan.supabase.gotrue.exception.AuthRestException -import io.github.jan.supabase.gotrue.exception.AuthWeakPasswordException -import io.github.jan.supabase.gotrue.minimalSettings -import io.github.jan.supabase.gotrue.providers.builtin.Email import io.github.jan.supabase.testing.createMockedSupabaseClient import io.github.jan.supabase.testing.respondJson import io.ktor.http.HttpStatusCode diff --git a/GoTrue/src/commonTest/kotlin/AuthTest.kt b/Auth/src/commonTest/kotlin/AuthTest.kt similarity index 94% rename from GoTrue/src/commonTest/kotlin/AuthTest.kt rename to Auth/src/commonTest/kotlin/AuthTest.kt index 584149532..ed071de96 100644 --- a/GoTrue/src/commonTest/kotlin/AuthTest.kt +++ b/Auth/src/commonTest/kotlin/AuthTest.kt @@ -1,13 +1,13 @@ import io.github.jan.supabase.SupabaseClientBuilder -import io.github.jan.supabase.gotrue.Auth -import io.github.jan.supabase.gotrue.MemorySessionManager -import io.github.jan.supabase.gotrue.SessionStatus -import io.github.jan.supabase.gotrue.auth -import io.github.jan.supabase.gotrue.minimalSettings -import io.github.jan.supabase.gotrue.providers.Github -import io.github.jan.supabase.gotrue.user.Identity -import io.github.jan.supabase.gotrue.user.UserInfo -import io.github.jan.supabase.gotrue.user.UserSession +import io.github.jan.supabase.auth.Auth +import io.github.jan.supabase.auth.MemorySessionManager +import io.github.jan.supabase.auth.SessionStatus +import io.github.jan.supabase.auth.auth +import io.github.jan.supabase.auth.minimalSettings +import io.github.jan.supabase.auth.providers.Github +import io.github.jan.supabase.auth.user.Identity +import io.github.jan.supabase.auth.user.UserInfo +import io.github.jan.supabase.auth.user.UserSession import io.github.jan.supabase.testing.createMockedSupabaseClient import io.github.jan.supabase.testing.pathAfterVersion import io.github.jan.supabase.testing.respondJson diff --git a/GoTrue/src/commonTest/kotlin/AuthTestUtils.kt b/Auth/src/commonTest/kotlin/AuthTestUtils.kt similarity index 62% rename from GoTrue/src/commonTest/kotlin/AuthTestUtils.kt rename to Auth/src/commonTest/kotlin/AuthTestUtils.kt index b271e5737..48439e432 100644 --- a/GoTrue/src/commonTest/kotlin/AuthTestUtils.kt +++ b/Auth/src/commonTest/kotlin/AuthTestUtils.kt @@ -1,5 +1,5 @@ -import io.github.jan.supabase.gotrue.Auth -import io.github.jan.supabase.gotrue.SessionStatus +import io.github.jan.supabase.auth.Auth +import io.github.jan.supabase.auth.SessionStatus fun Auth.sessionSource() = (sessionStatus.value as SessionStatus.Authenticated).source diff --git a/GoTrue/src/commonTest/kotlin/CodeVerifierCacheTest.kt b/Auth/src/commonTest/kotlin/CodeVerifierCacheTest.kt similarity index 92% rename from GoTrue/src/commonTest/kotlin/CodeVerifierCacheTest.kt rename to Auth/src/commonTest/kotlin/CodeVerifierCacheTest.kt index d22a34240..bc5fe97e9 100644 --- a/GoTrue/src/commonTest/kotlin/CodeVerifierCacheTest.kt +++ b/Auth/src/commonTest/kotlin/CodeVerifierCacheTest.kt @@ -1,4 +1,4 @@ -import io.github.jan.supabase.gotrue.MemoryCodeVerifierCache +import io.github.jan.supabase.auth.MemoryCodeVerifierCache import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals diff --git a/GoTrue/src/commonTest/kotlin/MemorySessionManagerTest.kt b/Auth/src/commonTest/kotlin/MemorySessionManagerTest.kt similarity index 94% rename from GoTrue/src/commonTest/kotlin/MemorySessionManagerTest.kt rename to Auth/src/commonTest/kotlin/MemorySessionManagerTest.kt index ee213fbbf..316c16a72 100644 --- a/GoTrue/src/commonTest/kotlin/MemorySessionManagerTest.kt +++ b/Auth/src/commonTest/kotlin/MemorySessionManagerTest.kt @@ -1,4 +1,4 @@ -import io.github.jan.supabase.gotrue.MemorySessionManager +import io.github.jan.supabase.auth.MemorySessionManager import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals diff --git a/GoTrue/src/commonTest/kotlin/MfaApiTest.kt b/Auth/src/commonTest/kotlin/MfaApiTest.kt similarity index 95% rename from GoTrue/src/commonTest/kotlin/MfaApiTest.kt rename to Auth/src/commonTest/kotlin/MfaApiTest.kt index df08a83d4..67571e6af 100644 --- a/GoTrue/src/commonTest/kotlin/MfaApiTest.kt +++ b/Auth/src/commonTest/kotlin/MfaApiTest.kt @@ -1,15 +1,15 @@ import app.cash.turbine.test import io.github.jan.supabase.SupabaseClientBuilder -import io.github.jan.supabase.gotrue.Auth -import io.github.jan.supabase.gotrue.auth -import io.github.jan.supabase.gotrue.mfa.AuthenticatorAssuranceLevel -import io.github.jan.supabase.gotrue.mfa.FactorType -import io.github.jan.supabase.gotrue.mfa.MfaStatus -import io.github.jan.supabase.gotrue.minimalSettings -import io.github.jan.supabase.gotrue.providers.builtin.Phone -import io.github.jan.supabase.gotrue.user.UserInfo -import io.github.jan.supabase.gotrue.user.UserMfaFactor -import io.github.jan.supabase.gotrue.user.UserSession +import io.github.jan.supabase.auth.Auth +import io.github.jan.supabase.auth.auth +import io.github.jan.supabase.auth.mfa.AuthenticatorAssuranceLevel +import io.github.jan.supabase.auth.mfa.FactorType +import io.github.jan.supabase.auth.mfa.MfaStatus +import io.github.jan.supabase.auth.minimalSettings +import io.github.jan.supabase.auth.providers.builtin.Phone +import io.github.jan.supabase.auth.user.UserInfo +import io.github.jan.supabase.auth.user.UserMfaFactor +import io.github.jan.supabase.auth.user.UserSession import io.github.jan.supabase.testing.assertMethodIs import io.github.jan.supabase.testing.assertPathIs import io.github.jan.supabase.testing.createMockedSupabaseClient diff --git a/GoTrue/src/desktopMain/kotlin/io/github/jan/supabase/gotrue/AuthConfig.kt b/Auth/src/desktopMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt similarity index 93% rename from GoTrue/src/desktopMain/kotlin/io/github/jan/supabase/gotrue/AuthConfig.kt rename to Auth/src/desktopMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt index 2a30aec7d..5304677f1 100644 --- a/GoTrue/src/desktopMain/kotlin/io/github/jan/supabase/gotrue/AuthConfig.kt +++ b/Auth/src/desktopMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt @@ -1,6 +1,6 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth -import io.github.jan.supabase.gotrue.server.HttpCallbackHtml +import io.github.jan.supabase.auth.server.HttpCallbackHtml import io.github.jan.supabase.plugins.CustomSerializationConfig import kotlin.time.Duration import kotlin.time.Duration.Companion.minutes diff --git a/GoTrue/src/desktopMain/kotlin/io/github/jan/supabase/gotrue/Utils.desktop.kt b/Auth/src/desktopMain/kotlin/io/github/jan/supabase/auth/Utils.desktop.kt similarity index 79% rename from GoTrue/src/desktopMain/kotlin/io/github/jan/supabase/gotrue/Utils.desktop.kt rename to Auth/src/desktopMain/kotlin/io/github/jan/supabase/auth/Utils.desktop.kt index 818987f8f..2e052c0fe 100644 --- a/GoTrue/src/desktopMain/kotlin/io/github/jan/supabase/gotrue/Utils.desktop.kt +++ b/Auth/src/desktopMain/kotlin/io/github/jan/supabase/auth/Utils.desktop.kt @@ -1,7 +1,7 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth -import io.github.jan.supabase.gotrue.server.createServer -import io.github.jan.supabase.gotrue.user.UserSession +import io.github.jan.supabase.auth.server.createServer +import io.github.jan.supabase.auth.user.UserSession import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO import kotlinx.coroutines.withContext diff --git a/GoTrue/src/desktopMain/kotlin/io/github/jan/supabase/gotrue/server/HttpCallbackHtml.kt b/Auth/src/desktopMain/kotlin/io/github/jan/supabase/auth/server/HttpCallbackHtml.kt similarity index 97% rename from GoTrue/src/desktopMain/kotlin/io/github/jan/supabase/gotrue/server/HttpCallbackHtml.kt rename to Auth/src/desktopMain/kotlin/io/github/jan/supabase/auth/server/HttpCallbackHtml.kt index 0c44d57bf..620cf2564 100644 --- a/GoTrue/src/desktopMain/kotlin/io/github/jan/supabase/gotrue/server/HttpCallbackHtml.kt +++ b/Auth/src/desktopMain/kotlin/io/github/jan/supabase/auth/server/HttpCallbackHtml.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue.server +package io.github.jan.supabase.auth.server internal object HttpCallbackHtml { diff --git a/GoTrue/src/desktopMain/kotlin/io/github/jan/supabase/gotrue/server/HttpCallbackRoutes.kt b/Auth/src/desktopMain/kotlin/io/github/jan/supabase/auth/server/HttpCallbackRoutes.kt similarity index 91% rename from GoTrue/src/desktopMain/kotlin/io/github/jan/supabase/gotrue/server/HttpCallbackRoutes.kt rename to Auth/src/desktopMain/kotlin/io/github/jan/supabase/auth/server/HttpCallbackRoutes.kt index 556916bd7..45c5b6575 100644 --- a/GoTrue/src/desktopMain/kotlin/io/github/jan/supabase/gotrue/server/HttpCallbackRoutes.kt +++ b/Auth/src/desktopMain/kotlin/io/github/jan/supabase/auth/server/HttpCallbackRoutes.kt @@ -1,11 +1,10 @@ -package io.github.jan.supabase.gotrue.server +package io.github.jan.supabase.auth.server -import io.github.jan.supabase.gotrue.Auth -import io.github.jan.supabase.gotrue.user.UserSession +import io.github.jan.supabase.auth.Auth +import io.github.jan.supabase.auth.user.UserSession import io.github.jan.supabase.logging.d import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode -import io.ktor.server.application.call import io.ktor.server.response.respondText import io.ktor.server.routing.Routing import io.ktor.server.routing.get diff --git a/GoTrue/src/desktopMain/kotlin/io/github/jan/supabase/gotrue/server/HttpCallbackServer.kt b/Auth/src/desktopMain/kotlin/io/github/jan/supabase/auth/server/HttpCallbackServer.kt similarity index 76% rename from GoTrue/src/desktopMain/kotlin/io/github/jan/supabase/gotrue/server/HttpCallbackServer.kt rename to Auth/src/desktopMain/kotlin/io/github/jan/supabase/auth/server/HttpCallbackServer.kt index 990000863..9b4facba6 100644 --- a/GoTrue/src/desktopMain/kotlin/io/github/jan/supabase/gotrue/server/HttpCallbackServer.kt +++ b/Auth/src/desktopMain/kotlin/io/github/jan/supabase/auth/server/HttpCallbackServer.kt @@ -1,17 +1,15 @@ -package io.github.jan.supabase.gotrue.server +package io.github.jan.supabase.auth.server -import io.github.jan.supabase.annotations.SupabaseExperimental -import io.github.jan.supabase.gotrue.Auth -import io.github.jan.supabase.gotrue.AuthImpl -import io.github.jan.supabase.gotrue.openExternalUrl -import io.github.jan.supabase.gotrue.user.UserSession +import io.github.jan.supabase.auth.Auth +import io.github.jan.supabase.auth.AuthImpl +import io.github.jan.supabase.auth.openExternalUrl +import io.github.jan.supabase.auth.user.UserSession import io.github.jan.supabase.logging.d import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode import io.ktor.server.application.ApplicationCall import io.ktor.server.application.ApplicationStopPreparing import io.ktor.server.cio.CIO -import io.ktor.server.engine.ApplicationEngineEnvironment import io.ktor.server.engine.embeddedServer import io.ktor.server.response.respondText import io.ktor.server.routing.routing @@ -23,7 +21,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.resume -@OptIn(SupabaseExperimental::class) internal suspend fun createServer( url: suspend (redirect: String) -> String, auth: Auth, @@ -41,7 +38,7 @@ internal suspend fun createServer( } coroutineScope { val timeoutScope = launch { - val port = server.resolvedConnectors().first().port + val port = server.engine.resolvedConnectors().first().port Auth.logger.d { "Started OAuth callback server on port $port. Opening url in browser..." } @@ -64,7 +61,7 @@ internal suspend fun createServer( } launch { suspendCancellableCoroutine { - server.environment.monitor.subscribe(ApplicationStopPreparing) { _ -> + server.monitor.subscribe(ApplicationStopPreparing) { _ -> it.resume(Unit) timeoutScope.cancel() } @@ -84,14 +81,9 @@ internal suspend fun shutdown(call: ApplicationCall, message: String) { val latch = CompletableDeferred() call.application.launch { + application.monitor.raise(ApplicationStopPreparing, environment) latch.join() - - environment.monitor.raise(ApplicationStopPreparing, environment) - if (environment is ApplicationEngineEnvironment) { - environment.stop() - } else { - application.dispose() - } + application.dispose() } try { diff --git a/GoTrue/src/iosMain/kotlin/io/github/jan/supabase/gotrue/AuthConfig.kt b/Auth/src/iosMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt similarity index 82% rename from GoTrue/src/iosMain/kotlin/io/github/jan/supabase/gotrue/AuthConfig.kt rename to Auth/src/iosMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt index 23d7921f0..b7229ecd9 100644 --- a/GoTrue/src/iosMain/kotlin/io/github/jan/supabase/gotrue/AuthConfig.kt +++ b/Auth/src/iosMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.plugins.CustomSerializationConfig diff --git a/GoTrue/src/iosMain/kotlin/io/github/jan/supabase/gotrue/providers/openUrl.kt b/Auth/src/iosMain/kotlin/io/github/jan/supabase/auth/providers/openUrl.kt similarity index 82% rename from GoTrue/src/iosMain/kotlin/io/github/jan/supabase/gotrue/providers/openUrl.kt rename to Auth/src/iosMain/kotlin/io/github/jan/supabase/auth/providers/openUrl.kt index b7fdaef8a..123aa8a9c 100644 --- a/GoTrue/src/iosMain/kotlin/io/github/jan/supabase/gotrue/providers/openUrl.kt +++ b/Auth/src/iosMain/kotlin/io/github/jan/supabase/auth/providers/openUrl.kt @@ -1,6 +1,6 @@ -package io.github.jan.supabase.gotrue.providers +package io.github.jan.supabase.auth.providers -import io.github.jan.supabase.gotrue.Auth +import io.github.jan.supabase.auth.Auth import io.github.jan.supabase.logging.d import io.github.jan.supabase.logging.e import platform.Foundation.NSURL diff --git a/GoTrue/src/jsMain/kotlin/io/github/jan/supabase/gotrue/AuthConfig.kt b/Auth/src/jsMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt similarity index 82% rename from GoTrue/src/jsMain/kotlin/io/github/jan/supabase/gotrue/AuthConfig.kt rename to Auth/src/jsMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt index 23d7921f0..b7229ecd9 100644 --- a/GoTrue/src/jsMain/kotlin/io/github/jan/supabase/gotrue/AuthConfig.kt +++ b/Auth/src/jsMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.plugins.CustomSerializationConfig diff --git a/GoTrue/src/jsMain/kotlin/io/github/jan/supabase/gotrue/RedirectUrl.js.kt b/Auth/src/jsMain/kotlin/io/github/jan/supabase/auth/RedirectUrl.js.kt similarity index 83% rename from GoTrue/src/jsMain/kotlin/io/github/jan/supabase/gotrue/RedirectUrl.js.kt rename to Auth/src/jsMain/kotlin/io/github/jan/supabase/auth/RedirectUrl.js.kt index 927ff28a0..9243dbb89 100644 --- a/GoTrue/src/jsMain/kotlin/io/github/jan/supabase/gotrue/RedirectUrl.js.kt +++ b/Auth/src/jsMain/kotlin/io/github/jan/supabase/auth/RedirectUrl.js.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.annotations.SupabaseInternal import kotlinx.browser.window diff --git a/GoTrue/src/jsMain/kotlin/io/github/jan/supabase/gotrue/Utils.js.kt b/Auth/src/jsMain/kotlin/io/github/jan/supabase/auth/Utils.js.kt similarity index 82% rename from GoTrue/src/jsMain/kotlin/io/github/jan/supabase/gotrue/Utils.js.kt rename to Auth/src/jsMain/kotlin/io/github/jan/supabase/auth/Utils.js.kt index cec38b30a..44cb623db 100644 --- a/GoTrue/src/jsMain/kotlin/io/github/jan/supabase/gotrue/Utils.js.kt +++ b/Auth/src/jsMain/kotlin/io/github/jan/supabase/auth/Utils.js.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.SupabaseClient import kotlinx.browser.window diff --git a/GoTrue/src/appleMain/kotlin/io/github/jan/supabase/gotrue/providers/ExternalAuthConfig.kt b/Auth/src/jsMain/kotlin/io/github/jan/supabase/auth/providers/ExternalAuthConfig.kt similarity index 76% rename from GoTrue/src/appleMain/kotlin/io/github/jan/supabase/gotrue/providers/ExternalAuthConfig.kt rename to Auth/src/jsMain/kotlin/io/github/jan/supabase/auth/providers/ExternalAuthConfig.kt index de8a14b38..267e6bf68 100644 --- a/GoTrue/src/appleMain/kotlin/io/github/jan/supabase/gotrue/providers/ExternalAuthConfig.kt +++ b/Auth/src/jsMain/kotlin/io/github/jan/supabase/auth/providers/ExternalAuthConfig.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue.providers +package io.github.jan.supabase.auth.providers /** * Configuration for external authentication providers like Google, Twitter, etc. diff --git a/GoTrue/src/jsMain/kotlin/io/github/jan/supabase/gotrue/setupPlatform.kt b/Auth/src/jsMain/kotlin/io/github/jan/supabase/auth/setupPlatform.kt similarity index 97% rename from GoTrue/src/jsMain/kotlin/io/github/jan/supabase/gotrue/setupPlatform.kt rename to Auth/src/jsMain/kotlin/io/github/jan/supabase/auth/setupPlatform.kt index 45aba1b18..f5eb65a32 100644 --- a/GoTrue/src/jsMain/kotlin/io/github/jan/supabase/gotrue/setupPlatform.kt +++ b/Auth/src/jsMain/kotlin/io/github/jan/supabase/auth/setupPlatform.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.annotations.SupabaseInternal import io.ktor.util.PlatformUtils.IS_BROWSER diff --git a/GoTrue/src/linuxTest/kotlin/platformSettings.kt b/Auth/src/jsTest/kotlin/platformSettings.kt similarity index 50% rename from GoTrue/src/linuxTest/kotlin/platformSettings.kt rename to Auth/src/jsTest/kotlin/platformSettings.kt index c97cb2bd9..f6fa8ceb8 100644 --- a/GoTrue/src/linuxTest/kotlin/platformSettings.kt +++ b/Auth/src/jsTest/kotlin/platformSettings.kt @@ -1,3 +1,3 @@ -import io.github.jan.supabase.gotrue.AuthConfig +import io.github.jan.supabase.auth.AuthConfig actual fun AuthConfig.platformSettings() = Unit \ No newline at end of file diff --git a/GoTrue/src/jvmMain/kotlin/io/github/jan/supabase/gotrue/RedirectUrl.jvm.kt b/Auth/src/jvmMain/kotlin/io/github/jan/supabase/auth/RedirectUrl.jvm.kt similarity index 79% rename from GoTrue/src/jvmMain/kotlin/io/github/jan/supabase/gotrue/RedirectUrl.jvm.kt rename to Auth/src/jvmMain/kotlin/io/github/jan/supabase/auth/RedirectUrl.jvm.kt index 86b68e1ed..4b039622f 100644 --- a/GoTrue/src/jvmMain/kotlin/io/github/jan/supabase/gotrue/RedirectUrl.jvm.kt +++ b/Auth/src/jvmMain/kotlin/io/github/jan/supabase/auth/RedirectUrl.jvm.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.annotations.SupabaseInternal diff --git a/GoTrue/src/jvmMain/kotlin/io/github/jan/supabase/gotrue/Utils.jvm.kt b/Auth/src/jvmMain/kotlin/io/github/jan/supabase/auth/Utils.jvm.kt similarity index 90% rename from GoTrue/src/jvmMain/kotlin/io/github/jan/supabase/gotrue/Utils.jvm.kt rename to Auth/src/jvmMain/kotlin/io/github/jan/supabase/auth/Utils.jvm.kt index f5e3261c4..a81e101dc 100644 --- a/GoTrue/src/jvmMain/kotlin/io/github/jan/supabase/gotrue/Utils.jvm.kt +++ b/Auth/src/jvmMain/kotlin/io/github/jan/supabase/auth/Utils.jvm.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.SupabaseClient import kotlinx.coroutines.Dispatchers diff --git a/GoTrue/src/androidMain/kotlin/io/github/jan/supabase/gotrue/providers/ExternalAuthConfig.kt b/Auth/src/jvmMain/kotlin/io/github/jan/supabase/auth/providers/ExternalAuthConfig.kt similarity index 76% rename from GoTrue/src/androidMain/kotlin/io/github/jan/supabase/gotrue/providers/ExternalAuthConfig.kt rename to Auth/src/jvmMain/kotlin/io/github/jan/supabase/auth/providers/ExternalAuthConfig.kt index de8a14b38..267e6bf68 100644 --- a/GoTrue/src/androidMain/kotlin/io/github/jan/supabase/gotrue/providers/ExternalAuthConfig.kt +++ b/Auth/src/jvmMain/kotlin/io/github/jan/supabase/auth/providers/ExternalAuthConfig.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue.providers +package io.github.jan.supabase.auth.providers /** * Configuration for external authentication providers like Google, Twitter, etc. diff --git a/GoTrue/src/appleMain/kotlin/io/github/jan/supabase/gotrue/setupPlatform.kt b/Auth/src/jvmMain/kotlin/io/github/jan/supabase/auth/setupPlatform.kt similarity index 75% rename from GoTrue/src/appleMain/kotlin/io/github/jan/supabase/gotrue/setupPlatform.kt rename to Auth/src/jvmMain/kotlin/io/github/jan/supabase/auth/setupPlatform.kt index 95b106316..a1b155557 100644 --- a/GoTrue/src/appleMain/kotlin/io/github/jan/supabase/gotrue/setupPlatform.kt +++ b/Auth/src/jvmMain/kotlin/io/github/jan/supabase/auth/setupPlatform.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.annotations.SupabaseInternal diff --git a/GoTrue/src/jsTest/kotlin/platformSettings.kt b/Auth/src/jvmTest/kotlin/platformSettings.kt similarity index 50% rename from GoTrue/src/jsTest/kotlin/platformSettings.kt rename to Auth/src/jvmTest/kotlin/platformSettings.kt index c97cb2bd9..f6fa8ceb8 100644 --- a/GoTrue/src/jsTest/kotlin/platformSettings.kt +++ b/Auth/src/jvmTest/kotlin/platformSettings.kt @@ -1,3 +1,3 @@ -import io.github.jan.supabase.gotrue.AuthConfig +import io.github.jan.supabase.auth.AuthConfig actual fun AuthConfig.platformSettings() = Unit \ No newline at end of file diff --git a/GoTrue/src/linuxMain/kotlin/io/github/jan/supabase/gotrue/SettingsUtil.kt b/Auth/src/linuxMain/kotlin/io/github/jan/supabase/auth/SettingsUtil.kt similarity index 88% rename from GoTrue/src/linuxMain/kotlin/io/github/jan/supabase/gotrue/SettingsUtil.kt rename to Auth/src/linuxMain/kotlin/io/github/jan/supabase/auth/SettingsUtil.kt index fde7e333a..30573d808 100644 --- a/GoTrue/src/linuxMain/kotlin/io/github/jan/supabase/gotrue/SettingsUtil.kt +++ b/Auth/src/linuxMain/kotlin/io/github/jan/supabase/auth/SettingsUtil.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.annotations.SupabaseInternal diff --git a/GoTrue/src/linuxMain/kotlin/io/github/jan/supabase/gotrue/Utils.linux.kt b/Auth/src/linuxMain/kotlin/io/github/jan/supabase/auth/Utils.linux.kt similarity index 82% rename from GoTrue/src/linuxMain/kotlin/io/github/jan/supabase/gotrue/Utils.linux.kt rename to Auth/src/linuxMain/kotlin/io/github/jan/supabase/auth/Utils.linux.kt index 4580f9005..6594987ea 100644 --- a/GoTrue/src/linuxMain/kotlin/io/github/jan/supabase/gotrue/Utils.linux.kt +++ b/Auth/src/linuxMain/kotlin/io/github/jan/supabase/auth/Utils.linux.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.SupabaseClient import platform.posix.system diff --git a/GoTrue/src/mingwMain/kotlin/io/github/jan/supabase/gotrue/generateRedirectUrl.kt b/Auth/src/linuxMain/kotlin/io/github/jan/supabase/auth/generateRedirectUrl.kt similarity index 79% rename from GoTrue/src/mingwMain/kotlin/io/github/jan/supabase/gotrue/generateRedirectUrl.kt rename to Auth/src/linuxMain/kotlin/io/github/jan/supabase/auth/generateRedirectUrl.kt index 86b68e1ed..4b039622f 100644 --- a/GoTrue/src/mingwMain/kotlin/io/github/jan/supabase/gotrue/generateRedirectUrl.kt +++ b/Auth/src/linuxMain/kotlin/io/github/jan/supabase/auth/generateRedirectUrl.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.annotations.SupabaseInternal diff --git a/Auth/src/linuxMain/kotlin/io/github/jan/supabase/auth/providers/ExternalAuthConfig.kt b/Auth/src/linuxMain/kotlin/io/github/jan/supabase/auth/providers/ExternalAuthConfig.kt new file mode 100644 index 000000000..267e6bf68 --- /dev/null +++ b/Auth/src/linuxMain/kotlin/io/github/jan/supabase/auth/providers/ExternalAuthConfig.kt @@ -0,0 +1,6 @@ +package io.github.jan.supabase.auth.providers + +/** + * Configuration for external authentication providers like Google, Twitter, etc. + */ +actual class ExternalAuthConfig: ExternalAuthConfigDefaults() \ No newline at end of file diff --git a/GoTrue/src/linuxMain/kotlin/io/github/jan/supabase/gotrue/setupPlatform.kt b/Auth/src/linuxMain/kotlin/io/github/jan/supabase/auth/setupPlatform.kt similarity index 86% rename from GoTrue/src/linuxMain/kotlin/io/github/jan/supabase/gotrue/setupPlatform.kt rename to Auth/src/linuxMain/kotlin/io/github/jan/supabase/auth/setupPlatform.kt index d20682368..3d6b9decc 100644 --- a/GoTrue/src/linuxMain/kotlin/io/github/jan/supabase/gotrue/setupPlatform.kt +++ b/Auth/src/linuxMain/kotlin/io/github/jan/supabase/auth/setupPlatform.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.annotations.SupabaseInternal import io.github.jan.supabase.logging.w diff --git a/GoTrue/src/appleTest/kotlin/platformSettings.kt b/Auth/src/linuxTest/kotlin/platformSettings.kt similarity index 50% rename from GoTrue/src/appleTest/kotlin/platformSettings.kt rename to Auth/src/linuxTest/kotlin/platformSettings.kt index c97cb2bd9..f6fa8ceb8 100644 --- a/GoTrue/src/appleTest/kotlin/platformSettings.kt +++ b/Auth/src/linuxTest/kotlin/platformSettings.kt @@ -1,3 +1,3 @@ -import io.github.jan.supabase.gotrue.AuthConfig +import io.github.jan.supabase.auth.AuthConfig actual fun AuthConfig.platformSettings() = Unit \ No newline at end of file diff --git a/GoTrue/src/macosMain/kotlin/io/github/jan/supabase/gotrue/providers/openUrl.kt b/Auth/src/macosMain/kotlin/io/github/jan/supabase/auth/providers/openUrl.kt similarity index 84% rename from GoTrue/src/macosMain/kotlin/io/github/jan/supabase/gotrue/providers/openUrl.kt rename to Auth/src/macosMain/kotlin/io/github/jan/supabase/auth/providers/openUrl.kt index 96de97dd7..3ff87b96b 100644 --- a/GoTrue/src/macosMain/kotlin/io/github/jan/supabase/gotrue/providers/openUrl.kt +++ b/Auth/src/macosMain/kotlin/io/github/jan/supabase/auth/providers/openUrl.kt @@ -1,6 +1,6 @@ -package io.github.jan.supabase.gotrue.providers +package io.github.jan.supabase.auth.providers -import io.github.jan.supabase.gotrue.Auth +import io.github.jan.supabase.auth.Auth import io.github.jan.supabase.logging.d import io.github.jan.supabase.logging.e import platform.AppKit.NSWorkspace diff --git a/GoTrue/src/mingwMain/kotlin/io/github/jan/supabase/gotrue/AuthConfig.kt b/Auth/src/mingwMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt similarity index 78% rename from GoTrue/src/mingwMain/kotlin/io/github/jan/supabase/gotrue/AuthConfig.kt rename to Auth/src/mingwMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt index ecd2559a0..3d476bd2e 100644 --- a/GoTrue/src/mingwMain/kotlin/io/github/jan/supabase/gotrue/AuthConfig.kt +++ b/Auth/src/mingwMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.plugins.CustomSerializationConfig diff --git a/GoTrue/src/mingwMain/kotlin/io/github/jan/supabase/gotrue/Utils.mingw.kt b/Auth/src/mingwMain/kotlin/io/github/jan/supabase/auth/Utils.mingw.kt similarity index 90% rename from GoTrue/src/mingwMain/kotlin/io/github/jan/supabase/gotrue/Utils.mingw.kt rename to Auth/src/mingwMain/kotlin/io/github/jan/supabase/auth/Utils.mingw.kt index b741c818f..1c197eb52 100644 --- a/GoTrue/src/mingwMain/kotlin/io/github/jan/supabase/gotrue/Utils.mingw.kt +++ b/Auth/src/mingwMain/kotlin/io/github/jan/supabase/auth/Utils.mingw.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.SupabaseClient import kotlinx.cinterop.ExperimentalForeignApi diff --git a/GoTrue/src/linuxMain/kotlin/io/github/jan/supabase/gotrue/generateRedirectUrl.kt b/Auth/src/mingwMain/kotlin/io/github/jan/supabase/auth/generateRedirectUrl.kt similarity index 79% rename from GoTrue/src/linuxMain/kotlin/io/github/jan/supabase/gotrue/generateRedirectUrl.kt rename to Auth/src/mingwMain/kotlin/io/github/jan/supabase/auth/generateRedirectUrl.kt index 86b68e1ed..4b039622f 100644 --- a/GoTrue/src/linuxMain/kotlin/io/github/jan/supabase/gotrue/generateRedirectUrl.kt +++ b/Auth/src/mingwMain/kotlin/io/github/jan/supabase/auth/generateRedirectUrl.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.annotations.SupabaseInternal diff --git a/GoTrue/src/mingwMain/kotlin/io/github/jan/supabase/gotrue/providers/ExternalAuthConfig.kt b/Auth/src/mingwMain/kotlin/io/github/jan/supabase/auth/providers/ExternalAuthConfig.kt similarity index 76% rename from GoTrue/src/mingwMain/kotlin/io/github/jan/supabase/gotrue/providers/ExternalAuthConfig.kt rename to Auth/src/mingwMain/kotlin/io/github/jan/supabase/auth/providers/ExternalAuthConfig.kt index 55fea02d6..990f41387 100644 --- a/GoTrue/src/mingwMain/kotlin/io/github/jan/supabase/gotrue/providers/ExternalAuthConfig.kt +++ b/Auth/src/mingwMain/kotlin/io/github/jan/supabase/auth/providers/ExternalAuthConfig.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue.providers +package io.github.jan.supabase.auth.providers /** * Configuration for external authentication providers like Google, Twitter, etc. diff --git a/GoTrue/src/mingwMain/kotlin/io/github/jan/supabase/gotrue/setupPlatform.kt b/Auth/src/mingwMain/kotlin/io/github/jan/supabase/auth/setupPlatform.kt similarity index 86% rename from GoTrue/src/mingwMain/kotlin/io/github/jan/supabase/gotrue/setupPlatform.kt rename to Auth/src/mingwMain/kotlin/io/github/jan/supabase/auth/setupPlatform.kt index eb7c34bf1..61ef450fd 100644 --- a/GoTrue/src/mingwMain/kotlin/io/github/jan/supabase/gotrue/setupPlatform.kt +++ b/Auth/src/mingwMain/kotlin/io/github/jan/supabase/auth/setupPlatform.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.annotations.SupabaseInternal import io.github.jan.supabase.logging.w diff --git a/Auth/src/mingwTest/kotlin/platformSettings.kt b/Auth/src/mingwTest/kotlin/platformSettings.kt new file mode 100644 index 000000000..f6fa8ceb8 --- /dev/null +++ b/Auth/src/mingwTest/kotlin/platformSettings.kt @@ -0,0 +1,3 @@ +import io.github.jan.supabase.auth.AuthConfig + +actual fun AuthConfig.platformSettings() = Unit \ No newline at end of file diff --git a/GoTrue/src/nonDesktopMain/kotlin/io/github/jan/supabase/gotrue/Utils.kt b/Auth/src/nonDesktopMain/kotlin/io/github/jan/supabase/auth/Utils.kt similarity index 75% rename from GoTrue/src/nonDesktopMain/kotlin/io/github/jan/supabase/gotrue/Utils.kt rename to Auth/src/nonDesktopMain/kotlin/io/github/jan/supabase/auth/Utils.kt index 7e68cf845..5f0b7cafa 100644 --- a/GoTrue/src/nonDesktopMain/kotlin/io/github/jan/supabase/gotrue/Utils.kt +++ b/Auth/src/nonDesktopMain/kotlin/io/github/jan/supabase/auth/Utils.kt @@ -1,7 +1,7 @@ @file:Suppress("RedundantSuspendModifier") -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth -import io.github.jan.supabase.gotrue.user.UserSession +import io.github.jan.supabase.auth.user.UserSession internal actual suspend fun Auth.startExternalAuth( redirectUrl: String?, diff --git a/GoTrue/src/settingsMain/kotlin/io/github/jan/supabase/gotrue/SettingsCodeVerifierCache.kt b/Auth/src/settingsMain/kotlin/io/github/jan/supabase/auth/SettingsCodeVerifierCache.kt similarity index 86% rename from GoTrue/src/settingsMain/kotlin/io/github/jan/supabase/gotrue/SettingsCodeVerifierCache.kt rename to Auth/src/settingsMain/kotlin/io/github/jan/supabase/auth/SettingsCodeVerifierCache.kt index 72eb75526..418bbac59 100644 --- a/GoTrue/src/settingsMain/kotlin/io/github/jan/supabase/gotrue/SettingsCodeVerifierCache.kt +++ b/Auth/src/settingsMain/kotlin/io/github/jan/supabase/auth/SettingsCodeVerifierCache.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import com.russhwolf.settings.ExperimentalSettingsApi import com.russhwolf.settings.Settings @@ -10,6 +10,7 @@ import com.russhwolf.settings.coroutines.toSuspendSettings * @param settings The [Settings] instance to use. Defaults to [createDefaultSettings]. * @param key The key to use for saving the code verifier. */ +@OptIn(ExperimentalSettingsApi::class) class SettingsCodeVerifierCache( private val settings: Settings = createDefaultSettings(), private val key: String = SETTINGS_KEY, @@ -31,20 +32,16 @@ class SettingsCodeVerifierCache( } } - @OptIn(ExperimentalSettingsApi::class) private val suspendSettings = settings.toSuspendSettings() - @OptIn(ExperimentalSettingsApi::class, ExperimentalSettingsApi::class) override suspend fun saveCodeVerifier(codeVerifier: String) { suspendSettings.putString(key, codeVerifier) } - @OptIn(ExperimentalSettingsApi::class) override suspend fun loadCodeVerifier(): String? { return suspendSettings.getStringOrNull(key) } - @OptIn(ExperimentalSettingsApi::class) override suspend fun deleteCodeVerifier() { suspendSettings.remove(key) } diff --git a/GoTrue/src/settingsMain/kotlin/io/github/jan/supabase/gotrue/SettingsSessionManager.kt b/Auth/src/settingsMain/kotlin/io/github/jan/supabase/auth/SettingsSessionManager.kt similarity index 90% rename from GoTrue/src/settingsMain/kotlin/io/github/jan/supabase/gotrue/SettingsSessionManager.kt rename to Auth/src/settingsMain/kotlin/io/github/jan/supabase/auth/SettingsSessionManager.kt index e924fa118..7d22aae95 100644 --- a/GoTrue/src/settingsMain/kotlin/io/github/jan/supabase/gotrue/SettingsSessionManager.kt +++ b/Auth/src/settingsMain/kotlin/io/github/jan/supabase/auth/SettingsSessionManager.kt @@ -1,9 +1,9 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import com.russhwolf.settings.ExperimentalSettingsApi import com.russhwolf.settings.Settings import com.russhwolf.settings.coroutines.toSuspendSettings -import io.github.jan.supabase.gotrue.user.UserSession +import io.github.jan.supabase.auth.user.UserSession import io.github.jan.supabase.logging.e import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json @@ -20,6 +20,7 @@ private val settingsJson = Json { * @param key The key to use for saving the session. * @param json The [Json] instance to use for serialization. Defaults to [settingsJson]. **Important: [JsonBuilder.encodeDefaults] must be set to true.** */ +@OptIn(ExperimentalSettingsApi::class) class SettingsSessionManager( private val settings: Settings = createDefaultSettings(), private val key: String = SETTINGS_KEY, @@ -44,12 +45,10 @@ class SettingsSessionManager( private val suspendSettings = settings.toSuspendSettings() - @OptIn(ExperimentalSettingsApi::class) override suspend fun saveSession(session: UserSession) { suspendSettings.putString(key, json.encodeToString(session)) } - @OptIn(ExperimentalSettingsApi::class) override suspend fun loadSession(): UserSession? { val session = suspendSettings.getStringOrNull(key) ?: return null return try { @@ -60,7 +59,6 @@ class SettingsSessionManager( } } - @OptIn(ExperimentalSettingsApi::class) override suspend fun deleteSession() { suspendSettings.remove(key) } diff --git a/GoTrue/src/settingsMain/kotlin/io/github/jan/supabase/gotrue/SettingsUtil.kt b/Auth/src/settingsMain/kotlin/io/github/jan/supabase/auth/SettingsUtil.kt similarity index 97% rename from GoTrue/src/settingsMain/kotlin/io/github/jan/supabase/gotrue/SettingsUtil.kt rename to Auth/src/settingsMain/kotlin/io/github/jan/supabase/auth/SettingsUtil.kt index d27d661d0..1b9d4b138 100644 --- a/GoTrue/src/settingsMain/kotlin/io/github/jan/supabase/gotrue/SettingsUtil.kt +++ b/Auth/src/settingsMain/kotlin/io/github/jan/supabase/auth/SettingsUtil.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import com.russhwolf.settings.Settings import io.github.jan.supabase.annotations.SupabaseInternal diff --git a/GoTrue/src/settingsTest/kotlin/SettingsCodeVerifierCacheTest.kt b/Auth/src/settingsTest/kotlin/SettingsCodeVerifierCacheTest.kt similarity index 95% rename from GoTrue/src/settingsTest/kotlin/SettingsCodeVerifierCacheTest.kt rename to Auth/src/settingsTest/kotlin/SettingsCodeVerifierCacheTest.kt index fd636b0d3..6a3e5fef7 100644 --- a/GoTrue/src/settingsTest/kotlin/SettingsCodeVerifierCacheTest.kt +++ b/Auth/src/settingsTest/kotlin/SettingsCodeVerifierCacheTest.kt @@ -1,5 +1,5 @@ import com.russhwolf.settings.MapSettings -import io.github.jan.supabase.gotrue.SettingsCodeVerifierCache +import io.github.jan.supabase.auth.SettingsCodeVerifierCache import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals diff --git a/GoTrue/src/settingsTest/kotlin/SettingsSessionManagerTest.kt b/Auth/src/settingsTest/kotlin/SettingsSessionManagerTest.kt similarity index 94% rename from GoTrue/src/settingsTest/kotlin/SettingsSessionManagerTest.kt rename to Auth/src/settingsTest/kotlin/SettingsSessionManagerTest.kt index 68c5440f2..0139c01b9 100644 --- a/GoTrue/src/settingsTest/kotlin/SettingsSessionManagerTest.kt +++ b/Auth/src/settingsTest/kotlin/SettingsSessionManagerTest.kt @@ -1,6 +1,6 @@ import com.russhwolf.settings.MapSettings -import io.github.jan.supabase.gotrue.SettingsSessionManager -import io.github.jan.supabase.gotrue.user.UserSession +import io.github.jan.supabase.auth.SettingsSessionManager +import io.github.jan.supabase.auth.user.UserSession import kotlinx.coroutines.test.runTest import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json diff --git a/GoTrue/src/settingsTest/kotlin/SettingsTest.kt b/Auth/src/settingsTest/kotlin/SettingsTest.kt similarity index 81% rename from GoTrue/src/settingsTest/kotlin/SettingsTest.kt rename to Auth/src/settingsTest/kotlin/SettingsTest.kt index 33df27f51..a2bef23a8 100644 --- a/GoTrue/src/settingsTest/kotlin/SettingsTest.kt +++ b/Auth/src/settingsTest/kotlin/SettingsTest.kt @@ -1,4 +1,4 @@ -import io.github.jan.supabase.gotrue.createDefaultSettingsKey +import io.github.jan.supabase.auth.createDefaultSettingsKey import kotlin.test.Test import kotlin.test.assertEquals diff --git a/GoTrue/src/watchosMain/kotlin/io/github/jan/supabase/gotrue/AuthConfig.kt b/Auth/src/tvosMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt similarity index 82% rename from GoTrue/src/watchosMain/kotlin/io/github/jan/supabase/gotrue/AuthConfig.kt rename to Auth/src/tvosMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt index 23d7921f0..b7229ecd9 100644 --- a/GoTrue/src/watchosMain/kotlin/io/github/jan/supabase/gotrue/AuthConfig.kt +++ b/Auth/src/tvosMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.plugins.CustomSerializationConfig diff --git a/GoTrue/src/tvosMain/kotlin/io/github/jan/supabase/gotrue/providers/openUrl.kt b/Auth/src/tvosMain/kotlin/io/github/jan/supabase/auth/providers/openUrl.kt similarity index 70% rename from GoTrue/src/tvosMain/kotlin/io/github/jan/supabase/gotrue/providers/openUrl.kt rename to Auth/src/tvosMain/kotlin/io/github/jan/supabase/auth/providers/openUrl.kt index 989d66799..471b13d3c 100644 --- a/GoTrue/src/tvosMain/kotlin/io/github/jan/supabase/gotrue/providers/openUrl.kt +++ b/Auth/src/tvosMain/kotlin/io/github/jan/supabase/auth/providers/openUrl.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue.providers +package io.github.jan.supabase.auth.providers import platform.Foundation.NSURL diff --git a/Auth/src/wasmJsMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt b/Auth/src/wasmJsMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt new file mode 100644 index 000000000..9fd9994f5 --- /dev/null +++ b/Auth/src/wasmJsMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt @@ -0,0 +1,8 @@ +package io.github.jan.supabase.auth + +import io.github.jan.supabase.plugins.CustomSerializationConfig + +/** + * The configuration for [Auth] + */ +actual class AuthConfig: CustomSerializationConfig, AuthConfigDefaults() \ No newline at end of file diff --git a/Auth/src/wasmJsMain/kotlin/io/github/jan/supabase/auth/RedirectUrl.kt b/Auth/src/wasmJsMain/kotlin/io/github/jan/supabase/auth/RedirectUrl.kt new file mode 100644 index 000000000..1ad88fcf5 --- /dev/null +++ b/Auth/src/wasmJsMain/kotlin/io/github/jan/supabase/auth/RedirectUrl.kt @@ -0,0 +1,7 @@ +package io.github.jan.supabase.auth + +import io.github.jan.supabase.annotations.SupabaseInternal +import kotlinx.browser.window + +@SupabaseInternal +actual fun Auth.defaultPlatformRedirectUrl(): String? = window.location.origin \ No newline at end of file diff --git a/Auth/src/wasmJsMain/kotlin/io/github/jan/supabase/auth/Utils.wasmJs.kt b/Auth/src/wasmJsMain/kotlin/io/github/jan/supabase/auth/Utils.wasmJs.kt new file mode 100644 index 000000000..44cb623db --- /dev/null +++ b/Auth/src/wasmJsMain/kotlin/io/github/jan/supabase/auth/Utils.wasmJs.kt @@ -0,0 +1,8 @@ +package io.github.jan.supabase.auth + +import io.github.jan.supabase.SupabaseClient +import kotlinx.browser.window + +internal actual suspend fun SupabaseClient.openExternalUrl(url: String) { + window.location.href = url +} \ No newline at end of file diff --git a/Auth/src/wasmJsMain/kotlin/io/github/jan/supabase/auth/providers/ExternalAuthConfig.kt b/Auth/src/wasmJsMain/kotlin/io/github/jan/supabase/auth/providers/ExternalAuthConfig.kt new file mode 100644 index 000000000..267e6bf68 --- /dev/null +++ b/Auth/src/wasmJsMain/kotlin/io/github/jan/supabase/auth/providers/ExternalAuthConfig.kt @@ -0,0 +1,6 @@ +package io.github.jan.supabase.auth.providers + +/** + * Configuration for external authentication providers like Google, Twitter, etc. + */ +actual class ExternalAuthConfig: ExternalAuthConfigDefaults() \ No newline at end of file diff --git a/Auth/src/wasmJsMain/kotlin/io/github/jan/supabase/auth/setupPlatform.kt b/Auth/src/wasmJsMain/kotlin/io/github/jan/supabase/auth/setupPlatform.kt new file mode 100644 index 000000000..8137057b1 --- /dev/null +++ b/Auth/src/wasmJsMain/kotlin/io/github/jan/supabase/auth/setupPlatform.kt @@ -0,0 +1,47 @@ +package io.github.jan.supabase.auth + +import io.github.jan.supabase.annotations.SupabaseInternal +import io.ktor.util.PlatformUtils.IS_BROWSER +import kotlinx.browser.window +import kotlinx.coroutines.launch +import org.w3c.dom.url.URL + +@SupabaseInternal +actual fun Auth.setupPlatform() { + this as AuthImpl + + fun checkForHash() { + if(window.location.hash.isBlank()) return + val afterHash = window.location.hash.substring(1) + + if(!afterHash.contains('=')) { + // No params after hash, no need to continue + return + } + parseFragmentAndImportSession(afterHash) { + val newURL = window.location.href.split("?")[0]; + window.history.replaceState(null, window.document.title, newURL); + } + } + + fun checkForPCKECode() { + val url = URL(window.location.href) + val code = url.searchParams.get("code") ?: return + authScope.launch { + val session = exchangeCodeForSession(code) + importSession(session) + } + val newURL = window.location.href.split("?")[0]; + window.history.replaceState(null, window.document.title, newURL); + } + + if(IS_BROWSER) { + window.onhashchange = { + checkForHash() + } + window.onload = { + checkForHash() + checkForPCKECode() + } + } +} \ No newline at end of file diff --git a/Auth/src/wasmJsTest/kotlin/platformSettings.kt b/Auth/src/wasmJsTest/kotlin/platformSettings.kt new file mode 100644 index 000000000..f6fa8ceb8 --- /dev/null +++ b/Auth/src/wasmJsTest/kotlin/platformSettings.kt @@ -0,0 +1,3 @@ +import io.github.jan.supabase.auth.AuthConfig + +actual fun AuthConfig.platformSettings() = Unit \ No newline at end of file diff --git a/GoTrue/src/tvosMain/kotlin/io/github/jan/supabase/gotrue/AuthConfig.kt b/Auth/src/watchosMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt similarity index 82% rename from GoTrue/src/tvosMain/kotlin/io/github/jan/supabase/gotrue/AuthConfig.kt rename to Auth/src/watchosMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt index 23d7921f0..b7229ecd9 100644 --- a/GoTrue/src/tvosMain/kotlin/io/github/jan/supabase/gotrue/AuthConfig.kt +++ b/Auth/src/watchosMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue +package io.github.jan.supabase.auth import io.github.jan.supabase.plugins.CustomSerializationConfig diff --git a/GoTrue/src/watchosMain/kotlin/io/github/jan/supabase/gotrue/providers/openUrl.kt b/Auth/src/watchosMain/kotlin/io/github/jan/supabase/auth/providers/openUrl.kt similarity index 70% rename from GoTrue/src/watchosMain/kotlin/io/github/jan/supabase/gotrue/providers/openUrl.kt rename to Auth/src/watchosMain/kotlin/io/github/jan/supabase/auth/providers/openUrl.kt index f87562bc7..d7e2f9131 100644 --- a/GoTrue/src/watchosMain/kotlin/io/github/jan/supabase/gotrue/providers/openUrl.kt +++ b/Auth/src/watchosMain/kotlin/io/github/jan/supabase/auth/providers/openUrl.kt @@ -1,4 +1,4 @@ -package io.github.jan.supabase.gotrue.providers +package io.github.jan.supabase.auth.providers import platform.Foundation.NSURL diff --git a/Functions/README.md b/Functions/README.md index 8324f6c53..55374b781 100644 --- a/Functions/README.md +++ b/Functions/README.md @@ -4,23 +4,27 @@ Extends Supabase-kt with a multiplatform Functions client. Supported targets: -| Target | **JVM** | **Android** | **JS** | **iOS** | **tvOS** | **watchOS** | **macOS** | **Windows** | **Linux** | -|--------|---------|-------------|--------|---------|----------|-------------|-----------|-------------|-----------| -| | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Target | **JVM** | **Android** | **JS** | **Wasm** | **Apple** | **Windows** | **Linux** | +|--------|---------|-------------|--------|----------|-----------|-------------|-----------| +| Status | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
In-depth Kotlin targets -**iOS:** iosArm64, iosSimulatorArm64, iosX64 - **JS**: Browser, NodeJS -**tvOS**: tvosArm64, tvosX64, tvosSimulatorArm64 +**Wasm**: wasm-js + +**Apple:** + +- iOS: iosArm64, iosSimulatorArm64, iosX64 + +- tvOS: tvosArm64, tvosX64, tvosSimulatorArm64 -**watchOS**: watchosArm64, watchosX64, watchosSimulatorArm64 +- watchOS: watchosArm64, watchosX64, watchosSimulatorArm64 -**MacOS**: macosX64, macosArm64 +- MacOS: macosX64, macosArm64 **Windows**: mingwX64 diff --git a/Functions/build.gradle.kts b/Functions/build.gradle.kts index a5837c36b..324abd7fa 100644 --- a/Functions/build.gradle.kts +++ b/Functions/build.gradle.kts @@ -15,7 +15,7 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - addModules(SupabaseModule.GOTRUE, SupabaseModule.SUPABASE) + addModules(SupabaseModule.AUTH, SupabaseModule.SUPABASE) } } val commonTest by getting { diff --git a/Functions/src/commonMain/kotlin/io/github/jan/supabase/functions/FunctionRegion.kt b/Functions/src/commonMain/kotlin/io/github/jan/supabase/functions/FunctionRegion.kt index 67d39f863..8219a921f 100644 --- a/Functions/src/commonMain/kotlin/io/github/jan/supabase/functions/FunctionRegion.kt +++ b/Functions/src/commonMain/kotlin/io/github/jan/supabase/functions/FunctionRegion.kt @@ -1,3 +1,4 @@ +@file:Suppress("UndocumentedPublicProperty") package io.github.jan.supabase.functions /** diff --git a/Functions/src/commonMain/kotlin/io/github/jan/supabase/functions/Functions.kt b/Functions/src/commonMain/kotlin/io/github/jan/supabase/functions/Functions.kt index fcc807269..752f9f1f4 100644 --- a/Functions/src/commonMain/kotlin/io/github/jan/supabase/functions/Functions.kt +++ b/Functions/src/commonMain/kotlin/io/github/jan/supabase/functions/Functions.kt @@ -3,13 +3,13 @@ package io.github.jan.supabase.functions import io.github.jan.supabase.SupabaseClient import io.github.jan.supabase.SupabaseSerializer import io.github.jan.supabase.annotations.SupabaseInternal +import io.github.jan.supabase.auth.Auth +import io.github.jan.supabase.auth.authenticatedSupabaseApi import io.github.jan.supabase.encode import io.github.jan.supabase.exceptions.BadRequestRestException import io.github.jan.supabase.exceptions.NotFoundRestException import io.github.jan.supabase.exceptions.RestException import io.github.jan.supabase.exceptions.UnauthorizedRestException -import io.github.jan.supabase.gotrue.Auth -import io.github.jan.supabase.gotrue.authenticatedSupabaseApi import io.github.jan.supabase.plugins.CustomSerializationConfig import io.github.jan.supabase.plugins.CustomSerializationPlugin import io.github.jan.supabase.plugins.MainConfig @@ -152,6 +152,9 @@ class Functions(override val config: Config, override val supabaseClient: Supaba } + /** + * @see Functions + */ companion object : SupabasePluginProvider { override val key = "functions" diff --git a/Functions/src/commonTest/kotlin/FunctionsTest.kt b/Functions/src/commonTest/kotlin/FunctionsTest.kt index 43a2a12d2..50ffc5116 100644 --- a/Functions/src/commonTest/kotlin/FunctionsTest.kt +++ b/Functions/src/commonTest/kotlin/FunctionsTest.kt @@ -1,10 +1,10 @@ import io.github.jan.supabase.SupabaseClientBuilder +import io.github.jan.supabase.auth.Auth +import io.github.jan.supabase.auth.auth +import io.github.jan.supabase.auth.minimalSettings import io.github.jan.supabase.functions.FunctionRegion import io.github.jan.supabase.functions.Functions import io.github.jan.supabase.functions.functions -import io.github.jan.supabase.gotrue.Auth -import io.github.jan.supabase.gotrue.auth -import io.github.jan.supabase.gotrue.minimalSettings import io.github.jan.supabase.testing.createMockedSupabaseClient import io.github.jan.supabase.testing.pathAfterVersion import io.github.jan.supabase.testing.toJsonElement diff --git a/GoTrue/README.md b/GoTrue/README.md index 453611ac9..aa2fddbec 100644 --- a/GoTrue/README.md +++ b/GoTrue/README.md @@ -1,30 +1,40 @@ +# Deprecation notice + +**Starting with version 3.0.0, the module is called `auth-kt`. Checkout the updated [README](/Auth)**. + +The `gotrue-kt` artifact will no longer be published after version 3.0.0. + # Supabase-kt GoTrue -Extends Supabase-kt with a multiplatform GoTrue client. +Extends Supabase-kt with a multiplatform Auth client. Supported targets: -| Target | **JVM** | **Android** | **JS** | **iOS** | **tvOS** | **watchOS** | **macOS** | **Windows** | **Linux** | -| ------ | ------- | ----------- | ------ | ------- | -------- | ----------- | --------- | ----------- | --------- | -| | ✅ | ✅ | ✅ | ✅ | ☑️ | ☑️ | ✅ | ☑️ | ☑️ | +| Target | **JVM** | **Android** | **JS** | **Wasm** | **Apple** | **Windows** | **Linux** | +|--------|---------|-------------|--------|----------|-----------|-------------|-----------| +| Status | ✅ | ✅ | ✅ | ✅ | ☑️* | ☑️ | ☑️ | -> Native support is experimental and needs feedback -> > ☑️ = No built-in OAuth support. Linux has no support for persistent session storage. +\* **iOS and macOS are fully supported** +
In-depth Kotlin targets -**iOS:** iosArm64, iosSimulatorArm64, iosX64 - **JS**: Browser, NodeJS -**tvOS**: tvosArm64, tvosX64, tvosSimulatorArm64 +**Wasm**: wasm-js -**watchOS**: watchosArm64, watchosX64, watchosSimulatorArm64 +**Apple:** -**MacOS**: macosX64, macosArm64 +- iOS: iosArm64, iosSimulatorArm64, iosX64 + +- tvOS: tvosArm64, tvosX64, tvosSimulatorArm64 + +- watchOS: watchosArm64, watchosX64, watchosSimulatorArm64 + +- MacOS: macosX64, macosArm64 **Windows**: mingwX64 @@ -36,6 +46,8 @@ Supported targets: Newest version: [![](https://img.shields.io/github/release/supabase-community/supabase-kt?label=)](https://github.com/supabase-community/supabase-kt/releases) +Versions above 3.0.0 are available under the `auth-kt` module. + ```kotlin dependencies { implementation("io.github.jan-tennert.supabase:gotrue-kt:VERSION") @@ -61,4 +73,4 @@ val supabase = createSupabaseClient( # Usage -See [GoTrue documentation](https://supabase.com/docs/reference/kotlin/auth-signup) for usage +See [Auth documentation](https://supabase.com/docs/reference/kotlin/auth-signup) for usage diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/SignOutScope.kt b/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/SignOutScope.kt deleted file mode 100644 index d93adcea6..000000000 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/SignOutScope.kt +++ /dev/null @@ -1,14 +0,0 @@ -package io.github.jan.supabase.gotrue - -/** - * Represents the scope of a sign-out action. - * - * The sign-out scope determines the scope of the sign-out action being performed. - * - * @property GLOBAL Sign-out action applies to all sessions across the entire system. - * @property LOCAL Sign-out action applies only to the current session. - * @property OTHERS Sign-out action applies to other sessions, excluding the current session. - */ -enum class SignOutScope { - GLOBAL, LOCAL, OTHERS -} \ No newline at end of file diff --git a/GoTrue/src/jsMain/kotlin/io/github/jan/supabase/gotrue/providers/ExternalAuthConfig.kt b/GoTrue/src/jsMain/kotlin/io/github/jan/supabase/gotrue/providers/ExternalAuthConfig.kt deleted file mode 100644 index 59b620952..000000000 --- a/GoTrue/src/jsMain/kotlin/io/github/jan/supabase/gotrue/providers/ExternalAuthConfig.kt +++ /dev/null @@ -1,17 +0,0 @@ -package io.github.jan.supabase.gotrue.providers - -import kotlinx.browser.window - -/** - * Configuration for external authentication providers like Google, Twitter, etc. - */ -actual class ExternalAuthConfig: ExternalAuthConfigDefaults() { - - /** - * The URL to redirect to after a successful login. - * - * Defaults to `Window.location.origin` - */ - var redirectUrl: String = window.location.origin - -} \ No newline at end of file diff --git a/GoTrue/src/mingwTest/kotlin/platformSettings.kt b/GoTrue/src/mingwTest/kotlin/platformSettings.kt deleted file mode 100644 index c97cb2bd9..000000000 --- a/GoTrue/src/mingwTest/kotlin/platformSettings.kt +++ /dev/null @@ -1,3 +0,0 @@ -import io.github.jan.supabase.gotrue.AuthConfig - -actual fun AuthConfig.platformSettings() = Unit \ No newline at end of file diff --git a/Postgrest/README.md b/Postgrest/README.md index 94da8b919..c7c4f75a9 100644 --- a/Postgrest/README.md +++ b/Postgrest/README.md @@ -4,23 +4,27 @@ Extends Supabase-kt with a multiplatform Postgrest client. Supported targets: -| Target | **JVM** | **Android** | **JS** | **iOS** | **tvOS** | **watchOS** | **macOS** | **Windows** | **Linux** | -| ------ | ------- | ----------- | ------ | ------- | -------- | ----------- | --------- | ----------- | --------- | -| | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Target | **JVM** | **Android** | **JS** | **Wasm** | **Apple** | **Windows** | **Linux** | +|--------|---------|-------------|--------|----------|-----------|-------------|-----------| +| Status | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
In-depth Kotlin targets -**iOS:** iosArm64, iosSimulatorArm64, iosX64 - **JS**: Browser, NodeJS -**tvOS**: tvosArm64, tvosX64, tvosSimulatorArm64 +**Wasm**: wasm-js + +**Apple:** + +- iOS: iosArm64, iosSimulatorArm64, iosX64 + +- tvOS: tvosArm64, tvosX64, tvosSimulatorArm64 -**watchOS**: watchosArm64, watchosX64, watchosSimulatorArm64 +- watchOS: watchosArm64, watchosX64, watchosSimulatorArm64 -**MacOS**: macosX64, macosArm64 +- MacOS: macosX64, macosArm64 **Windows**: mingwX64 diff --git a/Postgrest/build.gradle.kts b/Postgrest/build.gradle.kts index 05fb3309d..bdc496cbe 100644 --- a/Postgrest/build.gradle.kts +++ b/Postgrest/build.gradle.kts @@ -16,13 +16,14 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - addModules(SupabaseModule.GOTRUE) + addModules(SupabaseModule.AUTH) api(libs.kotlin.reflect) } } val commonTest by getting { dependencies { implementation(libs.bundles.testing) + implementation(project(":test-common")) } } } diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/Postgrest.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/Postgrest.kt index 15ff8d86d..9d163e831 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/Postgrest.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/Postgrest.kt @@ -2,6 +2,7 @@ package io.github.jan.supabase.postgrest import io.github.jan.supabase.SupabaseClient import io.github.jan.supabase.SupabaseSerializer +import io.github.jan.supabase.exceptions.RestException import io.github.jan.supabase.logging.SupabaseLogger import io.github.jan.supabase.plugins.CustomSerializationConfig import io.github.jan.supabase.plugins.CustomSerializationPlugin @@ -11,6 +12,9 @@ import io.github.jan.supabase.plugins.SupabasePluginProvider import io.github.jan.supabase.postgrest.query.PostgrestQueryBuilder import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder import io.github.jan.supabase.postgrest.query.PostgrestUpdate +import io.github.jan.supabase.postgrest.query.request.RpcRequestBuilder +import io.github.jan.supabase.postgrest.result.PostgrestResult +import kotlinx.serialization.json.JsonObject /** * Plugin to interact with the supabase Postgrest API @@ -53,11 +57,36 @@ sealed interface Postgrest : MainPlugin, CustomSerializationPl /** * Creates a new [PostgrestQueryBuilder] for the given schema and table - * @param schema The schema to use for the requests * @param table The table to use for the requests */ operator fun get(table: String): PostgrestQueryBuilder = from(table) + /** + * Executes a database function + * + * @param function The name of the function + * @param request Filter the result + * @throws RestException or one of its subclasses if the request failed + */ + suspend fun rpc( + function: String, + request: RpcRequestBuilder.() -> Unit = {} + ): PostgrestResult + + /** + * Executes a database function + * + * @param function The name of the function + * @param parameters The parameters for the function + * @param request Filter the result + * @throws RestException or one of its subclasses if the request failed + */ + suspend fun rpc( + function: String, + parameters: JsonObject, + request: RpcRequestBuilder.() -> Unit = {}, + ): PostgrestResult + /** * Config for the Postgrest plugin * @param defaultSchema The default schema to use for the requests. Defaults to "public" diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/PostgrestImpl.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/PostgrestImpl.kt index 86c37b69b..f4e806326 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/PostgrestImpl.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/PostgrestImpl.kt @@ -2,16 +2,21 @@ package io.github.jan.supabase.postgrest import io.github.jan.supabase.SupabaseClient import io.github.jan.supabase.annotations.SupabaseInternal +import io.github.jan.supabase.auth.authenticatedSupabaseApi import io.github.jan.supabase.bodyOrNull import io.github.jan.supabase.exceptions.BadRequestRestException import io.github.jan.supabase.exceptions.NotFoundRestException import io.github.jan.supabase.exceptions.RestException import io.github.jan.supabase.exceptions.UnauthorizedRestException import io.github.jan.supabase.exceptions.UnknownRestException -import io.github.jan.supabase.gotrue.authenticatedSupabaseApi +import io.github.jan.supabase.postgrest.executor.RestRequestExecutor import io.github.jan.supabase.postgrest.query.PostgrestQueryBuilder +import io.github.jan.supabase.postgrest.query.request.RpcRequestBuilder +import io.github.jan.supabase.postgrest.request.RpcRequest +import io.github.jan.supabase.postgrest.result.PostgrestResult import io.ktor.client.statement.HttpResponse import io.ktor.http.HttpStatusCode +import kotlinx.serialization.json.JsonObject internal class PostgrestImpl(override val supabaseClient: SupabaseClient, override val config: Postgrest.Config) : Postgrest { @@ -51,4 +56,31 @@ internal class PostgrestImpl(override val supabaseClient: SupabaseClient, overri } } + override suspend fun rpc( + function: String, + parameters: JsonObject, + request: RpcRequestBuilder.() -> Unit + ): PostgrestResult = rpcRequest(function, parameters, request) + + override suspend fun rpc(function: String, request: RpcRequestBuilder.() -> Unit): PostgrestResult = rpcRequest(function, null, request) + + private suspend fun rpcRequest(function: String, body: JsonObject? = null, request: RpcRequestBuilder.() -> Unit): PostgrestResult { + val requestBuilder = RpcRequestBuilder(config.defaultSchema, config.propertyConversionMethod).apply(request) + val urlParams = buildMap { + putAll(requestBuilder.params.mapToFirstValue()) + if(requestBuilder.method != RpcMethod.POST && body != null) { + putAll(body.mapValues { it.value.toString() }) + } + } + val rpcRequest = RpcRequest( + method = requestBuilder.method.httpMethod, + count = requestBuilder.count, + urlParams = urlParams, + body = body, + schema = requestBuilder.schema, + headers = requestBuilder.headers.build() + ) + return RestRequestExecutor.execute(postgrest = this, path = "rpc/$function", request = rpcRequest) + } + } \ No newline at end of file diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/PostgrestRpc.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/PostgrestRpc.kt index d98cb5265..0ee05f226 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/PostgrestRpc.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/PostgrestRpc.kt @@ -2,12 +2,10 @@ package io.github.jan.supabase.postgrest import io.github.jan.supabase.encodeToJsonElement -import io.github.jan.supabase.postgrest.executor.RestRequestExecutor -import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder -import io.github.jan.supabase.postgrest.request.RpcRequest +import io.github.jan.supabase.exceptions.RestException +import io.github.jan.supabase.postgrest.query.request.RpcRequestBuilder import io.github.jan.supabase.postgrest.result.PostgrestResult import io.ktor.http.HttpMethod -import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.jsonObject /** @@ -34,53 +32,11 @@ enum class RpcMethod(val httpMethod: HttpMethod) { * * @param function The name of the function * @param parameters The parameters for the function - * @param method The HTTP method to use. Default is POST * @param request Filter the result * @throws RestException or one of its subclasses if the request failed */ suspend inline fun Postgrest.rpc( function: String, parameters: T, - method: RpcMethod = RpcMethod.POST, - request: PostgrestRequestBuilder.() -> Unit = {}, -): PostgrestResult { - val encodedParameters = if (parameters is JsonElement) parameters else serializer.encodeToJsonElement(parameters) - val requestBuilder = PostgrestRequestBuilder(config.propertyConversionMethod).apply(request) - val urlParams = buildMap { - putAll(requestBuilder.params.mapToFirstValue()) - if(method != RpcMethod.POST) { - putAll(encodedParameters.jsonObject.mapValues { it.value.toString() }) - } - } - val rpcRequest = RpcRequest( - method = method.httpMethod, - count = requestBuilder.count, - urlParams = urlParams, - body = encodedParameters, - headers = requestBuilder.headers.build() - ) - return RestRequestExecutor.execute(postgrest = this, path = "rpc/$function", request = rpcRequest) -} - -/** - * Executes a database function - * - * @param function The name of the function - * @param method The HTTP method to use. Default is POST - * @param request Filter the result - * @throws RestException or one of its subclasses if the request failed - */ -suspend inline fun Postgrest.rpc( - function: String, - method: RpcMethod = RpcMethod.POST, - request: PostgrestRequestBuilder.() -> Unit = {} -): PostgrestResult { - val requestBuilder = PostgrestRequestBuilder(config.propertyConversionMethod).apply(request) - val rpcRequest = RpcRequest( - method = method.httpMethod, - count = requestBuilder.count, - urlParams = requestBuilder.params.mapToFirstValue(), - headers = requestBuilder.headers.build() - ) - return RestRequestExecutor.execute(postgrest = this, path = "rpc/$function", request = rpcRequest) -} + noinline request: RpcRequestBuilder.() -> Unit = {}, +): PostgrestResult = rpc(function, serializer.encodeToJsonElement(parameters).jsonObject, request) \ No newline at end of file diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/Order.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/Order.kt index e3c5b1fda..2ac8b6970 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/Order.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/Order.kt @@ -5,5 +5,13 @@ package io.github.jan.supabase.postgrest.query * @param value The value to be used in the query */ enum class Order(val value: String) { - ASCENDING("asc"), DESCENDING("desc"); + /** + * Order the data ascending + */ + ASCENDING("asc"), + + /** + * Order the data descending + */ + DESCENDING("desc"); } diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestQueryBuilder.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestQueryBuilder.kt index 80e55ef92..7b06faa30 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestQueryBuilder.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestQueryBuilder.kt @@ -1,13 +1,16 @@ @file:Suppress("UndocumentedPublicProperty") package io.github.jan.supabase.postgrest.query +import io.github.jan.supabase.auth.PostgrestFilterDSL import io.github.jan.supabase.encodeToJsonElement import io.github.jan.supabase.exceptions.HttpRequestException import io.github.jan.supabase.exceptions.RestException -import io.github.jan.supabase.gotrue.PostgrestFilterDSL import io.github.jan.supabase.postgrest.Postgrest import io.github.jan.supabase.postgrest.executor.RestRequestExecutor import io.github.jan.supabase.postgrest.mapToFirstValue +import io.github.jan.supabase.postgrest.query.request.InsertRequestBuilder +import io.github.jan.supabase.postgrest.query.request.SelectRequestBuilder +import io.github.jan.supabase.postgrest.query.request.UpsertRequestBuilder import io.github.jan.supabase.postgrest.request.DeleteRequest import io.github.jan.supabase.postgrest.request.InsertRequest import io.github.jan.supabase.postgrest.request.SelectRequest @@ -30,8 +33,7 @@ class PostgrestQueryBuilder( * Executes vertical filtering with select on [table] * * @param columns The columns to retrieve, defaults to [Columns.ALL]. You can also use [Columns.list], [Columns.type] or [Columns.raw] to specify the columns - * @param head If true, no body will be returned. Useful when using count. - * @param request Additional filtering to apply to the query + * @param request Additional configurations for the request including filters * @return PostgrestResult which is either an error, an empty JsonArray or the data you requested as an JsonArray * @throws RestException or one of its subclasses if receiving an error response * @throws HttpRequestTimeoutException if the request timed out @@ -39,14 +41,13 @@ class PostgrestQueryBuilder( */ suspend inline fun select( columns: Columns = Columns.ALL, - head: Boolean = false, - request: @PostgrestFilterDSL PostgrestRequestBuilder.() -> Unit = {} + request: @PostgrestFilterDSL SelectRequestBuilder.() -> Unit = {} ): PostgrestResult { - val requestBuilder = postgrestRequest(postgrest.config.propertyConversionMethod) { + val requestBuilder = SelectRequestBuilder(postgrest.config.propertyConversionMethod).apply { request(); params["select"] = listOf(columns.value) } val selectRequest = SelectRequest( - head = head, + head = requestBuilder.head, count = requestBuilder.count, urlParams = requestBuilder.params.mapToFirstValue(), schema = schema, @@ -57,38 +58,28 @@ class PostgrestQueryBuilder( /** * Perform an UPSERT on the table or view. Depending on the column(s) passed - * to [onConflict], [upsert] allows you to perform the equivalent of + * to [UpsertRequestBuilder.onConflict], [upsert] allows you to perform the equivalent of * `[insert] if a row with the corresponding onConflict columns doesn't * exist, or if it does exist, perform an alternative action depending on - * [ignoreDuplicates]. + * [UpsertRequestBuilder.ignoreDuplicates]. * * By default, upserted rows are not returned. To return it, call `[PostgrestRequestBuilder.select]`. * * @param values The values to insert, will automatically get serialized into json. - * @param request Additional filtering to apply to the query - * @param onConflict Comma-separated UNIQUE column(s) to specify how - * duplicate rows are determined. Two rows are duplicates if all the - * `onConflict` columns are equal. - * @param defaultToNull Make missing fields default to `null`. - * Otherwise, use the default value for the column. This only applies when - * inserting new rows, not when merging with existing rows under - * @param ignoreDuplicates If `true`, duplicate rows are ignored. If `false`, duplicate rows are merged with existing rows. + * @param request Additional configurations for the request including filters * @throws RestException or one of its subclasses if receiving an error response * @throws HttpRequestTimeoutException if the request timed out * @throws HttpRequestException on network related issues */ suspend inline fun upsert( values: List, - onConflict: String? = null, - defaultToNull: Boolean = true, - ignoreDuplicates: Boolean = false, - request: PostgrestRequestBuilder.() -> Unit = {} + request: UpsertRequestBuilder.() -> Unit = {} ): PostgrestResult { - val requestBuilder = postgrestRequest(postgrest.config.propertyConversionMethod, request) + val requestBuilder = UpsertRequestBuilder(postgrest.config.propertyConversionMethod).apply(request) val body = postgrest.serializer.encodeToJsonElement(values).jsonArray val columns = body.map { it.jsonObject.keys }.flatten().distinct() requestBuilder.params["columns"] = listOf(columns.joinToString(",")) - onConflict?.let { + requestBuilder.onConflict?.let { requestBuilder.params["on_conflict"] = listOf(it) } val insertRequest = InsertRequest( @@ -97,8 +88,8 @@ class PostgrestQueryBuilder( returning = requestBuilder.returning, count = requestBuilder.count, urlParams = requestBuilder.params.mapToFirstValue(), - defaultToNull = defaultToNull, - ignoreDuplicates = ignoreDuplicates, + defaultToNull = requestBuilder.defaultToNull, + ignoreDuplicates = requestBuilder.ignoreDuplicates, schema = schema, headers = requestBuilder.headers.build() ) @@ -107,52 +98,38 @@ class PostgrestQueryBuilder( /** * Perform an UPSERT on the table or view. Depending on the column(s) passed - * to [onConflict], [upsert] allows you to perform the equivalent of + * to [UpsertRequestBuilder.onConflict], [upsert] allows you to perform the equivalent of * `[insert] if a row with the corresponding onConflict columns doesn't * exist, or if it does exist, perform an alternative action depending on - * [ignoreDuplicates]. + * [UpsertRequestBuilder.ignoreDuplicates]. * * By default, upserted rows are not returned. To return it, call `[PostgrestRequestBuilder.select]`. * * @param value The value to insert, will automatically get serialized into json. * @param request Additional filtering to apply to the query - * @param onConflict Comma-separated UNIQUE column(s) to specify how - * duplicate rows are determined. Two rows are duplicates if all the - * `onConflict` columns are equal. - * @param defaultToNull Make missing fields default to `null`. - * Otherwise, use the default value for the column. This only applies when - * inserting new rows, not when merging with existing rows under - * @param ignoreDuplicates If `true`, duplicate rows are ignored. If `false`, duplicate rows are merged with existing rows. * @throws RestException or one of its subclasses if receiving an error response * @throws HttpRequestTimeoutException if the request timed out * @throws HttpRequestException on network related issues */ suspend inline fun upsert( value: T, - onConflict: String? = null, - defaultToNull: Boolean = true, - ignoreDuplicates: Boolean = false, - request: PostgrestRequestBuilder.() -> Unit = {} - ): PostgrestResult = upsert(listOf(value), onConflict, defaultToNull, ignoreDuplicates, request) + request: UpsertRequestBuilder.() -> Unit = {} + ): PostgrestResult = upsert(listOf(value), request) /** * Executes an insert operation on the [table] * * @param values The values to insert, will automatically get serialized into json. * @param request Additional filtering to apply to the query - * @param defaultToNull Make missing fields default to `null`. - * Otherwise, use the default value for the column. This only applies when - * inserting new rows, not when merging with existing rows under * @throws RestException or one of its subclasses if receiving an error response * @throws HttpRequestTimeoutException if the request timed out * @throws HttpRequestException on network related issues */ suspend inline fun insert( values: List, - defaultToNull: Boolean = true, - request: PostgrestRequestBuilder.() -> Unit = {} + request: InsertRequestBuilder.() -> Unit = {} ): PostgrestResult { - val requestBuilder = postgrestRequest(postgrest.config.propertyConversionMethod, request) + val requestBuilder = InsertRequestBuilder(postgrest.config.propertyConversionMethod).apply(request) val body = postgrest.serializer.encodeToJsonElement(values).jsonArray val columns = body.map { it.jsonObject.keys }.flatten().distinct() requestBuilder.params["columns"] = listOf(columns.joinToString(",")) @@ -163,7 +140,7 @@ class PostgrestQueryBuilder( urlParams = requestBuilder.params.mapToFirstValue(), schema = schema, headers = requestBuilder.headers.build(), - defaultToNull = defaultToNull + defaultToNull = requestBuilder.defaultToNull ) return RestRequestExecutor.execute(postgrest = postgrest, path = table, request = insertRequest) } @@ -173,18 +150,14 @@ class PostgrestQueryBuilder( * * @param value The value to insert, will automatically get serialized into json. * @param request Additional filtering to apply to the query - * @param defaultToNull Make missing fields default to `null`. - * Otherwise, use the default value for the column. This only applies when - * inserting new rows, not when merging with existing rows under * @throws RestException or one of its subclasses if receiving an error response * @throws HttpRequestTimeoutException if the request timed out * @throws HttpRequestException on network related issues */ suspend inline fun insert( value: T, - defaultToNull: Boolean = true, - request: PostgrestRequestBuilder.() -> Unit = {} - ) = insert(listOf(value), defaultToNull, request) + request: InsertRequestBuilder.() -> Unit = {} + ) = insert(listOf(value), request) /** * Executes an update operation on the [table]. @@ -201,7 +174,7 @@ class PostgrestQueryBuilder( crossinline update: PostgrestUpdate.() -> Unit = {}, request: PostgrestRequestBuilder.() -> Unit = {} ): PostgrestResult { - val requestBuilder = postgrestRequest(postgrest.config.propertyConversionMethod, request) + val requestBuilder = PostgrestRequestBuilder(postgrest.config.propertyConversionMethod).apply(request) val updateRequest = UpdateRequest( body = buildPostgrestUpdate(postgrest.config.propertyConversionMethod, postgrest.serializer, update), returning = requestBuilder.returning, @@ -228,7 +201,7 @@ class PostgrestQueryBuilder( value: T, request: PostgrestRequestBuilder.() -> Unit = {} ): PostgrestResult { - val requestBuilder = postgrestRequest(postgrest.config.propertyConversionMethod, request) + val requestBuilder = PostgrestRequestBuilder(postgrest.config.propertyConversionMethod).apply(request) val updateRequest = UpdateRequest( returning = requestBuilder.returning, count = requestBuilder.count, @@ -253,7 +226,7 @@ class PostgrestQueryBuilder( suspend inline fun delete( request: PostgrestRequestBuilder.() -> Unit = {} ): PostgrestResult { - val requestBuilder = postgrestRequest(postgrest.config.propertyConversionMethod, request) + val requestBuilder = PostgrestRequestBuilder(postgrest.config.propertyConversionMethod).apply(request) val deleteRequest = DeleteRequest( returning = requestBuilder.returning, count = requestBuilder.count, diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestRequestBuilder.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestRequestBuilder.kt index bb48d6bc0..656d016cb 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestRequestBuilder.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/PostgrestRequestBuilder.kt @@ -2,8 +2,7 @@ package io.github.jan.supabase.postgrest.query import io.github.jan.supabase.annotations.SupabaseExperimental -import io.github.jan.supabase.annotations.SupabaseInternal -import io.github.jan.supabase.gotrue.PostgrestFilterDSL +import io.github.jan.supabase.auth.PostgrestFilterDSL import io.github.jan.supabase.postgrest.PropertyConversionMethod import io.github.jan.supabase.postgrest.query.filter.PostgrestFilterBuilder import io.github.jan.supabase.postgrest.result.PostgrestResult @@ -15,7 +14,7 @@ import kotlin.js.JsName * A builder for Postgrest requests. */ @PostgrestFilterDSL -class PostgrestRequestBuilder(@PublishedApi internal val propertyConversionMethod: PropertyConversionMethod) { +open class PostgrestRequestBuilder(@PublishedApi internal val propertyConversionMethod: PropertyConversionMethod) { /** * The [Count] algorithm to use to count rows in the table or view. @@ -132,6 +131,7 @@ class PostgrestRequestBuilder(@PublishedApi internal val propertyConversionMetho * @param format - The format of the output, can be `"text"` (default) * or `"json"` */ + @Suppress("LongParameterList") fun explain( analyze: Boolean = false, verbose: Boolean = false, @@ -162,11 +162,3 @@ class PostgrestRequestBuilder(@PublishedApi internal val propertyConversionMetho } -@SupabaseInternal -inline fun postgrestRequest(propertyConversionMethod: PropertyConversionMethod = PropertyConversionMethod.CAMEL_CASE_TO_SNAKE_CASE, block: PostgrestRequestBuilder.() -> Unit): PostgrestRequestBuilder { - val filter = PostgrestRequestBuilder(propertyConversionMethod) - filter.block() - return filter -} - - diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/filter/PostgrestFilterBuilder.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/filter/PostgrestFilterBuilder.kt index 108ad8af6..0021ce0ff 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/filter/PostgrestFilterBuilder.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/filter/PostgrestFilterBuilder.kt @@ -1,7 +1,7 @@ @file:Suppress("UndocumentedPublicProperty", "ConstructorParameterNaming") package io.github.jan.supabase.postgrest.query.filter -import io.github.jan.supabase.gotrue.PostgrestFilterDSL +import io.github.jan.supabase.auth.PostgrestFilterDSL import io.github.jan.supabase.postgrest.PropertyConversionMethod import kotlin.reflect.KProperty1 diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/InsertRequestBuilder.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/InsertRequestBuilder.kt new file mode 100644 index 000000000..4c3f5ff28 --- /dev/null +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/InsertRequestBuilder.kt @@ -0,0 +1,19 @@ +package io.github.jan.supabase.postgrest.query.request + +import io.github.jan.supabase.postgrest.PropertyConversionMethod +import io.github.jan.supabase.postgrest.query.PostgrestQueryBuilder +import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder + +/** + * Request builder for [PostgrestQueryBuilder.insert] + */ +open class InsertRequestBuilder(propertyConversionMethod: PropertyConversionMethod): PostgrestRequestBuilder(propertyConversionMethod) { + + /** + * Make missing fields default to `null`. + * Otherwise, use the default value for the column. This only applies when + * inserting new rows, not when merging with existing rows under + */ + var defaultToNull: Boolean = true + +} \ No newline at end of file diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/RpcRequestBuilder.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/RpcRequestBuilder.kt new file mode 100644 index 000000000..8d7b1fe68 --- /dev/null +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/RpcRequestBuilder.kt @@ -0,0 +1,22 @@ +package io.github.jan.supabase.postgrest.query.request + +import io.github.jan.supabase.postgrest.PropertyConversionMethod +import io.github.jan.supabase.postgrest.RpcMethod +import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder + +/** + * Request builder for [Postgrest.rpcRequest] + */ +class RpcRequestBuilder(defaultSchema: String, propertyConversionMethod: PropertyConversionMethod): PostgrestRequestBuilder(propertyConversionMethod) { + + /** + * The HTTP method to use. Default is POST + */ + var method: RpcMethod = RpcMethod.POST + + /** + * The database schema + */ + var schema: String = defaultSchema + +} \ No newline at end of file diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/SelectRequestBuilder.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/SelectRequestBuilder.kt new file mode 100644 index 000000000..1ac90fac3 --- /dev/null +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/SelectRequestBuilder.kt @@ -0,0 +1,17 @@ +package io.github.jan.supabase.postgrest.query.request + +import io.github.jan.supabase.postgrest.PropertyConversionMethod +import io.github.jan.supabase.postgrest.query.PostgrestQueryBuilder +import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder + +/** + * Request builder for [PostgrestQueryBuilder.select] + */ +class SelectRequestBuilder(propertyConversionMethod: PropertyConversionMethod): PostgrestRequestBuilder(propertyConversionMethod) { + + /** + * If true, no body will be returned. Useful when using count. + */ + var head: Boolean = false + +} \ No newline at end of file diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/UpsertRequestBuilder.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/UpsertRequestBuilder.kt new file mode 100644 index 000000000..024e00338 --- /dev/null +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/request/UpsertRequestBuilder.kt @@ -0,0 +1,23 @@ +package io.github.jan.supabase.postgrest.query.request + +import io.github.jan.supabase.postgrest.PropertyConversionMethod +import io.github.jan.supabase.postgrest.query.PostgrestQueryBuilder + +/** + * Request builder for [PostgrestQueryBuilder.upsert] + */ +class UpsertRequestBuilder(propertyConversionMethod: PropertyConversionMethod): InsertRequestBuilder(propertyConversionMethod) { + + /** + * Comma-separated UNIQUE column(s) to specify how + * duplicate rows are determined. Two rows are duplicates if all the + * `onConflict` columns are equal. + */ + var onConflict: String? = null + + /** + * If `true`, duplicate rows are ignored. If `false`, duplicate rows are merged with existing rows. + */ + var ignoreDuplicates: Boolean = false + +} \ No newline at end of file diff --git a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/request/RpcRequest.kt b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/request/RpcRequest.kt index 4fe29b438..fdd9a3094 100644 --- a/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/request/RpcRequest.kt +++ b/Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/request/RpcRequest.kt @@ -1,9 +1,9 @@ package io.github.jan.supabase.postgrest.request import io.github.jan.supabase.postgrest.query.Count +import io.ktor.http.Headers import io.ktor.http.HttpMethod import kotlinx.serialization.json.JsonElement -import io.ktor.http.Headers @PublishedApi internal class RpcRequest( @@ -11,10 +11,10 @@ internal class RpcRequest( val count: Count? = null, override val urlParams: Map, override val body: JsonElement? = null, + override val schema: String = "public", override val headers: Headers = Headers.Empty ) : PostgrestRequest { - override val schema: String = "" override val prefer = if (count != null) listOf("count=${count.identifier}") else listOf() -} +} \ No newline at end of file diff --git a/Postgrest/src/commonTest/kotlin/PostgrestRequestBuilderTest.kt b/Postgrest/src/commonTest/kotlin/PostgrestRequestBuilderTest.kt index b84e13548..1ddc65858 100644 --- a/Postgrest/src/commonTest/kotlin/PostgrestRequestBuilderTest.kt +++ b/Postgrest/src/commonTest/kotlin/PostgrestRequestBuilderTest.kt @@ -1,7 +1,6 @@ import io.github.jan.supabase.postgrest.query.Count import io.github.jan.supabase.postgrest.query.Order import io.github.jan.supabase.postgrest.query.Returning -import io.github.jan.supabase.postgrest.query.postgrestRequest import io.ktor.http.HttpHeaders import kotlin.test.Test import kotlin.test.assertEquals @@ -129,4 +128,4 @@ class PostgrestRequestBuilderTest { assertEquals("application/vnd.pgrst.plan+json; for=\"application/json\"; options=analyze|verbose|settings|buffers|wal;", request.headers[HttpHeaders.Accept]) } -} \ No newline at end of file +} diff --git a/Postgrest/src/commonTest/kotlin/PostgrestTest.kt b/Postgrest/src/commonTest/kotlin/PostgrestTest.kt new file mode 100644 index 000000000..41827cd8f --- /dev/null +++ b/Postgrest/src/commonTest/kotlin/PostgrestTest.kt @@ -0,0 +1,343 @@ +import io.github.jan.supabase.SupabaseClient +import io.github.jan.supabase.SupabaseClientBuilder +import io.github.jan.supabase.postgrest.Postgrest +import io.github.jan.supabase.postgrest.RpcMethod +import io.github.jan.supabase.postgrest.from +import io.github.jan.supabase.postgrest.postgrest +import io.github.jan.supabase.postgrest.query.Columns +import io.github.jan.supabase.postgrest.query.request.InsertRequestBuilder +import io.github.jan.supabase.postgrest.query.request.UpsertRequestBuilder +import io.github.jan.supabase.postgrest.result.PostgrestResult +import io.github.jan.supabase.testing.assertMethodIs +import io.github.jan.supabase.testing.assertPathIs +import io.github.jan.supabase.testing.createMockedSupabaseClient +import io.github.jan.supabase.testing.pathAfterVersion +import io.github.jan.supabase.testing.toJsonElement +import io.ktor.client.engine.mock.MockRequestHandleScope +import io.ktor.client.engine.mock.respond +import io.ktor.client.request.HttpRequestData +import io.ktor.client.request.HttpResponseData +import io.ktor.http.HttpMethod +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.put +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertEquals + +class PostgrestTest { + + private val configureClient: SupabaseClientBuilder.() -> Unit = { + install(Postgrest) + } + + @Test + fun testSelectHttpMethodGet() { + val columns = Columns.list("column1", "column2") + testClient( + request = { table -> + from(table).select(columns) { + headers["custom"] = "value" + params["custom"] = listOf("value") + } + }, + requestHandler = { + assertMethodIs(HttpMethod.Get, it.method) + assertEquals(columns.value, it.url.parameters["select"]) + assertEquals("value", it.headers["custom"]) + assertEquals("value", it.url.parameters["custom"]) + respond("") + } + ) + } + + @Test + fun testSelectSchema() { + val columns = Columns.list("column1", "column2") + testClient( + request = { table -> + from("schema", table).select(columns) + }, + requestHandler = { + assertEquals("schema", it.headers["Accept-Profile"]) + assertMethodIs(HttpMethod.Get, it.method) + assertEquals(columns.value, it.url.parameters["select"]) + respond("") + } + ) + } + + @Test + fun testSelectHttpMethodHead() { + val columns = Columns.list("column1", "column2") + testClient( + request = { table -> + from(table).select(columns) { + head = true + } + }, + requestHandler = { + assertMethodIs(HttpMethod.Head, it.method) + assertEquals(columns.value, it.url.parameters["select"]) + respond("") + } + ) + } + + @Test + fun testInsert() { + insertTestClient( + request = { + }, + requestHandler = { + val prefer = it.headers["Prefer"]?.split(",") ?: emptyList() + assertContains(prefer, "return=minimal") //default + } + ) + } + + @Test + fun testInsertDefaultToNull() { + insertTestClient( + request = { + defaultToNull = false + }, + requestHandler = { + val prefer = it.headers["Prefer"]?.split(",") ?: emptyList() + assertContains(prefer, "return=minimal") //default + assertContains(prefer, "missing=default") + } + ) + } + + @Test + fun testInsertWithSelect() { + insertTestClient( + request = { + select(Columns.raw("column1,column2")) + }, + requestHandler = { + val prefer = it.headers["Prefer"]?.split(",") ?: emptyList() + assertEquals("column1,column2", it.url.parameters["select"]) + assertContains(prefer, "return=representation") + } + ) + } + + private fun insertTestClient( + request: InsertRequestBuilder.() -> Unit, + requestHandler: suspend MockRequestHandleScope.(HttpRequestData) -> Unit, + ) { + val mockData = buildJsonObject { + put("column1", "value1") + put("column2", "value2") + } + testClient( + request = { table -> + from("schema", table).insert(mockData) { + request() + headers["custom"] = "value" + params["custom"] = listOf("value") + } + }, + requestHandler = { + assertEquals(mockData, it.body.toJsonElement().jsonArray.first()) + requestHandler(it) + assertMethodIs(HttpMethod.Post, it.method) + assertEquals("value", it.headers["custom"]) + assertEquals("value", it.url.parameters["custom"]) + assertEquals("schema", it.headers["Content-Profile"]) + respond("") + } + ) + } + + @Test + fun testUpsert() { + upsertTestClient( + request = { + }, + requestHandler = { + val prefer = it.headers["Prefer"]?.split(",") ?: emptyList() + assertContains(prefer, "resolution=merge-duplicates") //default + } + ) + } + + @Test + fun testUpsertIgnoreDuplicates() { + upsertTestClient( + request = { + ignoreDuplicates = true + }, + requestHandler = { + val prefer = it.headers["Prefer"]?.split(",") ?: emptyList() + assertContains(prefer, "resolution=ignore-duplicates") + } + ) + } + + @Test + fun testUpsertWithSelect() { + upsertTestClient( + request = { + select(Columns.raw("column1,column2")) + }, + requestHandler = { + val prefer = it.headers["Prefer"]?.split(",") ?: emptyList() + assertEquals("column1,column2", it.url.parameters["select"]) + assertContains(prefer, "return=representation") + } + ) + } + + @Test + fun testUpsertWithDefaultToNull() { + upsertTestClient( + request = { + defaultToNull = false + }, + requestHandler = { + val prefer = it.headers["Prefer"]?.split(",") ?: emptyList() + assertContains(prefer, "missing=default") + } + ) + } + + @Test + fun testUpsertWithOnConflict() { + upsertTestClient( + request = { + onConflict = "column1" + }, + requestHandler = { + assertEquals("column1", it.url.parameters["on_conflict"]) + } + ) + } + + private fun upsertTestClient( + request: UpsertRequestBuilder.() -> Unit, + requestHandler: suspend MockRequestHandleScope.(HttpRequestData) -> Unit, + ) { + val mockData = buildJsonArray { + add(buildJsonObject { + put("column1", "value1") + put("column2", "value2") + }) + add(buildJsonObject { + put("column1", "value1") + put("column3", "value3") + }) + } + testClient( + request = { table -> + from("schema", table).upsert(mockData) { + request() + headers["custom"] = "value" + params["custom"] = listOf("value") + } + }, + requestHandler = { + assertEquals(mockData, it.body.toJsonElement()) + requestHandler(it) + assertMethodIs(HttpMethod.Post, it.method) + assertEquals("column1,column2,column3", it.url.parameters["columns"]) + assertEquals("value", it.headers["custom"]) + assertEquals("schema", it.headers["Content-Profile"]) + assertEquals("value", it.url.parameters["custom"]) + respond("") + } + ) + } + + @Test + fun testUpdate() { + val columns = Columns.list("column1", "column2") + val mockData = buildJsonObject { + put("column1", "value1") + put("column2", "value2") + } + testClient( + request = { table -> + from(table).update(mockData) { + select(columns) + headers["custom"] = "value" + params["custom"] = listOf("value") + } + }, + requestHandler = { + assertMethodIs(HttpMethod.Patch, it.method) + assertEquals(mockData, it.body.toJsonElement()) + val prefer = it.headers["Prefer"]?.split(",") ?: emptyList() + assertEquals("column1,column2", it.url.parameters["select"]) + assertContains(prefer, "return=representation") + assertEquals("value", it.headers["custom"]) + assertEquals("value", it.url.parameters["custom"]) + respond("") + } + ) + } + + @Test + fun testRpcNoParameters() { + val supabase = createMockedSupabaseClient( + configuration = configureClient + ) { + assertPathIs("/rpc/function", it.url.pathAfterVersion()) + assertMethodIs(HttpMethod.Head, it.method) + assertEquals("schema", it.headers["Accept-Profile"]) + assertEquals("value", it.headers["custom"]) + assertEquals("value", it.url.parameters["custom"]) + respond("") + } + runTest { + supabase.postgrest.rpc("function") { + method = RpcMethod.HEAD + schema = "schema" + headers["custom"] = "value" + params["custom"] = listOf("value") + } + } + } + + @Test + fun testRpcParameters() { + val mockData = buildJsonObject { + put("key", "value") + } + val supabase = createMockedSupabaseClient( + configuration = configureClient + ) { + assertPathIs("/rpc/function", it.url.pathAfterVersion()) + assertEquals(mockData, it.body.toJsonElement()) + assertMethodIs(HttpMethod.Post, it.method) + assertEquals("schema", it.headers["Content-Profile"]) + respond("") + } + runTest { + supabase.postgrest.rpc("function", mockData) { + schema = "schema" + } + } + } + + private fun testClient( + table: String = "table", + request: suspend SupabaseClient.(table: String) -> PostgrestResult, + requestHandler: suspend MockRequestHandleScope.(HttpRequestData) -> HttpResponseData = { respond("")}, + ) { + val supabase = createMockedSupabaseClient( + configuration = configureClient + ) { + assertPathIs("/$table", it.url.pathAfterVersion()) + requestHandler(it) + } + runTest { + supabase.request(table) + } + } + +} \ No newline at end of file diff --git a/Postgrest/src/commonTest/kotlin/Utils.kt b/Postgrest/src/commonTest/kotlin/Utils.kt new file mode 100644 index 000000000..b1db90d84 --- /dev/null +++ b/Postgrest/src/commonTest/kotlin/Utils.kt @@ -0,0 +1,10 @@ +import io.github.jan.supabase.annotations.SupabaseInternal +import io.github.jan.supabase.postgrest.PropertyConversionMethod +import io.github.jan.supabase.postgrest.query.PostgrestRequestBuilder + +@SupabaseInternal +inline fun postgrestRequest(propertyConversionMethod: PropertyConversionMethod = PropertyConversionMethod.CAMEL_CASE_TO_SNAKE_CASE, block: PostgrestRequestBuilder.() -> Unit): PostgrestRequestBuilder { + val filter = PostgrestRequestBuilder(propertyConversionMethod) + filter.block() + return filter +} \ No newline at end of file diff --git a/Postgrest/src/commonTest/kotlin/io.github.jan.supabase.postgrest/request/DeleteRequestTest.kt b/Postgrest/src/commonTest/kotlin/request/DeleteRequestTest.kt similarity index 89% rename from Postgrest/src/commonTest/kotlin/io.github.jan.supabase.postgrest/request/DeleteRequestTest.kt rename to Postgrest/src/commonTest/kotlin/request/DeleteRequestTest.kt index adf3b0821..2a0da7ac1 100644 --- a/Postgrest/src/commonTest/kotlin/io.github.jan.supabase.postgrest/request/DeleteRequestTest.kt +++ b/Postgrest/src/commonTest/kotlin/request/DeleteRequestTest.kt @@ -1,7 +1,9 @@ -package io.github.jan.supabase.postgrest.request +package request import io.github.jan.supabase.postgrest.query.Count import io.github.jan.supabase.postgrest.query.Returning +import io.github.jan.supabase.postgrest.request.DeleteRequest +import io.github.jan.supabase.postgrest.request.PostgrestRequest import kotlin.test.Test import kotlin.test.assertEquals diff --git a/Postgrest/src/commonTest/kotlin/io.github.jan.supabase.postgrest/request/InsertRequestTest.kt b/Postgrest/src/commonTest/kotlin/request/InsertRequestTest.kt similarity index 94% rename from Postgrest/src/commonTest/kotlin/io.github.jan.supabase.postgrest/request/InsertRequestTest.kt rename to Postgrest/src/commonTest/kotlin/request/InsertRequestTest.kt index 65e898bb1..b2d080e18 100644 --- a/Postgrest/src/commonTest/kotlin/io.github.jan.supabase.postgrest/request/InsertRequestTest.kt +++ b/Postgrest/src/commonTest/kotlin/request/InsertRequestTest.kt @@ -1,7 +1,9 @@ -package io.github.jan.supabase.postgrest.request +package request import io.github.jan.supabase.postgrest.query.Count import io.github.jan.supabase.postgrest.query.Returning +import io.github.jan.supabase.postgrest.request.InsertRequest +import io.github.jan.supabase.postgrest.request.PostgrestRequest import kotlinx.serialization.json.JsonArray import kotlin.test.Test import kotlin.test.assertEquals diff --git a/Postgrest/src/commonTest/kotlin/io.github.jan.supabase.postgrest/request/RpcRequestTest.kt b/Postgrest/src/commonTest/kotlin/request/RpcRequestTest.kt similarity index 86% rename from Postgrest/src/commonTest/kotlin/io.github.jan.supabase.postgrest/request/RpcRequestTest.kt rename to Postgrest/src/commonTest/kotlin/request/RpcRequestTest.kt index ba24a0e52..6857e646c 100644 --- a/Postgrest/src/commonTest/kotlin/io.github.jan.supabase.postgrest/request/RpcRequestTest.kt +++ b/Postgrest/src/commonTest/kotlin/request/RpcRequestTest.kt @@ -1,6 +1,8 @@ -package io.github.jan.supabase.postgrest.request +package request import io.github.jan.supabase.postgrest.query.Count +import io.github.jan.supabase.postgrest.request.PostgrestRequest +import io.github.jan.supabase.postgrest.request.RpcRequest import io.ktor.http.HttpMethod import kotlinx.serialization.json.JsonArray import kotlin.test.Test @@ -19,6 +21,7 @@ class RpcRequestTest { count = Count.EXACT, body = JsonArray(listOf()), urlParams = mapOf("Key1" to "Value1"), + schema = "mySchema" ) val count = (sut as RpcRequest).count @@ -31,7 +34,7 @@ class RpcRequestTest { "count=exact" ), sut.prefer ) - assertEquals("", sut.schema) + assertEquals("mySchema", sut.schema) assertEquals(mapOf("Key1" to "Value1"), sut.urlParams) assertEquals(JsonArray(listOf()), sut.body) } @@ -43,6 +46,7 @@ class RpcRequestTest { count = Count.EXACT, body = JsonArray(listOf()), urlParams = mapOf("Key1" to "Value1"), + schema = "mySchema" ) val count = (sut as RpcRequest).count assertNotNull(count) @@ -53,7 +57,7 @@ class RpcRequestTest { "count=exact" ), sut.prefer ) - assertEquals("", sut.schema) + assertEquals("mySchema", sut.schema) assertEquals(mapOf("Key1" to "Value1"), sut.urlParams) assertEquals(JsonArray(listOf()), sut.body) } @@ -65,6 +69,7 @@ class RpcRequestTest { count = null, body = JsonArray(listOf()), urlParams = mapOf("Key1" to "Value1"), + schema = "mySchema" ) val count = (sut as RpcRequest).count @@ -74,7 +79,7 @@ class RpcRequestTest { listOf( ), sut.prefer ) - assertEquals("", sut.schema) + assertEquals("mySchema", sut.schema) assertEquals(mapOf("Key1" to "Value1"), sut.urlParams) assertEquals(JsonArray(listOf()), sut.body) } @@ -86,6 +91,7 @@ class RpcRequestTest { count = null, body = null, urlParams = mapOf("Key1" to "Value1"), + schema = "mySchema" ) assertEquals("HEAD", sut.method.value) @@ -93,7 +99,7 @@ class RpcRequestTest { listOf( ), sut.prefer ) - assertEquals("", sut.schema) + assertEquals("mySchema", sut.schema) assertEquals(mapOf("Key1" to "Value1"), sut.urlParams) assertNull(sut.body) } @@ -104,6 +110,7 @@ class RpcRequestTest { count = null, body = JsonArray(listOf()), urlParams = mapOf("Key1" to "Value1"), + schema = "mySchema" ) assertEquals("POST", sut.method.value) @@ -111,7 +118,7 @@ class RpcRequestTest { listOf( ), sut.prefer ) - assertEquals("", sut.schema) + assertEquals("mySchema", sut.schema) assertEquals(mapOf("Key1" to "Value1"), sut.urlParams) assertEquals(JsonArray(listOf()), sut.body) } diff --git a/Postgrest/src/commonTest/kotlin/io.github.jan.supabase.postgrest/request/SelectRequestTest.kt b/Postgrest/src/commonTest/kotlin/request/SelectRequestTest.kt similarity index 94% rename from Postgrest/src/commonTest/kotlin/io.github.jan.supabase.postgrest/request/SelectRequestTest.kt rename to Postgrest/src/commonTest/kotlin/request/SelectRequestTest.kt index 8b2fe96a9..1effbc044 100644 --- a/Postgrest/src/commonTest/kotlin/io.github.jan.supabase.postgrest/request/SelectRequestTest.kt +++ b/Postgrest/src/commonTest/kotlin/request/SelectRequestTest.kt @@ -1,6 +1,8 @@ -package io.github.jan.supabase.postgrest.request +package request import io.github.jan.supabase.postgrest.query.Count +import io.github.jan.supabase.postgrest.request.PostgrestRequest +import io.github.jan.supabase.postgrest.request.SelectRequest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse diff --git a/Postgrest/src/commonTest/kotlin/io.github.jan.supabase.postgrest/request/UpdateRequestTest.kt b/Postgrest/src/commonTest/kotlin/request/UpdateRequestTest.kt similarity index 91% rename from Postgrest/src/commonTest/kotlin/io.github.jan.supabase.postgrest/request/UpdateRequestTest.kt rename to Postgrest/src/commonTest/kotlin/request/UpdateRequestTest.kt index e6d398ca3..a46cb0b3e 100644 --- a/Postgrest/src/commonTest/kotlin/io.github.jan.supabase.postgrest/request/UpdateRequestTest.kt +++ b/Postgrest/src/commonTest/kotlin/request/UpdateRequestTest.kt @@ -1,7 +1,9 @@ -package io.github.jan.supabase.postgrest.request +package request import io.github.jan.supabase.postgrest.query.Count import io.github.jan.supabase.postgrest.query.Returning +import io.github.jan.supabase.postgrest.request.PostgrestRequest +import io.github.jan.supabase.postgrest.request.UpdateRequest import kotlinx.serialization.json.JsonArray import kotlin.test.Test import kotlin.test.assertEquals diff --git a/Postgrest/src/wasmJsMain/kotlin/io/github/jan/supabase/postgrest/getColumnName.kt b/Postgrest/src/wasmJsMain/kotlin/io/github/jan/supabase/postgrest/getColumnName.kt new file mode 100644 index 000000000..f732884bb --- /dev/null +++ b/Postgrest/src/wasmJsMain/kotlin/io/github/jan/supabase/postgrest/getColumnName.kt @@ -0,0 +1,7 @@ +package io.github.jan.supabase.postgrest + +import io.github.jan.supabase.annotations.SupabaseInternal +import kotlin.reflect.KProperty1 + +@SupabaseInternal +actual fun getSerialName(property: KProperty1) = property.name diff --git a/README.md b/README.md index 89bc5b701..5919bf68a 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,12 @@ For information about supported Kotlin targets, see the corresponding module REA [Migrating from version 1.4.X to 2.0.0](/MIGRATION.md) -*Note: [WASM](https://github.com/supabase-community/supabase-kt/issues/86) build available: [2.5.4-wasm0](https://github.com/supabase-community/supabase-kt/releases/tag/2.5.4-wasm0)* +*Note: The `WASM-JS` target for supported modules is only available for version 3.0.0 and above* [![](https://img.shields.io/github/release/supabase-community/supabase-kt?label=stable)](https://github.com/supabase-community/supabase-kt/releases) [![](https://badgen.net/github/release/supabase-community/supabase-kt?label=prerelease)](https://central.sonatype.com/search?q=io.github.jan.supabase&smo=true) -[![Kotlin](https://img.shields.io/badge/kotlin-2.0.10-blue.svg?logo=kotlin)](http://kotlinlang.org) -![https://img.shields.io/badge/ktor-2.3.12-blue](https://img.shields.io/badge/ktor-2.3.12-blue) +[![Kotlin](https://img.shields.io/badge/kotlin-2.0.20-blue.svg?logo=kotlin)](http://kotlinlang.org) +[![Ktor](https://img.shields.io/badge/ktor-3.0.0--rc--1-blue)](https://ktor.io/) [![slack](https://img.shields.io/badge/slack-%23supabase--kt-purple.svg?logo=slack)](https://kotlinlang.slack.com/archives/C06QXPC7064) ### Links @@ -34,7 +34,9 @@ For information about supported Kotlin targets, see the corresponding module REA ### Add one or more modules to your project -**Available modules**: `gotrue-kt`, `postgrest-kt`, `functions-kt`, `storage-kt`, `realtime-kt`, `apollo-graphql`, `compose-auth`, `compose-auth-ui`, `coil-integration`, `imageloader-integration` +**Available modules**: `gotrue-kt`*, `postgrest-kt`, `functions-kt`, +`storage-kt`, `realtime-kt`, `apollo-graphql`, `compose-auth`, +`compose-auth-ui`, `coil-integration`, `coil3-integration`, `imageloader-integration` ```kotlin dependencies { @@ -42,6 +44,8 @@ dependencies { } ``` +\* After version 3.0.0, the module will be called `auth-kt`. + If you use multiple modules, you can use the bom dependency to get the correct versions for all modules: @@ -94,11 +98,15 @@ val iosMain by getting { **Note:** It is recommended to use the same Ktor version as supabase-kt: -![https://img.shields.io/badge/ktor-2.3.12-blue](https://img.shields.io/badge/ktor-2.3.12-blue) +__For 3.0.0 and above:__ +[![Ktor](https://img.shields.io/badge/ktor-3.0.0--rc--1-blue)](https://ktor.io/) + +__For versions below 3.0.0:__ +[![Ktor](https://img.shields.io/badge/ktor-2.3.12-blue)](https://ktor.io/) ## Main Modules -- [Authentication](/GoTrue) +- [Authentication](/GoTrue) - [Database/Postgrest](/Postgrest) - [Storage](/Storage) - [Realtime](/Realtime) @@ -109,7 +117,8 @@ val iosMain by getting { - [Apollo GraphQL integration](/plugins/ApolloGraphQL) - Creates an [Apollo GraphQL Client](https://github.com/apollographql/apollo-kotlin) for interacting with the Supabase API. - [Compose Auth](/plugins/ComposeAuth) - Provides easy Native Google & Apple Auth for Compose Multiplatform targets. - [Compose Auth UI](/plugins/ComposeAuthUI) - Provides UI Components for Compose Multiplatform. -- [Coil Integration](/plugins/CoilIntegration) - Provides a [Coil](https://github.com/coil-kt/coil) Integration for displaying images stored in Supabase Storage. +- [Coil Integration](/plugins/CoilIntegration) - Provides a [Coil2](https://github.com/coil-kt/coil) Integration for displaying images stored in Supabase Storage. Only supports Android. +- [Coil3 Integration](/plugins/Coil3Integration) - Provides a [Coil3](https://github.com/coil-kt/coil) Integration for displaying images stored in Supabase Storage. Supports all Compose Multiplatform targets. - [Compose-ImageLoader Integration](/plugins/ImageLoaderIntegration) - Provides a [Compose ImageLoader](https://github.com/qdsfdhvh/compose-imageloader) Integration for displaying images stored in Supabase Storage. ### Miscellaneous diff --git a/Realtime/README.md b/Realtime/README.md index bf1c2983d..3867f894d 100644 --- a/Realtime/README.md +++ b/Realtime/README.md @@ -4,23 +4,27 @@ Extends Supabase-kt with a multiplatform Realtime client. Supported targets: -| Target | **JVM** | **Android** | **JS** | **iOS** | **tvOS** | **watchOS** | **macOS** | **Windows** | **Linux** | -| ------ | ------- | ----------- | ------ | ------- | -------- | ----------- | --------- | ----------- | --------- | -| | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Target | **JVM** | **Android** | **JS** | **Wasm** | **Apple** | **Windows** | **Linux** | +|--------|---------|-------------|--------|----------|-----------|-------------|-----------| +| Status | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
In-depth Kotlin targets -**iOS:** iosArm64, iosSimulatorArm64, iosX64 - **JS**: Browser, NodeJS -**tvOS**: tvosArm64, tvosX64, tvosSimulatorArm64 +**Wasm**: wasm-js + +**Apple:** + +- iOS: iosArm64, iosSimulatorArm64, iosX64 + +- tvOS: tvosArm64, tvosX64, tvosSimulatorArm64 -**watchOS**: watchosArm64, watchosX64, watchosSimulatorArm64 +- watchOS: watchosArm64, watchosX64, watchosSimulatorArm64 -**MacOS**: macosX64, macosArm64 +- MacOS: macosX64, macosArm64 **Windows**: mingwX64 diff --git a/Realtime/build.gradle.kts b/Realtime/build.gradle.kts index ecfef9ab2..075fff6d1 100644 --- a/Realtime/build.gradle.kts +++ b/Realtime/build.gradle.kts @@ -16,7 +16,7 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - addModules(SupabaseModule.GOTRUE) + addModules(SupabaseModule.AUTH) api(project(":postgrest-kt")) api(libs.ktor.client.websockets) } diff --git a/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/PostgresChangeFilter.kt b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/PostgresChangeFilter.kt index 4d5f42fa8..8b94dac8d 100644 --- a/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/PostgresChangeFilter.kt +++ b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/PostgresChangeFilter.kt @@ -7,7 +7,6 @@ import io.github.jan.supabase.postgrest.query.filter.FilterOperator /** * Used to filter postgres changes */ -@Suppress("DEPRECATION") class PostgresChangeFilter(private val event: String, private val schema: String) { /** @@ -20,7 +19,7 @@ class PostgresChangeFilter(private val event: String, private val schema: String * E.g.: "user_id=eq.1" */ var filter: String? = null - @Deprecated("Use the new `filter` method instead") set + private set /** * Filters the received changes in your table. diff --git a/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/Realtime.kt b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/Realtime.kt index 37767e7fe..6ea5aa950 100644 --- a/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/Realtime.kt +++ b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/Realtime.kt @@ -67,10 +67,7 @@ sealed interface Realtime : MainPlugin, CustomSerializationPlug fun disconnect() @SupabaseInternal - fun RealtimeChannel.addChannel(channel: RealtimeChannel) - - @SupabaseInternal - fun RealtimeChannel.deleteChannel(channel: RealtimeChannel) + fun Realtime.addChannel(channel: RealtimeChannel) /** * Unsubscribes and removes a channel from the [subscriptions] @@ -152,8 +149,19 @@ sealed interface Realtime : MainPlugin, CustomSerializationPlug * The current status of the realtime connection */ enum class Status { + /** + * [Realtime] is disconnected from Supabase Realtime + */ DISCONNECTED, + + /** + * [Realtime] is connecting to Supabase Realtime + */ CONNECTING, + + /** + * [Realtime] is connected to Supabase Realtime + */ CONNECTED, } diff --git a/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/RealtimeChannel.kt b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/RealtimeChannel.kt index 7212a1662..2c461805f 100644 --- a/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/RealtimeChannel.kt +++ b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/RealtimeChannel.kt @@ -2,15 +2,14 @@ package io.github.jan.supabase.realtime import io.github.jan.supabase.SupabaseClient import io.github.jan.supabase.annotations.SupabaseInternal -import io.github.jan.supabase.decode import io.github.jan.supabase.encodeToJsonElement -import io.github.jan.supabase.logging.e -import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.callbackFlow import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonObject +import kotlin.reflect.KClass +import kotlin.reflect.KType +import kotlin.reflect.typeOf /** * Represents a realtime channel @@ -32,6 +31,11 @@ sealed interface RealtimeChannel { */ val supabaseClient: SupabaseClient + /** + * The realtime instance + */ + val realtime: Realtime + @SupabaseInternal val callbackManager: CallbackManager @@ -70,22 +74,70 @@ sealed interface RealtimeChannel { */ suspend fun untrack() + /** + * Non-inline variant of [postgresChangeFlow] for implementation and mocking purposes + */ + @SupabaseInternal + fun RealtimeChannel.postgresChangeFlowInternal(action: KClass, schema: String, filter: PostgresChangeFilter.() -> Unit = {}): Flow + + /** + * Non-inline variant of [broadcastFlow] for implementation and mocking purposes + */ + @SupabaseInternal + fun RealtimeChannel.broadcastFlowInternal(type: KType, event: String): Flow + + /** + * Listen for clients joining / leaving the channel using presences + * + * Example: + * ```kotlin + * val presenceChangeFlow = channel.presenceChangeFlow() + * + * presenceChangeFlow.collect { + * val joins = it.decodeJoinsAs() + * val leaves = it.decodeLeavesAs() + * } + * ``` + */ + fun presenceChangeFlow(): Flow + @SupabaseInternal fun RealtimeChannel.addPostgresChange(data: PostgresJoinConfig) @SupabaseInternal fun RealtimeChannel.removePostgresChange(data: PostgresJoinConfig) + @SupabaseInternal + fun updateStatus(status: Status) + /** * Represents the status of a channel */ enum class Status { + /** + * The [RealtimeChannel] is currently unsubscribed + */ UNSUBSCRIBED, + + /** + * The [RealtimeChannel] is currently in the process of subscribing + */ SUBSCRIBING, + + /** + * The [RealtimeChannel] is subscribed + */ SUBSCRIBED, + + /** + * The [RealtimeChannel] is in the process of unsubscribing + */ UNSUBSCRIBING, } + /** + * @see RealtimeChannel + */ @Suppress("UndocumentedPublicProperty") companion object { const val CHANNEL_EVENT_JOIN = "phx_join" @@ -104,31 +156,6 @@ sealed interface RealtimeChannel { } -/** - * Listen for clients joining / leaving the channel using presences - * - * Example: - * ```kotlin - * val presenceChangeFlow = channel.presenceChangeFlow() - * - * presenceChangeFlow.collect { - * val joins = it.decodeJoinsAs() - * val leaves = it.decodeLeavesAs() - * } - * ``` - */ -@OptIn(SupabaseInternal::class) -fun RealtimeChannel.presenceChangeFlow(): Flow { - return callbackFlow { - val callback: (PresenceAction) -> Unit = { action -> - trySend(action) - } - - val id = callbackManager.addPresenceCallback(callback) - awaitClose { callbackManager.removeCallbackById(id) } - } -} - /** * Listen for postgres changes in a channel. * @@ -144,34 +171,10 @@ fun RealtimeChannel.presenceChangeFlow(): Flow { * @param T The event type you want to listen to (e.g. [PostgresAction.Update] for updates or only [PostgresAction] for all) * @param schema The schema name of the table that is being monitored. For normal supabase tables that might be "public". */ -@OptIn(SupabaseInternal::class) -inline fun RealtimeChannel.postgresChangeFlow(schema: String, filter: PostgresChangeFilter.() -> Unit = {}): Flow { - if(status.value == RealtimeChannel.Status.SUBSCRIBED) error("You cannot call postgresChangeFlow after joining the channel") - val event = when(T::class) { - PostgresAction.Insert::class -> "INSERT" - PostgresAction.Update::class -> "UPDATE" - PostgresAction.Delete::class -> "DELETE" - PostgresAction.Select::class -> "SELECT" - PostgresAction::class -> "*" - else -> error("Unknown event type ${T::class}") - } - val postgrestBuilder = PostgresChangeFilter(event, schema).apply(filter) - val config = postgrestBuilder.buildConfig() - addPostgresChange(config) - return callbackFlow { - val callback: (PostgresAction) -> Unit = { - if (it is T) { - trySend(it) - } - } - - val id = callbackManager.addPostgresCallback(config, callback) - awaitClose { - callbackManager.removeCallbackById(id) - removePostgresChange(config) - } - } -} +inline fun RealtimeChannel.postgresChangeFlow( + schema: String, + noinline filter: PostgresChangeFilter.() -> Unit = {} +): Flow = postgresChangeFlowInternal(T::class, schema, filter) /** * Broadcasts can be messages sent by other clients within the same channel under a specific [event]. @@ -187,18 +190,7 @@ inline fun RealtimeChannel.postgresChangeFlow(schem * @param event When a message is sent by another client, it will be sent under a specific event. This is the event that you want to listen to */ @OptIn(SupabaseInternal::class) -inline fun RealtimeChannel.broadcastFlow(event: String): Flow = callbackFlow { - val id = callbackManager.addBroadcastCallback(event) { - val decodedValue = try { - supabaseClient.realtime.serializer.decode(it.toString()) - } catch(e: Exception) { - Realtime.logger.e(e) { "Couldn't decode $this as ${T::class.simpleName}. The corresponding handler wasn't called" } - null - } - decodedValue?.let { value -> trySend(value) } - } - awaitClose { callbackManager.removeCallbackById(id) } -} +inline fun RealtimeChannel.broadcastFlow(event: String): Flow = broadcastFlowInternal(typeOf(), event) /** * Sends a message to everyone who joined the channel. Can be used even if you aren't connected to the channel. diff --git a/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/RealtimeChannelImpl.kt b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/RealtimeChannelImpl.kt index 6b1c171e3..27bdca12a 100644 --- a/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/RealtimeChannelImpl.kt +++ b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/RealtimeChannelImpl.kt @@ -1,33 +1,31 @@ package io.github.jan.supabase.realtime import io.github.jan.supabase.annotations.SupabaseInternal +import io.github.jan.supabase.auth.resolveAccessToken import io.github.jan.supabase.collections.AtomicMutableList -import io.github.jan.supabase.decodeIfNotEmptyOrDefault -import io.github.jan.supabase.gotrue.resolveAccessToken import io.github.jan.supabase.logging.d import io.github.jan.supabase.logging.e -import io.github.jan.supabase.logging.w import io.github.jan.supabase.putJsonObject import io.github.jan.supabase.realtime.data.BroadcastApiBody import io.github.jan.supabase.realtime.data.BroadcastApiMessage -import io.github.jan.supabase.realtime.data.PostgresActionData -import io.github.jan.supabase.supabaseJson +import io.github.jan.supabase.realtime.event.RealtimeEvent import io.ktor.client.statement.bodyAsText import io.ktor.http.headers +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.first import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.decodeFromJsonElement import kotlinx.serialization.json.encodeToJsonElement -import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import kotlinx.serialization.json.longOrNull import kotlinx.serialization.json.put import kotlinx.serialization.json.putJsonObject +import kotlin.reflect.KClass +import kotlin.reflect.KType internal class RealtimeChannelImpl( private val realtimeImpl: RealtimeImpl, @@ -42,6 +40,7 @@ internal class RealtimeChannelImpl( override val callbackManager = CallbackManagerImpl(realtimeImpl.serializer) private val _status = MutableStateFlow(RealtimeChannel.Status.UNSUBSCRIBED) override val status = _status.asStateFlow() + override val realtime: Realtime = realtimeImpl override val supabaseClient = realtimeImpl.supabaseClient @@ -79,70 +78,13 @@ internal class RealtimeChannelImpl( } @OptIn(SupabaseInternal::class) - fun onMessage(message: RealtimeMessage) { - if(message.eventType == null) { - Realtime.logger.e { "Received message without event type: $message" } + suspend fun onMessage(message: RealtimeMessage) { + val event = RealtimeEvent.resolveEvent(message) + if(event == null) { + Realtime.logger.e { "Received message without event: $message" } return } - when(message.eventType) { - RealtimeMessage.EventType.TOKEN_EXPIRED -> { - Realtime.logger.w { "Received token expired event. This should not happen, please report this warning." } - } - RealtimeMessage.EventType.SYSTEM -> { - Realtime.logger.d { "Subscribed to channel ${message.topic}" } - _status.value = RealtimeChannel.Status.SUBSCRIBED - } - RealtimeMessage.EventType.SYSTEM_REPLY -> { - Realtime.logger.d { "Received system reply: ${message.payload}." } - if(status.value == RealtimeChannel.Status.UNSUBSCRIBING) { - _status.value = RealtimeChannel.Status.UNSUBSCRIBED - Realtime.logger.d { "Unsubscribed from channel ${message.topic}" } - } - } - RealtimeMessage.EventType.POSTGRES_SERVER_CHANGES -> { //check if the server postgres_changes match with the client's and add the given id to the postgres change objects (to identify them later in the events) - val serverPostgresChanges = message.payload["response"]?.jsonObject?.get("postgres_changes")?.jsonArray?.let { Json.decodeFromJsonElement>(it) } ?: listOf() //server postgres changes - callbackManager.setServerChanges(serverPostgresChanges) - if(status.value != RealtimeChannel.Status.SUBSCRIBED) { - Realtime.logger.d { "Joined channel ${message.topic}" } - _status.value = RealtimeChannel.Status.SUBSCRIBED - } - } - RealtimeMessage.EventType.POSTGRES_CHANGES -> { - val data = message.payload["data"]?.jsonObject ?: return - val ids = message.payload["ids"]?.jsonArray?.mapNotNull { it.jsonPrimitive.longOrNull } ?: emptyList() //the ids of the matching postgres changes - val postgresAction = supabaseJson.decodeFromJsonElement(data) - val action = when(data["type"]?.jsonPrimitive?.content ?: "") { - "UPDATE" -> PostgresAction.Update(postgresAction.record ?: error("Received no record on update event"), postgresAction.oldRecord ?: error("Received no old record on update event"), postgresAction.columns, postgresAction.commitTimestamp, realtimeImpl.serializer) - "DELETE" -> PostgresAction.Delete(postgresAction.oldRecord ?: error("Received no old record on delete event"), postgresAction.columns, postgresAction.commitTimestamp, realtimeImpl.serializer) - "INSERT" -> PostgresAction.Insert(postgresAction.record ?: error("Received no record on update event"), postgresAction.columns, postgresAction.commitTimestamp, realtimeImpl.serializer) - "SELECT" -> PostgresAction.Select(postgresAction.record ?: error("Received no record on update event"), postgresAction.columns, postgresAction.commitTimestamp, realtimeImpl.serializer) - else -> error("Unknown event type ${message.event}") - } - callbackManager.triggerPostgresChange(ids, action) - } - RealtimeMessage.EventType.BROADCAST -> { - val event = message.payload["event"]?.jsonPrimitive?.content ?: "" - callbackManager.triggerBroadcast(event, message.payload["payload"]?.jsonObject ?: JsonObject(mutableMapOf())) - } - RealtimeMessage.EventType.CLOSE -> { - realtimeImpl.run { - deleteChannel(this@RealtimeChannelImpl) - } - Realtime.logger.d { "Unsubscribed from channel ${message.topic}" } - } - RealtimeMessage.EventType.ERROR -> { - Realtime.logger.e { "Received an error in channel ${message.topic}. That could be as a result of an invalid access token" } - } - RealtimeMessage.EventType.PRESENCE_DIFF -> { - val joins = message.payload["joins"]?.jsonObject?.decodeIfNotEmptyOrDefault(mapOf()) ?: emptyMap() - val leaves = message.payload["leaves"]?.jsonObject?.decodeIfNotEmptyOrDefault(mapOf()) ?: emptyMap() - callbackManager.triggerPresenceDiff(joins, leaves) - } - RealtimeMessage.EventType.PRESENCE_STATE -> { - val joins = message.payload.decodeIfNotEmptyOrDefault(mapOf()) - callbackManager.triggerPresenceDiff(joins, mapOf()) - } - } + event.handle(this, message) } override suspend fun unsubscribe() { @@ -218,5 +160,63 @@ internal class RealtimeChannelImpl( ) } + @Suppress("UNCHECKED_CAST") + override fun RealtimeChannel.postgresChangeFlowInternal( + action: KClass, + schema: String, + filter: PostgresChangeFilter.() -> Unit + ): Flow { + if(status.value == RealtimeChannel.Status.SUBSCRIBED) error("You cannot call postgresChangeFlow after joining the channel") + val event = when(action) { + PostgresAction.Insert::class -> "INSERT" + PostgresAction.Update::class -> "UPDATE" + PostgresAction.Delete::class -> "DELETE" + PostgresAction.Select::class -> "SELECT" + PostgresAction::class -> "*" + else -> error("Unknown event type $action") + } + val postgrestBuilder = PostgresChangeFilter(event, schema).apply(filter) + val config = postgrestBuilder.buildConfig() + addPostgresChange(config) + return callbackFlow { + val callback: (PostgresAction) -> Unit = { + if (action.isInstance(it)) { + trySend(it as T) + } + } + + val id = callbackManager.addPostgresCallback(config, callback) + awaitClose { + callbackManager.removeCallbackById(id) + removePostgresChange(config) + } + } + } + + override fun RealtimeChannel.broadcastFlowInternal(type: KType, event: String): Flow = callbackFlow { + val id = callbackManager.addBroadcastCallback(event) { + val decodedValue = try { + supabaseClient.realtime.serializer.decode(type, it.toString()) + } catch(e: Exception) { + Realtime.logger.e(e) { "Couldn't decode $it as $type. The corresponding handler wasn't called" } + null + } + decodedValue?.let { value -> trySend(value) } + } + awaitClose { callbackManager.removeCallbackById(id) } + } + + override fun presenceChangeFlow(): Flow = callbackFlow { + val callback: (PresenceAction) -> Unit = { action -> + trySend(action) + } + val id = callbackManager.addPresenceCallback(callback) + awaitClose { callbackManager.removeCallbackById(id) } + } + + override fun updateStatus(status: RealtimeChannel.Status) { + _status.value = status + } + } diff --git a/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/RealtimeExt.kt b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/RealtimeExt.kt index d43ef41f8..39a1d115d 100644 --- a/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/RealtimeExt.kt +++ b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/RealtimeExt.kt @@ -30,7 +30,7 @@ internal fun List>.producer(data: Data): String = /** * Listens for presence changes and caches the presences based on their keys. This function automatically handles joins and leaves. * - * If you want more control, use the [presenceChangeFlow] function. + * If you want more control, use the [RealtimeChannel.presenceChangeFlow] function. * @return a [Flow] of the current presences in a list. This list is updated and emitted whenever a presence joins or leaves. */ inline fun RealtimeChannel.presenceDataFlow(): Flow> { diff --git a/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/RealtimeImpl.kt b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/RealtimeImpl.kt index 66b4d560e..816d362a7 100644 --- a/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/RealtimeImpl.kt +++ b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/RealtimeImpl.kt @@ -2,43 +2,35 @@ package io.github.jan.supabase.realtime import io.github.jan.supabase.SupabaseClient import io.github.jan.supabase.annotations.SupabaseInternal +import io.github.jan.supabase.auth.Auth +import io.github.jan.supabase.auth.SessionStatus import io.github.jan.supabase.buildUrl import io.github.jan.supabase.collections.AtomicMutableMap import io.github.jan.supabase.exceptions.RestException import io.github.jan.supabase.exceptions.UnknownRestException -import io.github.jan.supabase.gotrue.Auth -import io.github.jan.supabase.gotrue.SessionStatus import io.github.jan.supabase.logging.d import io.github.jan.supabase.logging.e import io.github.jan.supabase.logging.i import io.github.jan.supabase.logging.w import io.github.jan.supabase.realtime.websocket.KtorRealtimeWebsocketFactory import io.github.jan.supabase.realtime.websocket.RealtimeWebsocket -import io.github.jan.supabase.supabaseJson -import io.ktor.client.plugins.websocket.DefaultClientWebSocketSession -import io.ktor.client.plugins.websocket.sendSerialized import io.ktor.client.statement.HttpResponse import io.ktor.http.URLProtocol import io.ktor.http.path -import io.ktor.websocket.Frame -import io.ktor.websocket.readText import kotlinx.atomicfu.atomic import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.isActive -import kotlinx.coroutines.job import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.serialization.json.buildJsonObject -import kotlin.time.Duration.Companion.milliseconds @PublishedApi internal class RealtimeImpl(override val supabaseClient: SupabaseClient, override val config: Realtime.Config) : Realtime { @@ -163,7 +155,7 @@ import kotlin.time.Duration.Companion.milliseconds _status.value = Realtime.Status.DISCONNECTED } - private fun onMessage(message: RealtimeMessage) { + private suspend fun onMessage(message: RealtimeMessage) { Realtime.logger.d { "Received message $message" } val channel = subscriptions[message.topic] as? RealtimeChannelImpl if(message.ref?.toIntOrNull() == heartbeatRef) { @@ -208,11 +200,6 @@ import kotlin.time.Duration.Companion.milliseconds } } - @SupabaseInternal - override fun RealtimeChannel.deleteChannel(channel: RealtimeChannel) { - _subscriptions.remove(channel.topic) - } - override suspend fun removeAllChannels() { _subscriptions.forEach { (_, it) -> if(it.status.value == RealtimeChannel.Status.SUBSCRIBED) { @@ -227,7 +214,7 @@ import kotlin.time.Duration.Companion.milliseconds } @SupabaseInternal - override fun RealtimeChannel.addChannel(channel: RealtimeChannel) { + override fun Realtime.addChannel(channel: RealtimeChannel) { _subscriptions[channel.topic] = channel } diff --git a/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/RealtimeMessage.kt b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/RealtimeMessage.kt index 6832f15af..379017d35 100644 --- a/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/RealtimeMessage.kt +++ b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/RealtimeMessage.kt @@ -2,36 +2,11 @@ package io.github.jan.supabase.realtime import io.github.jan.supabase.annotations.SupabaseInternal import kotlinx.serialization.Serializable -import kotlinx.serialization.Transient import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive /** * Represents a message retrieved by the [RealtimeChannel] */ @Serializable @SupabaseInternal -data class RealtimeMessage(val topic: String, val event: String, val payload: JsonObject, val ref: String?) { - - @Transient - val eventType: EventType? = when { - event == RealtimeChannel.CHANNEL_EVENT_SYSTEM && payload["status"]?.jsonPrimitive?.content == "ok" -> EventType.SYSTEM - event == RealtimeChannel.CHANNEL_EVENT_REPLY && payload["response"]?.jsonObject?.containsKey(RealtimeChannel.CHANNEL_EVENT_POSTGRES_CHANGES) ?: false -> EventType.POSTGRES_SERVER_CHANGES - event == RealtimeChannel.CHANNEL_EVENT_REPLY && payload["status"]?.jsonPrimitive?.content == "ok" -> EventType.SYSTEM_REPLY - event == RealtimeChannel.CHANNEL_EVENT_POSTGRES_CHANGES -> EventType.POSTGRES_CHANGES - event == RealtimeChannel.CHANNEL_EVENT_BROADCAST -> EventType.BROADCAST - event == RealtimeChannel.CHANNEL_EVENT_CLOSE -> EventType.CLOSE - event == RealtimeChannel.CHANNEL_EVENT_ERROR -> EventType.ERROR - event == RealtimeChannel.CHANNEL_EVENT_PRESENCE_DIFF -> EventType.PRESENCE_DIFF - event == RealtimeChannel.CHANNEL_EVENT_PRESENCE_STATE -> EventType.PRESENCE_STATE - event == RealtimeChannel.CHANNEL_EVENT_SYSTEM && payload["message"]?.jsonPrimitive?.content?.contains("access token has expired") ?: false -> EventType.TOKEN_EXPIRED - else -> null - } - - enum class EventType { - SYSTEM, SYSTEM_REPLY, POSTGRES_SERVER_CHANGES, POSTGRES_CHANGES, BROADCAST, CLOSE, ERROR, PRESENCE_DIFF, PRESENCE_STATE, TOKEN_EXPIRED - } - -} - +data class RealtimeMessage(val topic: String, val event: String, val payload: JsonObject, val ref: String?) \ No newline at end of file diff --git a/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RBroadcastEvent.kt b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RBroadcastEvent.kt new file mode 100644 index 000000000..e163eda1b --- /dev/null +++ b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RBroadcastEvent.kt @@ -0,0 +1,23 @@ +package io.github.jan.supabase.realtime.event + +import io.github.jan.supabase.realtime.RealtimeChannel +import io.github.jan.supabase.realtime.RealtimeMessage +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive + +/** + * Handles broadcast events + */ +data object RBroadcastEvent : RealtimeEvent { + + override suspend fun handle(channel: RealtimeChannel, message: RealtimeMessage) { + val event = message.payload["event"]?.jsonPrimitive?.content ?: "" + channel.callbackManager.triggerBroadcast(event, message.payload["payload"]?.jsonObject ?: JsonObject(mutableMapOf())) + } + + override fun appliesTo(message: RealtimeMessage): Boolean { + return message.event == RealtimeChannel.CHANNEL_EVENT_BROADCAST + } + +} \ No newline at end of file diff --git a/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RCloseEvent.kt b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RCloseEvent.kt new file mode 100644 index 000000000..1ef7d52c8 --- /dev/null +++ b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RCloseEvent.kt @@ -0,0 +1,22 @@ +package io.github.jan.supabase.realtime.event + +import io.github.jan.supabase.logging.d +import io.github.jan.supabase.realtime.Realtime +import io.github.jan.supabase.realtime.RealtimeChannel +import io.github.jan.supabase.realtime.RealtimeMessage + +/** + * Event that handles the closing of a channel + */ +data object RCloseEvent : RealtimeEvent { + + override suspend fun handle(channel: RealtimeChannel, message: RealtimeMessage) { + channel.realtime.removeChannel(channel) + Realtime.logger.d { "Unsubscribed from channel ${message.topic}" } + } + + override fun appliesTo(message: RealtimeMessage): Boolean { + return message.event == RealtimeChannel.CHANNEL_EVENT_CLOSE + } + +} \ No newline at end of file diff --git a/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RErrorEvent.kt b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RErrorEvent.kt new file mode 100644 index 000000000..1a926475b --- /dev/null +++ b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RErrorEvent.kt @@ -0,0 +1,21 @@ +package io.github.jan.supabase.realtime.event + +import io.github.jan.supabase.logging.e +import io.github.jan.supabase.realtime.Realtime +import io.github.jan.supabase.realtime.RealtimeChannel +import io.github.jan.supabase.realtime.RealtimeMessage + +/** + * Event that handles an error event + */ +data object RErrorEvent : RealtimeEvent { + + override suspend fun handle(channel: RealtimeChannel, message: RealtimeMessage) { + Realtime.logger.e { "Received an error in channel ${message.topic}. That could be as a result of an invalid access token" } + } + + override fun appliesTo(message: RealtimeMessage): Boolean { + return message.event == RealtimeChannel.CHANNEL_EVENT_ERROR + } + +} \ No newline at end of file diff --git a/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RPostgresChangesEvent.kt b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RPostgresChangesEvent.kt new file mode 100644 index 000000000..ea318ba9b --- /dev/null +++ b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RPostgresChangesEvent.kt @@ -0,0 +1,59 @@ +package io.github.jan.supabase.realtime.event + +import io.github.jan.supabase.realtime.PostgresAction +import io.github.jan.supabase.realtime.RealtimeChannel +import io.github.jan.supabase.realtime.RealtimeMessage +import io.github.jan.supabase.realtime.data.PostgresActionData +import io.github.jan.supabase.realtime.realtime +import io.github.jan.supabase.supabaseJson +import kotlinx.serialization.json.decodeFromJsonElement +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.longOrNull + +/** + * Handles postgres changes events + */ +data object RPostgresChangesEvent : RealtimeEvent { + + override suspend fun handle(channel: RealtimeChannel, message: RealtimeMessage) { + val data = message.payload["data"]?.jsonObject ?: return + val ids = message.payload["ids"]?.jsonArray?.mapNotNull { it.jsonPrimitive.longOrNull } ?: emptyList() //the ids of the matching postgres changes + val postgresAction = supabaseJson.decodeFromJsonElement(data) + val action = when(data["type"]?.jsonPrimitive?.content ?: "") { + "UPDATE" -> PostgresAction.Update( + postgresAction.record ?: error("Received no record on update event"), + postgresAction.oldRecord ?: error("Received no old record on update event"), + postgresAction.columns, + postgresAction.commitTimestamp, + channel.supabaseClient.realtime.serializer + ) + "DELETE" -> PostgresAction.Delete( + postgresAction.oldRecord ?: error("Received no old record on delete event"), + postgresAction.columns, + postgresAction.commitTimestamp, + channel.supabaseClient.realtime.serializer + ) + "INSERT" -> PostgresAction.Insert( + postgresAction.record ?: error("Received no record on update event"), + postgresAction.columns, + postgresAction.commitTimestamp, + channel.supabaseClient.realtime.serializer + ) + "SELECT" -> PostgresAction.Select( + postgresAction.record ?: error("Received no record on update event"), + postgresAction.columns, + postgresAction.commitTimestamp, + channel.supabaseClient.realtime.serializer + ) + else -> error("Unknown event type ${message.event}") + } + channel.callbackManager.triggerPostgresChange(ids, action) + } + + override fun appliesTo(message: RealtimeMessage): Boolean { + return message.event == RealtimeChannel.CHANNEL_EVENT_POSTGRES_CHANGES + } + +} \ No newline at end of file diff --git a/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RPostgresServerChangesEvent.kt b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RPostgresServerChangesEvent.kt new file mode 100644 index 000000000..0f8834ef2 --- /dev/null +++ b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RPostgresServerChangesEvent.kt @@ -0,0 +1,33 @@ +package io.github.jan.supabase.realtime.event + +import io.github.jan.supabase.logging.d +import io.github.jan.supabase.realtime.PostgresJoinConfig +import io.github.jan.supabase.realtime.Realtime +import io.github.jan.supabase.realtime.RealtimeChannel +import io.github.jan.supabase.realtime.RealtimeMessage +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.decodeFromJsonElement +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject + +/** + * Event that handles the server changes + */ +data object RPostgresServerChangesEvent : RealtimeEvent { + + override suspend fun handle(channel: RealtimeChannel, message: RealtimeMessage) { + val serverPostgresChanges = message.payload["response"]?.jsonObject?.get("postgres_changes")?.jsonArray?.let { Json.decodeFromJsonElement>(it) } ?: listOf() //server postgres changes + channel.callbackManager.setServerChanges(serverPostgresChanges) + if(channel.status.value != RealtimeChannel.Status.SUBSCRIBED) { + Realtime.logger.d { "Joined channel ${message.topic}" } + channel.updateStatus(RealtimeChannel.Status.SUBSCRIBED) + } + } + + override fun appliesTo(message: RealtimeMessage): Boolean { + return message.event == RealtimeChannel.CHANNEL_EVENT_REPLY && message.payload["response"]?.jsonObject?.containsKey( + RealtimeChannel.CHANNEL_EVENT_POSTGRES_CHANGES + ) ?: false + } + +} diff --git a/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RPresenceDiffEvent.kt b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RPresenceDiffEvent.kt new file mode 100644 index 000000000..7d2be3910 --- /dev/null +++ b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RPresenceDiffEvent.kt @@ -0,0 +1,24 @@ +package io.github.jan.supabase.realtime.event + +import io.github.jan.supabase.decodeIfNotEmptyOrDefault +import io.github.jan.supabase.realtime.Presence +import io.github.jan.supabase.realtime.RealtimeChannel +import io.github.jan.supabase.realtime.RealtimeMessage +import kotlinx.serialization.json.jsonObject + +/** + * Event that handles the presence diff event + */ +data object RPresenceDiffEvent : RealtimeEvent { + + override suspend fun handle(channel: RealtimeChannel, message: RealtimeMessage) { + val joins = message.payload["joins"]?.jsonObject?.decodeIfNotEmptyOrDefault(mapOf()) ?: emptyMap() + val leaves = message.payload["leaves"]?.jsonObject?.decodeIfNotEmptyOrDefault(mapOf()) ?: emptyMap() + channel.callbackManager.triggerPresenceDiff(joins, leaves) + } + + override fun appliesTo(message: RealtimeMessage): Boolean { + return message.event == RealtimeChannel.CHANNEL_EVENT_PRESENCE_DIFF + } + +} \ No newline at end of file diff --git a/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RPresenceStateEvent.kt b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RPresenceStateEvent.kt new file mode 100644 index 000000000..53b65dbe1 --- /dev/null +++ b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RPresenceStateEvent.kt @@ -0,0 +1,22 @@ +package io.github.jan.supabase.realtime.event + +import io.github.jan.supabase.decodeIfNotEmptyOrDefault +import io.github.jan.supabase.realtime.Presence +import io.github.jan.supabase.realtime.RealtimeChannel +import io.github.jan.supabase.realtime.RealtimeMessage + +/** + * Event that handles the presence state event + */ +data object RPresenceStateEvent : RealtimeEvent { + + override suspend fun handle(channel: RealtimeChannel, message: RealtimeMessage) { + val joins = message.payload.decodeIfNotEmptyOrDefault(mapOf()) + channel.callbackManager.triggerPresenceDiff(joins, mapOf()) + } + + override fun appliesTo(message: RealtimeMessage): Boolean { + return message.event == RealtimeChannel.CHANNEL_EVENT_PRESENCE_STATE + } + +} \ No newline at end of file diff --git a/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RSystemEvent.kt b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RSystemEvent.kt new file mode 100644 index 000000000..39b2828f0 --- /dev/null +++ b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RSystemEvent.kt @@ -0,0 +1,23 @@ +package io.github.jan.supabase.realtime.event + +import io.github.jan.supabase.logging.d +import io.github.jan.supabase.realtime.Realtime +import io.github.jan.supabase.realtime.RealtimeChannel +import io.github.jan.supabase.realtime.RealtimeMessage +import kotlinx.serialization.json.jsonPrimitive + +/** + * Event that handles the system event + */ +data object RSystemEvent : RealtimeEvent { + + override suspend fun handle(channel: RealtimeChannel, message: RealtimeMessage) { + Realtime.logger.d { "Subscribed to channel ${message.topic}" } + channel.updateStatus(RealtimeChannel.Status.SUBSCRIBED) + } + + override fun appliesTo(message: RealtimeMessage): Boolean { + return message.event == RealtimeChannel.CHANNEL_EVENT_SYSTEM && message.payload["status"]?.jsonPrimitive?.content == "ok" + } + +} diff --git a/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RSystemReplyEvent.kt b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RSystemReplyEvent.kt new file mode 100644 index 000000000..715e85ae4 --- /dev/null +++ b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RSystemReplyEvent.kt @@ -0,0 +1,26 @@ +package io.github.jan.supabase.realtime.event + +import io.github.jan.supabase.logging.d +import io.github.jan.supabase.realtime.Realtime +import io.github.jan.supabase.realtime.RealtimeChannel +import io.github.jan.supabase.realtime.RealtimeMessage +import kotlinx.serialization.json.jsonPrimitive + +/** + * Event that handles the system reply event + */ +data object RSystemReplyEvent : RealtimeEvent { + + override suspend fun handle(channel: RealtimeChannel, message: RealtimeMessage) { + Realtime.logger.d { "Received system reply: ${message.payload}." } + if(channel.status.value == RealtimeChannel.Status.UNSUBSCRIBING) { + channel.updateStatus(RealtimeChannel.Status.UNSUBSCRIBED) + Realtime.logger.d { "Unsubscribed from channel ${message.topic}" } + } + } + + override fun appliesTo(message: RealtimeMessage): Boolean { + return message.event == RealtimeChannel.CHANNEL_EVENT_REPLY && message.payload["status"]?.jsonPrimitive?.content == "ok" + } + +} \ No newline at end of file diff --git a/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RTokenExpiredEvent.kt b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RTokenExpiredEvent.kt new file mode 100644 index 000000000..fd3802c19 --- /dev/null +++ b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RTokenExpiredEvent.kt @@ -0,0 +1,22 @@ +package io.github.jan.supabase.realtime.event + +import io.github.jan.supabase.logging.w +import io.github.jan.supabase.realtime.Realtime +import io.github.jan.supabase.realtime.RealtimeChannel +import io.github.jan.supabase.realtime.RealtimeMessage +import kotlinx.serialization.json.jsonPrimitive + +/** + * Event that handles the token expired event + */ +data object RTokenExpiredEvent : RealtimeEvent { + + override suspend fun handle(channel: RealtimeChannel, message: RealtimeMessage) { + Realtime.logger.w { "Received token expired event. This should not happen, please report this warning." } + } + + override fun appliesTo(message: RealtimeMessage): Boolean { + return message.event == RealtimeChannel.CHANNEL_EVENT_SYSTEM && message.payload["message"]?.jsonPrimitive?.content?.contains("access token has expired") ?: false + } + +} \ No newline at end of file diff --git a/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RealtimeEvent.kt b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RealtimeEvent.kt new file mode 100644 index 000000000..d74311006 --- /dev/null +++ b/Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/event/RealtimeEvent.kt @@ -0,0 +1,47 @@ +package io.github.jan.supabase.realtime.event + +import io.github.jan.supabase.realtime.RealtimeChannel +import io.github.jan.supabase.realtime.RealtimeMessage + +/** + * Interface for handling realtime events. + */ +internal sealed interface RealtimeEvent { + + /** + * Handles the event. + * @param channel The channel the event was received on. + * @param message The message that was received. + */ + suspend fun handle(channel: RealtimeChannel, message: RealtimeMessage) + + /** + * Checks if the event applies to the message. + */ + fun appliesTo(message: RealtimeMessage): Boolean + + companion object { + + private val EVENTS = setOf( // Kotlin doesn't provide a way to get all objects of a sealed interface outside the JVM, so we have to list them manually + RBroadcastEvent, + RCloseEvent, + RErrorEvent, + RPostgresChangesEvent, + RPostgresServerChangesEvent, + RPresenceStateEvent, + RPresenceDiffEvent, + RSystemEvent, + RTokenExpiredEvent, + RSystemReplyEvent + ) + + /** + * Resolves the event from a realtime message. + */ + fun resolveEvent(realtimeMessage: RealtimeMessage): RealtimeEvent? { + return EVENTS.firstOrNull { it.appliesTo(realtimeMessage) } + } + + } + +} \ No newline at end of file diff --git a/Realtime/src/commonTest/kotlin/RealtimeChannelTest.kt b/Realtime/src/commonTest/kotlin/RealtimeChannelTest.kt index 18e509219..5079b8475 100644 --- a/Realtime/src/commonTest/kotlin/RealtimeChannelTest.kt +++ b/Realtime/src/commonTest/kotlin/RealtimeChannelTest.kt @@ -1,7 +1,7 @@ import app.cash.turbine.test -import io.github.jan.supabase.gotrue.Auth -import io.github.jan.supabase.gotrue.auth -import io.github.jan.supabase.gotrue.minimalSettings +import io.github.jan.supabase.auth.Auth +import io.github.jan.supabase.auth.auth +import io.github.jan.supabase.auth.minimalSettings import io.github.jan.supabase.postgrest.query.filter.FilterOperation import io.github.jan.supabase.postgrest.query.filter.FilterOperator import io.github.jan.supabase.realtime.CallbackManagerImpl @@ -17,7 +17,6 @@ import io.github.jan.supabase.realtime.RealtimeMessage import io.github.jan.supabase.realtime.broadcastFlow import io.github.jan.supabase.realtime.channel import io.github.jan.supabase.realtime.postgresChangeFlow -import io.github.jan.supabase.realtime.presenceChangeFlow import io.github.jan.supabase.realtime.realtime import io.github.jan.supabase.testing.assertPathIs import io.github.jan.supabase.testing.pathAfterVersion diff --git a/Realtime/src/commonTest/kotlin/RealtimeMessageEventTypeTest.kt b/Realtime/src/commonTest/kotlin/RealtimeEventTest.kt similarity index 69% rename from Realtime/src/commonTest/kotlin/RealtimeMessageEventTypeTest.kt rename to Realtime/src/commonTest/kotlin/RealtimeEventTest.kt index fedffe9a2..872393dc3 100644 --- a/Realtime/src/commonTest/kotlin/RealtimeMessageEventTypeTest.kt +++ b/Realtime/src/commonTest/kotlin/RealtimeEventTest.kt @@ -1,11 +1,22 @@ import io.github.jan.supabase.realtime.RealtimeChannel import io.github.jan.supabase.realtime.RealtimeMessage +import io.github.jan.supabase.realtime.event.RBroadcastEvent +import io.github.jan.supabase.realtime.event.RCloseEvent +import io.github.jan.supabase.realtime.event.RErrorEvent +import io.github.jan.supabase.realtime.event.RPostgresChangesEvent +import io.github.jan.supabase.realtime.event.RPostgresServerChangesEvent +import io.github.jan.supabase.realtime.event.RPresenceDiffEvent +import io.github.jan.supabase.realtime.event.RPresenceStateEvent +import io.github.jan.supabase.realtime.event.RSystemEvent +import io.github.jan.supabase.realtime.event.RSystemReplyEvent +import io.github.jan.supabase.realtime.event.RTokenExpiredEvent +import io.github.jan.supabase.realtime.event.RealtimeEvent import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put import kotlin.test.Test import kotlin.test.assertEquals -class RealtimeMessageEventTypeTest { +class RealtimeEventTest { @Test fun testSystemType() { @@ -17,7 +28,7 @@ class RealtimeMessageEventTypeTest { }, ref = "" ) - assertEquals(RealtimeMessage.EventType.SYSTEM, message.eventType) + assertEquals(RSystemEvent, RealtimeEvent.resolveEvent(message)) } @Test @@ -30,7 +41,7 @@ class RealtimeMessageEventTypeTest { }, ref = "" ) - assertEquals(RealtimeMessage.EventType.SYSTEM_REPLY, message.eventType) + assertEquals(RSystemReplyEvent, RealtimeEvent.resolveEvent(message)) } @Test @@ -45,7 +56,7 @@ class RealtimeMessageEventTypeTest { }, ref = "" ) - assertEquals(RealtimeMessage.EventType.POSTGRES_SERVER_CHANGES, message.eventType) + assertEquals(RPostgresServerChangesEvent, RealtimeEvent.resolveEvent(message)) } @Test @@ -58,7 +69,7 @@ class RealtimeMessageEventTypeTest { }, ref = "" ) - assertEquals(RealtimeMessage.EventType.POSTGRES_CHANGES, message.eventType) + assertEquals(RPostgresChangesEvent, RealtimeEvent.resolveEvent(message)) } @Test @@ -71,7 +82,7 @@ class RealtimeMessageEventTypeTest { }, ref = "" ) - assertEquals(RealtimeMessage.EventType.BROADCAST, message.eventType) + assertEquals(RBroadcastEvent, RealtimeEvent.resolveEvent(message)) } @Test @@ -84,7 +95,7 @@ class RealtimeMessageEventTypeTest { }, ref = "" ) - assertEquals(RealtimeMessage.EventType.CLOSE, message.eventType) + assertEquals(RCloseEvent, RealtimeEvent.resolveEvent(message)) } @Test @@ -97,7 +108,7 @@ class RealtimeMessageEventTypeTest { }, ref = "" ) - assertEquals(RealtimeMessage.EventType.ERROR, message.eventType) + assertEquals(RErrorEvent, RealtimeEvent.resolveEvent(message)) } @Test @@ -110,7 +121,7 @@ class RealtimeMessageEventTypeTest { }, ref = "" ) - assertEquals(RealtimeMessage.EventType.PRESENCE_DIFF, message.eventType) + assertEquals(RPresenceDiffEvent, RealtimeEvent.resolveEvent(message)) } @Test @@ -123,7 +134,7 @@ class RealtimeMessageEventTypeTest { }, ref = "" ) - assertEquals(RealtimeMessage.EventType.PRESENCE_STATE, message.eventType) + assertEquals(RPresenceStateEvent, RealtimeEvent.resolveEvent(message)) } @Test @@ -136,7 +147,7 @@ class RealtimeMessageEventTypeTest { }, ref = "" ) - assertEquals(RealtimeMessage.EventType.TOKEN_EXPIRED, message.eventType) + assertEquals(RTokenExpiredEvent, RealtimeEvent.resolveEvent(message)) } } \ No newline at end of file diff --git a/Realtime/src/commonTest/kotlin/RealtimeTestUtils.kt b/Realtime/src/commonTest/kotlin/RealtimeTestUtils.kt index 957d1f694..34edb30fb 100644 --- a/Realtime/src/commonTest/kotlin/RealtimeTestUtils.kt +++ b/Realtime/src/commonTest/kotlin/RealtimeTestUtils.kt @@ -1,5 +1,5 @@ -import io.github.jan.supabase.gotrue.Auth -import io.github.jan.supabase.gotrue.user.UserSession +import io.github.jan.supabase.auth.Auth +import io.github.jan.supabase.auth.user.UserSession import io.github.jan.supabase.putJsonObject import io.github.jan.supabase.realtime.Presence import io.github.jan.supabase.realtime.RealtimeChannel.Companion.CHANNEL_EVENT_PRESENCE_DIFF diff --git a/Storage/README.md b/Storage/README.md index 9ca598dd4..5dad33b1d 100644 --- a/Storage/README.md +++ b/Storage/README.md @@ -4,9 +4,9 @@ Extends Supabase-kt with a multiplatform Storage client. Supported targets: -| Target | **JVM** | **Android** | **JS** | **iOS** | **tvOS** | **watchOS** | **macOS** | **Windows** | **Linux** | -| ------ | ------- | ----------- | ------ | ------- | -------- | ----------- | --------- | ----------- | --------- | -| | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Target | **JVM** | **Android** | **JS** | **Wasm** | **Apple** | **Windows** | **Linux** | +|--------|---------|-------------|--------|----------|-----------|-------------|-----------| +| Status | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | > Linux has no support for persistent resumable upload url caching. @@ -14,15 +14,19 @@ Supported targets: In-depth Kotlin targets -**iOS:** iosArm64, iosSimulatorArm64, iosX64 - **JS**: Browser, NodeJS -**tvOS**: tvosArm64, tvosX64, tvosSimulatorArm64 +**Wasm**: wasm-js + +**Apple:** + +- iOS: iosArm64, iosSimulatorArm64, iosX64 + +- tvOS: tvosArm64, tvosX64, tvosSimulatorArm64 -**watchOS**: watchosArm64, watchosX64, watchosSimulatorArm64 +- watchOS: watchosArm64, watchosX64, watchosSimulatorArm64 -**MacOS**: macosX64, macosArm64 +- MacOS: macosX64, macosArm64 **Windows**: mingwX64 diff --git a/Storage/build.gradle.kts b/Storage/build.gradle.kts index f609bd2ec..af6f30875 100644 --- a/Storage/build.gradle.kts +++ b/Storage/build.gradle.kts @@ -22,7 +22,7 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - addModules(SupabaseModule.GOTRUE) + addModules(SupabaseModule.AUTH) } } val commonTest by getting { diff --git a/Storage/src/androidAndJvmMain/kotlin/io/github/jan/supabase/storage/JvmUtils.kt b/Storage/src/androidAndJvmMain/kotlin/io/github/jan/supabase/storage/JvmUtils.kt index 2586f13d5..213e319fa 100644 --- a/Storage/src/androidAndJvmMain/kotlin/io/github/jan/supabase/storage/JvmUtils.kt +++ b/Storage/src/androidAndJvmMain/kotlin/io/github/jan/supabase/storage/JvmUtils.kt @@ -11,163 +11,170 @@ import kotlin.io.path.writeBytes * Uploads a file in [BucketApi.bucketId] under [path] * @param path The path to upload the file to * @param file The file to upload - * @param upsert Whether to overwrite an existing file + * @param options Additional options for the upload * @return the key to the uploaded file */ -suspend fun BucketApi.upload(path: String, file: File, upsert: Boolean = false) = upload(path, UploadData(file.readChannel(), file.length()), upsert) +suspend fun BucketApi.upload(path: String, file: File, options: UploadOptionBuilder.() -> Unit = {}) = upload(path, UploadData(file.readChannel(), file.length()), options) /** * Uploads a file in [BucketApi.bucketId] under [path] * @param path The path to upload the file to * @param file The file to upload - * @param upsert Whether to overwrite an existing file + * @param options Additional options for the upload * @return A flow that emits the upload progress and at last the key to the uploaded file */ -fun BucketApi.uploadAsFlow(path: String, file: File, upsert: Boolean = false) = uploadAsFlow(path, UploadData(file.readChannel(), file.length()), upsert) +fun BucketApi.uploadAsFlow(path: String, file: File, options: UploadOptionBuilder.() -> Unit = {}) = uploadAsFlow(path, UploadData(file.readChannel(), file.length()), options) /** * Uploads a file in [BucketApi.bucketId] under [path] * @param path The path to upload the file to * @param file The file to upload - * @param upsert Whether to overwrite an existing file + * @param options Additional options for the upload * @return the key to the uploaded file */ -suspend fun BucketApi.upload(path: String, file: Path, upsert: Boolean = false) = upload(path, UploadData(file.readChannel(), file.fileSize()), upsert) +suspend fun BucketApi.upload(path: String, file: Path, options: UploadOptionBuilder.() -> Unit = {}) = upload(path, UploadData(file.readChannel(), file.fileSize()), options) /** * Uploads a file in [BucketApi.bucketId] under [path] * @param path The path to upload the file to * @param file The file to upload - * @param upsert Whether to overwrite an existing file + * @param options Additional options for the upload * @return A flow that emits the upload progress and at last the key to the uploaded file */ -fun BucketApi.uploadAsFlow(path: String, file: Path, upsert: Boolean = false) = uploadAsFlow(path, UploadData(file.readChannel(), file.fileSize()), upsert) +fun BucketApi.uploadAsFlow(path: String, file: Path, options: UploadOptionBuilder.() -> Unit = {}) = uploadAsFlow(path, UploadData(file.readChannel(), file.fileSize()), options) /** * Uploads a file in [BucketApi.bucketId] under [path] using a presigned url * @param path The path to upload the file to - * @param token The presigned url token + * @param token The pre-signed url token * @param file The file to upload - * @param upsert Whether to overwrite an existing file + * @param options Additional options for the upload * @return the key to the uploaded file */ -suspend fun BucketApi.uploadToSignedUrl(path: String, token: String, file: File, upsert: Boolean = false) = uploadToSignedUrl(path, token, UploadData(file.readChannel(), file.length()), upsert) +suspend fun BucketApi.uploadToSignedUrl(path: String, token: String, file: File, options: UploadOptionBuilder.() -> Unit = {}) = uploadToSignedUrl(path, token, UploadData(file.readChannel(), file.length()), options) /** * Uploads a file in [BucketApi.bucketId] under [path] using a presigned url * @param path The path to upload the file to - * @param token The presigned url token + * @param token The pre-signed url token * @param file The file to upload - * @param upsert Whether to overwrite an existing file + * @param options Additional options for the upload * @return A flow that emits the upload progress and at last the key to the uploaded file */ -fun BucketApi.uploadToSignedUrlAsFlow(path: String, token: String, file: File, upsert: Boolean = false) = uploadToSignedUrlAsFlow(path, token, UploadData(file.readChannel(), file.length()), upsert) +fun BucketApi.uploadToSignedUrlAsFlow(path: String, token: String, file: File, options: UploadOptionBuilder.() -> Unit = {}) = uploadToSignedUrlAsFlow(path, token, UploadData(file.readChannel(), file.length()), options) /** * Uploads a file in [BucketApi.bucketId] under [path] using a presigned url * @param path The path to upload the file to - * @param token The presigned url token + * @param token The pre-signed url token * @param file The file to upload - * @param upsert Whether to overwrite an existing file + * @param options Additional options for the upload */ -suspend fun BucketApi.uploadToSignedUrl(path: String, token: String, file: Path, upsert: Boolean = false) = uploadToSignedUrl(path, token, UploadData(file.readChannel(), file.fileSize()), upsert) +suspend fun BucketApi.uploadToSignedUrl(path: String, token: String, file: Path, options: UploadOptionBuilder.() -> Unit = {}) = uploadToSignedUrl(path, token, UploadData(file.readChannel(), file.fileSize()), options) /** * Uploads a file in [BucketApi.bucketId] under [path] using a presigned url * @param path The path to upload the file to - * @param token The presigned url token + * @param token The pre-signed url token * @param file The file to upload - * @param upsert Whether to overwrite an existing file + * @param options Additional options for the upload * @return A flow that emits the upload progress and at last the key to the uploaded file */ -fun BucketApi.uploadToSignedUrlAsFlow(path: String, token: String, file: Path, upsert: Boolean = false) = uploadToSignedUrlAsFlow(path, token, UploadData(file.readChannel(), file.fileSize()), upsert) +fun BucketApi.uploadToSignedUrlAsFlow(path: String, token: String, file: Path, options: UploadOptionBuilder.() -> Unit = {}) = uploadToSignedUrlAsFlow(path, token, UploadData(file.readChannel(), file.fileSize()), options) /** * Updates a file in [BucketApi.bucketId] under [path] * @param path The path to be updated * @param file The new file - * @param upsert Whether to overwrite an existing file + * @param options Additional options for the upload */ -suspend fun BucketApi.update(path: String, file: Path, upsert: Boolean = false) = update(path, UploadData(file.readChannel(), file.fileSize()), upsert) +suspend fun BucketApi.update(path: String, file: Path, options: UploadOptionBuilder.() -> Unit = {}) = update(path, UploadData(file.readChannel(), file.fileSize()), options) /** * Updates a file in [BucketApi.bucketId] under [path] * @param path The path to be updated * @param file The new file - * @param upsert Whether to overwrite an existing file + * @param options Additional options for the upload * @return A flow that emits the upload progress and at last the key to the uploaded file */ -fun BucketApi.updateAsFlow(path: String, file: Path, upsert: Boolean = false) = updateAsFlow(path, UploadData(file.readChannel(), file.fileSize()), upsert) +fun BucketApi.updateAsFlow(path: String, file: Path, options: UploadOptionBuilder.() -> Unit = {}) = updateAsFlow(path, UploadData(file.readChannel(), file.fileSize()), options) /** * Updates a file in [BucketApi.bucketId] under [path] * @param path The path to be updated * @param file The new file - * @param upsert Whether to overwrite an existing file + * @param options Additional options for the upload */ -suspend fun BucketApi.update(path: String, file: File, upsert: Boolean = false) = update(path, UploadData(file.readChannel(), file.length()), upsert) +suspend fun BucketApi.update(path: String, file: File, options: UploadOptionBuilder.() -> Unit = {}) = update(path, UploadData(file.readChannel(), file.length()), options) /** * Updates a file in [BucketApi.bucketId] under [path] * @param path The path to be updated * @param file The new file - * @param upsert Whether to overwrite an existing file + * @param options Additional options for the upload * @return A flow that emits the upload progress and at last the key to the uploaded file */ -fun BucketApi.updateAsFlow(path: String, file: File, upsert: Boolean = false) = updateAsFlow(path, UploadData(file.readChannel(), file.length()), upsert) +fun BucketApi.updateAsFlow(path: String, file: File, options: UploadOptionBuilder.() -> Unit = {}) = updateAsFlow(path, UploadData(file.readChannel(), file.length()), options) /** * Downloads a file from [BucketApi.bucketId] under [path] and saves it to [file] * @param path The path to download the file from * @param file The file to save the data to + * @param options Additional options for the download */ -suspend fun BucketApi.downloadAuthenticatedTo(path: String, file: File, transform: ImageTransformation.() -> Unit = {}) = downloadAuthenticated(path, file.writeChannel(), transform) +suspend fun BucketApi.downloadAuthenticatedTo(path: String, file: File, options: DownloadOptionBuilder.() -> Unit = {}) = downloadAuthenticated(path, file.writeChannel(), options) /** * Downloads a file from [BucketApi.bucketId] under [path] and saves it to [file] * @param path The path to download the file from * @param file The file to save the data to + * @param options Additional options for the download * @return A flow that emits the download progress and at last the key to the downloaded file */ -fun BucketApi.downloadAuthenticatedToAsFlow(path: String, file: File, transform: ImageTransformation.() -> Unit = {}) = downloadAuthenticatedAsFlow(path, file.writeChannel(), transform) +fun BucketApi.downloadAuthenticatedToAsFlow(path: String, file: File, options: DownloadOptionBuilder.() -> Unit = {}) = downloadAuthenticatedAsFlow(path, file.writeChannel(), options) /** * Downloads a file from [BucketApi.bucketId] under [path] and saves it to [file] * @param path The path to download the file from * @param file The file to save the data to + * @param options Additional options for the download */ -suspend fun BucketApi.downloadAuthenticatedTo(path: String, file: Path, transform: ImageTransformation.() -> Unit = {}) = downloadAuthenticated(path, file.toFile().writeChannel(), transform) +suspend fun BucketApi.downloadAuthenticatedTo(path: String, file: Path, options: DownloadOptionBuilder.() -> Unit = {}) = downloadAuthenticated(path, file.toFile().writeChannel(), options) /** * Downloads a file from [BucketApi.bucketId] under [path] and saves it to [file] * @param path The path to download the file from * @param file The file to save the data to + * @param options Additional options for the download * @return A flow that emits the download progress and at last the key to the downloaded file */ -fun BucketApi.downloadAuthenticatedToAsFlow(path: String, file: Path, transform: ImageTransformation.() -> Unit = {}) = downloadAuthenticatedAsFlow(path, file.toFile().writeChannel(), transform) +fun BucketApi.downloadAuthenticatedToAsFlow(path: String, file: Path, options: DownloadOptionBuilder.() -> Unit = {}) = downloadAuthenticatedAsFlow(path, file.toFile().writeChannel(), options) /** * Downloads a file from [BucketApi.bucketId] under [path] and saves it to [file] * @param path The path to download the file from * @param file The file to save the data to + * @param options Additional options for the download */ -suspend fun BucketApi.downloadPublicTo(path: String, file: File, transform: ImageTransformation.() -> Unit = {}) = downloadPublic(path, file.writeChannel(), transform) +suspend fun BucketApi.downloadPublicTo(path: String, file: File, options: DownloadOptionBuilder.() -> Unit = {}) = downloadPublic(path, file.writeChannel(), options) /** * Downloads a file from [BucketApi.bucketId] under [path] and saves it to [file] * @param path The path to download the file from * @param file The file to save the data to + * @param options Additional options for the download * @return A flow that emits the download progress and at last the key to the downloaded file */ -fun BucketApi.downloadPublicToAsFlow(path: String, file: File, transform: ImageTransformation.() -> Unit = {}) = downloadPublicAsFlow(path, file.writeChannel(), transform) +fun BucketApi.downloadPublicToAsFlow(path: String, file: File, options: DownloadOptionBuilder.() -> Unit = {}) = downloadPublicAsFlow(path, file.writeChannel(), options) /** * Downloads a file from [BucketApi.bucketId] under [path] and saves it to [file] * @param path The path to download the file from * @param file The file to save the data to + * @param options Additional options for the download */ -suspend fun BucketApi.downloadPublicTo(path: String, file: Path, transform: ImageTransformation.() -> Unit = {}) { - val bytes = downloadPublic(path, transform) +suspend fun BucketApi.downloadPublicTo(path: String, file: Path, options: DownloadOptionBuilder.() -> Unit = {}) { + val bytes = downloadPublic(path, options) file.writeBytes(bytes) } @@ -175,6 +182,7 @@ suspend fun BucketApi.downloadPublicTo(path: String, file: Path, transform: Imag * Downloads a file from [BucketApi.bucketId] under [path] and saves it to [file] * @param path The path to download the file from * @param file The file to save the data to + * @param options Additional options for the download * @return A flow that emits the download progress and at last the key to the downloaded file */ -fun BucketApi.downloadPublicToAsFlow(path: String, file: Path, transform: ImageTransformation.() -> Unit = {}) = downloadPublicAsFlow(path, file.toFile().writeChannel(), transform) \ No newline at end of file +fun BucketApi.downloadPublicToAsFlow(path: String, file: Path, options: DownloadOptionBuilder.() -> Unit = {}) = downloadPublicAsFlow(path, file.toFile().writeChannel(), options) \ No newline at end of file diff --git a/Storage/src/androidAndJvmMain/kotlin/io/github/jan/supabase/storage/ResumableUtils.kt b/Storage/src/androidAndJvmMain/kotlin/io/github/jan/supabase/storage/ResumableUtils.kt index 71ef1bbb7..c39d054ac 100644 --- a/Storage/src/androidAndJvmMain/kotlin/io/github/jan/supabase/storage/ResumableUtils.kt +++ b/Storage/src/androidAndJvmMain/kotlin/io/github/jan/supabase/storage/ResumableUtils.kt @@ -3,6 +3,7 @@ package io.github.jan.supabase.storage import io.github.jan.supabase.storage.resumable.Fingerprint import io.github.jan.supabase.storage.resumable.ResumableClient import io.ktor.util.cio.readChannel +import io.ktor.utils.io.discard import java.io.File import java.nio.file.Path import kotlin.io.path.absolutePathString diff --git a/Storage/src/androidMain/kotlin/io/github/jan/supabase/storage/AndroidUtils.kt b/Storage/src/androidMain/kotlin/io/github/jan/supabase/storage/AndroidUtils.kt index 48f883f99..e30c50706 100644 --- a/Storage/src/androidMain/kotlin/io/github/jan/supabase/storage/AndroidUtils.kt +++ b/Storage/src/androidMain/kotlin/io/github/jan/supabase/storage/AndroidUtils.kt @@ -9,56 +9,56 @@ import io.ktor.utils.io.jvm.javaio.toByteReadChannel * Uploads a file in [BucketApi.bucketId] under [path] * @param path The path to upload the file to * @param uri The uri to upload - * @param upsert Whether to overwrite an existing file + * @param options Additional options for the upload * @return the key to the updated file */ -suspend fun BucketApi.upload(path: String, uri: Uri, upsert: Boolean = false) = upload(path, UploadData(uri.readChannel(), uri.contentSize), upsert) +suspend fun BucketApi.upload(path: String, uri: Uri, options: UploadOptionBuilder.() -> Unit = {}) = upload(path, UploadData(uri.readChannel(), uri.contentSize), options) /** * Uploads a file in [BucketApi.bucketId] under [path] * @param path The path to upload the file to * @param uri The uri to upload - * @param upsert Whether to overwrite an existing file + * @param options Additional options for the upload * @return A flow that emits the upload progress and at last the key to the updated file */ -fun BucketApi.uploadAsFlow(path: String, uri: Uri, upsert: Boolean = false) = uploadAsFlow(path, UploadData(uri.readChannel(), uri.contentSize), upsert) +fun BucketApi.uploadAsFlow(path: String, uri: Uri, options: UploadOptionBuilder.() -> Unit = {}) = uploadAsFlow(path, UploadData(uri.readChannel(), uri.contentSize), options) /** * Uploads a file in [BucketApi.bucketId] under [path] using a presigned url * @param path The path to upload the file to - * @param token The presigned url token + * @param token The pre-signed url token * @param uri The uri to upload * @return the key to the updated file */ -suspend fun BucketApi.uploadToSignedUrl(path: String, token: String, uri: Uri, upsert: Boolean = false) = uploadToSignedUrl(path, token, UploadData(uri.readChannel(), uri.contentSize), upsert) +suspend fun BucketApi.uploadToSignedUrl(path: String, token: String, uri: Uri, options: UploadOptionBuilder.() -> Unit = {}) = uploadToSignedUrl(path, token, UploadData(uri.readChannel(), uri.contentSize), options) /** * Uploads a file in [BucketApi.bucketId] under [path] using a presigned url * @param path The path to upload the file to - * @param token The presigned url token + * @param token The pre-signed url token * @param uri The uri to upload - * @param upsert Whether to overwrite an existing file + * @param options Additional options for the upload * @return A flow that emits the upload progress and at last the key to the updated file */ -fun BucketApi.uploadToSignedUrlAsFlow(path: String, token: String, uri: Uri, upsert: Boolean = false) = uploadToSignedUrlAsFlow(path, token, UploadData(uri.readChannel(), uri.contentSize), upsert) +fun BucketApi.uploadToSignedUrlAsFlow(path: String, token: String, uri: Uri, options: UploadOptionBuilder.() -> Unit = {}) = uploadToSignedUrlAsFlow(path, token, UploadData(uri.readChannel(), uri.contentSize), options) /** * Updates a file in [BucketApi.bucketId] under [path] * @param path The path to update the file to * @param uri The uri to update - * @param upsert Whether to overwrite an existing file + * @param options Additional options for the upload * @return the key to the updated file */ -suspend fun BucketApi.update(path: String, uri: Uri, upsert: Boolean = false) = update(path, UploadData(uri.readChannel(), uri.contentSize), upsert) +suspend fun BucketApi.update(path: String, uri: Uri, options: UploadOptionBuilder.() -> Unit = {}) = update(path, UploadData(uri.readChannel(), uri.contentSize), options) /** * Updates a file in [BucketApi.bucketId] under [path] * @param path The path to update the file to * @param uri The uri to update - * @param upsert Whether to overwrite an existing file + * @param options Additional options for the upload * @return A flow that emits the upload progress and at last the key to the updated file */ -fun BucketApi.updateAsFlow(path: String, uri: Uri, upsert: Boolean = false) = updateAsFlow(path, UploadData(uri.readChannel(), uri.contentSize), upsert) +fun BucketApi.updateAsFlow(path: String, uri: Uri, options: UploadOptionBuilder.() -> Unit = {}) = updateAsFlow(path, UploadData(uri.readChannel(), uri.contentSize), options) @SuppressLint("Recycle") //toByteReadChannel closes the input stream automatically private fun Uri.readChannel(): ByteReadChannel { diff --git a/Storage/src/androidMain/kotlin/io/github/jan/supabase/storage/ResumableAndroidUtils.kt b/Storage/src/androidMain/kotlin/io/github/jan/supabase/storage/ResumableAndroidUtils.kt index 000091b68..85651b333 100644 --- a/Storage/src/androidMain/kotlin/io/github/jan/supabase/storage/ResumableAndroidUtils.kt +++ b/Storage/src/androidMain/kotlin/io/github/jan/supabase/storage/ResumableAndroidUtils.kt @@ -5,6 +5,7 @@ import android.content.res.AssetFileDescriptor import android.net.Uri import io.github.jan.supabase.storage.resumable.ResumableClient import io.ktor.utils.io.ByteReadChannel +import io.ktor.utils.io.discard import io.ktor.utils.io.jvm.javaio.toByteReadChannel /** diff --git a/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/BucketApi.kt b/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/BucketApi.kt index a4c7da748..5003106c9 100644 --- a/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/BucketApi.kt +++ b/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/BucketApi.kt @@ -1,9 +1,9 @@ package io.github.jan.supabase.storage import io.github.jan.supabase.SupabaseClient +import io.github.jan.supabase.auth.resolveAccessToken import io.github.jan.supabase.exceptions.HttpRequestException import io.github.jan.supabase.exceptions.RestException -import io.github.jan.supabase.gotrue.resolveAccessToken import io.github.jan.supabase.storage.resumable.ResumableClient import io.ktor.client.plugins.HttpRequestTimeoutException import io.ktor.utils.io.ByteReadChannel @@ -34,43 +34,47 @@ sealed interface BucketApi { * Uploads a file in [bucketId] under [path] * @param path The path to upload the file to * @param data The data to upload - * @param upsert Whether to overwrite an existing file + * @param options Additional options for the upload * @return the key to the uploaded file * @throws IllegalArgumentException if data to upload is empty * @throws RestException or one of its subclasses if receiving an error response * @throws HttpRequestTimeoutException if the request timed out * @throws HttpRequestException on network related issues */ - suspend fun upload(path: String, data: ByteArray, upsert: Boolean = false): FileUploadResponse { + suspend fun upload(path: String, data: ByteArray, options: UploadOptionBuilder.() -> Unit = {}): FileUploadResponse { require(data.isNotEmpty()) { "The data to upload should not be empty" } - return upload(path, UploadData(ByteReadChannel(data), data.size.toLong()), upsert) + return upload(path, UploadData(ByteReadChannel(data), data.size.toLong()), options) } /** * Uploads a file in [bucketId] under [path] * @param path The path to upload the file to * @param data The data to upload - * @param upsert Whether to overwrite an existing file + * @param options Additional options for the upload * @return the key to the uploaded file * @throws RestException or one of its subclasses if receiving an error response * @throws HttpRequestTimeoutException if the request timed out * @throws HttpRequestException on network related issues */ - suspend fun upload(path: String, data: UploadData, upsert: Boolean = false): FileUploadResponse + suspend fun upload(path: String, data: UploadData, options: UploadOptionBuilder.() -> Unit = {}): FileUploadResponse /** - * Uploads a file in [bucketId] under [path] using a presigned url + * Uploads a file in [bucketId] under [path] using a pre-signed url * @param path The path to upload the file to - * @param token The presigned url token + * @param token The pre-signed url token * @param data The data to upload - * @param upsert Whether to overwrite an existing file + * @param options Additional options for the upload * @return the key of the uploaded file * @throws IllegalArgumentException if data to upload is empty */ - suspend fun uploadToSignedUrl(path: String, token: String, data: ByteArray, upsert: Boolean = false + suspend fun uploadToSignedUrl( + path: String, + token: String, + data: ByteArray, + options: UploadOptionBuilder.() -> Unit = {} ): FileUploadResponse { require(data.isNotEmpty()) { "The data to upload should not be empty" } - return uploadToSignedUrl(path, token, UploadData(ByteReadChannel(data), data.size.toLong()), upsert) + return uploadToSignedUrl(path, token, UploadData(ByteReadChannel(data), data.size.toLong()), options) } /** @@ -78,42 +82,42 @@ sealed interface BucketApi { * @param path The path to upload the file to * @param token The presigned url token * @param data The data to upload - * @param upsert Whether to overwrite an existing file + * @param options Additional options for the upload * @return the key of the uploaded file * @throws RestException or one of its subclasses if receiving an error response * @throws HttpRequestTimeoutException if the request timed out * @throws HttpRequestException on network related issues * @throws HttpRequestException on network related issues */ - suspend fun uploadToSignedUrl(path: String, token: String, data: UploadData, upsert: Boolean = false): FileUploadResponse + suspend fun uploadToSignedUrl(path: String, token: String, data: UploadData, options: UploadOptionBuilder.() -> Unit = {}): FileUploadResponse /** * Updates a file in [bucketId] under [path] * @param path The path to update the file to * @param data The new data - * @param upsert Whether to overwrite an existing file + * @param options Additional options for the upload * @return the key to the updated file * @throws IllegalArgumentException if data to upload is empty * @throws RestException or one of its subclasses if receiving an error response * @throws HttpRequestTimeoutException if the request timed out * @throws HttpRequestException on network related issues */ - suspend fun update(path: String, data: ByteArray, upsert: Boolean = false): FileUploadResponse { + suspend fun update(path: String, data: ByteArray, options: UploadOptionBuilder.() -> Unit = {}): FileUploadResponse { require(data.isNotEmpty()) { "The data to upload should not be empty" } - return update(path, UploadData(ByteReadChannel(data), data.size.toLong()), upsert) + return update(path, UploadData(ByteReadChannel(data), data.size.toLong()), options) } /** * Updates a file in [bucketId] under [path] * @param path The path to update the file to * @param data The new data - * @param upsert Whether to overwrite an existing file + * @param options Additional options for the upload * @return the key to the updated file * @throws RestException or one of its subclasses if receiving an error response * @throws HttpRequestTimeoutException if the request timed out * @throws HttpRequestException on network related issues */ - suspend fun update(path: String, data: UploadData, upsert: Boolean = false): FileUploadResponse + suspend fun update(path: String, data: UploadData, options: UploadOptionBuilder.() -> Unit = {}): FileUploadResponse /** * Deletes all files in [bucketId] with in [paths] @@ -199,51 +203,50 @@ sealed interface BucketApi { /** * Downloads a file from [bucketId] under [path] * @param path The path to download - * @param transform The transformation to apply to the image + * @param options Additional options for the download * @return The file as a byte array * @throws RestException or one of its subclasses if receiving an error response * @throws HttpRequestTimeoutException if the request timed out * @throws HttpRequestException on network related issues */ - suspend fun downloadAuthenticated(path: String, transform: ImageTransformation.() -> Unit = {}): ByteArray + suspend fun downloadAuthenticated(path: String, options: DownloadOptionBuilder.() -> Unit = {}): ByteArray /** * Downloads a file from [bucketId] under [path] * @param path The path to download * @param channel The channel to write the data to - * @param transform The transformation to apply to the image + * @param options Additional options for the download * @throws RestException or one of its subclasses if receiving an error response * @throws HttpRequestTimeoutException if the request timed out * @throws HttpRequestException on network related issues */ - suspend fun downloadAuthenticated(path: String, channel: ByteWriteChannel, transform: ImageTransformation.() -> Unit = {}) + suspend fun downloadAuthenticated(path: String, channel: ByteWriteChannel, options: DownloadOptionBuilder.() -> Unit = {}) /** * Downloads a file from [bucketId] under [path] using the public url * @param path The path to download - * @param transform The transformation to apply to the image + * @param options Additional options for the download * @return The file as a byte array * @throws RestException or one of its subclasses if receiving an error response * @throws HttpRequestTimeoutException if the request timed out * @throws HttpRequestException on network related issues */ - suspend fun downloadPublic(path: String, transform: ImageTransformation.() -> Unit = {}): ByteArray + suspend fun downloadPublic(path: String, options: DownloadOptionBuilder.() -> Unit = {}): ByteArray /** * Downloads a file from [bucketId] under [path] using the public url * @param path The path to download * @param channel The channel to write the data to - * @param transform The transformation to apply to the image + * @param options Additional options for the download * @throws RestException or one of its subclasses if receiving an error response * @throws HttpRequestTimeoutException if the request timed out * @throws HttpRequestException on network related issues */ - suspend fun downloadPublic(path: String, channel: ByteWriteChannel, transform: ImageTransformation.() -> Unit = {}) + suspend fun downloadPublic(path: String, channel: ByteWriteChannel, options: DownloadOptionBuilder.() -> Unit = {}) /** - * Searches for buckets with the given [prefix] and [filter] - * @return The filtered buckets + * Searches for files with the given [prefix] and [filter] * @throws RestException or one of its subclasses if receiving an error response * @throws HttpRequestTimeoutException if the request timed out * @throws HttpRequestException on network related issues @@ -251,7 +254,7 @@ sealed interface BucketApi { suspend fun list( prefix: String = "", filter: BucketListFilter.() -> Unit = {} - ): List + ): List /** * Changes the bucket's public status to [public] diff --git a/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/BucketApiImpl.kt b/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/BucketApiImpl.kt index 4d664684c..986f2582d 100644 --- a/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/BucketApiImpl.kt +++ b/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/BucketApiImpl.kt @@ -8,7 +8,6 @@ import io.github.jan.supabase.storage.resumable.ResumableClientImpl import io.ktor.client.call.body import io.ktor.client.request.HttpRequestBuilder import io.ktor.client.request.header -import io.ktor.client.request.parameter import io.ktor.client.request.setBody import io.ktor.client.request.url import io.ktor.client.statement.bodyAsChannel @@ -20,7 +19,6 @@ import io.ktor.http.content.OutgoingContent import io.ktor.http.defaultForFilePath import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.ByteWriteChannel -import io.ktor.utils.io.close import io.ktor.utils.io.copyTo import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.add @@ -37,18 +35,24 @@ internal class BucketApiImpl(override val bucketId: String, val storage: Storage override val resumable = ResumableClientImpl(this, resumableCache) - override suspend fun update(path: String, data: UploadData, upsert: Boolean): FileUploadResponse = + override suspend fun update( + path: String, + data: UploadData, + options: UploadOptionBuilder.() -> Unit + ): FileUploadResponse = uploadOrUpdate( - HttpMethod.Put, bucketId, path, data, upsert + HttpMethod.Put, defaultUploadUrl(path), data, options ) override suspend fun uploadToSignedUrl( path: String, token: String, data: UploadData, - upsert: Boolean + options: UploadOptionBuilder.() -> Unit ): FileUploadResponse { - return uploadToSignedUrl(path, token, data, upsert) {} + return uploadOrUpdate( + HttpMethod.Put, uploadToSignedUrlUrl(path, token), data, options + ) } override suspend fun createSignedUploadUrl(path: String): UploadSignedUrl { @@ -64,9 +68,13 @@ internal class BucketApiImpl(override val bucketId: String, val storage: Storage ) } - override suspend fun upload(path: String, data: UploadData, upsert: Boolean): FileUploadResponse = + override suspend fun upload( + path: String, + data: UploadData, + options: UploadOptionBuilder.() -> Unit + ): FileUploadResponse = uploadOrUpdate( - HttpMethod.Post, bucketId, path, data, upsert + HttpMethod.Post, defaultUploadUrl(path), data, options ) override suspend fun delete(paths: Collection) { @@ -128,72 +136,74 @@ internal class BucketApiImpl(override val bucketId: String, val storage: Storage override suspend fun downloadAuthenticated( path: String, - transform: ImageTransformation.() -> Unit - ): ByteArray { - return storage.api.rawRequest { - prepareDownloadRequest(path, false, transform) - }.body() - } + options: DownloadOptionBuilder.() -> Unit + ): ByteArray = normalDownloadRequest(path, false, options) override suspend fun downloadPublic( path: String, - transform: ImageTransformation.() -> Unit + options: DownloadOptionBuilder.() -> Unit + ): ByteArray = normalDownloadRequest(path, true, options) + + private suspend fun normalDownloadRequest( + path: String, + public: Boolean, + options: DownloadOptionBuilder.() -> Unit ): ByteArray { + val downloadOptions = DownloadOptionBuilder().apply(options) return storage.api.rawRequest { - prepareDownloadRequest(path, true, transform) + prepareDownloadRequest(path, public, downloadOptions) + downloadOptions.httpRequestOverrides.forEach { it() } }.body() } - override suspend fun downloadAuthenticated( path: String, channel: ByteWriteChannel, - transform: ImageTransformation.() -> Unit + options: DownloadOptionBuilder.() -> Unit ) { - channelDownloadRequest(path, channel, false, transform) + channelDownloadRequest(path, channel, false, options) } override suspend fun downloadPublic( path: String, channel: ByteWriteChannel, - transform: ImageTransformation.() -> Unit + options: DownloadOptionBuilder.() -> Unit ) { - channelDownloadRequest(path, channel, true, transform) + channelDownloadRequest(path, channel, true, options) } internal suspend fun channelDownloadRequest( path: String, channel: ByteWriteChannel, public: Boolean, - transform: ImageTransformation.() -> Unit, - extra: HttpRequestBuilder.() -> Unit = {} + options: DownloadOptionBuilder.() -> Unit, ) { + val downloadOptions = DownloadOptionBuilder().apply(options) storage.api.prepareRequest { - prepareDownloadRequest(path, public, transform) - extra() + prepareDownloadRequest(path, public, downloadOptions) + downloadOptions.httpRequestOverrides.forEach { it() } }.execute { it.bodyAsChannel().copyTo(channel) } - channel.close() + channel.flushAndClose() } internal fun HttpRequestBuilder.prepareDownloadRequest( path: String, public: Boolean, - transform: ImageTransformation.() -> Unit + options: DownloadOptionBuilder ) { - val transformation = ImageTransformation().apply(transform).queryString() + val transformation = ImageTransformation().apply(options.transform).queryString() val url = when (public) { true -> if (transformation.isBlank()) publicUrl(path) else publicRenderUrl( path, - transform + options.transform ) - false -> if (transformation.isBlank()) authenticatedUrl(path) else authenticatedRenderUrl( path, - transform + options.transform ) } method = HttpMethod.Get @@ -203,31 +213,29 @@ internal class BucketApiImpl(override val bucketId: String, val storage: Storage override suspend fun list( prefix: String, filter: BucketListFilter.() -> Unit - ): List { + ): List { return storage.api.postJson("object/list/$bucketId", buildJsonObject { put("prefix", prefix) putJsonObject(BucketListFilter().apply(filter).build()) }).safeBody() } + private fun defaultUploadUrl(path: String) = "object/$bucketId/$path" + + private fun uploadToSignedUrlUrl(path: String, token: String) = "object/upload/sign/$bucketId/$path?token=$token" + internal suspend fun uploadOrUpdate( method: HttpMethod, - bucket: String, - path: String, + url: String, data: UploadData, - upsert: Boolean, - extra: HttpRequestBuilder.() -> Unit = {} + options: UploadOptionBuilder.() -> Unit, ): FileUploadResponse { - val response = storage.api.request("object/$bucket/$path") { + val path = url.substringAfterLast('/').substringBeforeLast("?") + val optionBuilder = UploadOptionBuilder(storage.serializer).apply(options) + val response = storage.api.request(url) { this.method = method - setBody(object : OutgoingContent.ReadChannelContent() { - override val contentType: ContentType = ContentType.defaultForFilePath(path) - override val contentLength: Long = data.size - override fun readFrom(): ByteReadChannel = data.stream - }) - header(HttpHeaders.ContentType, ContentType.defaultForFilePath(path)) - header(UPSERT_HEADER, upsert.toString()) - extra() + defaultUploadRequest(path, data, optionBuilder) + optionBuilder.httpRequestOverrides.forEach { it() } }.body() val key = response["Key"]?.jsonPrimitive?.content ?: error("Expected a key in a upload response") @@ -236,28 +244,18 @@ internal class BucketApiImpl(override val bucketId: String, val storage: Storage return FileUploadResponse(id, path, key) } - internal suspend fun uploadToSignedUrl( + private fun HttpRequestBuilder.defaultUploadRequest( path: String, - token: String, data: UploadData, - upsert: Boolean, - extra: HttpRequestBuilder.() -> Unit = {} - ): FileUploadResponse { - val response = storage.api.put("object/upload/sign/$bucketId/$path") { - parameter("token", token) - setBody(object : OutgoingContent.ReadChannelContent() { - override val contentType: ContentType = ContentType.defaultForFilePath(path) - override val contentLength: Long = data.size - override fun readFrom(): ByteReadChannel = data.stream - }) - header(HttpHeaders.ContentType, ContentType.defaultForFilePath(path)) - header("x-upsert", upsert.toString()) - extra() - }.body() - val key = response["Key"]?.jsonPrimitive?.content - ?: error("Expected a key in a upload response") - val id = response["Id"]?.jsonPrimitive?.content ?: error("Expected an id in a upload response") - return FileUploadResponse(id, path, key) + optionBuilder: UploadOptionBuilder, + ) { + setBody(object : OutgoingContent.ReadChannelContent() { + override val contentType: ContentType = optionBuilder.contentType ?: ContentType.defaultForFilePath(path) + override val contentLength: Long = data.size + override fun readFrom(): ByteReadChannel = data.stream + }) + header(HttpHeaders.ContentType, optionBuilder.contentType ?: ContentType.defaultForFilePath(path)) + header(UPSERT_HEADER, optionBuilder.upsert.toString()) } override suspend fun changePublicStatusTo(public: Boolean) = storage.updateBucket(bucketId) { diff --git a/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/DownloadOptionBuilder.kt b/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/DownloadOptionBuilder.kt new file mode 100644 index 000000000..9110f7d7b --- /dev/null +++ b/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/DownloadOptionBuilder.kt @@ -0,0 +1,29 @@ +package io.github.jan.supabase.storage + +import io.github.jan.supabase.network.HttpRequestOverride + +/** + * Builder for downloading files with additional options + */ +class DownloadOptionBuilder( + internal var transform: ImageTransformation.() -> Unit = {}, + internal val httpRequestOverrides: MutableList = mutableListOf() +) { + + /** + * Transforms the image before downloading + * @param transform The transformation to apply + */ + fun transform(transform: ImageTransformation.() -> Unit) { + this.transform = transform + } + + /** + * Overrides the HTTP request + * @param override The override to apply + */ + fun httpOverride(override: HttpRequestOverride) { + httpRequestOverrides.add(override) + } + +} \ No newline at end of file diff --git a/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/BucketItem.kt b/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/FileObject.kt similarity index 97% rename from Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/BucketItem.kt rename to Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/FileObject.kt index 595eb8d96..8caf02a8a 100644 --- a/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/BucketItem.kt +++ b/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/FileObject.kt @@ -15,7 +15,7 @@ import kotlinx.serialization.json.JsonObject * @param metadata The metadata of the item */ @Serializable -data class BucketItem( +data class FileObject( val name: String, val id: String?, @SerialName("updated_at") diff --git a/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/FlowExt.kt b/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/FlowExt.kt deleted file mode 100644 index d473c0972..000000000 --- a/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/FlowExt.kt +++ /dev/null @@ -1,225 +0,0 @@ -package io.github.jan.supabase.storage - -import io.github.jan.supabase.exceptions.HttpRequestException -import io.github.jan.supabase.exceptions.RestException -import io.ktor.client.call.body -import io.ktor.client.plugins.HttpRequestTimeoutException -import io.ktor.client.plugins.onDownload -import io.ktor.client.plugins.onUpload -import io.ktor.http.HttpMethod -import io.ktor.utils.io.ByteReadChannel -import io.ktor.utils.io.ByteWriteChannel -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow - -/** - * Uploads a file in [bucketId] under [path] - * @param path The path to upload the file to - * @param upsert Whether to overwrite an existing file - * @param data The data to upload - * @return A flow that emits the upload progress and at last the key to the uploaded file - * @throws RestException or one of its subclasses if receiving an error response - * @throws HttpRequestTimeoutException if the request timed out - * @throws HttpRequestException on network related issues - */ -fun BucketApi.updateAsFlow(path: String, data: UploadData, upsert: Boolean = false): Flow = callbackFlow { - this@updateAsFlow as BucketApiImpl - val key = uploadOrUpdate(HttpMethod.Put, bucketId, path, data, upsert) { - onUpload { bytesSentTotal, contentLength -> - trySend(UploadStatus.Progress(bytesSentTotal, contentLength)) - } - } - trySend(UploadStatus.Success(key)) - close() -} - -/** - * Uploads a file in [bucketId] under [path] - * @param path The path to upload the file to - * @param upsert Whether to overwrite an existing file - * @param data The data to upload - * @return A flow that emits the upload progress and at last the key to the uploaded file - * @throws RestException or one of its subclasses if receiving an error response - * @throws HttpRequestTimeoutException if the request timed out - * @throws HttpRequestException on network related issues - */ -fun BucketApi.uploadAsFlow(path: String, data: ByteArray, upsert: Boolean = false): Flow = uploadAsFlow(path, UploadData( - ByteReadChannel(data), data.size.toLong()), upsert) - -/** - * Uploads a file in [bucketId] under [path] using a presigned url - * @param path The path to upload the file to - * @param token The presigned url token - * @param data The data to upload - * @param upsert Whether to overwrite an existing file - * @return A flow that emits the upload progress and at last the key to the uploaded file - * @throws RestException or one of its subclasses if receiving an error response - * @throws HttpRequestTimeoutException if the request timed out - * @throws HttpRequestException on network related issues - */ -fun BucketApi.uploadToSignedUrlAsFlow( - path: String, - token: String, - data: UploadData, - upsert: Boolean = false -): Flow { - return callbackFlow { - this@uploadToSignedUrlAsFlow as BucketApiImpl - val key = uploadToSignedUrl(path, token, data, upsert) { - onUpload { bytesSentTotal, contentLength -> - trySend(UploadStatus.Progress(bytesSentTotal, contentLength)) - } - } - trySend(UploadStatus.Success(key)) - close() - } -} - -/** - * Uploads a file in [bucketId] under [path] using a presigned url - * @param path The path to upload the file to - * @param token The presigned url token - * @param data The data to upload - * @param upsert Whether to overwrite an existing file - * @return A flow that emits the upload progress and at last the key to the uploaded file - */ -fun BucketApi.uploadToSignedUrlAsFlow(path: String, token: String, data: ByteArray, upsert: Boolean = false): Flow = uploadToSignedUrlAsFlow(path, token, UploadData(ByteReadChannel(data), data.size.toLong()), upsert) - -/** - * Updates a file in [bucketId] under [path] - * @param path The path to update the file to - * @param data The new data - * @param upsert Whether to overwrite an existing file - * @return A flow that emits the upload progress and at last the key to the uploaded file - * @throws RestException or one of its subclasses if receiving an error response - * @throws HttpRequestTimeoutException if the request timed out - * @throws HttpRequestException on network related issues - */ -fun BucketApi.uploadAsFlow(path: String, data: UploadData, upsert: Boolean = false): Flow { - return callbackFlow { - this@uploadAsFlow as BucketApiImpl - val key = uploadOrUpdate(HttpMethod.Post, bucketId, path, data, upsert) { - onUpload { bytesSentTotal, contentLength -> - trySend(UploadStatus.Progress(bytesSentTotal, contentLength)) - } - } - trySend(UploadStatus.Success(key)) - close() - } -} - -/** - * Updates a file in [bucketId] under [path] - * @param path The path to update the file to - * @param data The new data - * @param upsert Whether to overwrite an existing file - * @return A flow that emits the upload progress and at last the key to the uploaded file - * @throws RestException or one of its subclasses if receiving an error response - * @throws HttpRequestTimeoutException if the request timed out - * @throws HttpRequestException on network related issues - */ -fun BucketApi.updateAsFlow(path: String, data: ByteArray, upsert: Boolean = false): Flow = updateAsFlow(path, UploadData(ByteReadChannel(data), data.size.toLong()), upsert) - -/** - * Downloads a file from [bucketId] under [path] - * @param path The path to download - * @param transform The transformation to apply to the image - * @return A flow that emits the download progress and at last the data as a byte array - * @throws RestException or one of its subclasses if receiving an error response - * @throws HttpRequestTimeoutException if the request timed out - * @throws HttpRequestException on network related issues - */ -fun BucketApi.downloadAuthenticatedAsFlow( - path: String, - transform: ImageTransformation.() -> Unit = {} -): Flow { - return callbackFlow { - this@downloadAuthenticatedAsFlow as BucketApiImpl - val data = storage.api.rawRequest { - prepareDownloadRequest(path, false, transform) - onDownload { bytesSentTotal, contentLength -> - trySend(DownloadStatus.Progress(bytesSentTotal, contentLength)) - } - }.body() - trySend(DownloadStatus.Success) - trySend(DownloadStatus.ByteData(data)) - close() - } -} - -/** - * Downloads a file from [bucketId] under [path] - * @param path The path to download - * @param channel The channel to write the data to - * @param transform The transformation to apply to the image - * @return A flow that emits the download progress and at last the data as a byte array - * @throws RestException or one of its subclasses if receiving an error response - * @throws HttpRequestTimeoutException if the request timed out - * @throws HttpRequestException on network related issues - */ -fun BucketApi.downloadAuthenticatedAsFlow( - path: String, - channel: ByteWriteChannel, - transform: ImageTransformation.() -> Unit = {} -): Flow { - this as BucketApiImpl - return flowChannelDownloadRequest(path, channel, false, transform) -} - -/** - * Downloads a file from [bucketId] under [path] using the public url - * @param path The path to download - * @param transform The transformation to apply to the image - * @return A flow that emits the download progress and at last the data as a byte array - * @throws RestException or one of its subclasses if receiving an error response - * @throws HttpRequestTimeoutException if the request timed out - * @throws HttpRequestException on network related issues - */ -fun BucketApi.downloadPublicAsFlow(path: String, transform: ImageTransformation.() -> Unit = {}): Flow { - return callbackFlow { - this@downloadPublicAsFlow as BucketApiImpl - val data = storage.api.rawRequest { - prepareDownloadRequest(path, true, transform) - onDownload { bytesSentTotal, contentLength -> - trySend(DownloadStatus.Progress(bytesSentTotal, contentLength)) - } - }.body() - trySend(DownloadStatus.Success) - trySend(DownloadStatus.ByteData(data)) - close() - } -} - -/** - * Downloads a file from [bucketId] under [path] using the public url - * @param path The path to download - * @param channel The channel to write the data to - * @param transform The transformation to apply to the image - * @return A flow that emits the download progress and at last the data as a byte array - * @throws RestException or one of its subclasses if receiving an error response - * @throws HttpRequestTimeoutException if the request timed out - * @throws HttpRequestException on network related issues - */ -fun BucketApi.downloadPublicAsFlow( - path: String, - channel: ByteWriteChannel, - transform: ImageTransformation.() -> Unit = {} -): Flow { - this as BucketApiImpl - return flowChannelDownloadRequest(path, channel, true, transform) -} - -private fun BucketApiImpl.flowChannelDownloadRequest( - path: String, - channel: ByteWriteChannel, - public: Boolean, - transform: ImageTransformation.() -> Unit -): Flow = callbackFlow { - channelDownloadRequest(path, channel, public, transform) { - onDownload { bytesSentTotal, contentLength -> - trySend(DownloadStatus.Progress(bytesSentTotal, contentLength)) - } - } - trySend(DownloadStatus.Success) - close() -} \ No newline at end of file diff --git a/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/FlowExtension.kt b/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/FlowExtension.kt new file mode 100644 index 000000000..146b0bbb1 --- /dev/null +++ b/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/FlowExtension.kt @@ -0,0 +1,229 @@ +package io.github.jan.supabase.storage + +import io.github.jan.supabase.exceptions.HttpRequestException +import io.github.jan.supabase.exceptions.RestException +import io.github.jan.supabase.network.HttpRequestOverride +import io.ktor.client.plugins.HttpRequestTimeoutException +import io.ktor.client.plugins.onDownload +import io.ktor.client.plugins.onUpload +import io.ktor.utils.io.ByteReadChannel +import io.ktor.utils.io.ByteWriteChannel +import kotlinx.coroutines.channels.ProducerScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow + +private fun downloadOverride(flowProducer: ProducerScope): HttpRequestOverride = { + onDownload { bytesSentTotal, contentLength -> + flowProducer.trySend(DownloadStatus.Progress(bytesSentTotal, contentLength ?: 0)) + } +} + +private fun uploadOverride(flowProducer: ProducerScope): HttpRequestOverride = { + onUpload { bytesSentTotal, contentLength -> + flowProducer.trySend(UploadStatus.Progress(bytesSentTotal, contentLength ?: 0)) + } +} + +/** + * Uploads a file in [BucketApi.bucketId] under [path] + * @param path The path to upload the file to + * @param options Additional options for the upload + * @param data The data to upload + * @return A flow that emits the upload progress and at last the key to the uploaded file + * @throws RestException or one of its subclasses if receiving an error response + * @throws HttpRequestTimeoutException if the request timed out + * @throws HttpRequestException on network related issues + */ +fun BucketApi.updateAsFlow( + path: String, + data: UploadData, + options: UploadOptionBuilder.() -> Unit = {} +): Flow = uploadAsFlowRequest { + update(path, data) { + options() + httpOverride(it) + } +} + +/** + * Uploads a file in [BucketApi.bucketId] under [path] + * @param path The path to upload the file to + * @param options Additional options for the upload + * @param data The data to upload + * @return A flow that emits the upload progress and at last the key to the uploaded file + * @throws RestException or one of its subclasses if receiving an error response + * @throws HttpRequestTimeoutException if the request timed out + * @throws HttpRequestException on network related issues + */ +fun BucketApi.uploadAsFlow(path: String, data: ByteArray, options: UploadOptionBuilder.() -> Unit = {}): Flow = uploadAsFlow(path, UploadData( + ByteReadChannel(data), data.size.toLong()), options) + +/** + * Uploads a file in [BucketApi.bucketId] under [path] using a presigned url + * @param path The path to upload the file to + * @param token The presigned url token + * @param data The data to upload + * @param options Additional options for the upload + * @return A flow that emits the upload progress and at last the key to the uploaded file + * @throws RestException or one of its subclasses if receiving an error response + * @throws HttpRequestTimeoutException if the request timed out + * @throws HttpRequestException on network related issues + */ +fun BucketApi.uploadToSignedUrlAsFlow( + path: String, + token: String, + data: UploadData, + options: UploadOptionBuilder.() -> Unit = {} +): Flow = uploadAsFlowRequest { + uploadToSignedUrl(path, token, data) { + options() + httpOverride(it) + } +} + +/** + * Uploads a file in [BucketApi.bucketId] under [path] using a presigned url + * @param path The path to upload the file to + * @param token The presigned url token + * @param data The data to upload + * @param options Additional options for the upload + * @return A flow that emits the upload progress and at last the key to the uploaded file + */ +fun BucketApi.uploadToSignedUrlAsFlow(path: String, token: String, data: ByteArray, options: UploadOptionBuilder.() -> Unit = {}): Flow = uploadToSignedUrlAsFlow(path, token, UploadData(ByteReadChannel(data), data.size.toLong()), options) + +/** + * Updates a file in [BucketApi.bucketId] under [path] + * @param path The path to update the file to + * @param data The new data + * @param options Additional options for the upload + * @return A flow that emits the upload progress and at last the key to the uploaded file + * @throws RestException or one of its subclasses if receiving an error response + * @throws HttpRequestTimeoutException if the request timed out + * @throws HttpRequestException on network related issues + */ +fun BucketApi.uploadAsFlow( + path: String, + data: UploadData, + options: UploadOptionBuilder.() -> Unit = {} +): Flow = uploadAsFlowRequest { + upload(path, data) { + options() + httpOverride(it) + } +} + +/** + * Updates a file in [BucketApi.bucketId] under [path] + * @param path The path to update the file to + * @param data The new data + * @param options Additional options for the upload + * @return A flow that emits the upload progress and at last the key to the uploaded file + * @throws RestException or one of its subclasses if receiving an error response + * @throws HttpRequestTimeoutException if the request timed out + * @throws HttpRequestException on network related issues + */ +fun BucketApi.updateAsFlow(path: String, data: ByteArray, options: UploadOptionBuilder.() -> Unit = {}): Flow = updateAsFlow(path, UploadData(ByteReadChannel(data), data.size.toLong()), options) + +private fun BucketApi.uploadAsFlowRequest( + producer: suspend (HttpRequestOverride) -> FileUploadResponse +) = callbackFlow { + val key = producer(uploadOverride(this@callbackFlow)) + trySend(UploadStatus.Success(key)) + close() +} + +/** + * Downloads a file from [BucketApi.bucketId] under [path] + * @param path The path to download + * @param options Additional options for the download + * @return A flow that emits the download progress and at last the data as a byte array + * @throws RestException or one of its subclasses if receiving an error response + * @throws HttpRequestTimeoutException if the request timed out + * @throws HttpRequestException on network related issues + */ +fun BucketApi.downloadAuthenticatedAsFlow( + path: String, + options: DownloadOptionBuilder.() -> Unit = {} +): Flow = downloadAsFlowRequest { + downloadAuthenticated(path) { + options() + httpOverride(it) + } +} + +/** + * Downloads a file from [BucketApi.bucketId] under [path] using the public url + * @param path The path to download + * @param options Additional options for the download + * @return A flow that emits the download progress and at last the data as a byte array + * @throws RestException or one of its subclasses if receiving an error response + * @throws HttpRequestTimeoutException if the request timed out + * @throws HttpRequestException on network related issues + */ +fun BucketApi.downloadPublicAsFlow( + path: String, + options: DownloadOptionBuilder.() -> Unit = {} +): Flow = + downloadAsFlowRequest { + downloadPublic(path) { + options() + httpOverride(it) + } + } + + +/** + * Downloads a file from [BucketApi.bucketId] under [path] + * @param path The path to download + * @param channel The channel to write the data to + * @param options Additional options for the download + * @return A flow that emits the download progress and at last the data as a byte array + * @throws RestException or one of its subclasses if receiving an error response + * @throws HttpRequestTimeoutException if the request timed out + * @throws HttpRequestException on network related issues + */ +fun BucketApi.downloadAuthenticatedAsFlow( + path: String, + channel: ByteWriteChannel, + options: DownloadOptionBuilder.() -> Unit = {} +): Flow = downloadAsFlowRequest { + downloadAuthenticated(path, channel) { + options() + httpOverride(it) + } + null +} + +/** + * Downloads a file from [BucketApi.bucketId] under [path] using the public url + * @param path The path to download + * @param channel The channel to write the data to + * @param options Additional options for the download + * @return A flow that emits the download progress and at last the data as a byte array + * @throws RestException or one of its subclasses if receiving an error response + * @throws HttpRequestTimeoutException if the request timed out + * @throws HttpRequestException on network related issues + */ +fun BucketApi.downloadPublicAsFlow( + path: String, + channel: ByteWriteChannel, + options: DownloadOptionBuilder.() -> Unit = {} +): Flow = downloadAsFlowRequest { + downloadPublic(path, channel) { + options() + httpOverride(it) + } + null +} + +private fun BucketApi.downloadAsFlowRequest( + producer: suspend (HttpRequestOverride) -> ByteArray? +) = callbackFlow { + //If null, the data gets streamed to a channel, so we don't need to emit it later + val data = producer(downloadOverride(this@callbackFlow)) + trySend(DownloadStatus.Success) + if (data != null) { + trySend(DownloadStatus.ByteData(data)) + } + close() +} diff --git a/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/Storage.kt b/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/Storage.kt index 64eea8f3c..53bb5a247 100644 --- a/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/Storage.kt +++ b/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/Storage.kt @@ -1,7 +1,9 @@ package io.github.jan.supabase.storage import io.github.jan.supabase.SupabaseClient +import io.github.jan.supabase.SupabaseSerializer import io.github.jan.supabase.annotations.SupabaseInternal +import io.github.jan.supabase.auth.authenticatedSupabaseApi import io.github.jan.supabase.bodyOrNull import io.github.jan.supabase.collections.AtomicMutableMap import io.github.jan.supabase.exceptions.BadRequestRestException @@ -10,9 +12,10 @@ import io.github.jan.supabase.exceptions.NotFoundRestException import io.github.jan.supabase.exceptions.RestException import io.github.jan.supabase.exceptions.UnauthorizedRestException import io.github.jan.supabase.exceptions.UnknownRestException -import io.github.jan.supabase.gotrue.authenticatedSupabaseApi import io.github.jan.supabase.logging.SupabaseLogger import io.github.jan.supabase.logging.w +import io.github.jan.supabase.plugins.CustomSerializationConfig +import io.github.jan.supabase.plugins.CustomSerializationPlugin import io.github.jan.supabase.plugins.MainConfig import io.github.jan.supabase.plugins.MainPlugin import io.github.jan.supabase.plugins.SupabasePluginProvider @@ -46,7 +49,7 @@ import kotlin.time.Duration.Companion.seconds * val bytes = bucket.downloadAuthenticated("icon.png") * ``` */ -sealed interface Storage : MainPlugin { +sealed interface Storage : MainPlugin, CustomSerializationPlugin { /** * Creates a new bucket in the storage @@ -116,8 +119,9 @@ sealed interface Storage : MainPlugin { */ data class Config( var transferTimeout: Duration = 120.seconds, - @PublishedApi internal var resumable: Resumable = Resumable() - ) : MainConfig() { + @PublishedApi internal var resumable: Resumable = Resumable(), + override var serializer: SupabaseSerializer? = null + ) : MainConfig(), CustomSerializationConfig { /** * @param cache the cache for caching resumable upload urls @@ -185,6 +189,8 @@ internal class StorageImpl(override val supabaseClient: SupabaseClient, override override val apiVersion: Int get() = Storage.API_VERSION + override val serializer: SupabaseSerializer = config.serializer ?: supabaseClient.defaultSerializer + @OptIn(SupabaseInternal::class) internal val api = supabaseClient.authenticatedSupabaseApi(this) { timeout { diff --git a/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/UploadOptionBuilder.kt b/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/UploadOptionBuilder.kt new file mode 100644 index 000000000..61bfa326e --- /dev/null +++ b/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/UploadOptionBuilder.kt @@ -0,0 +1,27 @@ +package io.github.jan.supabase.storage + +import io.github.jan.supabase.SupabaseSerializer +import io.github.jan.supabase.network.HttpRequestOverride +import io.ktor.http.ContentType + +/** + * Builder for uploading files with additional options + * @param serializer The serializer to use for encoding the metadata + * @param upsert Whether to update the file if it already exists + * @param contentType The content type of the file. If null, the content type will be inferred from the file extension + */ +class UploadOptionBuilder( + @PublishedApi internal val serializer: SupabaseSerializer, + var upsert: Boolean = false, + var contentType: ContentType? = null, + internal val httpRequestOverrides: MutableList = mutableListOf() +) { + + /** + * Overrides the HTTP request + */ + fun httpOverride(override: HttpRequestOverride) { + httpRequestOverrides.add(override) + } + +} diff --git a/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/resumable/ResumableClient.kt b/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/resumable/ResumableClient.kt index bd866dede..3e4a616bf 100644 --- a/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/resumable/ResumableClient.kt +++ b/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/resumable/ResumableClient.kt @@ -3,7 +3,7 @@ package io.github.jan.supabase.storage.resumable import io.github.jan.supabase.annotations.SupabaseInternal -import io.github.jan.supabase.gotrue.Auth +import io.github.jan.supabase.auth.Auth import io.github.jan.supabase.logging.d import io.github.jan.supabase.storage.BucketApi import io.github.jan.supabase.storage.Storage @@ -21,6 +21,7 @@ import io.ktor.http.defaultForFilePath import io.ktor.http.isSuccess import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.core.toByteArray +import io.ktor.utils.io.discard import kotlinx.coroutines.Deferred import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope diff --git a/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/resumable/ResumableUpload.kt b/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/resumable/ResumableUpload.kt index b8f557b5e..fb761c98e 100644 --- a/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/resumable/ResumableUpload.kt +++ b/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/resumable/ResumableUpload.kt @@ -1,7 +1,7 @@ package io.github.jan.supabase.storage.resumable import io.github.jan.supabase.annotations.SupabaseInternal -import io.github.jan.supabase.gotrue.Auth +import io.github.jan.supabase.auth.Auth import io.github.jan.supabase.logging.d import io.github.jan.supabase.logging.e import io.github.jan.supabase.logging.w @@ -21,6 +21,8 @@ import io.ktor.client.statement.bodyAsText import io.ktor.http.HttpStatusCode import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.cancel +import io.ktor.utils.io.readAvailable +import io.ktor.utils.io.writeFully import kotlinx.atomicfu.atomic import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -69,6 +71,7 @@ sealed interface ResumableUpload { } +@Suppress("LongParameterList") //TODO: refactor internal class ResumableUploadImpl( override val fingerprint: Fingerprint, private val path: String, @@ -149,7 +152,8 @@ internal class ResumableUploadImpl( private suspend fun uploadChunk(): Int { val limit = min(chunkSize, size.toInt() - offset) val buffer = ByteArray(limit.toInt()) - dataStream.readFully(buffer, 0, limit.toInt()) + dataStream.readAvailable(buffer, 0, limit.toInt()) + //dataStream.readFully(buffer, 0, limit.toInt()) val uploadResponse = httpClient.patch(locationUrl) { header("Tus-Resumable", TUS_VERSION) header("Content-Type", "application/offset+octet-stream") diff --git a/Storage/src/commonTest/kotlin/BucketApiFlowTest.kt b/Storage/src/commonTest/kotlin/BucketApiFlowTest.kt index c0eb57575..939a73065 100644 --- a/Storage/src/commonTest/kotlin/BucketApiFlowTest.kt +++ b/Storage/src/commonTest/kotlin/BucketApiFlowTest.kt @@ -73,7 +73,9 @@ class BucketApiFlowTest { @Test fun testUpsertAsFlowMethodWithByteArray() { testUploadAsFlow(upsert = true) { client, expectedPath, data -> - val flow = client.storage[bucketId].uploadAsFlow(expectedPath, data, upsert = true) + val flow = client.storage[bucketId].uploadAsFlow(expectedPath, data) { + upsert = true + } testUploadFlowWithByteArray(flow, expectedPath, data) } } @@ -95,7 +97,9 @@ class BucketApiFlowTest { val channel = ByteReadChannel(bytes) val expectedSize = bytes.size.toLong() val data = UploadData(channel, expectedSize) - val flow = client.storage[bucketId].uploadAsFlow(expectedPath, data, upsert = true) + val flow = client.storage[bucketId].uploadAsFlow(expectedPath, data) { + upsert = true + } testUploadFlowWithByteArray(flow, expectedPath, bytes) } } @@ -131,7 +135,9 @@ class BucketApiFlowTest { @Test fun testUpdateUpsertAsFlowMethodWithByteArray() { testUpdateAsFlow(upsert = true) { client, expectedPath, data -> - val flow = client.storage[bucketId].updateAsFlow(expectedPath, data, upsert = true) + val flow = client.storage[bucketId].updateAsFlow(expectedPath, data) { + upsert = true + } testUploadFlowWithByteArray(flow, expectedPath, data) } } @@ -153,7 +159,9 @@ class BucketApiFlowTest { val channel = ByteReadChannel(bytes) val expectedSize = bytes.size.toLong() val data = UploadData(channel, expectedSize) - val flow = client.storage[bucketId].updateAsFlow(expectedPath, data, upsert = true) + val flow = client.storage[bucketId].updateAsFlow(expectedPath, data) { + upsert = true + } testUploadFlowWithByteArray(flow, expectedPath, bytes) } } @@ -206,7 +214,9 @@ class BucketApiFlowTest { val channel = ByteReadChannel(bytes) val expectedSize = bytes.size.toLong() val data = UploadData(channel, expectedSize) - val flow = client.storage[bucketId].uploadToSignedUrlAsFlow(expectedPath, expectedToken, data, true) + val flow = client.storage[bucketId].uploadToSignedUrlAsFlow(expectedPath, expectedToken, data) { + upsert = true + } testUploadFlowWithByteArray(flow, expectedPath, bytes) } } @@ -214,7 +224,9 @@ class BucketApiFlowTest { @Test fun testUpsertToSignedUrlAsFlowMethodByteArray() { testUploadToSignedUrl(upsert = true) { client, expectedPath, bytes -> - val flow = client.storage[bucketId].uploadToSignedUrlAsFlow(expectedPath, expectedToken, bytes, true) + val flow = client.storage[bucketId].uploadToSignedUrlAsFlow(expectedPath, expectedToken, bytes) { + upsert = true + } testUploadFlowWithByteArray(flow, expectedPath, bytes) } } diff --git a/Storage/src/commonTest/kotlin/BucketApiTest.kt b/Storage/src/commonTest/kotlin/BucketApiTest.kt index b5e56849e..ece065241 100644 --- a/Storage/src/commonTest/kotlin/BucketApiTest.kt +++ b/Storage/src/commonTest/kotlin/BucketApiTest.kt @@ -21,11 +21,14 @@ import io.ktor.http.HttpMethod import io.ktor.http.headersOf import kotlinx.coroutines.test.runTest import kotlinx.datetime.Clock +import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.int import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.long +import kotlinx.serialization.json.put +import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.test.Test import kotlin.test.assertContentEquals import kotlin.test.assertEquals @@ -74,7 +77,9 @@ class BucketApiTest { ) }, request = { client, expectedPath, data -> - client.storage[bucketId].upload(expectedPath, data, upsert = true) + client.storage[bucketId].upload(expectedPath, data) { + upsert = true + } } ) } @@ -98,7 +103,9 @@ class BucketApiTest { ) }, request = { client, expectedPath, data -> - client.storage[bucketId].uploadToSignedUrl(path = expectedPath, token = expectedToken, data = data, upsert = false) + client.storage[bucketId].uploadToSignedUrl(path = expectedPath, token = expectedToken, data = data) { + upsert = false + } } ) } @@ -122,7 +129,9 @@ class BucketApiTest { ) }, request = { client, expectedPath, data -> - client.storage[bucketId].uploadToSignedUrl(path = expectedPath, token = expectedToken, data = data, upsert = true) + client.storage[bucketId].uploadToSignedUrl(path = expectedPath, token = expectedToken, data = data) { + upsert = true + } } ) } @@ -158,7 +167,9 @@ class BucketApiTest { ) }, request = { client, expectedPath, data -> - client.storage[bucketId].update(expectedPath, data, upsert = true) + client.storage[bucketId].update(expectedPath, data) { + upsert = true + } } ) } @@ -458,11 +469,12 @@ class BucketApiTest { quality = expectedQuality resize = expectedResize } - val data = if(authenticated) client.storage[bucketId].downloadAuthenticated(expectedPath, transform) else client.storage[bucketId].downloadPublic(expectedPath, transform) + val data = if(authenticated) client.storage[bucketId].downloadAuthenticated(expectedPath) { transform(transform) } else client.storage[bucketId].downloadPublic(expectedPath) { transform(transform) } assertContentEquals(expectedData, data, "Data should be [1, 2, 3]") } } + @OptIn(ExperimentalEncodingApi::class) private fun testUploadMethod( method: HttpMethod, urlPath: String, @@ -472,6 +484,9 @@ class BucketApiTest { ) { runTest { val expectedData = byteArrayOf(1, 2, 3) + val expectedMetadata = buildJsonObject { + put("key", "value") + } val client = createMockedSupabaseClient(configuration = configureClient) { val data = it.body.toByteArray() assertMethodIs(method, it.method) diff --git a/Storage/src/commonTest/kotlin/StorageTest.kt b/Storage/src/commonTest/kotlin/StorageTest.kt index efa878bd3..ab972775f 100644 --- a/Storage/src/commonTest/kotlin/StorageTest.kt +++ b/Storage/src/commonTest/kotlin/StorageTest.kt @@ -1,7 +1,7 @@ import io.github.jan.supabase.SupabaseClientBuilder -import io.github.jan.supabase.gotrue.Auth -import io.github.jan.supabase.gotrue.auth -import io.github.jan.supabase.gotrue.minimalSettings +import io.github.jan.supabase.auth.Auth +import io.github.jan.supabase.auth.auth +import io.github.jan.supabase.auth.minimalSettings import io.github.jan.supabase.storage.Storage import io.github.jan.supabase.storage.resumable.MemoryResumableCache import io.github.jan.supabase.storage.storage diff --git a/Storage/src/settingsMain/kotlin/io/github/jan/supabase/storage/resumable/SettingsResumableCache.kt b/Storage/src/settingsMain/kotlin/io/github/jan/supabase/storage/resumable/SettingsResumableCache.kt index 80036697a..9e558d836 100644 --- a/Storage/src/settingsMain/kotlin/io/github/jan/supabase/storage/resumable/SettingsResumableCache.kt +++ b/Storage/src/settingsMain/kotlin/io/github/jan/supabase/storage/resumable/SettingsResumableCache.kt @@ -12,29 +12,25 @@ import kotlinx.serialization.json.Json * A [ResumableCache] implementation using [com.russhwolf.settings.Settings]. This implementation saves the urls on the disk. If you want a memory only cache, use [Memory]. * Unsupported on Linux. */ +@OptIn(ExperimentalSettingsApi::class) class SettingsResumableCache(settings: Settings = Settings()) : ResumableCache { - @OptIn(ExperimentalSettingsApi::class) private val settings = settings.toSuspendSettings() - @OptIn(ExperimentalSettingsApi::class) override suspend fun set(fingerprint: Fingerprint, entry: ResumableCacheEntry) { settings.putString(fingerprint.value, Json.encodeToString(entry)) } - @OptIn(ExperimentalSettingsApi::class) override suspend fun get(fingerprint: Fingerprint): ResumableCacheEntry? { return settings.getStringOrNull(fingerprint.value)?.let { Json.decodeFromString(it) } } - @OptIn(ExperimentalSettingsApi::class) override suspend fun remove(fingerprint: Fingerprint) { settings.remove(fingerprint.value) } - @OptIn(ExperimentalSettingsApi::class) override suspend fun clear() { settings.keys().forEach { if(it.split(Fingerprint.FINGERPRINT_SEPARATOR).size == Fingerprint.FINGERPRINT_PARTS) remove(Fingerprint(it) ?: error("Invalid fingerprint $it")) diff --git a/Supabase/README.md b/Supabase/README.md index c1db735a0..af076ae14 100644 --- a/Supabase/README.md +++ b/Supabase/README.md @@ -4,23 +4,27 @@ The main Supabase-kt library. It provides a plugin system to extend the client w Supported targets: -| Target | **JVM** | **Android** | **JS** | **iOS** | **tvOS** | **watchOS** | **macOS** | **Windows** | **Linux** | -| ------ | ------- | ----------- | ------ | ------- | -------- | ----------- | --------- | ----------- | --------- | -| | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Target | **JVM** | **Android** | **JS** | **Wasm** | **Apple** | **Windows** | **Linux** | +|--------|---------|-------------|--------|----------|-----------|-------------|-----------| +| Status | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
In-depth Kotlin targets -**iOS:** iosArm64, iosSimulatorArm64, iosX64 - **JS**: Browser, NodeJS -**tvOS**: tvosArm64, tvosX64, tvosSimulatorArm64 +**Wasm**: wasm-js + +**Apple:** + +- iOS: iosArm64, iosSimulatorArm64, iosX64 + +- tvOS: tvosArm64, tvosX64, tvosSimulatorArm64 -**watchOS**: watchosArm64, watchosX64, watchosSimulatorArm64 +- watchOS: watchosArm64, watchosX64, watchosSimulatorArm64 -**MacOS**: macosX64, macosArm64 +- MacOS: macosX64, macosArm64 **Windows**: mingwX64 @@ -53,7 +57,7 @@ val supabase = createSupabaseClient( defaultLogLevel = LogLevel.DEBUG //Install a plugin - install(Auth) //from gotrue-kt + install(Auth) //from auth-kt } diff --git a/Supabase/src/commonMain/kotlin/io/github/jan/supabase/PlatformTarget.kt b/Supabase/src/commonMain/kotlin/io/github/jan/supabase/PlatformTarget.kt index 74c57051b..6cc798d57 100644 --- a/Supabase/src/commonMain/kotlin/io/github/jan/supabase/PlatformTarget.kt +++ b/Supabase/src/commonMain/kotlin/io/github/jan/supabase/PlatformTarget.kt @@ -1,10 +1,11 @@ +@file:Suppress("UndocumentedPublicProperty") package io.github.jan.supabase /** * Represents a target platform */ enum class PlatformTarget { - JVM, ANDROID, JS, WASM, IOS, WINDOWS, MACOS, TVOS, WATCHOS, LINUX; + JVM, ANDROID, JS, WASM_JS, IOS, WINDOWS, MACOS, TVOS, WATCHOS, LINUX; } /** diff --git a/Supabase/src/commonMain/kotlin/io/github/jan/supabase/SupabaseClient.kt b/Supabase/src/commonMain/kotlin/io/github/jan/supabase/SupabaseClient.kt index f9bbd39de..4d5a97d2f 100644 --- a/Supabase/src/commonMain/kotlin/io/github/jan/supabase/SupabaseClient.kt +++ b/Supabase/src/commonMain/kotlin/io/github/jan/supabase/SupabaseClient.kt @@ -14,7 +14,7 @@ import io.ktor.client.engine.HttpClientEngine /** * The main class to interact with Supabase. * - * To add functionality, add plugins like **GoTrue** or **Functions** within the [SupabaseClientBuilder] + * To add functionality, add plugins like **Auth** or **Functions** within the [SupabaseClientBuilder] */ sealed interface SupabaseClient { @@ -85,6 +85,7 @@ sealed interface SupabaseClient { } +@Suppress("LongParameterList") //TODO: maybe refactor internal class SupabaseClientImpl( override val supabaseUrl: String, override val supabaseKey: String, diff --git a/Supabase/src/commonMain/kotlin/io/github/jan/supabase/network/KtorSupabaseHttpClient.kt b/Supabase/src/commonMain/kotlin/io/github/jan/supabase/network/KtorSupabaseHttpClient.kt index a46079bf3..b86c9bf2f 100644 --- a/Supabase/src/commonMain/kotlin/io/github/jan/supabase/network/KtorSupabaseHttpClient.kt +++ b/Supabase/src/commonMain/kotlin/io/github/jan/supabase/network/KtorSupabaseHttpClient.kt @@ -29,6 +29,11 @@ import kotlin.time.Duration.Companion.milliseconds private const val HTTPS_PORT = 443 +/** + * A function that can be used to override the default request configuration + */ +typealias HttpRequestOverride = HttpRequestBuilder.() -> Unit + /** * A [SupabaseHttpClient] that uses ktor to send requests */ diff --git a/Supabase/src/commonMain/kotlin/io/github/jan/supabase/plugins/MainPlugin.kt b/Supabase/src/commonMain/kotlin/io/github/jan/supabase/plugins/MainPlugin.kt index 7657147b0..8a8a3e1bc 100644 --- a/Supabase/src/commonMain/kotlin/io/github/jan/supabase/plugins/MainPlugin.kt +++ b/Supabase/src/commonMain/kotlin/io/github/jan/supabase/plugins/MainPlugin.kt @@ -18,7 +18,7 @@ open class MainConfig { var customUrl: String? = null /** - * The jwt token used for this module. If null, the client will use the token from GoTrue's current session + * The jwt token used for this module. If null, the client will use the token from Auth's current session */ var jwtToken: String? = null diff --git a/Supabase/src/wasmJsMain/kotlin/io/github/jan/supabase/PlatformTarget.wasmJs.kt b/Supabase/src/wasmJsMain/kotlin/io/github/jan/supabase/PlatformTarget.wasmJs.kt new file mode 100644 index 000000000..ea6e549ee --- /dev/null +++ b/Supabase/src/wasmJsMain/kotlin/io/github/jan/supabase/PlatformTarget.wasmJs.kt @@ -0,0 +1,6 @@ +package io.github.jan.supabase + +/** + * The current target platform + */ +actual val CurrentPlatformTarget: PlatformTarget = PlatformTarget.WASM_JS \ No newline at end of file diff --git a/Supabase/src/wasmJsTest/kotlin/PlatformTargetTest.kt b/Supabase/src/wasmJsTest/kotlin/PlatformTargetTest.kt new file mode 100644 index 000000000..41a03d9a5 --- /dev/null +++ b/Supabase/src/wasmJsTest/kotlin/PlatformTargetTest.kt @@ -0,0 +1,13 @@ +import io.github.jan.supabase.CurrentPlatformTarget +import io.github.jan.supabase.PlatformTarget +import kotlin.test.Test +import kotlin.test.assertEquals + +actual class PlatformTargetTest { + + @Test + actual fun testPlatformTarget() { + assertEquals(PlatformTarget.WASM_JS, CurrentPlatformTarget, "The current platform target should be WASM_JS") + } + +} \ No newline at end of file diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 76caef3d8..2214c4ba1 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -77,7 +77,7 @@ Not all Ktor client [engines](https://ktor.io/docs/http-client-engines.html#limi ### Auth -
GoTrue#sessionStatus doesn't change when signing up +
Auth#sessionStatus doesn't change when signing up If you don't have auto confirm enabled, your user has to confirm their email/phone number before they can sign in. Once they confirm, the session status will change. On Android and iOS, you can use deeplinking to automatically sign-in when the user clicks on the confirmation link. diff --git a/build.gradle.kts b/build.gradle.kts index b52d74856..d090d7278 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension val excludedModules = listOf("plugins", "serializers", "test-common") fun libraryModules(withBom: Boolean = true, init: Project.() -> Unit) = configure( - allprojects.filter { it.name !in excludedModules && !it.path.contains("sample") && if(withBom) true else it.name != "bom" }, + allprojects.filter { it.name !in excludedModules && !it.path.contains("sample") && if(withBom) true else it.name != "bom" && it.name != it.rootProject.name }, init ) @@ -45,11 +45,13 @@ libraryModules { applyPublishing() } -libraryModules(false) { - apply(plugin = "io.gitlab.arturbosch.detekt") +val reportMerge by tasks.registering(io.gitlab.arturbosch.detekt.report.ReportMergeTask::class) { + output.set(rootProject.layout.buildDirectory.file("reports/detekt/merge.sarif")) +} +libraryModules(false) { applyDokkaWithConfiguration() - applyDetektWithConfiguration() + applyDetektWithConfiguration(reportMerge) } tasks.register("detektAll") { diff --git a/buildSrc/src/main/kotlin/Detekt.kt b/buildSrc/src/main/kotlin/Detekt.kt index 00c3a22ec..ff81e7b51 100644 --- a/buildSrc/src/main/kotlin/Detekt.kt +++ b/buildSrc/src/main/kotlin/Detekt.kt @@ -1,12 +1,15 @@ import io.gitlab.arturbosch.detekt.DetektPlugin import io.gitlab.arturbosch.detekt.extensions.DetektExtension +import io.gitlab.arturbosch.detekt.report.ReportMergeTask import org.gradle.api.Project +import org.gradle.api.tasks.TaskProvider import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.findPlugin +import org.gradle.kotlin.dsl.invoke import org.gradle.kotlin.dsl.withType -fun Project.applyDetektWithConfiguration() { +fun Project.applyDetektWithConfiguration(reportMerge: TaskProvider) { apply(plugin = "io.gitlab.arturbosch.detekt") plugins.findPlugin(DetektPlugin::class)?.let { @@ -20,12 +23,21 @@ fun Project.applyDetektWithConfiguration() { tasks.withType().configureEach { jvmTarget = "1.8" reports { - xml.required.set(true) - html.required.set(true) - txt.required.set(true) + xml.required.set(false) + html.required.set(false) + txt.required.set(false) sarif.required.set(true) - md.required.set(true) + md.required.set(false) } basePath = rootDir.absolutePath } + + tasks.withType().configureEach { + finalizedBy(reportMerge) + } + + reportMerge.invoke { + input.from(tasks.withType().map { it.sarifReportFile }) + } + } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Dokka.kt b/buildSrc/src/main/kotlin/Dokka.kt index d31a3e2db..65ade563b 100644 --- a/buildSrc/src/main/kotlin/Dokka.kt +++ b/buildSrc/src/main/kotlin/Dokka.kt @@ -7,8 +7,9 @@ fun Project.applyDokkaWithConfiguration() { dokkaSourceSets.configureEach { sourceLink { val name = when(moduleName.get()) { + "supabase-kt" -> "Supabase" "functions-kt" -> "Functions" - "gotrue-kt" -> "GoTrue" + "auth-kt" -> "Auth" "postgrest-kt" -> "Postgrest" "realtime-kt" -> "Realtime" "storage-kt" -> "Storage" @@ -16,6 +17,7 @@ fun Project.applyDokkaWithConfiguration() { "compose-auth" -> "plugins/ComposeAuth" "compose-auth-ui" -> "plugins/ComposeAuthUI" "coil-integration" -> "plugins/CoilIntegration" + "coil3-integration" -> "plugins/Coil3Integration" "imageloader-integration" -> "plugins/ImageLoaderIntegration" "serializer-moshi" -> "serializers/Moshi" "serializer-jackson" -> "serializers/Jackson" diff --git a/buildSrc/src/main/kotlin/KotlinPlugin.kt b/buildSrc/src/main/kotlin/KotlinPlugin.kt index cdafe1e5d..ad9119619 100644 --- a/buildSrc/src/main/kotlin/KotlinPlugin.kt +++ b/buildSrc/src/main/kotlin/KotlinPlugin.kt @@ -10,5 +10,5 @@ fun KotlinMultiplatformExtension.defaultConfig() { } compilerOptions.freeCompilerArgs.add("-Xexpect-actual-classes") applyDefaultHierarchyTemplate() - jvmToolchain(8) + // jvmToolchain(8) } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/KotlinTargets.kt b/buildSrc/src/main/kotlin/KotlinTargets.kt index b65d5fdbe..8b6e82e7f 100644 --- a/buildSrc/src/main/kotlin/KotlinTargets.kt +++ b/buildSrc/src/main/kotlin/KotlinTargets.kt @@ -1,4 +1,8 @@ +import org.gradle.kotlin.dsl.assign +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl fun KotlinMultiplatformExtension.iosTargets() { iosX64() @@ -29,13 +33,25 @@ fun KotlinMultiplatformExtension.desktopTargets() { linuxX64() } -fun KotlinMultiplatformExtension.jvmTargets() { - jvm() +fun KotlinMultiplatformExtension.configuredJvmTarget() { + jvm { + compilerOptions.jvmTarget = JvmTarget.JVM_1_8 + } +} + +fun KotlinMultiplatformExtension.configuredAndroidTarget() { androidTarget { publishLibraryVariants("release", "debug") + compilerOptions.jvmTarget = JvmTarget.JVM_1_8 } } +@OptIn(ExperimentalKotlinGradlePluginApi::class) +fun KotlinMultiplatformExtension.jvmTargets() { + configuredAndroidTarget() + configuredJvmTarget() +} + fun KotlinMultiplatformExtension.jsTarget() { js { browser { @@ -51,6 +67,22 @@ fun KotlinMultiplatformExtension.jsTarget() { } } +fun KotlinMultiplatformExtension.wasmJsTarget() { + wasmJs { + browser { + testTask { + enabled = false + } + } + nodejs { + testTask { + enabled = false + } + } + } +} + +@OptIn(ExperimentalWasmDsl::class) fun KotlinMultiplatformExtension.allTargets() { jvmTargets() jsTarget() @@ -58,10 +90,12 @@ fun KotlinMultiplatformExtension.allTargets() { watchosTargets() tvosTargets() desktopTargets() + wasmJsTarget() } fun KotlinMultiplatformExtension.composeTargets() { jvmTargets() jsTarget() iosTargets() + wasmJsTarget() } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Modules.kt b/buildSrc/src/main/kotlin/Modules.kt index 0cdea329d..4cf5bdc56 100644 --- a/buildSrc/src/main/kotlin/Modules.kt +++ b/buildSrc/src/main/kotlin/Modules.kt @@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler enum class SupabaseModule(val module: String) { SUPABASE("supabase-kt"), - GOTRUE("gotrue-kt"), + AUTH("auth-kt"), STORAGE("storage-kt"), REALTIME("realtime-kt"), FUNCTIONS("functions-kt"), diff --git a/buildSrc/src/main/kotlin/TargetHierarchy.kt b/buildSrc/src/main/kotlin/TargetHierarchy.kt index b152143f4..f9bb83202 100644 --- a/buildSrc/src/main/kotlin/TargetHierarchy.kt +++ b/buildSrc/src/main/kotlin/TargetHierarchy.kt @@ -17,5 +17,6 @@ fun KotlinHierarchyBuilder.settingsGroup() { withJs() withMingwX64() withApple() + withWasmJs() } } \ No newline at end of file diff --git a/demos/android-login/README.md b/demos/android-login/README.md index 8a2a312e3..b26e76ff5 100644 --- a/demos/android-login/README.md +++ b/demos/android-login/README.md @@ -1,10 +1,12 @@ +# Deprecation note +This sample has been deprecated. Checkout the [official video](https://www.youtube.com/watch?v=P_jZMDmodG4&pp=ygUWc3VwYWJhc2UgZ29vZ2xlIGtvdGxpbg%3D%3D) on how to use the Google Credential Manager to natively sign in with Google or checkout [Compose Auth](/plugins/ComposeAuth) # Android Login Demo This is a demo of an android app using Google OneTap/Native Sign-In & WebView for authentication **Available platforms:** Android -**Modules used:** GoTrue +**Modules used:** Auth https://user-images.githubusercontent.com/26686035/235766941-6a62c415-e07e-4d18-9706-0a246e6821eb.mp4 diff --git a/demos/android-login/common/src/androidMain/kotlin/io/github/jan/supabase/common/di/viewModel.kt b/demos/android-login/common/src/androidMain/kotlin/io/github/jan/supabase/common/di/viewModel.kt index 6964690a2..4f17e99bb 100644 --- a/demos/android-login/common/src/androidMain/kotlin/io/github/jan/supabase/common/di/viewModel.kt +++ b/demos/android-login/common/src/androidMain/kotlin/io/github/jan/supabase/common/di/viewModel.kt @@ -1,6 +1,5 @@ package io.github.jan.supabase.common.di -import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.core.module.Module actual fun Module.viewModel() { diff --git a/demos/android-login/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/components/OAuthWebView.kt b/demos/android-login/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/components/OAuthWebView.kt index b69f4ab4b..31c5d6854 100644 --- a/demos/android-login/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/components/OAuthWebView.kt +++ b/demos/android-login/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/components/OAuthWebView.kt @@ -1,13 +1,11 @@ package io.github.jan.supabase.common.ui.components import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import com.google.accompanist.web.WebView import com.google.accompanist.web.WebViewNavigator import com.google.accompanist.web.WebViewState -import io.ktor.client.plugins.UserAgent import io.ktor.http.Url @Composable diff --git a/demos/android-login/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/screen/OAuthScreen.kt b/demos/android-login/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/screen/OAuthScreen.kt index cd0f34fac..19c895edb 100644 --- a/demos/android-login/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/screen/OAuthScreen.kt +++ b/demos/android-login/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/screen/OAuthScreen.kt @@ -5,8 +5,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Refresh @@ -18,7 +16,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp import com.google.accompanist.web.rememberWebViewNavigator import com.google.accompanist.web.rememberWebViewState import io.github.jan.supabase.common.ui.components.OAuthWebView diff --git a/demos/multiplatform-deeplinks/common/src/androidMain/kotlin/io/github/jan/supabase/common/di/viewModel.kt b/demos/multiplatform-deeplinks/common/src/androidMain/kotlin/io/github/jan/supabase/common/di/viewModel.kt index 6964690a2..4f17e99bb 100644 --- a/demos/multiplatform-deeplinks/common/src/androidMain/kotlin/io/github/jan/supabase/common/di/viewModel.kt +++ b/demos/multiplatform-deeplinks/common/src/androidMain/kotlin/io/github/jan/supabase/common/di/viewModel.kt @@ -1,6 +1,5 @@ package io.github.jan.supabase.common.di -import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.core.module.Module actual fun Module.viewModel() { diff --git a/demos/multiplatform-deeplinks/common/src/commonMain/kotlin/io/github/jan/supabase/common/AppViewModel.kt b/demos/multiplatform-deeplinks/common/src/commonMain/kotlin/io/github/jan/supabase/common/AppViewModel.kt index 02b67d540..9f1a45adf 100644 --- a/demos/multiplatform-deeplinks/common/src/commonMain/kotlin/io/github/jan/supabase/common/AppViewModel.kt +++ b/demos/multiplatform-deeplinks/common/src/commonMain/kotlin/io/github/jan/supabase/common/AppViewModel.kt @@ -1,13 +1,12 @@ package io.github.jan.supabase.common -import co.touchlab.kermit.Logger import io.github.jan.supabase.SupabaseClient +import io.github.jan.supabase.annotations.SupabaseInternal import io.github.jan.supabase.gotrue.gotrue import io.github.jan.supabase.gotrue.parseFragmentAndImportSession import io.github.jan.supabase.gotrue.providers.Google import io.github.jan.supabase.gotrue.providers.builtin.Email -import io.github.jan.supabase.annotations.SupabaseInternal import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch diff --git a/detekt.yml b/detekt.yml index 71f38d60c..0762d3878 100644 --- a/detekt.yml +++ b/detekt.yml @@ -67,7 +67,7 @@ comments: endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)' KDocReferencesNonPublicProperty: active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**'] + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**', '**/wasmJsTest/**'] OutdatedDocumentation: active: false matchTypeParameters: true @@ -75,10 +75,10 @@ comments: allowParamOnConstructorProperties: false UndocumentedPublicClass: active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**'] + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**', '**/wasmJsTest/**'] searchInNestedClass: true searchInInnerClass: true - searchInInnerObject: true + searchInInnerObject: false searchInInnerInterface: true searchInProtectedClass: false ignoreAnnotated: @@ -86,14 +86,14 @@ comments: - 'SupabaseExperimental' UndocumentedPublicFunction: active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**'] + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**', '**/wasmJsTest/**'] searchProtectedFunction: false ignoreAnnotated: - 'SupabaseInternal' - 'SupabaseExperimental' UndocumentedPublicProperty: active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**'] + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**', '**/wasmJsTest/**'] searchProtectedProperty: false ignoreAnnotated: - 'SupabaseInternal' @@ -114,7 +114,7 @@ complexity: includePrivateDeclarations: false ignoreOverloaded: false CyclomaticComplexMethod: - active: false + active: true threshold: 15 ignoreSingleWhenExpression: false ignoreSimpleWhenEntries: false @@ -139,12 +139,14 @@ complexity: active: true threshold: 70 LongParameterList: - active: false + active: true functionThreshold: 6 constructorThreshold: 7 ignoreDefaultParameters: false ignoreDataClasses: true ignoreAnnotatedParameter: [] + ignoreAnnotated: + - 'Composable' MethodOverloading: active: false threshold: 6 @@ -168,14 +170,14 @@ complexity: active: false StringLiteralDuplication: active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**'] + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**', '**/wasmJsTest/**'] threshold: 3 ignoreAnnotation: true excludeStringsWithLessThan5Characters: true ignoreStringsRegex: '$^' TooManyFunctions: active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**'] + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**', '**/wasmJsTest/**'] thresholdInFiles: 11 thresholdInClasses: 11 thresholdInInterfaces: 11 @@ -188,7 +190,7 @@ complexity: coroutines: active: true GlobalCoroutineUsage: - active: false + active: true InjectDispatcher: active: false dispatcherNames: @@ -252,7 +254,7 @@ exceptions: - 'toString' InstanceOfCheckForException: active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**'] + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**', '**/wasmJsTest/**'] NotImplementedDeclaration: active: false ObjectExtendsThrowable: @@ -278,7 +280,7 @@ exceptions: active: false ThrowingExceptionsWithoutMessageOrCause: active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**'] + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**', '**/wasmJsTest/**'] exceptions: - 'ArrayIndexOutOfBoundsException' - 'Exception' @@ -293,7 +295,7 @@ exceptions: active: true TooGenericExceptionCaught: active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**'] + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**', '**/wasmJsTest/**'] exceptionNames: - 'ArrayIndexOutOfBoundsException' - 'Error' @@ -339,7 +341,7 @@ naming: minimumFunctionNameLength: 3 FunctionNaming: active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**'] + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**', '**/wasmJsTest/**'] functionPattern: '[a-z][a-zA-Z0-9]*' excludeClassPattern: '$^' ignoreAnnotated: @@ -399,10 +401,10 @@ performance: threshold: 3 ForEachOnRange: active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**'] + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**', '**/wasmJsTest/**'] SpreadOperator: active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**'] + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**', '**/wasmJsTest/**'] UnnecessaryPartOfBinaryExpression: active: false UnnecessaryTemporaryInstantiation: @@ -475,7 +477,7 @@ potential-bugs: active: true LateinitUsage: active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**'] + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**', '**/wasmJsTest/**'] ignoreOnClassesPattern: '' MapGetWithNotNullAssertionOperator: active: true @@ -502,7 +504,7 @@ potential-bugs: active: true UnsafeCallOnNullableType: active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**'] + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/mingwX64Test/**', '**/macosTest/**', '**/appleTest/**', '**/linuxTest/**', '**/wasmJsTest/**'] UnsafeCast: active: true UnusedUnaryOperator: diff --git a/gradle.properties b/gradle.properties index d55d38382..c3421909c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,11 +3,12 @@ android.useAndroidX=true org.gradle.jvmargs=-Xmx4096m kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.native.ignoreDisabledTargets=true -kotlin.native.ignoreIncorrectDependencies=true org.gradle.parallel=true +kotlin.suppressGradlePluginWarnings=IncorrectCompileOnlyDependencyWarning org.jetbrains.compose.experimental.uikit.enabled=true org.jetbrains.compose.experimental.jscanvas.enabled=true +org.jetbrains.compose.experimental.wasm.enabled=true -supabase-version = 2.6.1 +supabase-version = 3.0.0-beta-1 base-group = io.github.jan-tennert.supabase diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 24db4733d..d369f90c4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,37 +1,38 @@ [versions] -accompanist-permissions = "0.35.1-alpha" -kotlin = "2.0.10" -ktor = "2.3.12" +kotlin = "2.0.20" +accompanist-permissions = "0.36.0" +ktor = "3.0.0-rc-1" dokka = "1.9.20" -kotlinx-datetime = "0.6.0" +kotlinx-datetime = "0.6.1" kermit = "2.0.4" atomicfu = "0.25.0" -coroutines = "1.8.1" -android-lifecycle = "2.8.4" +coroutines = "1.9.0" +android-lifecycle = "2.8.5" androidx-startup = "1.1.1" -androidx-activity-compose = "1.9.1" -multiplatform-settings = "1.1.1" +androidx-activity-compose = "1.9.2" +multiplatform-settings = "1.2.0" complete-kotlin = "1.1.0" agp = "8.3.2" maven-publish = "0.29.0" -apollo-kotlin = "3.8.5" +apollo-kotlin = "4.0.0" korlibs = "4.0.10" -detekt = "1.23.6" +detekt = "1.23.7" moshi = "1.15.1" jackson = "2.17.2" browser = "1.8.0" googleid = "1.1.1" compose = "1.6.11" androidsvg = "1.4" -imageloader = "1.8.2" -coil = "2.7.0" -okio = "3.9.0" +imageloader = "1.8.3" +coil2 = "2.7.0" +coil3 = "3.0.0-alpha10" +okio = "3.9.1" credentials = "1.2.2" koin = "3.5.6" androidx-core = "1.13.1" androidx-compat = "1.7.0" -androidx-lifecycle = "2.8.4" -filekit = "0.7.0" +androidx-lifecycle = "2.8.5" +filekit = "0.8.2" [plugins] kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } @@ -93,7 +94,7 @@ multiplatform-settings-no-arg = { module = "com.russhwolf:multiplatform-settings multiplatform-settings-coroutines = { module = "com.russhwolf:multiplatform-settings-coroutines", version.ref = "multiplatform-settings" } multiplatform-settings-test = { module = "com.russhwolf:multiplatform-settings-test", version.ref = "multiplatform-settings" } -apollo-kotlin = { module = "com.apollographql.apollo3:apollo-runtime", version.ref = "apollo-kotlin" } +apollo-kotlin = { module = "com.apollographql.apollo:apollo-runtime", version.ref = "apollo-kotlin" } krypto = { module = "com.soywiz.korlibs.krypto:krypto", version.ref = "korlibs" } okio = { module = "com.squareup.okio:okio", version.ref = "okio" } @@ -107,7 +108,9 @@ jackson-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin" androidsvg = { module = "com.caverock:androidsvg-aar", version.ref = "androidsvg" } turbine = { module = "app.cash.turbine:turbine", version = "1.1.0" } -coil = { module = "io.coil-kt:coil", version.ref = "coil" } +coil3 = { module = "io.coil-kt.coil3:coil", version.ref = "coil3" } +coil3-network-core = { module = "io.coil-kt.coil3:coil-network-core", version.ref = "coil3" } +coil2 = { module = "io.coil-kt:coil", version.ref = "coil2" } imageloader = { module = "io.github.qdsfdhvh:image-loader", version.ref = "imageloader" } # Sample dependencies @@ -116,8 +119,8 @@ koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" } filekit-core = { module = "io.github.vinceglb:filekit-core", version.ref = "filekit" } filekit-compose = { module = "io.github.vinceglb:filekit-compose", version.ref = "filekit" } accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist-permissions" } -coil-svg = { module = "io.coil-kt:coil-svg", version.ref = "coil" } -coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" } +coil2-svg = { module = "io.coil-kt:coil-svg", version.ref = "coil2" } +coil2-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil2" } [bundles] ktor-client = ["ktor-client-core", "ktor-client-content-negotiation", "ktor-json"] @@ -125,3 +128,4 @@ multiplatform-settings = ["multiplatform-settings-no-arg", "multiplatform-settin testing = ["kotlinx-coroutines-test", "multiplatform-settings-test", "ktor-client-mock", "kotlin-test"] moshi = ["moshi", "moshi-kotlin"] jackson = ["jackson", "jackson-kotlin"] +coil3 = ["coil3", "coil3-network-core"] diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136f..a4b76b953 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a4413138c..09523c0e5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a426..f5feea6d6 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 25da30dbd..9d21a2183 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## diff --git a/plugins/ApolloGraphQL/README.md b/plugins/ApolloGraphQL/README.md index 264f7269a..73440032a 100644 --- a/plugins/ApolloGraphQL/README.md +++ b/plugins/ApolloGraphQL/README.md @@ -4,27 +4,27 @@ Extends Supabase-kt with an Apollo GraphQL Client. Supported targets: -| Target | **JVM** | **Android** | **JS** | **iOS** | **tvOS** | **watchOS** | **macOS** | **Windows** | **Linux** | -| ------ | ------- | ----------- | ------ | ------- | -------- | ----------- | --------- | ----------- | --------- | -| | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | +| Target | **JVM** | **Android** | **JS** | **Wasm** | **Apple** | +|--------|---------|-------------|--------|----------|-----------| +| Status | ✅ | ✅ | ✅ | ✅ | ✅ |
In-depth Kotlin targets -**iOS:** iosArm64, iosSimulatorArm64, iosX64 - **JS**: Browser, NodeJS -**tvOS**: tvosArm64, tvosX64, tvosSimulatorArm64 +**Wasm**: wasm-js + +**Apple:** -**watchOS**: watchosArm64, watchosX64, watchosSimulatorArm64 +- iOS: iosArm64, iosSimulatorArm64, iosX64 -**MacOS**: macosX64, macosArm64 +- tvOS: tvosArm64, tvosX64, tvosSimulatorArm64 -**Windows**: mingwX64 +- watchOS: watchosArm64, watchosX64, watchosSimulatorArm64 -**Linux**: linuxX64 +- MacOS: macosX64, macosArm64
diff --git a/plugins/ApolloGraphQL/build.gradle.kts b/plugins/ApolloGraphQL/build.gradle.kts index af8f4e717..677476d97 100644 --- a/plugins/ApolloGraphQL/build.gradle.kts +++ b/plugins/ApolloGraphQL/build.gradle.kts @@ -18,14 +18,21 @@ kotlin { macosTargets() watchosArm64() watchosSimulatorArm64() + wasmJsTarget() tvosTargets() sourceSets { val commonMain by getting { dependencies { - addModules(SupabaseModule.GOTRUE, SupabaseModule.SUPABASE) + addModules(SupabaseModule.AUTH, SupabaseModule.SUPABASE) api(libs.apollo.kotlin) } } + val commonTest by getting { + dependencies { + implementation(libs.bundles.testing) + implementation(project(":test-common")) + } + } } } diff --git a/plugins/ApolloGraphQL/src/commonMain/kotlin/io/github/jan/supabase/graphql/ApolloHttpInterceptor.kt b/plugins/ApolloGraphQL/src/commonMain/kotlin/io/github/jan/supabase/graphql/ApolloHttpInterceptor.kt new file mode 100644 index 000000000..608d37fd7 --- /dev/null +++ b/plugins/ApolloGraphQL/src/commonMain/kotlin/io/github/jan/supabase/graphql/ApolloHttpInterceptor.kt @@ -0,0 +1,26 @@ +package io.github.jan.supabase.graphql + +import com.apollographql.apollo.network.http.HttpInterceptor +import com.apollographql.apollo.network.http.HttpInterceptorChain +import io.github.jan.supabase.SupabaseClient +import io.github.jan.supabase.auth.resolveAccessToken +import io.github.jan.supabase.logging.d +import io.ktor.http.HttpHeaders +import com.apollographql.apollo.api.http.HttpRequest as ApolloHttpRequest +import com.apollographql.apollo.api.http.HttpResponse as ApolloHttpResponse + +internal class ApolloHttpInterceptor(private val supabaseClient: SupabaseClient, private val config: GraphQL.Config) : HttpInterceptor { + + override suspend fun intercept( + request: ApolloHttpRequest, + chain: HttpInterceptorChain + ): ApolloHttpResponse { + GraphQL.logger.d { "Intercepting Apollo request with url ${request.url}" } + val accessToken = supabaseClient.resolveAccessToken(config.jwtToken) ?: error("Access token should not be null") + val newRequest = request.newBuilder().apply { + addHeader(HttpHeaders.Authorization, "Bearer $accessToken") + } + return chain.proceed(newRequest.build()) + } + +} diff --git a/plugins/ApolloGraphQL/src/commonMain/kotlin/io/github/jan/supabase/graphql/GraphQL.kt b/plugins/ApolloGraphQL/src/commonMain/kotlin/io/github/jan/supabase/graphql/GraphQL.kt index cf6d88508..b6f752ba6 100644 --- a/plugins/ApolloGraphQL/src/commonMain/kotlin/io/github/jan/supabase/graphql/GraphQL.kt +++ b/plugins/ApolloGraphQL/src/commonMain/kotlin/io/github/jan/supabase/graphql/GraphQL.kt @@ -1,21 +1,14 @@ package io.github.jan.supabase.graphql -import com.apollographql.apollo3.ApolloClient -import com.apollographql.apollo3.network.http.HttpInterceptor -import com.apollographql.apollo3.network.http.HttpInterceptorChain +import com.apollographql.apollo.ApolloClient import io.github.jan.supabase.BuildConfig import io.github.jan.supabase.SupabaseClient import io.github.jan.supabase.exceptions.RestException -import io.github.jan.supabase.gotrue.Auth import io.github.jan.supabase.logging.SupabaseLogger -import io.github.jan.supabase.logging.d import io.github.jan.supabase.plugins.MainConfig import io.github.jan.supabase.plugins.MainPlugin import io.github.jan.supabase.plugins.SupabasePluginProvider import io.ktor.client.statement.HttpResponse -import io.ktor.http.HttpHeaders -import com.apollographql.apollo3.api.http.HttpRequest as ApolloHttpRequest -import com.apollographql.apollo3.api.http.HttpResponse as ApolloHttpResponse /** * Adds an Apollo GraphQL client to supabase-kt with all necessary headers automatically managed. @@ -78,7 +71,7 @@ internal class GraphQLImpl(override val config: GraphQL.Config, override val sup serverUrl(config.customUrl ?: resolveUrl()) addHttpHeader("apikey", supabaseClient.supabaseKey) addHttpHeader("X-Client-Info", "supabase-kt/${BuildConfig.PROJECT_VERSION}") - addHttpInterceptor(ApolloHttpInterceptor()) + addHttpInterceptor(ApolloHttpInterceptor(supabaseClient, config)) apply(config.apolloConfiguration) }.build() @@ -86,21 +79,6 @@ internal class GraphQLImpl(override val config: GraphQL.Config, override val sup throw UnsupportedOperationException("Use apolloClient for GraphQL requests") } - inner class ApolloHttpInterceptor: HttpInterceptor { - override suspend fun intercept( - request: ApolloHttpRequest, - chain: HttpInterceptorChain - ): ApolloHttpResponse { - GraphQL.logger.d { "Intercepting Apollo request with url ${request.url}" } - val accessToken = config.jwtToken ?: supabaseClient.pluginManager.getPluginOrNull(Auth)?.currentAccessTokenOrNull() ?: supabaseClient.supabaseKey - val newRequest = request.newBuilder().apply { - addHeader(HttpHeaders.Authorization, "Bearer $accessToken") - } - return chain.proceed(newRequest.build()) - } - - } - } diff --git a/plugins/ApolloGraphQL/src/commonTest/kotlin/ApolloHttpInterceptorTest.kt b/plugins/ApolloGraphQL/src/commonTest/kotlin/ApolloHttpInterceptorTest.kt new file mode 100644 index 000000000..27d500de5 --- /dev/null +++ b/plugins/ApolloGraphQL/src/commonTest/kotlin/ApolloHttpInterceptorTest.kt @@ -0,0 +1,92 @@ +import com.apollographql.apollo.annotations.ApolloExperimental +import com.apollographql.apollo.api.http.HttpMethod +import com.apollographql.apollo.api.http.HttpRequest +import com.apollographql.apollo.api.http.HttpResponse +import com.apollographql.apollo.api.http.get +import com.apollographql.apollo.network.http.HttpInterceptorChain +import io.github.jan.supabase.SupabaseClient +import io.github.jan.supabase.auth.Auth +import io.github.jan.supabase.auth.auth +import io.github.jan.supabase.auth.minimalSettings +import io.github.jan.supabase.graphql.ApolloHttpInterceptor +import io.github.jan.supabase.graphql.GraphQL +import io.github.jan.supabase.graphql.graphql +import io.github.jan.supabase.testing.createMockedSupabaseClient +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertEquals + +class ApolloHttpInterceptorTest { + + @Test + fun testApolloHttpInterceptorWithApiKey() { + runTest { + val supabaseClient = createMockedSupabaseClient( + supabaseKey = "testkey", + configuration = { + install(GraphQL) + } + ) + testInterceptor(supabaseClient, "testkey") + } + } + + @Test + fun testApolloHttpInterceptorWithAuthToken() { + runTest { + val supabaseClient = createMockedSupabaseClient( + configuration = { + install(GraphQL) + install(Auth) { + minimalSettings() + } + } + ) + supabaseClient.auth.importAuthToken("testtoken") + testInterceptor(supabaseClient, "testtoken") + } + } + + @Test + fun testApolloHttpInterceptorWithAccessToken() { + runTest { + val supabaseClient = createMockedSupabaseClient( + configuration = { + install(GraphQL) + accessToken = { + "testtoken" + } + } + ) + testInterceptor(supabaseClient, "testtoken") + } + } + + @Test + fun testApolloHttpInterceptorWithACustomToken() { + runTest { + val supabaseClient = createMockedSupabaseClient( + configuration = { + install(GraphQL) { + jwtToken = "testtoken" + } + } + ) + testInterceptor(supabaseClient, "testtoken") + } + } + + @OptIn(ApolloExperimental::class) + private suspend fun testInterceptor(supabaseClient: SupabaseClient, token: String) { + val interceptor = ApolloHttpInterceptor(supabaseClient, supabaseClient.graphql.config) + interceptor.intercept(HttpRequest.Builder(HttpMethod.Get, "").build(), object : HttpInterceptorChain { + + override suspend fun proceed(request: HttpRequest): HttpResponse { + assertEquals("Bearer $token", request.headers.get("Authorization")) + return HttpResponse.Builder(200).build() + } + + }) + } + +} diff --git a/plugins/ApolloGraphQL/src/commonTest/kotlin/GraphQLTest.kt b/plugins/ApolloGraphQL/src/commonTest/kotlin/GraphQLTest.kt new file mode 100644 index 000000000..bc9baf677 --- /dev/null +++ b/plugins/ApolloGraphQL/src/commonTest/kotlin/GraphQLTest.kt @@ -0,0 +1,37 @@ +import com.apollographql.apollo.api.http.HttpHeader +import io.github.jan.supabase.BuildConfig +import io.github.jan.supabase.graphql.ApolloHttpInterceptor +import io.github.jan.supabase.graphql.GraphQL +import io.github.jan.supabase.graphql.graphql +import io.github.jan.supabase.testing.createMockedSupabaseClient +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class GraphQLTest { + + @Test + fun testCreatedGraphQLClient() { + val supabaseClient = createMockedSupabaseClient( + supabaseUrl = "https://test.supabase.co", + supabaseKey = "testkey", + configuration = { + install(GraphQL) { + apolloConfiguration { + webSocketIdleTimeoutMillis(1000) + } + } + } + ) + val client = supabaseClient.graphql.apolloClient + assertEquals("https://test.supabase.co/graphql/v1", client.newBuilder().httpServerUrl) + assertNotNull(client.httpHeaders) + assertContains(client.httpHeaders!!, HttpHeader("apikey", "testkey")) + assertContains(client.httpHeaders!!, HttpHeader("X-Client-Info", "supabase-kt/${BuildConfig.PROJECT_VERSION}")) + assertTrue { client.newBuilder().httpInterceptors.any { it is ApolloHttpInterceptor } } + assertEquals(1000, client.newBuilder().webSocketIdleTimeoutMillis) + } + +} \ No newline at end of file diff --git a/plugins/Coil3Integration/README.md b/plugins/Coil3Integration/README.md new file mode 100644 index 000000000..35b3389aa --- /dev/null +++ b/plugins/Coil3Integration/README.md @@ -0,0 +1,105 @@ +# Supabase-kt Coil 3 Integration + +Extends supabase-kt with a Coil3 integration for image loading. +**Requires supabase-kt `3.0.0` or higher.** + +Current supported Coil3 version: `3.0.0-alpha10` + +Supported targets: + +| Target | **JVM** | **Android** | **JS** | **Wasm** | **iOS** | +|--------|---------|-------------|--------|----------|---------| +| Status | ✅ | ✅ | ✅ | ✅ | ✅ | + +
+ +In-depth Kotlin targets + +**JS**: Browser + +**Wasm**: wasm-js + +**iOS**: iosArm64, iosSimulatorArm64, iosX64 + +
+ +# Installation + +Newest version: [![](https://img.shields.io/github/release/supabase-community/supabase-kt?label=)](https://github.com/supabase-community/supabase-kt/releases) + +```kotlin +dependencies { + implementation("io.github.jan-tennert.supabase:coil3-integration:VERSION") +} +``` + +Install plugin in main SupabaseClient. See the [documentation](https://supabase.com/docs/reference/kotlin/initializing) for more information +```kotlin +val client = createSupabaseClient( + supabaseUrl = "https://id.supabase.co", + supabaseKey = "apikey" +) { + //... + install(Storage) { + //your config + } + install(Coil3Integration) +} +``` + +If you don't have a coil-network artifact in your dependencies, you will need to add it. See the [Coil documentation](https://coil-kt.github.io/coil/upgrading_to_coil3/#network-images) for more information. + +# Usage + +### Add Supabase Fetcher to Coil + +Create a new ImageLoader with the Supabase Fetcher and a [network fetcher](https://coil-kt.github.io/coil/upgrading_to_coil3/#network-images). + +```kotlin +ImageLoader.Builder(context) + .components { + add(supabaseClient.coil3) + //You also need the add the network fetcher factory if you don't have it already + //Depending on the network artifact you added, this will be different + add(KtorNetworkFetcherFactory()) + } + .build() +``` + +You can also replace the default Coil Image Loader in your application. +For Compose Multiplatform Applications using the `coil-compose` dependency, you can use the `setSingletonImageLoaderFactory` composable function: +```kotlin +setSingletonImageLoaderFactory { platformContext -> + ImageLoader.Builder(platformContext) + .components { + add(supabaseClient.coil3) + //Your network fetcher factory + add(KtorNetworkFetcherFactory()) + } + .build() +} +``` +You call this composable before any `Image` composable is used. Presumably in your `Root` composable. + +See the [Coil documentation](https://coil-kt.github.io/coil/getting_started/#image-loaders) for more information. + +### Display images from Supabase Storage + +You can easily create an image request like this: + +```kotlin +val request = ImageRequest.Builder(context) + .data(authenticatedStorageItem("icons", "profile.png")) //for non-public buckets + .build() +``` + +Or if you are using [Compose Multiplatform](https://coil-kt.github.io/coil/compose/): + +```kotlin +AsyncImage( + model = publicStorageItem("icons", "profile.png"), //for public buckets + contentDescription = null, +) +``` + +The Coil integration will automatically add the Authorization header to the request if the bucket is not public. \ No newline at end of file diff --git a/plugins/Coil3Integration/build.gradle.kts b/plugins/Coil3Integration/build.gradle.kts new file mode 100644 index 000000000..9ef71811a --- /dev/null +++ b/plugins/Coil3Integration/build.gradle.kts @@ -0,0 +1,27 @@ +plugins { + id(libs.plugins.kotlin.multiplatform.get().pluginId) + id(libs.plugins.android.library.get().pluginId) +} + +description = "Extends supabase-kt with a Coil3 integration for easy image loading" + +repositories { + mavenCentral() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") +} + +@OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class) +kotlin { + defaultConfig() + composeTargets() + sourceSets { + val commonMain by getting { + dependencies { + api(project(":storage-kt")) + api(libs.bundles.coil3) + } + } + } +} + +configureLibraryAndroidTarget() \ No newline at end of file diff --git a/plugins/Coil3Integration/src/androidMain/AndroidManifest.xml b/plugins/Coil3Integration/src/androidMain/AndroidManifest.xml new file mode 100644 index 000000000..1d26c87a1 --- /dev/null +++ b/plugins/Coil3Integration/src/androidMain/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/plugins/Coil3Integration/src/commonMain/kotlin/io/github/jan/supabase/coil/Coil3Integration.kt b/plugins/Coil3Integration/src/commonMain/kotlin/io/github/jan/supabase/coil/Coil3Integration.kt new file mode 100644 index 000000000..f6a740412 --- /dev/null +++ b/plugins/Coil3Integration/src/commonMain/kotlin/io/github/jan/supabase/coil/Coil3Integration.kt @@ -0,0 +1,61 @@ +package io.github.jan.supabase.coil + +import coil3.ImageLoader +import coil3.fetch.Fetcher +import coil3.request.ImageRequest +import coil3.request.Options +import io.github.jan.supabase.SupabaseClient +import io.github.jan.supabase.annotations.SupabaseExperimental +import io.github.jan.supabase.logging.SupabaseLogger +import io.github.jan.supabase.logging.d +import io.github.jan.supabase.plugins.SupabasePlugin +import io.github.jan.supabase.plugins.SupabasePluginProvider +import io.github.jan.supabase.storage.StorageItem +import io.github.jan.supabase.storage.storage + +/** + * A plugin that implements [Fetcher.Factory] to support using [StorageItem] as data when creating an [ImageRequest] or using it as a model in Compose Multiplatform. + */ +interface Coil3Integration: SupabasePlugin, Fetcher.Factory { + + /** + * The configuration for the coil integration. + */ + class Config + + companion object : SupabasePluginProvider { + + override val key = "coil3" + + override val logger: SupabaseLogger = SupabaseClient.createLogger("Supabase-Coil3Integration") + + override fun create(supabaseClient: SupabaseClient, config: Config): Coil3Integration { + return Coil3IntegrationImpl(supabaseClient, config) + } + + override fun createConfig(init: Config.() -> Unit): Config { + return Config().apply(init) + } + + } + +} + +internal class Coil3IntegrationImpl( + override val supabaseClient: SupabaseClient, + override val config: Coil3Integration.Config +) : Coil3Integration { + + override fun create(data: StorageItem, options: Options, imageLoader: ImageLoader): Fetcher { + Coil3Integration.logger.d { "Creating Storage Fetcher" } + return SupabaseStorageFetcher(supabaseClient.storage, data, options, imageLoader) + } + +} + +/** + * With the [Coil3Integration] plugin installed, you can use this property to access the coil fetcher factory. + */ +@SupabaseExperimental +val SupabaseClient.coil3: Coil3Integration + get() = pluginManager.getPlugin(Coil3Integration) \ No newline at end of file diff --git a/plugins/Coil3Integration/src/commonMain/kotlin/io/github/jan/supabase/coil/SupabaseStorageFetcher.kt b/plugins/Coil3Integration/src/commonMain/kotlin/io/github/jan/supabase/coil/SupabaseStorageFetcher.kt new file mode 100644 index 000000000..b1d4d8d6e --- /dev/null +++ b/plugins/Coil3Integration/src/commonMain/kotlin/io/github/jan/supabase/coil/SupabaseStorageFetcher.kt @@ -0,0 +1,46 @@ +package io.github.jan.supabase.coil + +import coil3.Extras +import coil3.ImageLoader +import coil3.annotation.ExperimentalCoilApi +import coil3.fetch.FetchResult +import coil3.fetch.Fetcher +import coil3.network.httpHeaders +import coil3.request.Options +import coil3.toUri +import io.github.jan.supabase.logging.d +import io.github.jan.supabase.storage.Storage +import io.github.jan.supabase.storage.StorageItem +import io.github.jan.supabase.storage.authenticatedRequest + +internal class SupabaseStorageFetcher( + private val storage: Storage, + private val item: StorageItem, + private val options: Options, + private val imageLoader: ImageLoader +) : Fetcher { + + @OptIn(ExperimentalCoilApi::class) + override suspend fun fetch(): FetchResult? { + Coil3Integration.logger.d { "Received fetcher request for item $item" } + val bucket = storage[item.bucketId] + val (token, url) = if (item.authenticated) { + bucket.authenticatedRequest(item.path) + } else { + null to bucket.publicUrl(item.path) + } + val extras = options.extras.newBuilder() + if (item.authenticated) { + extras[Extras.Key.httpHeaders] = options.httpHeaders.newBuilder().apply { + set("Authorization", "Bearer $token") + }.build() + } + val (fetcher, _) = imageLoader.components.newFetcher( + url.toUri(), + options.copy(extras = extras.build()), + imageLoader + ) ?: error("No fetcher found for $url") + return fetcher.fetch() + } + +} \ No newline at end of file diff --git a/plugins/CoilIntegration/README.md b/plugins/CoilIntegration/README.md index bccdf6fcf..fe6a67644 100644 --- a/plugins/CoilIntegration/README.md +++ b/plugins/CoilIntegration/README.md @@ -4,29 +4,11 @@ Extends supabase-kt with a Coil integration for image loading. Supported targets: -| Target | **JVM** | **Android** | **JS** | **iOS** | **tvOS** | **watchOS** | **macOS** | **Windows** | **Linux** | -|--------|---------|-------------|--------|---------|----------|-------------|-----------|-------------|-----------| -| Status | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| Target | **Android** | +|--------|-------------| +| Status | ✅ | -
- -In-depth Kotlin targets - -**iOS:** iosArm64, iosSimulatorArm64, iosX64 - -**JS**: Browser, NodeJS - -**tvOS**: tvosArm64, tvosX64, tvosSimulatorArm64 - -**watchOS**: watchosArm64, watchosX64, watchosSimulatorArm64 - -**MacOS**: macosX64, macosArm64 - -**Windows**: mingwX64 - -**Linux**: linuxX64 - -
+For Compose Multiplatform support, [checkout the Coil 3 integration](/plugins/Coil3Integration) # Installation diff --git a/plugins/CoilIntegration/build.gradle.kts b/plugins/CoilIntegration/build.gradle.kts index aba4ed921..08b436180 100644 --- a/plugins/CoilIntegration/build.gradle.kts +++ b/plugins/CoilIntegration/build.gradle.kts @@ -13,14 +13,12 @@ repositories { @OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class) kotlin { defaultConfig() - androidTarget { - publishLibraryVariants("release", "debug") - } + configuredAndroidTarget() sourceSets { val commonMain by getting { dependencies { api(project(":storage-kt")) - api(libs.coil) + api(libs.coil2) } } } diff --git a/plugins/CoilIntegration/src/commonMain/kotlin/io/github/jan/supabase/coil/CoilIntegration.kt b/plugins/CoilIntegration/src/commonMain/kotlin/io/github/jan/supabase/coil/CoilIntegration.kt index f64f0a8ad..bc35f0ff0 100644 --- a/plugins/CoilIntegration/src/commonMain/kotlin/io/github/jan/supabase/coil/CoilIntegration.kt +++ b/plugins/CoilIntegration/src/commonMain/kotlin/io/github/jan/supabase/coil/CoilIntegration.kt @@ -14,7 +14,7 @@ import io.github.jan.supabase.storage.StorageItem import io.github.jan.supabase.storage.storage /** - * A plugin that implements [Fetcher.Factory] to support using [StorageItem] as data when creating a [ImageRequest] or using it as a model in Jetpack Compose. + * A plugin that implements [Fetcher.Factory] to support using [StorageItem] as data when creating an [ImageRequest] or using it as a model in Jetpack Compose. */ interface CoilIntegration: SupabasePlugin, Fetcher.Factory { diff --git a/plugins/ComposeAuth/README.md b/plugins/ComposeAuth/README.md index ccc42b1c2..eea2ad884 100644 --- a/plugins/ComposeAuth/README.md +++ b/plugins/ComposeAuth/README.md @@ -4,31 +4,21 @@ Extends gotrue-kt with Native Auth composables for Compose Multiplatform Supported targets: -| Target | **JVM** | **Android** | **JS** | **iOS** | **tvOS** | **watchOS** | **macOS** | **Windows** | **Linux** | -| ------ | ------- | ----------- | ------ | ------- | -------- | ----------- | --------- | ----------- | --------- | -| | ☑️ | ✅ | ☑️ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | +| Target | **JVM** | **Android** | **JS** | **Wasm** | **iOS** | +|--------|---------|-------------|--------|----------|---------| +| Status | ✅ | ✅ | ✅ | ✅ | ✅ | -> Note: iOS support is experimental and needs feedback -> -> ☑️ = Has no support for neither Google nor Apple Native Auth, relies on gotrue-kt for OAuth. +> Native Google Auth is only supported on Android and Native Apple Auth is only supported on iOS. Other targets or combinations rely on `gotrue-kt` for OAuth.
In-depth Kotlin targets -**iOS:** iosArm64, iosSimulatorArm64, iosX64 +**JS**: Browser -**JS**: Browser, NodeJS +**Wasm**: wasm-js -**tvOS**: tvosArm64, tvosX64, tvosSimulatorArm64 - -**watchOS**: watchosArm64, watchosX64, watchosSimulatorArm64 - -**MacOS**: macosX64, macosArm64 - -**Windows**: mingwX64 - -**Linux**: linuxX64 +**iOS**: iosArm64, iosSimulatorArm64, iosX64
diff --git a/plugins/ComposeAuth/build.gradle.kts b/plugins/ComposeAuth/build.gradle.kts index 74ee169cb..527f4bc69 100644 --- a/plugins/ComposeAuth/build.gradle.kts +++ b/plugins/ComposeAuth/build.gradle.kts @@ -24,6 +24,7 @@ kotlin { group("noDefault") { withJvm() withJs() + withWasmJs() } } } @@ -31,7 +32,7 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - addModules(SupabaseModule.GOTRUE) + addModules(SupabaseModule.AUTH) implementation(compose.runtime) implementation(libs.krypto) } diff --git a/plugins/ComposeAuth/src/commonMain/kotlin/io/github/jan/supabase/compose/auth/ComposeAuth.kt b/plugins/ComposeAuth/src/commonMain/kotlin/io/github/jan/supabase/compose/auth/ComposeAuth.kt index 199d84dcd..1b0b9f18d 100644 --- a/plugins/ComposeAuth/src/commonMain/kotlin/io/github/jan/supabase/compose/auth/ComposeAuth.kt +++ b/plugins/ComposeAuth/src/commonMain/kotlin/io/github/jan/supabase/compose/auth/ComposeAuth.kt @@ -2,13 +2,13 @@ package io.github.jan.supabase.compose.auth import io.github.jan.supabase.SupabaseClient import io.github.jan.supabase.SupabaseSerializer +import io.github.jan.supabase.auth.SessionStatus +import io.github.jan.supabase.auth.auth +import io.github.jan.supabase.auth.providers.Apple +import io.github.jan.supabase.auth.providers.Google +import io.github.jan.supabase.auth.providers.IDTokenProvider +import io.github.jan.supabase.auth.providers.builtin.IDToken import io.github.jan.supabase.compose.auth.composable.NativeSignInState -import io.github.jan.supabase.gotrue.SessionStatus -import io.github.jan.supabase.gotrue.auth -import io.github.jan.supabase.gotrue.providers.Apple -import io.github.jan.supabase.gotrue.providers.Google -import io.github.jan.supabase.gotrue.providers.IDTokenProvider -import io.github.jan.supabase.gotrue.providers.builtin.IDToken import io.github.jan.supabase.logging.SupabaseLogger import io.github.jan.supabase.logging.d import io.github.jan.supabase.plugins.CustomSerializationConfig diff --git a/plugins/ComposeAuth/src/commonMain/kotlin/io/github/jan/supabase/compose/auth/composable/NativeAppleAuth.kt b/plugins/ComposeAuth/src/commonMain/kotlin/io/github/jan/supabase/compose/auth/composable/NativeAppleAuth.kt index 307801b66..71655a27b 100644 --- a/plugins/ComposeAuth/src/commonMain/kotlin/io/github/jan/supabase/compose/auth/composable/NativeAppleAuth.kt +++ b/plugins/ComposeAuth/src/commonMain/kotlin/io/github/jan/supabase/compose/auth/composable/NativeAppleAuth.kt @@ -1,9 +1,9 @@ package io.github.jan.supabase.compose.auth.composable import androidx.compose.runtime.Composable +import io.github.jan.supabase.auth.providers.Apple import io.github.jan.supabase.compose.auth.ComposeAuth import io.github.jan.supabase.compose.auth.fallbackLogin -import io.github.jan.supabase.gotrue.providers.Apple /** * Composable function that implements Native Apple Auth. diff --git a/plugins/ComposeAuth/src/commonMain/kotlin/io/github/jan/supabase/compose/auth/composable/NativeGoogleAuth.kt b/plugins/ComposeAuth/src/commonMain/kotlin/io/github/jan/supabase/compose/auth/composable/NativeGoogleAuth.kt index da45306dd..8fbb4899d 100644 --- a/plugins/ComposeAuth/src/commonMain/kotlin/io/github/jan/supabase/compose/auth/composable/NativeGoogleAuth.kt +++ b/plugins/ComposeAuth/src/commonMain/kotlin/io/github/jan/supabase/compose/auth/composable/NativeGoogleAuth.kt @@ -1,9 +1,9 @@ package io.github.jan.supabase.compose.auth.composable import androidx.compose.runtime.Composable +import io.github.jan.supabase.auth.providers.Google import io.github.jan.supabase.compose.auth.ComposeAuth import io.github.jan.supabase.compose.auth.fallbackLogin -import io.github.jan.supabase.gotrue.providers.Google /** * Composable function that implements Native Google Auth. diff --git a/plugins/ComposeAuthUI/README.md b/plugins/ComposeAuthUI/README.md index 8a6575f72..8d5dbc2fb 100644 --- a/plugins/ComposeAuthUI/README.md +++ b/plugins/ComposeAuthUI/README.md @@ -4,27 +4,19 @@ Extends Supabase-kt with UI composables Supported targets: -| Target | **JVM** | **Android** | **JS** | **iOS** | **tvOS** | **watchOS** | **macOS** | **Windows** | **Linux** | -|--------|---------|-------------|--------|---------|----------|-------------|-----------|-------------|-----------| -| | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | +| Target | **JVM** | **Android** | **JS** | **Wasm** | **iOS** | +|--------|---------|-------------|--------|----------|---------| +| Status | ✅ | ✅ | ✅ | ✅ | ✅ |
In-depth Kotlin targets -**iOS:** iosArm64, iosSimulatorArm64, iosX64 +**JS**: Browser -**JS**: Browser, NodeJS +**Wasm**: wasm-js -**tvOS**: tvosArm64, tvosX64, tvosSimulatorArm64 - -**watchOS**: watchosArm64, watchosX64, watchosSimulatorArm64 - -**MacOS**: macosX64, macosArm64 - -**Windows**: mingwX64 - -**Linux**: linuxX64 +**iOS**: iosArm64, iosSimulatorArm64, iosX64
diff --git a/plugins/ComposeAuthUI/build.gradle.kts b/plugins/ComposeAuthUI/build.gradle.kts index ea5e72dfa..7e83ad6ed 100644 --- a/plugins/ComposeAuthUI/build.gradle.kts +++ b/plugins/ComposeAuthUI/build.gradle.kts @@ -23,6 +23,7 @@ kotlin { group("nonJvm") { withIos() withJs() + withWasmJs() } } } @@ -31,7 +32,7 @@ kotlin { val commonMain by getting { dependencies { api(compose.ui) - addModules(SupabaseModule.GOTRUE) + addModules(SupabaseModule.AUTH) implementation(compose.material3) } } diff --git a/plugins/ComposeAuthUI/src/commonMain/kotlin/io/github/jan/supabase/compose/auth/ui/ProviderButton.kt b/plugins/ComposeAuthUI/src/commonMain/kotlin/io/github/jan/supabase/compose/auth/ui/ProviderButton.kt index 4fb738497..abaf0d7eb 100644 --- a/plugins/ComposeAuthUI/src/commonMain/kotlin/io/github/jan/supabase/compose/auth/ui/ProviderButton.kt +++ b/plugins/ComposeAuthUI/src/commonMain/kotlin/io/github/jan/supabase/compose/auth/ui/ProviderButton.kt @@ -11,8 +11,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp +import io.github.jan.supabase.auth.providers.OAuthProvider import io.github.jan.supabase.compose.auth.ui.annotations.AuthUiExperimental -import io.github.jan.supabase.gotrue.providers.OAuthProvider internal val DEFAULT_ICON_SIZE = 24.dp //from Material3 diff --git a/plugins/ComposeAuthUI/src/commonMain/kotlin/io/github/jan/supabase/compose/auth/ui/SvgPainter.kt b/plugins/ComposeAuthUI/src/commonMain/kotlin/io/github/jan/supabase/compose/auth/ui/SvgPainter.kt index c45b4ee38..21218df44 100644 --- a/plugins/ComposeAuthUI/src/commonMain/kotlin/io/github/jan/supabase/compose/auth/ui/SvgPainter.kt +++ b/plugins/ComposeAuthUI/src/commonMain/kotlin/io/github/jan/supabase/compose/auth/ui/SvgPainter.kt @@ -6,31 +6,32 @@ import androidx.compose.runtime.produceState import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.unit.Density import io.github.jan.supabase.annotations.SupabaseInternal -import io.github.jan.supabase.gotrue.providers.Apple -import io.github.jan.supabase.gotrue.providers.Azure -import io.github.jan.supabase.gotrue.providers.Bitbucket -import io.github.jan.supabase.gotrue.providers.Discord -import io.github.jan.supabase.gotrue.providers.Facebook -import io.github.jan.supabase.gotrue.providers.Figma -import io.github.jan.supabase.gotrue.providers.Github -import io.github.jan.supabase.gotrue.providers.Gitlab -import io.github.jan.supabase.gotrue.providers.Google -import io.github.jan.supabase.gotrue.providers.Kakao -import io.github.jan.supabase.gotrue.providers.Keycloak -import io.github.jan.supabase.gotrue.providers.LinkedIn -import io.github.jan.supabase.gotrue.providers.Notion -import io.github.jan.supabase.gotrue.providers.OAuthProvider -import io.github.jan.supabase.gotrue.providers.Slack -import io.github.jan.supabase.gotrue.providers.Spotify -import io.github.jan.supabase.gotrue.providers.Twitch -import io.github.jan.supabase.gotrue.providers.Twitter -import io.github.jan.supabase.gotrue.providers.WorkOS +import io.github.jan.supabase.auth.providers.Apple +import io.github.jan.supabase.auth.providers.Azure +import io.github.jan.supabase.auth.providers.Bitbucket +import io.github.jan.supabase.auth.providers.Discord +import io.github.jan.supabase.auth.providers.Facebook +import io.github.jan.supabase.auth.providers.Figma +import io.github.jan.supabase.auth.providers.Github +import io.github.jan.supabase.auth.providers.Gitlab +import io.github.jan.supabase.auth.providers.Google +import io.github.jan.supabase.auth.providers.Kakao +import io.github.jan.supabase.auth.providers.Keycloak +import io.github.jan.supabase.auth.providers.LinkedIn +import io.github.jan.supabase.auth.providers.Notion +import io.github.jan.supabase.auth.providers.OAuthProvider +import io.github.jan.supabase.auth.providers.Slack +import io.github.jan.supabase.auth.providers.Spotify +import io.github.jan.supabase.auth.providers.Twitch +import io.github.jan.supabase.auth.providers.Twitter +import io.github.jan.supabase.auth.providers.WorkOS @SupabaseInternal expect fun svgPainter(bytes: ByteArray, density: Density): Painter @SupabaseInternal @Composable +@Suppress("CyclomaticComplexMethod") fun providerPainter(provider: OAuthProvider, density: Density): Painter? { val painter by produceState(null) { val data = when(provider) { diff --git a/plugins/ImageLoaderIntegration/README.md b/plugins/ImageLoaderIntegration/README.md index 637ca3c32..57fb831e6 100644 --- a/plugins/ImageLoaderIntegration/README.md +++ b/plugins/ImageLoaderIntegration/README.md @@ -4,27 +4,19 @@ Extends supabase-kt with a [Compose-ImageLoader](https://github.com/qdsfdhvh/com Supported targets: -| Target | **JVM** | **Android** | **JS** | **iOS** | **tvOS** | **watchOS** | **macOS** | **Windows** | **Linux** | -|--------|---------|-------------|--------|---------|----------|-------------|-----------|-------------|-----------| -| Status | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | +| Target | **JVM** | **Android** | **JS** | **iOS** | +|--------|---------|-------------|--------|---------| +| Status | ✅ | ✅ | ✅ | ✅ | + +> Wasm-JS is currently not supported due to a bug in the Compose-ImageLoader library.
In-depth Kotlin targets -**iOS:** iosArm64, iosSimulatorArm64, iosX64 - -**JS**: Browser, NodeJS - -**tvOS**: tvosArm64, tvosX64, tvosSimulatorArm64 - -**watchOS**: watchosArm64, watchosX64, watchosSimulatorArm64 - -**MacOS**: macosX64, macosArm64 - -**Windows**: mingwX64 +**JS**: Browser -**Linux**: linuxX64 +**iOS**: iosArm64, iosSimulatorArm64, iosX64
diff --git a/plugins/ImageLoaderIntegration/build.gradle.kts b/plugins/ImageLoaderIntegration/build.gradle.kts index 7871b199f..94af58310 100644 --- a/plugins/ImageLoaderIntegration/build.gradle.kts +++ b/plugins/ImageLoaderIntegration/build.gradle.kts @@ -13,7 +13,10 @@ repositories { @OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class) kotlin { defaultConfig() - composeTargets() + // composeTargets() + jsTarget() + jvmTargets() + iosTargets() sourceSets { val commonMain by getting { dependencies { diff --git a/sample/chat-demo-mpp/README.md b/sample/chat-demo-mpp/README.md index c870b4b56..1b3c82632 100644 --- a/sample/chat-demo-mpp/README.md +++ b/sample/chat-demo-mpp/README.md @@ -4,7 +4,9 @@ This is a demo of a chat app using Compose Multiplatform, Koin and supabase-kt. **Available platforms:** Android, iOS, Desktop, JS Canvas -**Modules used:** Realtime, GoTrue, Postgrest, Compose Auth UI +**Modules used:** Realtime, Auth*, Postgrest, Compose Auth UI + +* Integrated flows: Password, Google login & password recovery https://user-images.githubusercontent.com/26686035/216710629-d809ff58-cd3b-449f-877f-4c6c773daec4.mp4 diff --git a/sample/chat-demo-mpp/android/src/main/java/io/github/jan/supabase/android/MainActivity.kt b/sample/chat-demo-mpp/android/src/main/java/io/github/jan/supabase/android/MainActivity.kt index 99e15d065..746c205de 100644 --- a/sample/chat-demo-mpp/android/src/main/java/io/github/jan/supabase/android/MainActivity.kt +++ b/sample/chat-demo-mpp/android/src/main/java/io/github/jan/supabase/android/MainActivity.kt @@ -4,9 +4,9 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.material3.MaterialTheme +import io.github.jan.supabase.auth.handleDeeplinks import io.github.jan.supabase.common.App import io.github.jan.supabase.common.ChatViewModel -import io.github.jan.supabase.gotrue.handleDeeplinks import org.koin.android.ext.android.inject class MainActivity : ComponentActivity() { diff --git a/sample/chat-demo-mpp/common/build.gradle.kts b/sample/chat-demo-mpp/common/build.gradle.kts index 8608f1d2d..b4c4e25e2 100644 --- a/sample/chat-demo-mpp/common/build.gradle.kts +++ b/sample/chat-demo-mpp/common/build.gradle.kts @@ -1,5 +1,8 @@ @file:OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class) +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi + + plugins { id(libs.plugins.kotlin.multiplatform.get().pluginId) id(libs.plugins.compose.plugin.get().pluginId) @@ -12,6 +15,7 @@ group = "io.github.jan.supabase" version = "1.0-SNAPSHOT" kotlin { + @OptIn(ExperimentalKotlinGradlePluginApi::class) applyDefaultHierarchyTemplate { common { group("jvmAndAndroid") { @@ -42,7 +46,7 @@ kotlin { api(compose.foundation) api(compose.material3) api(compose.materialIconsExtended) - addModules(SupabaseModule.GOTRUE, SupabaseModule.POSTGREST, SupabaseModule.REALTIME, SupabaseModule.COMPOSE_AUTH, SupabaseModule.COMPOSE_AUTH_UI) + addModules(SupabaseModule.AUTH, SupabaseModule.POSTGREST, SupabaseModule.REALTIME, SupabaseModule.COMPOSE_AUTH, SupabaseModule.COMPOSE_AUTH_UI) api(libs.koin.core) } } diff --git a/sample/chat-demo-mpp/common/src/androidMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt b/sample/chat-demo-mpp/common/src/androidMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt index 119060501..6deb296ea 100644 --- a/sample/chat-demo-mpp/common/src/androidMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt +++ b/sample/chat-demo-mpp/common/src/androidMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt @@ -1,8 +1,10 @@ package io.github.jan.supabase.common.di -import io.github.jan.supabase.gotrue.AuthConfig +import io.github.jan.supabase.auth.AuthConfig +import io.github.jan.supabase.auth.ExternalAuthAction actual fun AuthConfig.platformGoTrueConfig() { scheme = "io.jan.supabase" host = "login" + defaultExternalAuthAction = ExternalAuthAction.CustomTabs() } \ No newline at end of file diff --git a/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/App.kt b/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/App.kt index 093bb5fef..9e8568586 100644 --- a/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/App.kt +++ b/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/App.kt @@ -11,9 +11,9 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import io.github.jan.supabase.auth.SessionStatus import io.github.jan.supabase.common.ui.screen.ChatScreen import io.github.jan.supabase.common.ui.screen.LoginScreen -import io.github.jan.supabase.gotrue.SessionStatus @Composable fun App(viewModel: ChatViewModel) { diff --git a/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/ChatViewModel.kt b/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/ChatViewModel.kt index 6e0100c8c..0aee98f1e 100644 --- a/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/ChatViewModel.kt +++ b/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/ChatViewModel.kt @@ -3,23 +3,18 @@ package io.github.jan.supabase.common import co.touchlab.kermit.Logger import io.github.jan.supabase.SupabaseClient +import io.github.jan.supabase.auth.SessionStatus +import io.github.jan.supabase.common.net.AuthApi import io.github.jan.supabase.common.net.Message import io.github.jan.supabase.common.net.MessageApi -import io.github.jan.supabase.gotrue.auth -import io.github.jan.supabase.gotrue.providers.Google -import io.github.jan.supabase.gotrue.providers.builtin.Email -import io.github.jan.supabase.realtime.PostgresAction -import io.github.jan.supabase.realtime.RealtimeChannel -import io.github.jan.supabase.realtime.decodeRecord -import io.github.jan.supabase.realtime.postgresChangeFlow import io.github.jan.supabase.realtime.realtime import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import kotlinx.serialization.json.int -import kotlinx.serialization.json.jsonPrimitive expect open class MPViewModel() { @@ -28,27 +23,25 @@ expect open class MPViewModel() { } class ChatViewModel( val supabaseClient: SupabaseClient, - private val realtimeChannel: RealtimeChannel, - private val messageApi: MessageApi + private val messageApi: MessageApi, + private val authApi: AuthApi ) : MPViewModel() { - val sessionStatus = supabaseClient.auth.sessionStatus - val loginAlert = MutableStateFlow(null) + val sessionStatus = authApi.sessionStatus().stateIn(coroutineScope, SharingStarted.Eagerly, SessionStatus.NotAuthenticated(false)) + val alert = MutableStateFlow(null) val messages = MutableStateFlow>(emptyList()) + val passwordReset = MutableStateFlow(false) //Auth fun signUp(email: String, password: String) { coroutineScope.launch { kotlin.runCatching { - supabaseClient.auth.signUpWith(Email) { - this.email = email - this.password = password - } + authApi.signUp(email, password) }.onSuccess { - loginAlert.value = "Successfully registered! Check your E-Mail to verify your account." + alert.value = "Successfully registered! Check your E-Mail to verify your account." }.onFailure { - loginAlert.value = "There was an error while registering: ${it.message}" + alert.value = "There was an error while registering: ${it.message}" } } } @@ -56,13 +49,10 @@ class ChatViewModel( fun login(email: String, password: String) { coroutineScope.launch { kotlin.runCatching { - supabaseClient.auth.signInWith(Email) { - this.email = email - this.password = password - } + authApi.signIn(email, password) }.onFailure { it.printStackTrace() - loginAlert.value = "There was an error while logging in. Check your credentials and try again." + alert.value = "There was an error while logging in. Check your credentials and try again." } } } @@ -70,47 +60,63 @@ class ChatViewModel( fun loginWithGoogle() { coroutineScope.launch { kotlin.runCatching { - supabaseClient.auth.signInWith(Google) + authApi.signInWithGoogle() } } } - fun logout() { + fun loginWithOTP(email: String, code: String, reset: Boolean) { coroutineScope.launch { kotlin.runCatching { - supabaseClient.auth.signOut() - messages.value = emptyList() + authApi.verifyOtp(email, code) + }.onSuccess { + passwordReset.value = reset + }.onFailure { + alert.value = "There was an error while verifying the OTP: ${it.message}" } } } - //Realtime - fun connectToRealtime() { + fun resetPassword(email: String) { coroutineScope.launch { kotlin.runCatching { - realtimeChannel.postgresChangeFlow("public") { - table = "messages" - }.onEach { - when(it) { - is PostgresAction.Delete -> messages.value = messages.value.filter { message -> message.id != it.oldRecord["id"]!!.jsonPrimitive.int } - is PostgresAction.Insert -> messages.value = messages.value + it.decodeRecord() - is PostgresAction.Select -> error("Select should not be possible") - is PostgresAction.Update -> error("Update should not be possible") - } - }.launchIn(coroutineScope) - - realtimeChannel.subscribe() + authApi.resetPassword(email) + } + } + } + fun changePassword(password: String) { + coroutineScope.launch { + kotlin.runCatching { + authApi.changePassword(password) + }.onSuccess { + alert.value = "Password changed successfully!" }.onFailure { - it.printStackTrace() + alert.value = "There was an error while changing the password: ${it.message}" } } } - fun disconnectFromRealtime() { + fun logout() { coroutineScope.launch { kotlin.runCatching { - supabaseClient.realtime.disconnect() + authApi.signOut() + messages.value = emptyList() + } + } + } + + //Realtime + fun retrieveMessages() { + coroutineScope.launch { + kotlin.runCatching { + messageApi.retrieveMessages() + .onEach { + messages.value = it + } + .launchIn(coroutineScope) + }.onFailure { + Logger.e(it) { "Error while retrieving messages" } } } } @@ -136,14 +142,10 @@ class ChatViewModel( } } - fun retrieveMessages() { + fun disconnectFromRealtime() { coroutineScope.launch { kotlin.runCatching { - messageApi.retrieveMessages() - }.onSuccess { - messages.value = it - }.onFailure { - Logger.e(it) { "Error while retrieving messages" } + supabaseClient.realtime.removeAllChannels() } } } diff --git a/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/di/netModule.kt b/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/di/netModule.kt index 9e109bdc1..b698a6c60 100644 --- a/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/di/netModule.kt +++ b/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/di/netModule.kt @@ -1,9 +1,12 @@ package io.github.jan.supabase.common.di +import io.github.jan.supabase.common.net.AuthApi +import io.github.jan.supabase.common.net.AuthApiImpl import io.github.jan.supabase.common.net.MessageApi import io.github.jan.supabase.common.net.MessageApiImpl import org.koin.dsl.module val netModule = module { single { MessageApiImpl(get()) } + single { AuthApiImpl(get()) } } \ No newline at end of file diff --git a/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/di/supabaseModule.kt b/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/di/supabaseModule.kt index 75a1cc94d..42060f8e8 100644 --- a/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/di/supabaseModule.kt +++ b/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/di/supabaseModule.kt @@ -1,12 +1,12 @@ package io.github.jan.supabase.common.di -import io.github.jan.supabase.SupabaseClient +import io.github.jan.supabase.auth.Auth +import io.github.jan.supabase.auth.AuthConfig +import io.github.jan.supabase.auth.FlowType import io.github.jan.supabase.createSupabaseClient -import io.github.jan.supabase.gotrue.Auth -import io.github.jan.supabase.gotrue.AuthConfig +import io.github.jan.supabase.logging.LogLevel import io.github.jan.supabase.postgrest.Postgrest import io.github.jan.supabase.realtime.Realtime -import io.github.jan.supabase.realtime.channel import org.koin.dsl.module expect fun AuthConfig.platformGoTrueConfig() @@ -17,14 +17,13 @@ val supabaseModule = module { supabaseUrl = "YOUR_URL", supabaseKey = "YOUR_KEY" ) { + defaultLogLevel = LogLevel.DEBUG install(Postgrest) install(Auth) { platformGoTrueConfig() + flowType = FlowType.PKCE } install(Realtime) } } - single { - get().channel("messages") - } } \ No newline at end of file diff --git a/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/net/AuthApi.kt b/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/net/AuthApi.kt new file mode 100644 index 000000000..e6c6315fe --- /dev/null +++ b/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/net/AuthApi.kt @@ -0,0 +1,78 @@ +package io.github.jan.supabase.common.net + +import io.github.jan.supabase.SupabaseClient +import io.github.jan.supabase.auth.OtpType +import io.github.jan.supabase.auth.SessionStatus +import io.github.jan.supabase.auth.auth +import io.github.jan.supabase.auth.providers.Google +import io.github.jan.supabase.auth.providers.builtin.Email +import kotlinx.coroutines.flow.Flow + +sealed interface AuthApi { + + suspend fun signIn(email: String, password: String) + + suspend fun signUp(email: String, password: String) + + suspend fun signInWithGoogle() + + suspend fun verifyOtp(email: String, otp: String) + + suspend fun signOut() + + suspend fun resetPassword(email: String) + + suspend fun changePassword(newPassword: String) + + fun sessionStatus(): Flow + +} + +internal class AuthApiImpl( + private val client: SupabaseClient +) : AuthApi { + + private val auth = client.auth + + override fun sessionStatus(): Flow { + return auth.sessionStatus + } + + override suspend fun verifyOtp(email: String, otp: String) { + auth.verifyEmailOtp(OtpType.Email.EMAIL, email, otp) + } + + override suspend fun signInWithGoogle() { + auth.signInWith(Google) + } + + override suspend fun signIn(email: String, password: String) { + auth.signInWith(Email) { + this.email = email + this.password = password + } + } + + override suspend fun signUp(email: String, password: String) { + auth.signUpWith(Email) { + this.email = email + this.password = password + } + } + + override suspend fun changePassword(newPassword: String) { + auth.updateUser { + this.password = newPassword + } + } + + override suspend fun signOut() { + auth.signOut() + } + + override suspend fun resetPassword(email: String) { + auth.resetPasswordForEmail(email) + } + + +} \ No newline at end of file diff --git a/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/net/MessageApi.kt b/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/net/MessageApi.kt index 6161333c2..a45d7be41 100644 --- a/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/net/MessageApi.kt +++ b/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/net/MessageApi.kt @@ -1,8 +1,11 @@ package io.github.jan.supabase.common.net import io.github.jan.supabase.SupabaseClient -import io.github.jan.supabase.gotrue.auth +import io.github.jan.supabase.annotations.SupabaseExperimental +import io.github.jan.supabase.auth.auth import io.github.jan.supabase.postgrest.postgrest +import io.github.jan.supabase.realtime.selectAsFlow +import kotlinx.coroutines.flow.Flow import kotlinx.datetime.Instant import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -21,7 +24,7 @@ data class Message( sealed interface MessageApi { - suspend fun retrieveMessages(): List + suspend fun retrieveMessages(): Flow> suspend fun createMessage(content: String): Message @@ -35,7 +38,8 @@ internal class MessageApiImpl( private val table = client.postgrest["messages"] - override suspend fun retrieveMessages(): List = table.select().decodeList() + @OptIn(SupabaseExperimental::class) + override suspend fun retrieveMessages(): Flow> = table.selectAsFlow(Message::id) override suspend fun createMessage(content: String): Message { val user = (client.auth.currentSessionOrNull() ?: error("No session available")).user ?: error("No user available") diff --git a/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/components/MessageCard.kt b/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/components/MessageCard.kt index f5297f713..6a13cff02 100644 --- a/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/components/MessageCard.kt +++ b/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/components/MessageCard.kt @@ -28,7 +28,7 @@ fun MessageCard(message: Message, own: Boolean, modifier: Modifier, onDelete: () ElevatedCard(modifier = Modifier.widthIn(max = 200.dp), colors = CardDefaults.elevatedCardColors(containerColor = backgroundColor)) { Column(modifier = Modifier.padding(12.dp)) { Text(message.content) - Text(message.creatorId, fontSize = 8.sp, modifier = Modifier.padding(top = 4.dp)) + Text("UID: " + message.creatorId, fontSize = 8.sp, modifier = Modifier.padding(top = 4.dp)) } } if(own) { diff --git a/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/components/OTPDialog.kt b/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/components/OTPDialog.kt new file mode 100644 index 000000000..0ab4883be --- /dev/null +++ b/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/components/OTPDialog.kt @@ -0,0 +1,60 @@ +package io.github.jan.supabase.common.ui.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.text.input.KeyboardType + +sealed interface OTPDialogState { + data object Invisible : OTPDialogState + data class Visible(val title: String = "Sign in using an OTP", val resetFlow: Boolean = false, val email: String? = null) : OTPDialogState +} + +@Composable +fun OTPDialog( + email: String? = null, + title: String, + onDismiss: () -> Unit, + onConfirm: (email: String, code: String) -> Unit +) { + var code by remember { mutableStateOf("") } + var otpEmail by remember { mutableStateOf("") } + AlertDialog( + onDismissRequest = onDismiss, + title = { Text(title) }, + text = { + Column { + if(email == null) { + OutlinedTextField(otpEmail, { otpEmail = it }, label = { Text("Email") }, singleLine = true, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)) + } else { + Text("Please enter the code sent to $email.") + } + OutlinedTextField(code, { code = it }, label = { Text("Code") }, singleLine = true, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)) + } + }, + confirmButton = { + TextButton( + onClick = { + onConfirm(email ?: otpEmail, code) + onDismiss() + }, + enabled = (email ?: otpEmail).isNotBlank() && code.isNotBlank() + ) { + Text("Confirm") + } + }, + dismissButton = { + TextButton(onClick = onDismiss) { + Text("Dismiss") + } + } + ) +} diff --git a/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/components/PasswordChangeDialog.kt b/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/components/PasswordChangeDialog.kt new file mode 100644 index 000000000..638fb015c --- /dev/null +++ b/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/components/PasswordChangeDialog.kt @@ -0,0 +1,61 @@ +package io.github.jan.supabase.common.ui.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import io.github.jan.supabase.compose.auth.ui.AuthForm +import io.github.jan.supabase.compose.auth.ui.LocalAuthState +import io.github.jan.supabase.compose.auth.ui.annotations.AuthUiExperimental +import io.github.jan.supabase.compose.auth.ui.password.OutlinedPasswordField + +@OptIn(AuthUiExperimental::class, ExperimentalMaterial3Api::class) +@Composable +fun PasswordChangeDialog( + onDismiss: () -> Unit, + onConfirm: (newPassword: String) -> Unit +) { + var password by remember { mutableStateOf("") } + AuthForm { + AlertDialog( + onDismissRequest = onDismiss, + title = { Text("Password change") }, + text = { + Column { + Text("Please enter your new password.") + Spacer(Modifier.height(8.dp)) + OutlinedPasswordField( + value = password, + onValueChange = { password = it }, + ) + } + }, + confirmButton = { + TextButton( + onClick = { + onConfirm(password) + onDismiss() + }, + enabled = LocalAuthState.current.validForm + ) { + Text("Confirm") + } + }, + dismissButton = { + TextButton(onClick = onDismiss) { + Text("Dismiss") + } + } + ) + } +} \ No newline at end of file diff --git a/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/components/PasswordRecoverDialog.kt b/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/components/PasswordRecoverDialog.kt new file mode 100644 index 000000000..f2d561230 --- /dev/null +++ b/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/components/PasswordRecoverDialog.kt @@ -0,0 +1,48 @@ +package io.github.jan.supabase.common.ui.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.text.input.KeyboardType + +@Composable +fun PasswordRecoveryDialog( + onDismiss: () -> Unit, + onConfirm: (email: String) -> Unit +) { + var email by remember { mutableStateOf("") } + AlertDialog( + onDismissRequest = onDismiss, + title = { Text("Password recovery") }, + text = { + Column { + Text("Please enter your new password.") + OutlinedTextField(email, { email = it }, label = { Text("Email") }, singleLine = true, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)) + } + }, + confirmButton = { + TextButton( + onClick = { + onConfirm(email) + onDismiss() + }, + enabled = email.isNotBlank() + ) { + Text("Confirm") + } + }, + dismissButton = { + TextButton(onClick = onDismiss) { + Text("Dismiss") + } + } + ) +} diff --git a/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/screen/ChatScreen.kt b/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/screen/ChatScreen.kt index 1ac492751..12d4274d8 100644 --- a/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/screen/ChatScreen.kt +++ b/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/screen/ChatScreen.kt @@ -9,13 +9,15 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Send -import androidx.compose.material3.Button -import androidx.compose.material3.Divider +import androidx.compose.material.icons.automirrored.filled.Logout +import androidx.compose.material.icons.automirrored.filled.Send +import androidx.compose.material3.AlertDialog import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -29,9 +31,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import io.github.jan.supabase.CurrentPlatformTarget import io.github.jan.supabase.PlatformTarget +import io.github.jan.supabase.auth.user.UserInfo import io.github.jan.supabase.common.ChatViewModel import io.github.jan.supabase.common.ui.components.MessageCard -import io.github.jan.supabase.gotrue.user.UserInfo +import io.github.jan.supabase.common.ui.components.PasswordChangeDialog import kotlinx.coroutines.flow.map @OptIn(ExperimentalMaterial3Api::class) @@ -40,11 +43,12 @@ fun ChatScreen(viewModel: ChatViewModel, user: UserInfo) { val messages by viewModel.messages.map { it.reversed() }.collectAsState(emptyList()) var message by remember { mutableStateOf("") } val ownId = user.id + val reset by viewModel.passwordReset.collectAsState() + val alert by viewModel.alert.collectAsState() LaunchedEffect(Unit) { if(CurrentPlatformTarget in listOf(PlatformTarget.JVM, PlatformTarget.JS, PlatformTarget.ANDROID)) { viewModel.retrieveMessages() - viewModel.connectToRealtime() } } @@ -62,7 +66,7 @@ fun ChatScreen(viewModel: ChatViewModel, user: UserInfo) { } } } - Divider(thickness = 1.dp, modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp)) + HorizontalDivider(thickness = 1.dp, modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp)) Row(verticalAlignment = Alignment.CenterVertically) { TextField( value = message, @@ -73,18 +77,55 @@ fun ChatScreen(viewModel: ChatViewModel, user: UserInfo) { viewModel.createMessage(message) message = "" }, enabled = message.isNotBlank()) { - Icon(Icons.Filled.Send, "Send") + Icon(Icons.AutoMirrored.Filled.Send, "Send") } } } Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.TopStart) { - Button({ - viewModel.disconnectFromRealtime() - viewModel.logout() - }, enabled = true, modifier = Modifier.padding(5.dp)) { - Text("Logout") + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + IconButton({ + viewModel.disconnectFromRealtime() + viewModel.logout() + }, modifier = Modifier.padding(5.dp)) { + Icon(Icons.AutoMirrored.Filled.Logout, "Logout") + } + TextButton( + onClick = { + viewModel.passwordReset.value = true + } + ) { + Text("Reset password") + } } } + if(reset) { + PasswordChangeDialog( + onDismiss = { viewModel.passwordReset.value = false }, + onConfirm = { viewModel.changePassword(it) } + ) + } + + if(alert != null) { + AlertDialog( + onDismissRequest = { + viewModel.alert.value = null + }, + title = { Text("Info") }, + text = { Text(alert!!) }, + confirmButton = { + TextButton( + onClick = { + viewModel.alert.value = null + } + ) { + Text("Ok") + } + } + ) + } + } \ No newline at end of file diff --git a/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/screen/LoginScreen.kt b/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/screen/LoginScreen.kt index d4cc03004..7ec089f16 100644 --- a/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/screen/LoginScreen.kt +++ b/sample/chat-demo-mpp/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/screen/LoginScreen.kt @@ -31,18 +31,23 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import io.github.jan.supabase.annotations.SupabaseExperimental +import io.github.jan.supabase.auth.providers.Google import io.github.jan.supabase.common.ChatViewModel +import io.github.jan.supabase.common.ui.components.OTPDialog +import io.github.jan.supabase.common.ui.components.OTPDialogState import io.github.jan.supabase.common.ui.components.PasswordField +import io.github.jan.supabase.common.ui.components.PasswordRecoveryDialog import io.github.jan.supabase.compose.auth.ui.ProviderButtonContent import io.github.jan.supabase.compose.auth.ui.annotations.AuthUiExperimental -import io.github.jan.supabase.gotrue.providers.Google @OptIn(ExperimentalMaterial3Api::class, SupabaseExperimental::class, AuthUiExperimental::class) @Composable fun LoginScreen(viewModel: ChatViewModel) { var signUp by remember { mutableStateOf(false) } - val loginAlert by viewModel.loginAlert.collectAsState() + val loginAlert by viewModel.alert.collectAsState() var email by remember { mutableStateOf("") } + var otpDialogState by remember { mutableStateOf(OTPDialogState.Invisible) } + var showPasswordRecoveryDialog by remember { mutableStateOf(false) } Column( modifier = Modifier.fillMaxSize(), @@ -90,6 +95,12 @@ fun LoginScreen(viewModel: ChatViewModel) { ProviderButtonContent(Google, text = if (signUp) "Sign Up with Google" else "Login with Google") } + TextButton( + onClick = { otpDialogState = OTPDialogState.Visible(email) } + ) { + Text("Login with an OTP") + } + } Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.BottomCenter) { TextButton(onClick = { signUp = !signUp }) { @@ -97,17 +108,45 @@ fun LoginScreen(viewModel: ChatViewModel) { } } + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.BottomEnd) { + TextButton(onClick = { showPasswordRecoveryDialog = true }) { + Text("Forgot password?") + } + } + + if(otpDialogState is OTPDialogState.Visible) { + val state = (otpDialogState as OTPDialogState.Visible) + OTPDialog( + email = state.email, + title = state.title, + onDismiss = { otpDialogState = OTPDialogState.Invisible }, + onConfirm = { email, code -> + viewModel.loginWithOTP(email, code, state.resetFlow) + } + ) + } + + if(showPasswordRecoveryDialog) { + PasswordRecoveryDialog( + onDismiss = { showPasswordRecoveryDialog = false }, + onConfirm = { email -> + viewModel.resetPassword(email) + otpDialogState = OTPDialogState.Visible(title = "Password recovery", email = email, resetFlow = true) + } + ) + } + if(loginAlert != null) { AlertDialog( onDismissRequest = { - viewModel.loginAlert.value = null + viewModel.alert.value = null }, text = { Text(loginAlert!!) }, confirmButton = { TextButton(onClick = { - viewModel.loginAlert.value = null + viewModel.alert.value = null }) { Text("Ok") } diff --git a/sample/chat-demo-mpp/common/src/desktopMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt b/sample/chat-demo-mpp/common/src/desktopMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt index c2c86e17e..9db4758c4 100644 --- a/sample/chat-demo-mpp/common/src/desktopMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt +++ b/sample/chat-demo-mpp/common/src/desktopMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt @@ -1,6 +1,6 @@ package io.github.jan.supabase.common.di -import io.github.jan.supabase.gotrue.AuthConfig +import io.github.jan.supabase.auth.AuthConfig actual fun AuthConfig.platformGoTrueConfig() { httpCallbackConfig { diff --git a/sample/chat-demo-mpp/common/src/iosMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt b/sample/chat-demo-mpp/common/src/iosMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt index 119060501..efac2ce79 100644 --- a/sample/chat-demo-mpp/common/src/iosMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt +++ b/sample/chat-demo-mpp/common/src/iosMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt @@ -1,6 +1,6 @@ package io.github.jan.supabase.common.di -import io.github.jan.supabase.gotrue.AuthConfig +import io.github.jan.supabase.auth.AuthConfig actual fun AuthConfig.platformGoTrueConfig() { scheme = "io.jan.supabase" diff --git a/sample/chat-demo-mpp/common/src/jsMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt b/sample/chat-demo-mpp/common/src/jsMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt index 7331d7f76..494af766f 100644 --- a/sample/chat-demo-mpp/common/src/jsMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt +++ b/sample/chat-demo-mpp/common/src/jsMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt @@ -1,5 +1,5 @@ package io.github.jan.supabase.common.di -import io.github.jan.supabase.gotrue.AuthConfig +import io.github.jan.supabase.auth.AuthConfig actual fun AuthConfig.platformGoTrueConfig() = Unit \ No newline at end of file diff --git a/sample/file-upload/android/src/main/java/io/github/jan/supabase/android/MainActivity.kt b/sample/file-upload/android/src/main/java/io/github/jan/supabase/android/MainActivity.kt index 83fbd6d08..03b11f632 100644 --- a/sample/file-upload/android/src/main/java/io/github/jan/supabase/android/MainActivity.kt +++ b/sample/file-upload/android/src/main/java/io/github/jan/supabase/android/MainActivity.kt @@ -8,8 +8,6 @@ import androidx.compose.runtime.SideEffect import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.PermissionStatus import com.google.accompanist.permissions.rememberPermissionState - -import co.touchlab.kermit.Logger import io.github.jan.supabase.common.App import io.github.jan.supabase.common.UploadViewModel import org.koin.android.ext.android.inject diff --git a/sample/file-upload/common/src/androidMain/kotlin/io/github/jan/supabase/common/Uploads.android.kt b/sample/file-upload/common/src/androidMain/kotlin/io/github/jan/supabase/common/Uploads.android.kt index 57ac73cca..df8f65fe4 100644 --- a/sample/file-upload/common/src/androidMain/kotlin/io/github/jan/supabase/common/Uploads.android.kt +++ b/sample/file-upload/common/src/androidMain/kotlin/io/github/jan/supabase/common/Uploads.android.kt @@ -6,6 +6,7 @@ import android.provider.OpenableColumns import io.github.vinceglb.filekit.core.PlatformFile import io.ktor.util.cio.toByteReadChannel import io.ktor.utils.io.ByteReadChannel +import io.ktor.utils.io.discard actual val PlatformFile.dataProducer: suspend (offset: Long) -> ByteReadChannel get() { diff --git a/sample/file-upload/common/src/commonMain/kotlin/io/github/jan/supabase/common/Utils.kt b/sample/file-upload/common/src/commonMain/kotlin/io/github/jan/supabase/common/Utils.kt index 41436ede8..782ffcd24 100644 --- a/sample/file-upload/common/src/commonMain/kotlin/io/github/jan/supabase/common/Utils.kt +++ b/sample/file-upload/common/src/commonMain/kotlin/io/github/jan/supabase/common/Utils.kt @@ -4,7 +4,6 @@ import androidx.compose.ui.ExperimentalComposeUiApi import io.github.jan.supabase.storage.resumable.ResumableClient import io.github.jan.supabase.storage.resumable.ResumableUpload import io.github.vinceglb.filekit.core.PlatformFile -import io.ktor.utils.io.ByteReadChannel import kotlinx.coroutines.Deferred @OptIn(ExperimentalComposeUiApi::class) @@ -12,13 +11,4 @@ expect fun parseFileTreeFromURIs(paths: List): List expect fun parseFileTreeFromPath(path: String): List -suspend fun ByteReadChannel.readAllBytes(size: Long): ByteArray { - val buffer = ByteArray(size.toInt()) - var read = 0 - while(read < size) { - read += readAvailable(buffer, read, size.toInt() - read) - } - return buffer -} - expect suspend fun ResumableClient.continuePreviousPlatformUploads(): List> \ No newline at end of file diff --git a/sample/multi-factor-auth/android/src/main/java/io/github/jan/supabase/android/MainActivity.kt b/sample/multi-factor-auth/android/src/main/java/io/github/jan/supabase/android/MainActivity.kt index 8fa4fecb3..66f6464a7 100644 --- a/sample/multi-factor-auth/android/src/main/java/io/github/jan/supabase/android/MainActivity.kt +++ b/sample/multi-factor-auth/android/src/main/java/io/github/jan/supabase/android/MainActivity.kt @@ -4,9 +4,9 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.material3.MaterialTheme +import io.github.jan.supabase.auth.handleDeeplinks import io.github.jan.supabase.common.App import io.github.jan.supabase.common.AppViewModel -import io.github.jan.supabase.gotrue.handleDeeplinks import org.koin.android.ext.android.inject class MainActivity : ComponentActivity() { diff --git a/sample/multi-factor-auth/common/build.gradle.kts b/sample/multi-factor-auth/common/build.gradle.kts index 481d32c42..148d73afc 100644 --- a/sample/multi-factor-auth/common/build.gradle.kts +++ b/sample/multi-factor-auth/common/build.gradle.kts @@ -35,7 +35,7 @@ kotlin { api(compose.foundation) api(compose.material3) api(compose.materialIconsExtended) - addModules(SupabaseModule.GOTRUE) + addModules(SupabaseModule.AUTH) api(libs.koin.core) } } @@ -51,13 +51,12 @@ kotlin { api(libs.koin.android) api(libs.androidx.lifecycle.viewmodel.ktx) api(libs.androidx.lifecycle.viewmodel.compose) - api(libs.coil.svg) - api(libs.coil.compose) - api(libs.coil) + api(libs.coil2.svg) + api(libs.coil2.compose) + api(libs.coil2) } } val desktopMain by getting { - dependsOn(nonJsMain) dependencies { api(compose.preview) } diff --git a/sample/multi-factor-auth/common/src/androidMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt b/sample/multi-factor-auth/common/src/androidMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt index 119060501..efac2ce79 100644 --- a/sample/multi-factor-auth/common/src/androidMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt +++ b/sample/multi-factor-auth/common/src/androidMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt @@ -1,6 +1,6 @@ package io.github.jan.supabase.common.di -import io.github.jan.supabase.gotrue.AuthConfig +import io.github.jan.supabase.auth.AuthConfig actual fun AuthConfig.platformGoTrueConfig() { scheme = "io.jan.supabase" diff --git a/sample/multi-factor-auth/common/src/androidMain/kotlin/io/github/jan/supabase/common/ui/components/QRCode.kt b/sample/multi-factor-auth/common/src/androidMain/kotlin/io/github/jan/supabase/common/ui/components/QRCode.kt index 33e0db1f7..226be5461 100644 --- a/sample/multi-factor-auth/common/src/androidMain/kotlin/io/github/jan/supabase/common/ui/components/QRCode.kt +++ b/sample/multi-factor-auth/common/src/androidMain/kotlin/io/github/jan/supabase/common/ui/components/QRCode.kt @@ -3,7 +3,6 @@ package io.github.jan.supabase.common.ui.components import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import coil.ImageLoader import coil.compose.AsyncImage diff --git a/sample/multi-factor-auth/common/src/commonMain/kotlin/io/github/jan/supabase/common/App.kt b/sample/multi-factor-auth/common/src/commonMain/kotlin/io/github/jan/supabase/common/App.kt index 3959a8792..1eac458f4 100644 --- a/sample/multi-factor-auth/common/src/commonMain/kotlin/io/github/jan/supabase/common/App.kt +++ b/sample/multi-factor-auth/common/src/commonMain/kotlin/io/github/jan/supabase/common/App.kt @@ -11,10 +11,10 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import io.github.jan.supabase.auth.SessionStatus import io.github.jan.supabase.common.ui.components.AlertDialog import io.github.jan.supabase.common.ui.screen.LoginScreen import io.github.jan.supabase.common.ui.screen.MfaScreen -import io.github.jan.supabase.gotrue.SessionStatus @Composable fun App(viewModel: AppViewModel) { diff --git a/sample/multi-factor-auth/common/src/commonMain/kotlin/io/github/jan/supabase/common/AppViewModel.kt b/sample/multi-factor-auth/common/src/commonMain/kotlin/io/github/jan/supabase/common/AppViewModel.kt index ec6222a62..f0e6f6b5f 100644 --- a/sample/multi-factor-auth/common/src/commonMain/kotlin/io/github/jan/supabase/common/AppViewModel.kt +++ b/sample/multi-factor-auth/common/src/commonMain/kotlin/io/github/jan/supabase/common/AppViewModel.kt @@ -2,12 +2,12 @@ package io.github.jan.supabase.common import io.github.jan.supabase.SupabaseClient +import io.github.jan.supabase.auth.auth +import io.github.jan.supabase.auth.mfa.FactorType +import io.github.jan.supabase.auth.mfa.MfaFactor +import io.github.jan.supabase.auth.providers.Google +import io.github.jan.supabase.auth.providers.builtin.Email import io.github.jan.supabase.exceptions.RestException -import io.github.jan.supabase.gotrue.auth -import io.github.jan.supabase.gotrue.mfa.FactorType -import io.github.jan.supabase.gotrue.mfa.MfaFactor -import io.github.jan.supabase.gotrue.providers.Google -import io.github.jan.supabase.gotrue.providers.builtin.Email import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch diff --git a/sample/multi-factor-auth/common/src/commonMain/kotlin/io/github/jan/supabase/common/di/supabaseModule.kt b/sample/multi-factor-auth/common/src/commonMain/kotlin/io/github/jan/supabase/common/di/supabaseModule.kt index d63fa54d7..c5aadbe0c 100644 --- a/sample/multi-factor-auth/common/src/commonMain/kotlin/io/github/jan/supabase/common/di/supabaseModule.kt +++ b/sample/multi-factor-auth/common/src/commonMain/kotlin/io/github/jan/supabase/common/di/supabaseModule.kt @@ -1,8 +1,8 @@ package io.github.jan.supabase.common.di +import io.github.jan.supabase.auth.Auth +import io.github.jan.supabase.auth.AuthConfig import io.github.jan.supabase.createSupabaseClient -import io.github.jan.supabase.gotrue.Auth -import io.github.jan.supabase.gotrue.AuthConfig import org.koin.dsl.module expect fun AuthConfig.platformGoTrueConfig() diff --git a/sample/multi-factor-auth/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/screen/LoginScreen.kt b/sample/multi-factor-auth/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/screen/LoginScreen.kt index 88ca1f152..ec2591321 100644 --- a/sample/multi-factor-auth/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/screen/LoginScreen.kt +++ b/sample/multi-factor-auth/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/screen/LoginScreen.kt @@ -16,7 +16,6 @@ import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -29,7 +28,6 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import io.github.jan.supabase.common.AppViewModel -import io.github.jan.supabase.common.ui.components.AlertDialog import io.github.jan.supabase.common.ui.components.GoogleButton import io.github.jan.supabase.common.ui.components.PasswordField diff --git a/sample/multi-factor-auth/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/screen/MfaScreen.kt b/sample/multi-factor-auth/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/screen/MfaScreen.kt index bd53c2270..2a5d04ec8 100644 --- a/sample/multi-factor-auth/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/screen/MfaScreen.kt +++ b/sample/multi-factor-auth/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/screen/MfaScreen.kt @@ -3,8 +3,8 @@ package io.github.jan.supabase.common.ui.screen import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import io.github.jan.supabase.auth.mfa.MfaStatus import io.github.jan.supabase.common.AppViewModel -import io.github.jan.supabase.gotrue.mfa.MfaStatus @Composable fun MfaScreen(viewModel: AppViewModel) { diff --git a/sample/multi-factor-auth/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/screen/MfaSetupScreen.kt b/sample/multi-factor-auth/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/screen/MfaSetupScreen.kt index 76337956b..8ad5636fd 100644 --- a/sample/multi-factor-auth/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/screen/MfaSetupScreen.kt +++ b/sample/multi-factor-auth/common/src/commonMain/kotlin/io/github/jan/supabase/common/ui/screen/MfaSetupScreen.kt @@ -1,7 +1,6 @@ package io.github.jan.supabase.common.ui.screen import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize @@ -15,7 +14,6 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text -import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -24,14 +22,13 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.scale import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.unit.dp +import io.github.jan.supabase.auth.mfa.FactorType +import io.github.jan.supabase.auth.mfa.MfaFactor import io.github.jan.supabase.common.AppViewModel import io.github.jan.supabase.common.ui.components.QRCode -import io.github.jan.supabase.gotrue.mfa.FactorType -import io.github.jan.supabase.gotrue.mfa.MfaFactor @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/sample/multi-factor-auth/common/src/desktopMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt b/sample/multi-factor-auth/common/src/desktopMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt index ca4d938cf..daa63e2b1 100644 --- a/sample/multi-factor-auth/common/src/desktopMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt +++ b/sample/multi-factor-auth/common/src/desktopMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt @@ -1,6 +1,6 @@ package io.github.jan.supabase.common.di -import io.github.jan.supabase.gotrue.AuthConfig +import io.github.jan.supabase.auth.AuthConfig actual fun AuthConfig.platformGoTrueConfig() { httpCallbackConfig { htmlTitle = "Chat App" } diff --git a/sample/multi-factor-auth/common/src/desktopMain/kotlin/io/github/jan/supabase/common/ui/components/QRCode.kt b/sample/multi-factor-auth/common/src/desktopMain/kotlin/io/github/jan/supabase/common/ui/components/QRCode.kt index 4fec6dc29..0d4514743 100644 --- a/sample/multi-factor-auth/common/src/desktopMain/kotlin/io/github/jan/supabase/common/ui/components/QRCode.kt +++ b/sample/multi-factor-auth/common/src/desktopMain/kotlin/io/github/jan/supabase/common/ui/components/QRCode.kt @@ -3,11 +3,9 @@ package io.github.jan.supabase.common.ui.components import androidx.compose.foundation.Image import androidx.compose.runtime.Composable import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.res.loadSvgPainter import androidx.compose.ui.unit.Density -import java.io.ByteArrayInputStream @Composable actual fun QRCode(svgData: String, modifier: Modifier) { diff --git a/sample/multi-factor-auth/common/src/jsMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt b/sample/multi-factor-auth/common/src/jsMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt index 5919bfd33..a0e2b25b3 100644 --- a/sample/multi-factor-auth/common/src/jsMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt +++ b/sample/multi-factor-auth/common/src/jsMain/kotlin/io/github/jan/supabase/common/di/platformGoTrueConfig.kt @@ -1,6 +1,6 @@ package io.github.jan.supabase.common.di -import io.github.jan.supabase.gotrue.AuthConfig +import io.github.jan.supabase.auth.AuthConfig actual fun AuthConfig.platformGoTrueConfig() { } \ No newline at end of file diff --git a/sample/multi-factor-auth/common/src/jsMain/kotlin/io/github/jan/supabase/common/ui/components/QRCode.kt b/sample/multi-factor-auth/common/src/jsMain/kotlin/io/github/jan/supabase/common/ui/components/QRCode.kt index 6105b8bac..c6c27b5c0 100644 --- a/sample/multi-factor-auth/common/src/jsMain/kotlin/io/github/jan/supabase/common/ui/components/QRCode.kt +++ b/sample/multi-factor-auth/common/src/jsMain/kotlin/io/github/jan/supabase/common/ui/components/QRCode.kt @@ -1,7 +1,6 @@ package io.github.jan.supabase.common.ui.components import androidx.compose.foundation.Canvas -import androidx.compose.foundation.layout.Spacer import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier diff --git a/serializers/Jackson/build.gradle.kts b/serializers/Jackson/build.gradle.kts index 23c1e834b..1b5e1145c 100644 --- a/serializers/Jackson/build.gradle.kts +++ b/serializers/Jackson/build.gradle.kts @@ -12,10 +12,8 @@ repositories { @OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class) kotlin { defaultConfig() - jvm() - androidTarget { - publishLibraryVariants("release", "debug") - } + configuredAndroidTarget() + configuredJvmTarget() sourceSets { val commonMain by getting { dependencies { diff --git a/serializers/Moshi/build.gradle.kts b/serializers/Moshi/build.gradle.kts index b0d89e2b4..ea836ba61 100644 --- a/serializers/Moshi/build.gradle.kts +++ b/serializers/Moshi/build.gradle.kts @@ -12,10 +12,8 @@ repositories { @OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class) kotlin { defaultConfig() - jvm() - androidTarget { - publishLibraryVariants("release", "debug") - } + configuredAndroidTarget() + configuredJvmTarget() sourceSets { val commonMain by getting { dependencies { diff --git a/settings.gradle.kts b/settings.gradle.kts index 1af111188..431bff73d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1,4 @@ + pluginManagement { repositories { google() @@ -11,7 +12,7 @@ plugins { } // Main Modules -include("GoTrue") +include("Auth") include("Postgrest") include("Storage") include("Realtime") @@ -32,6 +33,7 @@ project(":serializers:Jackson").name = "serializer-jackson" include(":plugins:ApolloGraphQL") include(":plugins:ComposeAuth") include(":plugins:ComposeAuthUI") +include(":plugins:Coil3Integration") include(":plugins:CoilIntegration") include(":plugins:ImageLoaderIntegration") @@ -43,7 +45,7 @@ if (System.getProperty("LibrariesOnly") != "true") { } // Renames -project(":GoTrue").name = "gotrue-kt" +project(":Auth").name = "auth-kt" project(":Postgrest").name = "postgrest-kt" project(":Storage").name = "storage-kt" project(":Realtime").name = "realtime-kt" @@ -52,6 +54,7 @@ project(":Supabase").name = "supabase-kt" project(":plugins:ApolloGraphQL").name = "apollo-graphql" project(":plugins:ComposeAuth").name = "compose-auth" project(":plugins:ComposeAuthUI").name = "compose-auth-ui" +project(":plugins:Coil3Integration").name = "coil3-integration" project(":plugins:CoilIntegration").name = "coil-integration" project(":plugins:ImageLoaderIntegration").name = "imageloader-integration" rootProject.name = "supabase-kt"