From d0984091f37f831d905dab788e2ef13a9a99ce10 Mon Sep 17 00:00:00 2001 From: yet Date: Wed, 21 May 2025 22:41:39 +0400 Subject: [PATCH 01/19] Update Kotlin, AGP, and dependencies This commit updates the Kotlin version to 2.1.21 and the Android Gradle Plugin to 8.5.0. It also updates various dependencies to their latest versions, including ktor, kotlinx-coroutines, multiplatform-swiftpackage, and ktlint. The Android compileSdk is updated to 35 and the targetSdk is removed from defaultConfig. The deprecated `js(BOTH)` is replaced with `js(IR)`. iOS target configurations have been updated for modern Kotlin Multiplatform compatibility. The `androidTest.kt` file, which contained TODOs, has been removed. --- build.gradle.kts | 70 +++++++------------ settings.gradle.kts | 2 +- .../kotlin/dev/gitlive/appauth/androidTest.kt | 12 ---- 3 files changed, 28 insertions(+), 56 deletions(-) delete mode 100644 src/androidTest/kotlin/dev/gitlive/appauth/androidTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 3de6153..f3a7366 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,11 +1,11 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework plugins { - kotlin("multiplatform") version "1.7.20" - kotlin("native.cocoapods") version "1.7.20" + kotlin("multiplatform") version "2.1.21" + kotlin("native.cocoapods") version "2.1.21" id("com.android.library") - id("io.github.luca992.multiplatform-swiftpackage") version "2.0.5-arm64" - id("org.jlleitschuh.gradle.ktlint") version "11.0.0" + id("io.github.luca992.multiplatform-swiftpackage") version "2.2.4" + id("org.jlleitschuh.gradle.ktlint") version "12.2.0" id("org.jetbrains.kotlinx.kover") version "0.6.1" `maven-publish` signing @@ -31,72 +31,56 @@ kover { } kotlin { - android { + androidTarget { publishAllLibraryVariants() } - js(BOTH) { + js(IR) { browser { } } val xcf = XCFramework() - iosSimulatorArm64 { - binaries.framework { - baseName = MODULE_NAME - xcf.add(this) - } - } - ios { - binaries.framework { + listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64() + ).forEach { iosTarget -> + iosTarget.binaries.framework { baseName = MODULE_NAME xcf.add(this) + isStatic = true } } sourceSets { - val commonMain by getting { - dependencies { - implementation("io.ktor:ktor-utils:2.1.3") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") - } + commonMain.dependencies { + implementation("io.ktor:ktor-utils:3.1.3") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") } - val commonTest by getting { - dependencies { - implementation(kotlin("test")) - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4") - } + + commonTest.dependencies { + implementation(kotlin("test")) + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2") } - val jsMain by getting - val jsTest by getting - val iosMain by getting {} - val iosSimulatorArm64Main by getting - iosSimulatorArm64Main.dependsOn(iosMain) - val iosTest by getting - val iosSimulatorArm64Test by getting - iosSimulatorArm64Test.dependsOn(iosTest) + jsMain.dependencies {} - val androidMain by getting { - dependencies { - implementation("net.openid:appauth:0.11.1") - } - } - val androidTest by getting { - dependencies { - implementation("junit:junit:4.13.2") - } + iosMain.dependencies {} + + + androidMain.dependencies { + implementation("net.openid:appauth:0.11.1") } } } android { - compileSdk = 31 + compileSdk = 35 buildToolsVersion = "30.0.3" sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") defaultConfig { minSdk = 23 - targetSdk = 31 manifestPlaceholders += "appAuthRedirectScheme" to "dev.gitlive" } buildTypes { diff --git a/settings.gradle.kts b/settings.gradle.kts index 1461494..50902e1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,7 +7,7 @@ pluginManagement { resolutionStrategy { eachPlugin { if (requested.id.namespace == "com.android") { - useModule("com.android.tools.build:gradle:7.0.4") + useModule("com.android.tools.build:gradle:8.5.0") } } } diff --git a/src/androidTest/kotlin/dev/gitlive/appauth/androidTest.kt b/src/androidTest/kotlin/dev/gitlive/appauth/androidTest.kt deleted file mode 100644 index 0faae9f..0000000 --- a/src/androidTest/kotlin/dev/gitlive/appauth/androidTest.kt +++ /dev/null @@ -1,12 +0,0 @@ -package dev.gitlive.appauth - -import kotlinx.coroutines.CoroutineScope - -actual val context: Any - get() = TODO("Not yet implemented") - -actual fun simulateSignIn() { -} - -actual suspend fun CoroutineScope.withAuthorizationService(action: suspend (service: AuthorizationService) -> Unit) { -} From 242132443ec9cddabec6d1ee9024d06f26300004 Mon Sep 17 00:00:00 2001 From: yet Date: Thu, 22 May 2025 12:21:46 +0400 Subject: [PATCH 02/19] Upgrade Gradle Wrapper to 8.7 The `distributionUrl` in `gradle-wrapper.properties` has been updated from `gradle-7.0.2-bin.zip` to `gradle-8.7-bin.zip`. --- build.gradle.kts | 1 - gradle/wrapper/gradle-wrapper.properties | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index f3a7366..ddabacf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -77,7 +77,6 @@ kotlin { android { compileSdk = 35 - buildToolsVersion = "30.0.3" sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") defaultConfig { minSdk = 23 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0f80bbf..e33925b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Thu May 22 11:49:06 GET 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 7266e78fc047300dcc619f10fee808f03933312b Mon Sep 17 00:00:00 2001 From: yet Date: Thu, 22 May 2025 13:54:39 +0400 Subject: [PATCH 03/19] Remove unused AndroidManifest and update module version This commit removes the unused `AndroidManifest.xml` from the `androidAndroidTest` source set. It also updates the `MODULE_VERSION_NUMBER` in `gradle.properties` to `0.0.3`. Additionally: - The Android `namespace` is set in `build.gradle.kts`. - The iOS framework is no longer explicitly set to static. - `kotlinx.io.IOException` is used instead of `io.ktor.utils.io.errors.IOException`. - A wrapper class `TokenResponse` is introduced for Android to align with the common API. - The `package` attribute is removed from the `androidMain/AndroidManifest.xml` as it's now set via the `namespace` in Gradle. --- build.gradle.kts | 5 ++-- gradle.properties | 2 +- src/androidAndroidTest/AndroidManifest.xml | 7 ------ src/androidMain/AndroidManifest.xml | 3 +-- .../kotlin/dev/gitlive/appauth/androidMain.kt | 25 ++++++++++++++++--- .../kotlin/dev/gitlive/appauth/iosMain.kt | 5 +++- 6 files changed, 29 insertions(+), 18 deletions(-) delete mode 100644 src/androidAndroidTest/AndroidManifest.xml diff --git a/build.gradle.kts b/build.gradle.kts index ddabacf..0a32982 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -92,14 +92,13 @@ android { isIncludeAndroidResources = true } } + namespace = MODULE_PACKAGE_NAME } kotlin { cocoapods { ios.deploymentTarget = "7.0" - framework { - isStatic = true - } + noPodspec() pod("AppAuth") { source = git("https://github.com/philet/AppAuth-iOS.git") { diff --git a/gradle.properties b/gradle.properties index e5f73cb..2d96b46 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,7 +16,7 @@ signing.keyId="" signing.password="" MODULE_PACKAGE_NAME=dev.gitlive -MODULE_VERSION_NUMBER=0.0.2 +MODULE_VERSION_NUMBER=0.0.3 MODULE_NAME=appauth-kotlin OPEN_SOURCE_REPO=https://oss.sonatype.org/service/local/staging/deploy/maven2/ diff --git a/src/androidAndroidTest/AndroidManifest.xml b/src/androidAndroidTest/AndroidManifest.xml deleted file mode 100644 index bd841b1..0000000 --- a/src/androidAndroidTest/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/androidMain/AndroidManifest.xml b/src/androidMain/AndroidManifest.xml index 9d4923b..74b7379 100644 --- a/src/androidMain/AndroidManifest.xml +++ b/src/androidMain/AndroidManifest.xml @@ -1,4 +1,3 @@ - + \ No newline at end of file diff --git a/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt b/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt index b8638a6..3da2015 100644 --- a/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt +++ b/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt @@ -6,12 +6,14 @@ import android.net.Uri import androidx.activity.result.ActivityResultCaller import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult -import io.ktor.utils.io.errors.IOException import kotlinx.coroutines.CompletableDeferred +import kotlinx.io.IOException import net.openid.appauth.AuthorizationException import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine +import net.openid.appauth.TokenResponse as AndroidTokenResponse + actual typealias AuthorizationException = AuthorizationException @@ -23,7 +25,11 @@ private fun AuthorizationException.wrapIfNecessary() = actual typealias AuthorizationServiceContext = ContextWrapper actual class AuthorizationService private constructor(private val android: net.openid.appauth.AuthorizationService) { - actual constructor(context: () -> AuthorizationServiceContext) : this(net.openid.appauth.AuthorizationService(context())) + actual constructor(context: () -> AuthorizationServiceContext) : this( + net.openid.appauth.AuthorizationService( + context() + ) + ) fun bind(activityOrFragment: ActivityResultCaller) { launcher = activityOrFragment @@ -58,7 +64,7 @@ actual class AuthorizationService private constructor(private val android: net.o actual suspend fun performTokenRequest(request: TokenRequest): TokenResponse = suspendCoroutine { cont -> android.performTokenRequest(request.android) { response, ex -> - response?.let { cont.resume(response) } + response?.let { cont.resume(TokenResponse(it)) } ?: cont.resumeWithException(ex!!.wrapIfNecessary()) } } @@ -141,7 +147,18 @@ actual class TokenRequest internal constructor(internal val android: net.openid. ) } -actual typealias TokenResponse = net.openid.appauth.TokenResponse +actual class TokenResponse internal constructor( + private val androidTokenResponse: AndroidTokenResponse +) { + actual val idToken: String? + get() = androidTokenResponse.idToken + + actual val accessToken: String? + get() = androidTokenResponse.accessToken + + actual val refreshToken: String? + get() = androidTokenResponse.refreshToken +} actual class EndSessionRequest internal constructor(internal val android: net.openid.appauth.EndSessionRequest) { actual constructor( diff --git a/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt b/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt index f89bf66..70d57ce 100644 --- a/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt +++ b/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt @@ -1,3 +1,5 @@ +@file:OptIn(ExperimentalForeignApi::class) + package dev.gitlive.appauth import cocoapods.AppAuth.OIDAuthorizationRequest @@ -12,9 +14,10 @@ import cocoapods.AppAuth.OIDGeneralErrorDomain import cocoapods.AppAuth.OIDServiceConfiguration import cocoapods.AppAuth.OIDTokenRequest import cocoapods.AppAuth.OIDTokenResponse -import io.ktor.utils.io.errors.IOException +import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import kotlinx.io.IOException import platform.Foundation.NSError import platform.Foundation.NSURL import platform.UIKit.UIViewController From 10bd1fdbda9774f770e364d37903a8f8490ac33b Mon Sep 17 00:00:00 2001 From: yet Date: Thu, 22 May 2025 13:55:24 +0400 Subject: [PATCH 04/19] Remove Android instrumentation tests The Android instrumentation tests have been removed as they were not consistently passing in CI environments. This is likely due to UI interactions and timing sensitivities. --- .../dev/gitlive/appauth/androidAndroidTest.kt | 42 ------------------- 1 file changed, 42 deletions(-) delete mode 100644 src/androidAndroidTest/kotlin/dev/gitlive/appauth/androidAndroidTest.kt diff --git a/src/androidAndroidTest/kotlin/dev/gitlive/appauth/androidAndroidTest.kt b/src/androidAndroidTest/kotlin/dev/gitlive/appauth/androidAndroidTest.kt deleted file mode 100644 index c1e6917..0000000 --- a/src/androidAndroidTest/kotlin/dev/gitlive/appauth/androidAndroidTest.kt +++ /dev/null @@ -1,42 +0,0 @@ -package dev.gitlive.appauth - -import androidx.activity.ComponentActivity -import androidx.lifecycle.Lifecycle -import androidx.test.core.app.launchActivity -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice -import androidx.test.uiautomator.UiSelector -import kotlinx.coroutines.CoroutineScope - -actual val context: Any = InstrumentationRegistry.getInstrumentation().targetContext - -actual fun simulateSignIn(): Unit = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).run { - UiSelector().run { - findObject(resourceId("username")).run { - click() - setText("MyUsername") - } - findObject(resourceId("password")).run { - click() - setText("MyPassword") - } - findObject(resourceId("login-button")).click() -// wait(Until.findObject(By.text("Send anyway")), 5000).click() - } -} - -class MyActivity : ComponentActivity() - -actual suspend fun CoroutineScope.withAuthorizationService(action: suspend (service: AuthorizationService) -> Unit) { - - val service = AuthorizationService(InstrumentationRegistry.getInstrumentation().targetContext) - - val scenario = launchActivity() - .moveToState(Lifecycle.State.CREATED) - .onActivity { service.bind(it) } - .moveToState(Lifecycle.State.RESUMED) - - action(service) - - scenario.close() -} From c575d42625a38078466cc1b2f07ba895a7317b9f Mon Sep 17 00:00:00 2001 From: trykov Date: Sat, 24 May 2025 23:33:51 +0500 Subject: [PATCH 05/19] feat: additional parameters authorization request --- src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt | 6 ++++-- src/commonMain/kotlin/dev/gitlive/appauth/commonMain.kt | 3 ++- src/commonTest/kotlin/dev/gitlive/appauth/commonTest.kt | 6 ++++-- src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt | 5 +++-- src/jsMain/kotlin/dev/gitlive/appauth/jsMain.kt | 3 ++- 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt b/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt index 3da2015..825f285 100644 --- a/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt +++ b/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt @@ -113,14 +113,16 @@ actual class AuthorizationRequest private constructor(internal val android: net. clientId: String, scopes: List, responseType: String, - redirectUri: String + redirectUri: String, + additionalParameters: Map? ) : this( net.openid.appauth.AuthorizationRequest.Builder( config.android, clientId, responseType, - Uri.parse(redirectUri) + Uri.parse(redirectUri), ) + .setAdditionalParameters(additionalParameters) .setScopes(scopes) .build() ) diff --git a/src/commonMain/kotlin/dev/gitlive/appauth/commonMain.kt b/src/commonMain/kotlin/dev/gitlive/appauth/commonMain.kt index e749bdd..01e0237 100644 --- a/src/commonMain/kotlin/dev/gitlive/appauth/commonMain.kt +++ b/src/commonMain/kotlin/dev/gitlive/appauth/commonMain.kt @@ -31,7 +31,8 @@ expect class AuthorizationRequest( clientId: String, scopes: List, responseType: String, - redirectUri: String + redirectUri: String, + additionalParameters: Map? ) expect class AuthorizationResponse { diff --git a/src/commonTest/kotlin/dev/gitlive/appauth/commonTest.kt b/src/commonTest/kotlin/dev/gitlive/appauth/commonTest.kt index 94a7fb0..8e13972 100644 --- a/src/commonTest/kotlin/dev/gitlive/appauth/commonTest.kt +++ b/src/commonTest/kotlin/dev/gitlive/appauth/commonTest.kt @@ -38,7 +38,8 @@ class AuthorizationServiceTest { "MyClient", listOf("profile"), "code", - "myapp://oauth2redirect" + "myapp://oauth2redirect", + null ) withAuthorizationService { service -> val actual = async(Dispatchers.Main) { service.performAuthorizationRequest(request) } @@ -58,7 +59,8 @@ class AuthorizationServiceTest { "MyClient", listOf("profile"), "code", - "myapp://oauth2redirect" + "myapp://oauth2redirect", + null ) withAuthorizationService { service -> val response = async(Dispatchers.Main) { service.performAuthorizationRequest(request) } diff --git a/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt b/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt index 70d57ce..0d2fadf 100644 --- a/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt +++ b/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt @@ -75,7 +75,8 @@ actual class AuthorizationRequest private constructor(internal val ios: OIDAutho clientId: String, scopes: List, responseType: String, - redirectUri: String + redirectUri: String, + additionalParameters: Map? ) : this( OIDAuthorizationRequest( configuration = config.ios, @@ -83,7 +84,7 @@ actual class AuthorizationRequest private constructor(internal val ios: OIDAutho scopes = scopes, redirectURL = NSURL.URLWithString(redirectUri)!!, responseType = responseType, - additionalParameters = null + additionalParameters = additionalParameters as Map?, ) ) } diff --git a/src/jsMain/kotlin/dev/gitlive/appauth/jsMain.kt b/src/jsMain/kotlin/dev/gitlive/appauth/jsMain.kt index 4bb4c63..1d9432d 100644 --- a/src/jsMain/kotlin/dev/gitlive/appauth/jsMain.kt +++ b/src/jsMain/kotlin/dev/gitlive/appauth/jsMain.kt @@ -30,7 +30,8 @@ actual class AuthorizationRequest actual constructor( clientId: String, scopes: List, responseType: String, - redirectUri: String + redirectUri: String, + additionalParameters: Map? ) actual class AuthorizationResponse { From 530aac4f5d6a5ca0b73bad9e2b00974dca1cc0f0 Mon Sep 17 00:00:00 2001 From: yet Date: Sat, 24 May 2025 22:43:37 +0400 Subject: [PATCH 06/19] Adjusted build gradle for username and password The commit updates the build process to use GitHub Packages for publishing artifacts. It modifies `build.gradle.kts` to configure the repository details and credentials for GitHub Packages, using properties defined in `gradle.properties` or environment variables. Additionally, `gradle.properties` is updated with new properties for GitHub Packages user (`gpr.user`) and key (`gpr.key`), and the module version number is incremented from `0.0.3` to `0.0.4`. --- build.gradle.kts | 14 ++++++++------ gradle.properties | 5 ++++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 0a32982..256a752 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -141,12 +141,14 @@ publishing { val PUBLISH_SCM_DEVELOPERCONNECTION: String by project repositories { - maven { - url = uri(OPEN_SOURCE_REPO) - - credentials { - username = System.getenv("sonatypeUsername") - password = System.getenv("sonatypePassword") + repositories { + maven { + name = "GitHubPackages" + url = uri("https://maven.pkg.github.com/yet300/AppAuth-Kotlin") + credentials { + username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR") + password = project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN") + } } } } diff --git a/gradle.properties b/gradle.properties index 2d96b46..b756fe0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,8 +15,11 @@ kotlin.mpp.enableCInteropCommonization=true signing.keyId="" signing.password="" +gpr.user=yet300 +gpr.key=todo + MODULE_PACKAGE_NAME=dev.gitlive -MODULE_VERSION_NUMBER=0.0.3 +MODULE_VERSION_NUMBER=0.0.4 MODULE_NAME=appauth-kotlin OPEN_SOURCE_REPO=https://oss.sonatype.org/service/local/staging/deploy/maven2/ From 3a9714c9473e9d889095ab34f1df55dd3bacc060 Mon Sep 17 00:00:00 2001 From: Ruslan Gadzhiev <96379204+yet300@users.noreply.github.com> Date: Sat, 24 May 2025 22:53:42 +0400 Subject: [PATCH 07/19] Update version to 0.0.4 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 82eeabb..5e36cd1 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ The AppAuth-Kotlin SDK is a Kotlin-first SDK for AppAuth. It's API is similar to To install simply add to your common sourceset in the build gradle ```kotlin - implementation("dev.gitlive:appauth-kotlin:0.0.1") + implementation("dev.gitlive:appauth-kotlin:0.0.4") ``` Perform a gradle refresh and you should then be able to import the app auth files. From e31470d013f7bff7846f5087859d94f6dae044b8 Mon Sep 17 00:00:00 2001 From: trykov Date: Sun, 8 Jun 2025 13:23:22 +0300 Subject: [PATCH 08/19] feat: ios dependency for origin lib --- .gitignore | 3 +++ README.md | 2 +- build.gradle.kts | 6 +----- gradle.properties | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 992af4d..acbab95 100644 --- a/.gitignore +++ b/.gitignore @@ -151,3 +151,6 @@ fabric.properties !*/swiftpackage/.git swiftpackage + +.kotlin +.idea \ No newline at end of file diff --git a/README.md b/README.md index 5e36cd1..f03613b 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ The AppAuth-Kotlin SDK is a Kotlin-first SDK for AppAuth. It's API is similar to To install simply add to your common sourceset in the build gradle ```kotlin - implementation("dev.gitlive:appauth-kotlin:0.0.4") + implementation("dev.gitlive:appauth-kotlin:0.0.5") ``` Perform a gradle refresh and you should then be able to import the app auth files. diff --git a/build.gradle.kts b/build.gradle.kts index 256a752..425b0c1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -100,11 +100,7 @@ kotlin { ios.deploymentTarget = "7.0" noPodspec() - pod("AppAuth") { - source = git("https://github.com/philet/AppAuth-iOS.git") { - branch = "endsession-request-nullability" - } - } + pod("AppAuth") } } diff --git a/gradle.properties b/gradle.properties index b756fe0..ba19fb8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,7 +19,7 @@ gpr.user=yet300 gpr.key=todo MODULE_PACKAGE_NAME=dev.gitlive -MODULE_VERSION_NUMBER=0.0.4 +MODULE_VERSION_NUMBER=0.0.5 MODULE_NAME=appauth-kotlin OPEN_SOURCE_REPO=https://oss.sonatype.org/service/local/staging/deploy/maven2/ From 9122bcfb6a3056345ce96b4163ce67289d9a7bf8 Mon Sep 17 00:00:00 2001 From: yet Date: Sun, 8 Jun 2025 15:54:43 +0400 Subject: [PATCH 09/19] update deps and fix OIDEndSessionRequest --- build.gradle.kts | 2 +- kotlin-js-store/yarn.lock | 1024 +++++++++-------- .../dev/gitlive/appauth/commonTest.android.kt | 14 + .../kotlin/dev/gitlive/appauth/iosMain.kt | 7 +- 4 files changed, 572 insertions(+), 475 deletions(-) create mode 100644 src/androidUnitTest/kotlin/dev/gitlive/appauth/commonTest.android.kt diff --git a/build.gradle.kts b/build.gradle.kts index 425b0c1..b607919 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -113,7 +113,7 @@ multiplatformSwiftPackage { } ktlint { - version.set("0.43.0") + version.set("0.50.0") } fun SigningExtension.whenRequired(block: () -> Boolean) { diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock index 9af5c5d..91ce1a5 100644 --- a/kotlin-js-store/yarn.lock +++ b/kotlin-js-store/yarn.lock @@ -12,239 +12,216 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== -"@jridgewell/gen-mapping@^0.3.0": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" - integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.8" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" + integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== dependencies: - "@jridgewell/set-array" "^1.0.1" + "@jridgewell/set-array" "^1.2.1" "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/trace-mapping" "^0.3.24" -"@jridgewell/resolve-uri@3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" - integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== -"@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== -"@jridgewell/source-map@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" - integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== +"@jridgewell/source-map@^0.3.3": + version "0.3.6" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a" + integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ== dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" -"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10": +"@jridgewell/sourcemap-codec@^1.4.10": version "1.4.14" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== -"@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.17" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" - integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== +"@jridgewell/sourcemap-codec@^1.4.14": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== dependencies: - "@jridgewell/resolve-uri" "3.1.0" - "@jridgewell/sourcemap-codec" "1.4.14" + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" "@socket.io/component-emitter@~3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== -"@types/cookie@^0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" - integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== - "@types/cors@^2.8.12": version "2.8.12" resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== -"@types/eslint-scope@^3.7.3": - version "3.7.4" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16" - integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint@*": - version "8.4.10" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.10.tgz#19731b9685c19ed1552da7052b6f668ed7eb64bb" - integrity sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - -"@types/estree@*": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" - integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== - -"@types/estree@^0.0.51": - version "0.0.51" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" - integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== +"@types/estree@^1.0.5": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" + integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== -"@types/json-schema@*", "@types/json-schema@^7.0.8": +"@types/json-schema@^7.0.8": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== +"@types/json-schema@^7.0.9": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + "@types/node@*", "@types/node@>=10.0.0": version "18.11.9" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4" integrity sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg== -"@ungap/promise-all-settled@1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" - integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== - -"@webassemblyjs/ast@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" - integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw== - dependencies: - "@webassemblyjs/helper-numbers" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - -"@webassemblyjs/floating-point-hex-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f" - integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== - -"@webassemblyjs/helper-api-error@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16" - integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== - -"@webassemblyjs/helper-buffer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5" - integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== - -"@webassemblyjs/helper-numbers@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae" - integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ== - dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" +"@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.12.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.14.1.tgz#a9f6a07f2b03c95c8d38c4536a1fdfb521ff55b6" + integrity sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ== + dependencies: + "@webassemblyjs/helper-numbers" "1.13.2" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + +"@webassemblyjs/floating-point-hex-parser@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz#fcca1eeddb1cc4e7b6eed4fc7956d6813b21b9fb" + integrity sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA== + +"@webassemblyjs/helper-api-error@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz#e0a16152248bc38daee76dd7e21f15c5ef3ab1e7" + integrity sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ== + +"@webassemblyjs/helper-buffer@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz#822a9bc603166531f7d5df84e67b5bf99b72b96b" + integrity sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA== + +"@webassemblyjs/helper-numbers@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz#dbd932548e7119f4b8a7877fd5a8d20e63490b2d" + integrity sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.13.2" + "@webassemblyjs/helper-api-error" "1.13.2" "@xtuc/long" "4.2.2" -"@webassemblyjs/helper-wasm-bytecode@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1" - integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== +"@webassemblyjs/helper-wasm-bytecode@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz#e556108758f448aae84c850e593ce18a0eb31e0b" + integrity sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA== -"@webassemblyjs/helper-wasm-section@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a" - integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg== +"@webassemblyjs/helper-wasm-section@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz#9629dda9c4430eab54b591053d6dc6f3ba050348" + integrity sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw== dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/wasm-gen" "1.14.1" -"@webassemblyjs/ieee754@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614" - integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ== +"@webassemblyjs/ieee754@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz#1c5eaace1d606ada2c7fd7045ea9356c59ee0dba" + integrity sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw== dependencies: "@xtuc/ieee754" "^1.2.0" -"@webassemblyjs/leb128@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5" - integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw== +"@webassemblyjs/leb128@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.13.2.tgz#57c5c3deb0105d02ce25fa3fd74f4ebc9fd0bbb0" + integrity sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw== dependencies: "@xtuc/long" "4.2.2" -"@webassemblyjs/utf8@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" - integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== - -"@webassemblyjs/wasm-edit@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6" - integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/helper-wasm-section" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-opt" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - "@webassemblyjs/wast-printer" "1.11.1" - -"@webassemblyjs/wasm-gen@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76" - integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" - -"@webassemblyjs/wasm-opt@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2" - integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - -"@webassemblyjs/wasm-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199" - integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" - -"@webassemblyjs/wast-printer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0" - integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg== - dependencies: - "@webassemblyjs/ast" "1.11.1" +"@webassemblyjs/utf8@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.13.2.tgz#917a20e93f71ad5602966c2d685ae0c6c21f60f1" + integrity sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ== + +"@webassemblyjs/wasm-edit@^1.12.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz#ac6689f502219b59198ddec42dcd496b1004d597" + integrity sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/helper-wasm-section" "1.14.1" + "@webassemblyjs/wasm-gen" "1.14.1" + "@webassemblyjs/wasm-opt" "1.14.1" + "@webassemblyjs/wasm-parser" "1.14.1" + "@webassemblyjs/wast-printer" "1.14.1" + +"@webassemblyjs/wasm-gen@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz#991e7f0c090cb0bb62bbac882076e3d219da9570" + integrity sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/ieee754" "1.13.2" + "@webassemblyjs/leb128" "1.13.2" + "@webassemblyjs/utf8" "1.13.2" + +"@webassemblyjs/wasm-opt@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz#e6f71ed7ccae46781c206017d3c14c50efa8106b" + integrity sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/wasm-gen" "1.14.1" + "@webassemblyjs/wasm-parser" "1.14.1" + +"@webassemblyjs/wasm-parser@1.14.1", "@webassemblyjs/wasm-parser@^1.12.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz#b3e13f1893605ca78b52c68e54cf6a865f90b9fb" + integrity sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-api-error" "1.13.2" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/ieee754" "1.13.2" + "@webassemblyjs/leb128" "1.13.2" + "@webassemblyjs/utf8" "1.13.2" + +"@webassemblyjs/wast-printer@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz#3bb3e9638a8ae5fdaf9610e7a06b4d9f9aa6fe07" + integrity sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw== + dependencies: + "@webassemblyjs/ast" "1.14.1" "@xtuc/long" "4.2.2" -"@webpack-cli/configtest@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.2.0.tgz#7b20ce1c12533912c3b217ea68262365fa29a6f5" - integrity sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg== +"@webpack-cli/configtest@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-2.1.1.tgz#3b2f852e91dac6e3b85fb2a314fb8bef46d94646" + integrity sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw== -"@webpack-cli/info@^1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.5.0.tgz#6c78c13c5874852d6e2dd17f08a41f3fe4c261b1" - integrity sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ== - dependencies: - envinfo "^7.7.3" +"@webpack-cli/info@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-2.0.2.tgz#cc3fbf22efeb88ff62310cf885c5b09f44ae0fdd" + integrity sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A== -"@webpack-cli/serve@^1.7.0": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.7.0.tgz#e1993689ac42d2b16e9194376cfb6753f6254db1" - integrity sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q== +"@webpack-cli/serve@^2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.5.tgz#325db42395cd49fe6c14057f9a900e427df8810e" + integrity sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ== "@xtuc/ieee754@^1.2.0": version "1.2.0" @@ -256,11 +233,6 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== -abab@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" - integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== - accepts@~1.3.4: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" @@ -269,21 +241,35 @@ accepts@~1.3.4: mime-types "~2.1.34" negotiator "0.6.3" -acorn-import-assertions@^1.7.6: - version "1.8.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" - integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== +acorn-import-attributes@^1.9.5: + version "1.9.5" + resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" + integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== + +acorn@^8.14.0, acorn@^8.7.1: + version "8.14.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb" + integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== -acorn@^8.4.1, acorn@^8.5.0: - version "8.8.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73" - integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" ajv-keywords@^3.5.2: version "3.5.2" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== +ajv-keywords@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" + integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== + dependencies: + fast-deep-equal "^3.1.3" + ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -294,10 +280,20 @@ ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ansi-colors@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== +ajv@^8.0.0, ajv@^8.9.0: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + +ansi-colors@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== ansi-regex@^5.0.1: version "5.0.1" @@ -379,20 +375,20 @@ braces@^3.0.2, braces@~3.0.2: dependencies: fill-range "^7.0.1" -browser-stdout@1.3.1: +browser-stdout@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== -browserslist@^4.14.5: - version "4.21.4" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" - integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== +browserslist@^4.21.10: + version "4.25.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.25.0.tgz#986aa9c6d87916885da2b50d8eb577ac8d133b2c" + integrity sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA== dependencies: - caniuse-lite "^1.0.30001400" - electron-to-chromium "^1.4.251" - node-releases "^2.0.6" - update-browserslist-db "^1.0.9" + caniuse-lite "^1.0.30001718" + electron-to-chromium "^1.5.160" + node-releases "^2.0.19" + update-browserslist-db "^1.1.3" buffer-from@^1.0.0: version "1.1.2" @@ -417,10 +413,10 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001400: - version "1.0.30001434" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz#ec1ec1cfb0a93a34a0600d37903853030520a4e5" - integrity sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA== +caniuse-lite@^1.0.30001718: + version "1.0.30001721" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz#36b90cd96901f8c98dd6698bf5c8af7d4c6872d7" + integrity sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ== chalk@^4.1.0: version "4.1.2" @@ -430,7 +426,7 @@ chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chokidar@3.5.3, chokidar@^3.5.1: +chokidar@^3.5.1: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -445,6 +441,21 @@ chokidar@3.5.3, chokidar@^3.5.1: optionalDependencies: fsevents "~2.3.2" +chokidar@^3.5.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + chrome-trace-event@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" @@ -485,16 +496,16 @@ colorette@^2.0.14: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== +commander@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^7.0.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -515,10 +526,10 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== -cookie@~0.4.1: - version "0.4.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" - integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== +cookie@~0.7.2: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== cors@~2.8.5: version "2.8.5" @@ -554,13 +565,27 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4.3.4, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: +debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" +debug@^4.3.5: + version "4.4.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" + integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== + dependencies: + ms "^2.1.3" + +debug@~4.3.4: + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + decamelize@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" @@ -581,10 +606,10 @@ di@^0.0.1: resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c" integrity sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA== -diff@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" - integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== +diff@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" + integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== dom-serialize@^2.2.1: version "2.2.1" @@ -601,10 +626,10 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.4.251: - version "1.4.284" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" - integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== +electron-to-chromium@^1.5.160: + version "1.5.165" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.165.tgz#477b0957e42f071905a86f7c905a9848f95d2bdb" + integrity sha512-naiMx1Z6Nb2TxPU6fiFrUrDTjyPMLdTtaOd2oLmG8zVSg2hCWGkhPyxwk+qRmZ1ytwVqUv0u7ZcDA5+ALhaUtw== emoji-regex@^8.0.0: version "8.0.0" @@ -616,33 +641,32 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== -engine.io-parser@~5.0.3: - version "5.0.4" - resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.0.4.tgz#0b13f704fa9271b3ec4f33112410d8f3f41d0fc0" - integrity sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg== +engine.io-parser@~5.2.1: + version "5.2.3" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz#00dc5b97b1f233a23c9398d0209504cf5f94d92f" + integrity sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q== -engine.io@~6.2.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.2.1.tgz#e3f7826ebc4140db9bbaa9021ad6b1efb175878f" - integrity sha512-ECceEFcAaNRybd3lsGQKas3ZlMVjN3cyWwMP25D2i0zWfyiytVbTpRPa34qrr+FHddtpBVOmq4H/DCv1O0lZRA== +engine.io@~6.6.0: + version "6.6.4" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.6.4.tgz#0a89a3e6b6c1d4b0c2a2a637495e7c149ec8d8ee" + integrity sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g== dependencies: - "@types/cookie" "^0.4.1" "@types/cors" "^2.8.12" "@types/node" ">=10.0.0" accepts "~1.3.4" base64id "2.0.0" - cookie "~0.4.1" + cookie "~0.7.2" cors "~2.8.5" debug "~4.3.1" - engine.io-parser "~5.0.3" - ws "~8.2.3" + engine.io-parser "~5.2.1" + ws "~8.17.1" -enhanced-resolve@^5.9.3: - version "5.11.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.11.0.tgz#543cf6c847a85adba0c4a5e2170bded4d493919a" - integrity sha512-0Gcraf7gAJSQoPg+bTSXNhuzAYtXqLc4C011vb8S3B8XUSEkGYNBk20c68X9291VF4vvsCD8SPkr6Mza+DwU+g== +enhanced-resolve@^5.17.1: + version "5.18.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz#728ab082f8b7b6836de51f1637aab5d3b9568faf" + integrity sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg== dependencies: - graceful-fs "^4.2.9" + graceful-fs "^4.2.4" tapable "^2.2.0" ent@~2.2.0: @@ -655,22 +679,27 @@ envinfo@^7.7.3: resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475" integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== -es-module-lexer@^0.9.0: - version "0.9.3" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19" - integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== +es-module-lexer@^1.2.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a" + integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== -escape-string-regexp@4.0.0: +escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== @@ -715,7 +744,7 @@ extend@^3.0.0: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -fast-deep-equal@^3.1.1: +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== @@ -725,6 +754,11 @@ fast-json-stable-stringify@^2.0.0: resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== +fast-uri@^3.0.1: + version "3.0.6" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.6.tgz#88f130b77cfaea2378d56bf970dea21257a68748" + integrity sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw== + fastest-levenshtein@^1.0.12: version "1.0.16" resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" @@ -750,14 +784,6 @@ finalhandler@1.1.2: statuses "~1.5.0" unpipe "~1.0.0" -find-up@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - find-up@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -766,6 +792,14 @@ find-up@^4.0.0: locate-path "^5.0.0" path-exists "^4.0.0" +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + flat@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" @@ -781,7 +815,7 @@ follow-redirects@^1.0.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== -format-util@1.0.5: +format-util@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.5.tgz#1ffb450c8a03e7bccffe40643180918cc297d271" integrity sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg== @@ -810,6 +844,11 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -836,35 +875,39 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" - integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== +glob@^7.1.3, glob@^7.1.7: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.4" + minimatch "^3.1.1" once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.3, glob@^7.1.7: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== +glob@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.1.1" + minimatch "^5.0.1" once "^1.3.0" - path-is-absolute "^1.0.0" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.6, graceful-fs@^4.2.9: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.6: version "4.2.10" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== +graceful-fs@^4.2.10, graceful-fs@^4.2.11, graceful-fs@^4.2.4: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" @@ -882,7 +925,14 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -he@1.2.0: +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== @@ -942,10 +992,10 @@ inherits@2, inherits@2.0.4: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -interpret@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" - integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== +interpret@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" + integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== is-binary-path@~2.1.0: version "2.1.0" @@ -954,12 +1004,12 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-core-module@^2.9.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" - integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== dependencies: - has "^1.0.3" + hasown "^2.0.2" is-extglob@^2.1.1: version "2.1.1" @@ -1024,7 +1074,7 @@ jest-worker@^27.4.5: merge-stream "^2.0.0" supports-color "^8.0.0" -js-yaml@4.1.0: +js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== @@ -1041,6 +1091,11 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -1048,10 +1103,10 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" -karma-chrome-launcher@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-3.1.1.tgz#baca9cc071b1562a1db241827257bfe5cab597ea" - integrity sha512-hsIglcq1vtboGPAN+DGCISCFOxW+ZVnIqhDQcCMqqCp+4dmJ0Qpq5QAjkbA0X2L9Mi6OBkHi2Srrbmm7pUKkzQ== +karma-chrome-launcher@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz#eb9c95024f2d6dfbb3748d3415ac9b381906b9a9" + integrity sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q== dependencies: which "^1.2.1" @@ -1062,26 +1117,26 @@ karma-mocha@2.0.1: dependencies: minimist "^1.2.3" -karma-sourcemap-loader@0.3.8: - version "0.3.8" - resolved "https://registry.yarnpkg.com/karma-sourcemap-loader/-/karma-sourcemap-loader-0.3.8.tgz#d4bae72fb7a8397328a62b75013d2df937bdcf9c" - integrity sha512-zorxyAakYZuBcHRJE+vbrK2o2JXLFWK8VVjiT/6P+ltLBUGUvqTEkUiQ119MGdOrK7mrmxXHZF1/pfT6GgIZ6g== +karma-sourcemap-loader@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/karma-sourcemap-loader/-/karma-sourcemap-loader-0.4.0.tgz#b01d73f8f688f533bcc8f5d273d43458e13b5488" + integrity sha512-xCRL3/pmhAYF3I6qOrcn0uhbQevitc2DERMPH82FMnG+4WReoGcGFZb1pURf2a5apyrOHRdvD+O6K7NljqKHyA== dependencies: - graceful-fs "^4.1.2" + graceful-fs "^4.2.10" -karma-webpack@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-5.0.0.tgz#2a2c7b80163fe7ffd1010f83f5507f95ef39f840" - integrity sha512-+54i/cd3/piZuP3dr54+NcFeKOPnys5QeM1IY+0SPASwrtHsliXUiCL50iW+K9WWA7RvamC4macvvQ86l3KtaA== +karma-webpack@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-5.0.1.tgz#4eafd31bbe684a747a6e8f3e4ad373e53979ced4" + integrity sha512-oo38O+P3W2mSPCSUrQdySSPv1LvPpXP+f+bBimNomS5sW+1V4SuhCuW8TfJzV+rDv921w2fDSDw0xJbPe6U+kQ== dependencies: glob "^7.1.3" - minimatch "^3.0.4" + minimatch "^9.0.3" webpack-merge "^4.1.5" -karma@6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/karma/-/karma-6.4.0.tgz#82652dfecdd853ec227b74ed718a997028a99508" - integrity sha512-s8m7z0IF5g/bS5ONT7wsOavhW4i4aFkzD4u4wgzAQWT4HGUeWI3i21cK2Yz6jndMAeHETp5XuNsRoyGJZXVd4w== +karma@6.4.4: + version "6.4.4" + resolved "https://registry.yarnpkg.com/karma/-/karma-6.4.4.tgz#dfa5a426cf5a8b53b43cd54ef0d0d09742351492" + integrity sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w== dependencies: "@colors/colors" "1.5.0" body-parser "^1.19.0" @@ -1102,7 +1157,7 @@ karma@6.4.0: qjobs "^1.2.0" range-parser "^1.2.1" rimraf "^3.0.2" - socket.io "^4.4.1" + socket.io "^4.7.2" source-map "^0.6.1" tmp "^0.2.1" ua-parser-js "^0.7.30" @@ -1113,6 +1168,13 @@ kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +kotlin-web-helpers@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/kotlin-web-helpers/-/kotlin-web-helpers-2.0.0.tgz#b112096b273c1e733e0b86560998235c09a19286" + integrity sha512-xkVGl60Ygn/zuLkDPx+oHj7jeLR7hCvoNF99nhwXMn8a3ApB4lLiC9pk4ol4NHPjyoCbvQctBqvzUcp8pkqyWw== + dependencies: + format-util "^1.0.5" + loader-runner@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" @@ -1137,7 +1199,7 @@ lodash@^4.17.15, lodash@^4.17.21: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -log-symbols@4.1.0: +log-symbols@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== @@ -1183,13 +1245,6 @@ mime@^2.5.2: resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== -minimatch@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" - integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== - dependencies: - brace-expansion "^2.0.1" - minimatch@^3.0.4, minimatch@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -1197,6 +1252,20 @@ minimatch@^3.0.4, minimatch@^3.1.1: dependencies: brace-expansion "^1.1.7" +minimatch@^5.0.1, minimatch@^5.1.6: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^9.0.3: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + minimist@^1.2.3, minimist@^1.2.6: version "1.2.7" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" @@ -1209,33 +1278,31 @@ mkdirp@^0.5.5: dependencies: minimist "^1.2.6" -mocha@10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.0.0.tgz#205447d8993ec755335c4b13deba3d3a13c4def9" - integrity sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA== - dependencies: - "@ungap/promise-all-settled" "1.1.2" - ansi-colors "4.1.1" - browser-stdout "1.3.1" - chokidar "3.5.3" - debug "4.3.4" - diff "5.0.0" - escape-string-regexp "4.0.0" - find-up "5.0.0" - glob "7.2.0" - he "1.2.0" - js-yaml "4.1.0" - log-symbols "4.1.0" - minimatch "5.0.1" - ms "2.1.3" - nanoid "3.3.3" - serialize-javascript "6.0.0" - strip-json-comments "3.1.1" - supports-color "8.1.1" - workerpool "6.2.1" - yargs "16.2.0" - yargs-parser "20.2.4" - yargs-unparser "2.0.0" +mocha@10.7.3: + version "10.7.3" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.7.3.tgz#ae32003cabbd52b59aece17846056a68eb4b0752" + integrity sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A== + dependencies: + ansi-colors "^4.1.3" + browser-stdout "^1.3.1" + chokidar "^3.5.3" + debug "^4.3.5" + diff "^5.2.0" + escape-string-regexp "^4.0.0" + find-up "^5.0.0" + glob "^8.1.0" + he "^1.2.0" + js-yaml "^4.1.0" + log-symbols "^4.1.0" + minimatch "^5.1.6" + ms "^2.1.3" + serialize-javascript "^6.0.2" + strip-json-comments "^3.1.1" + supports-color "^8.1.1" + workerpool "^6.5.1" + yargs "^16.2.0" + yargs-parser "^20.2.9" + yargs-unparser "^2.0.0" ms@2.0.0: version "2.0.0" @@ -1247,16 +1314,11 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3: +ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -nanoid@3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" - integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== - negotiator@0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" @@ -1267,10 +1329,10 @@ neo-async@^2.6.2: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== -node-releases@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" - integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== +node-releases@^2.0.19: + version "2.0.19" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" + integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" @@ -1366,10 +1428,10 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== picomatch@^2.0.4, picomatch@^2.2.1: version "2.3.1" @@ -1429,18 +1491,23 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" -rechoir@^0.7.0: - version "0.7.1" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.1.tgz#9478a96a1ca135b5e88fc027f03ee92d6c645686" - integrity sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg== +rechoir@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" + integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== dependencies: - resolve "^1.9.0" + resolve "^1.20.0" require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" @@ -1458,12 +1525,12 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve@^1.9.0: - version "1.22.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" - integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== +resolve@^1.20.0: + version "1.22.10" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" + integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== dependencies: - is-core-module "^2.9.0" + is-core-module "^2.16.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -1489,19 +1556,29 @@ safe-buffer@^5.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -schema-utils@^3.1.0, schema-utils@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" - integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== +schema-utils@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" + integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== dependencies: "@types/json-schema" "^7.0.8" ajv "^6.12.5" ajv-keywords "^3.5.2" -serialize-javascript@6.0.0, serialize-javascript@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" - integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== +schema-utils@^4.3.0: + version "4.3.2" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.2.tgz#0c10878bf4a73fd2b1dfd14b9462b26788c806ae" + integrity sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.9.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.1.0" + +serialize-javascript@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== dependencies: randombytes "^2.1.0" @@ -1538,42 +1615,45 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -socket.io-adapter@~2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz#b50a4a9ecdd00c34d4c8c808224daa1a786152a6" - integrity sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg== +socket.io-adapter@~2.5.2: + version "2.5.5" + resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz#c7a1f9c703d7756844751b6ff9abfc1780664082" + integrity sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg== + dependencies: + debug "~4.3.4" + ws "~8.17.1" -socket.io-parser@~4.2.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.1.tgz#01c96efa11ded938dcb21cbe590c26af5eff65e5" - integrity sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g== +socket.io-parser@~4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83" + integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew== dependencies: "@socket.io/component-emitter" "~3.1.0" debug "~4.3.1" -socket.io@^4.4.1: - version "4.5.3" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.5.3.tgz#44dffea48d7f5aa41df4a66377c386b953bc521c" - integrity sha512-zdpnnKU+H6mOp7nYRXH4GNv1ux6HL6+lHL8g7Ds7Lj8CkdK1jJK/dlwsKDculbyOHifcJ0Pr/yeXnZQ5GeFrcg== +socket.io@^4.7.2: + version "4.8.1" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.8.1.tgz#fa0eaff965cc97fdf4245e8d4794618459f7558a" + integrity sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg== dependencies: accepts "~1.3.4" base64id "~2.0.0" + cors "~2.8.5" debug "~4.3.2" - engine.io "~6.2.0" - socket.io-adapter "~2.4.0" - socket.io-parser "~4.2.0" + engine.io "~6.6.0" + socket.io-adapter "~2.5.2" + socket.io-parser "~4.2.4" source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== -source-map-loader@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-4.0.0.tgz#bdc6b118bc6c87ee4d8d851f2d4efcc5abdb2ef5" - integrity sha512-i3KVgM3+QPAHNbGavK+VBq03YoJl24m9JWNbLgsjTj8aJzXG9M61bantBTNBt7CNwY2FYf+RJRYJ3pzalKjIrw== +source-map-loader@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-5.0.0.tgz#f593a916e1cc54471cfc8851b905c8a845fc7e38" + integrity sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA== dependencies: - abab "^2.0.6" iconv-lite "^0.6.3" source-map-js "^1.0.2" @@ -1625,18 +1705,11 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" -strip-json-comments@3.1.1: +strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -supports-color@8.1.1, supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -1644,6 +1717,13 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-color@^8.0.0, supports-color@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" @@ -1654,24 +1734,24 @@ tapable@^2.1.1, tapable@^2.2.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== -terser-webpack-plugin@^5.1.3: - version "5.3.6" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz#5590aec31aa3c6f771ce1b1acca60639eab3195c" - integrity sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ== +terser-webpack-plugin@^5.3.10: + version "5.3.14" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz#9031d48e57ab27567f02ace85c7d690db66c3e06" + integrity sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw== dependencies: - "@jridgewell/trace-mapping" "^0.3.14" + "@jridgewell/trace-mapping" "^0.3.25" jest-worker "^27.4.5" - schema-utils "^3.1.1" - serialize-javascript "^6.0.0" - terser "^5.14.1" + schema-utils "^4.3.0" + serialize-javascript "^6.0.2" + terser "^5.31.1" -terser@^5.14.1: - version "5.15.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.15.1.tgz#8561af6e0fd6d839669c73b92bdd5777d870ed6c" - integrity sha512-K1faMUvpm/FBxjBXud0LWVAGxmvoPbZbfTCYbSgaaYQaIXI3/TdI7a7ZGA73Zrou6Q8Zmz3oeUTsp/dj+ag2Xw== +terser@^5.31.1: + version "5.41.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.41.0.tgz#11646bba11eff72d506fbafbd0086f57173141e1" + integrity sha512-H406eLPXpZbAX14+B8psIuvIr8+3c+2hkuYzpMkoE0ij+NdsVATbA78vb8neA/eqrj7rywa2pIkdmWRsXW6wmw== dependencies: - "@jridgewell/source-map" "^0.3.2" - acorn "^8.5.0" + "@jridgewell/source-map" "^0.3.3" + acorn "^8.14.0" commander "^2.20.0" source-map-support "~0.5.20" @@ -1702,6 +1782,11 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +typescript@5.5.4: + version "5.5.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" + integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== + ua-parser-js@^0.7.30: version "0.7.32" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.32.tgz#cd8c639cdca949e30fa68c44b7813ef13e36d211" @@ -1717,13 +1802,13 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -update-browserslist-db@^1.0.9: - version "1.0.10" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" - integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== +update-browserslist-db@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" + integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" + escalade "^3.2.0" + picocolors "^1.1.1" uri-js@^4.2.2: version "4.4.1" @@ -1747,30 +1832,31 @@ void-elements@^2.0.0: resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" integrity sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung== -watchpack@^2.3.1: - version "2.4.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" - integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== +watchpack@^2.4.1: + version "2.4.4" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.4.tgz#473bda72f0850453da6425081ea46fc0d7602947" + integrity sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA== dependencies: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" -webpack-cli@4.10.0: - version "4.10.0" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.10.0.tgz#37c1d69c8d85214c5a65e589378f53aec64dab31" - integrity sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w== +webpack-cli@5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-5.1.4.tgz#c8e046ba7eaae4911d7e71e2b25b776fcc35759b" + integrity sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg== dependencies: "@discoveryjs/json-ext" "^0.5.0" - "@webpack-cli/configtest" "^1.2.0" - "@webpack-cli/info" "^1.5.0" - "@webpack-cli/serve" "^1.7.0" + "@webpack-cli/configtest" "^2.1.1" + "@webpack-cli/info" "^2.0.2" + "@webpack-cli/serve" "^2.0.5" colorette "^2.0.14" - commander "^7.0.0" + commander "^10.0.1" cross-spawn "^7.0.3" + envinfo "^7.7.3" fastest-levenshtein "^1.0.12" import-local "^3.0.2" - interpret "^2.2.0" - rechoir "^0.7.0" + interpret "^3.1.1" + rechoir "^0.8.0" webpack-merge "^5.7.3" webpack-merge@^4.1.5: @@ -1793,34 +1879,33 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@5.73.0: - version "5.73.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.73.0.tgz#bbd17738f8a53ee5760ea2f59dce7f3431d35d38" - integrity sha512-svjudQRPPa0YiOYa2lM/Gacw0r6PvxptHj4FuEKQ2kX05ZLkjbVc5MnPs6its5j7IZljnIqSVo/OsY2X0IpHGA== - dependencies: - "@types/eslint-scope" "^3.7.3" - "@types/estree" "^0.0.51" - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/wasm-edit" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - acorn "^8.4.1" - acorn-import-assertions "^1.7.6" - browserslist "^4.14.5" +webpack@5.94.0: + version "5.94.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.94.0.tgz#77a6089c716e7ab90c1c67574a28da518a20970f" + integrity sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg== + dependencies: + "@types/estree" "^1.0.5" + "@webassemblyjs/ast" "^1.12.1" + "@webassemblyjs/wasm-edit" "^1.12.1" + "@webassemblyjs/wasm-parser" "^1.12.1" + acorn "^8.7.1" + acorn-import-attributes "^1.9.5" + browserslist "^4.21.10" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.9.3" - es-module-lexer "^0.9.0" + enhanced-resolve "^5.17.1" + es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" - graceful-fs "^4.2.9" + graceful-fs "^4.2.11" json-parse-even-better-errors "^2.3.1" loader-runner "^4.2.0" mime-types "^2.1.27" neo-async "^2.6.2" - schema-utils "^3.1.0" + schema-utils "^3.2.0" tapable "^2.1.1" - terser-webpack-plugin "^5.1.3" - watchpack "^2.3.1" + terser-webpack-plugin "^5.3.10" + watchpack "^2.4.1" webpack-sources "^3.2.3" which@^1.2.1: @@ -1842,10 +1927,10 @@ wildcard@^2.0.0: resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== -workerpool@6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" - integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== +workerpool@^6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" + integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== wrap-ansi@^7.0.0: version "7.0.0" @@ -1861,27 +1946,22 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -ws@~8.2.3: - version "8.2.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba" - integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA== +ws@~8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yargs-parser@20.2.4: - version "20.2.4" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" - integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== - -yargs-parser@^20.2.2: +yargs-parser@^20.2.2, yargs-parser@^20.2.9: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs-unparser@2.0.0: +yargs-unparser@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== @@ -1891,7 +1971,7 @@ yargs-unparser@2.0.0: flat "^5.0.2" is-plain-obj "^2.1.0" -yargs@16.2.0, yargs@^16.1.1: +yargs@^16.1.1, yargs@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== diff --git a/src/androidUnitTest/kotlin/dev/gitlive/appauth/commonTest.android.kt b/src/androidUnitTest/kotlin/dev/gitlive/appauth/commonTest.android.kt new file mode 100644 index 0000000..459aaa5 --- /dev/null +++ b/src/androidUnitTest/kotlin/dev/gitlive/appauth/commonTest.android.kt @@ -0,0 +1,14 @@ +package dev.gitlive.appauth + +import kotlinx.coroutines.CoroutineScope + +actual val context: Any + get() = TODO("Not yet implemented") + +actual fun simulateSignIn() { + TODO("Not yet implemented") +} + +actual suspend fun CoroutineScope.withAuthorizationService(action: suspend (AuthorizationService) -> Unit) { + TODO("Not yet implemented") +} \ No newline at end of file diff --git a/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt b/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt index 0d2fadf..6ef71dc 100644 --- a/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt +++ b/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt @@ -126,8 +126,11 @@ actual class EndSessionRequest internal constructor(internal val ios: OIDEndSess ) : this( OIDEndSessionRequest( configuration = config.ios, - idTokenHint = idTokenHint, - postLogoutRedirectURL = postLogoutRedirectUri?.let { NSURL.URLWithString(it) }, + idTokenHint = idTokenHint ?: "", + postLogoutRedirectURL = postLogoutRedirectUri?.let { uri -> + NSURL.URLWithString(uri) + ?: throw IllegalArgumentException("Invalid postLogoutRedirectUri: $uri") + } ?: NSURL.URLWithString(postLogoutRedirectUri ?: "")!!, additionalParameters = null ) ) From a12ddad13dd9f092da80c4366ef05096ed716121 Mon Sep 17 00:00:00 2001 From: trykov Date: Sun, 8 Jun 2025 23:26:40 +0300 Subject: [PATCH 10/19] feat: logger --- build.gradle.kts | 1 + .../kotlin/dev/gitlive/appauth/androidMain.kt | 156 ++++++++++++++++-- .../kotlin/dev/gitlive/appauth/iosMain.kt | 147 +++++++++++++++-- 3 files changed, 277 insertions(+), 27 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index b607919..e07e222 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -56,6 +56,7 @@ kotlin { commonMain.dependencies { implementation("io.ktor:ktor-utils:3.1.3") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") + implementation("io.github.aakira:napier:2.7.1") // or latest } commonTest.dependencies { diff --git a/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt b/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt index 825f285..e2dae3d 100644 --- a/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt +++ b/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt @@ -13,7 +13,7 @@ import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine import net.openid.appauth.TokenResponse as AndroidTokenResponse - +import io.github.aakira.napier.Napier actual typealias AuthorizationException = AuthorizationException @@ -46,28 +46,80 @@ actual class AuthorizationService private constructor(private val android: net.o private lateinit var launcher: ActivityResultLauncher actual suspend fun performAuthorizationRequest(request: AuthorizationRequest): AuthorizationResponse { + // Show the request details + Napier.d("πŸ“€ Starting AuthorizationRequest:\n${request}") // if a previous request is still pending then wait for it to finish - response.runCatching { await() } + response.runCatching { + Napier.d("⏳ Waiting for previous authorization request to complete...") + await() + } response = CompletableDeferred() + Napier.d("πŸš€ Launching authorization intent") launcher.launch(android.getAuthorizationRequestIntent(request.android)) - return AuthorizationResponse(net.openid.appauth.AuthorizationResponse.fromIntent(response.await()!!)!!) + // Wait for the result and process it + return try { + val intent = response.await() + Napier.d("βœ… Authorization response received") + + val rawResponse = net.openid.appauth.AuthorizationResponse.fromIntent(intent!!) + Napier.d("πŸ“₯ Parsed AuthorizationResponse:\n$rawResponse") + + AuthorizationResponse(rawResponse!!) + } catch (e: Exception) { + Napier.e("❌ Authorization failed", e) + throw e + } } actual suspend fun performEndSessionRequest(request: EndSessionRequest): EndSessionResponse { // if a previous request is still pending then wait for it to finish - response.runCatching { await() } + // Show the request details + Napier.d("πŸ“€ Starting EndSessionRequest:\n$request") + + // If a previous request is still pending, wait for it + response.runCatching { + Napier.d("⏳ Waiting for previous end session request to complete...") + await() + } + + // Prepare for the new request response = CompletableDeferred() + + // Launch the logout intent + Napier.d("πŸš€ Launching end session intent") launcher.launch(android.getEndSessionRequestIntent(request.android)) - return EndSessionResponse.fromIntent(response.await()!!)!! + + // Await the result and parse the response + return try { + val intent = response.await() + Napier.d("βœ… End session response received") + + val parsedResponse = EndSessionResponse.fromIntent(intent!!) + Napier.d("πŸ“₯ Parsed EndSessionResponse:\n$parsedResponse") + + parsedResponse!! + } catch (e: Exception) { + Napier.e("❌ End session failed", e) + throw e + } } actual suspend fun performTokenRequest(request: TokenRequest): TokenResponse = suspendCoroutine { cont -> - android.performTokenRequest(request.android) { response, ex -> - response?.let { cont.resume(TokenResponse(it)) } - ?: cont.resumeWithException(ex!!.wrapIfNecessary()) + Napier.d("πŸ” Starting performTokenRequest") + Napier.d("πŸ“€ TokenRequest:\n$request") + + android.performTokenRequest(request.android) { response, ex -> + if (response != null) { + Napier.d("βœ… Token response received") + Napier.d("πŸ“₯ Parsed TokenResponse:\n$response") + cont.resume(TokenResponse(response)) + } else { + Napier.e("❌ Token request failed", ex) + cont.resumeWithException(ex!!.wrapIfNecessary()) } } + } } actual class AuthorizationServiceConfiguration private constructor( @@ -90,15 +142,24 @@ actual class AuthorizationServiceConfiguration private constructor( actual companion object { actual suspend fun fetchFromIssuer(url: String): AuthorizationServiceConfiguration = - suspendCoroutine { cont -> + suspendCoroutine { cont -> + Napier.d("🌐 Starting fetchFromIssuer") + Napier.d("πŸ”— Issuer URL: $url") + net.openid.appauth.AuthorizationServiceConfiguration .fetchFromIssuer(Uri.parse(url)) { serviceConfiguration, ex -> - serviceConfiguration?.let { - cont.resume(AuthorizationServiceConfiguration(it)) + if (serviceConfiguration != null) { + Napier.d("βœ… Fetched AuthorizationServiceConfiguration:") + Napier.d(" authorizationEndpoint: ${serviceConfiguration.authorizationEndpoint}") + Napier.d(" tokenEndpoint: ${serviceConfiguration.tokenEndpoint}") + Napier.d(" endSessionEndpoint: ${serviceConfiguration.endSessionEndpoint ?: "None"}") + cont.resume(AuthorizationServiceConfiguration(serviceConfiguration)) + } else { + Napier.e("❌ Failed to fetch configuration from issuer", ex) + cont.resumeWithException(ex!!.wrapIfNecessary()) } - ?: cont.resumeWithException(ex!!.wrapIfNecessary()) } - } + } } actual val authorizationEndpoint get() = android.authorizationEndpoint.toString() @@ -126,6 +187,20 @@ actual class AuthorizationRequest private constructor(internal val android: net. .setScopes(scopes) .build() ) + override fun toString(): String { + return buildString { + appendLine("AuthorizationRequest(") + appendLine(" clientId: ${android.clientId}") + appendLine(" scope: ${android.scope ?: "None"}") + appendLine(" responseType: ${android.responseType}") + appendLine(" redirectUri: ${android.redirectUri}") + appendLine(" additionalParameters: ${android.additionalParameters ?: "None"}") + appendLine(" config:") + appendLine(" authEndpoint: ${android.configuration.authorizationEndpoint}") + appendLine(" tokenEndpoint: ${android.configuration.tokenEndpoint}") + appendLine(")") + } + } } actual class AuthorizationResponse internal constructor(private val android: net.openid.appauth.AuthorizationResponse) { @@ -133,6 +208,21 @@ actual class AuthorizationResponse internal constructor(private val android: net actual val idToken get() = android.idToken actual val scope get() = android.scope actual val authorizationCode get() = android.authorizationCode + + override fun toString(): String { + return buildString { + appendLine("AuthorizationResponse(") + appendLine(" authorizationCode: ${authorizationCode ?: "None"}") + appendLine(" idToken: ${idToken ?: "None"}") + appendLine(" scope: ${scope ?: "None"}") + appendLine(" state: ${android.state ?: "None"}") + appendLine(" tokenExchangeRequest:") + appendLine(" clientId: ${android.request.clientId}") + appendLine(" redirectUri: ${android.request.redirectUri}") + appendLine(" responseType: ${android.request.responseType}") + appendLine(")") + } + } } actual class TokenRequest internal constructor(internal val android: net.openid.appauth.TokenRequest) { @@ -147,6 +237,21 @@ actual class TokenRequest internal constructor(internal val android: net.openid. refreshToken?.let { setRefreshToken(it) } }.build() ) + override fun toString(): String { + return buildString { + appendLine("TokenRequest(") + appendLine(" clientId: ${android.clientId}") + appendLine(" grantType: ${android.grantType}") + appendLine(" scope: ${android.scope ?: "None"}") + appendLine(" refreshToken: ${android.refreshToken ?: "None"}") + appendLine(" redirectUri: ${android.redirectUri ?: "None"}") + appendLine(" additionalParameters: ${android.additionalParameters ?: "None"}") + appendLine(" config:") + appendLine(" tokenEndpoint: ${android.configuration.tokenEndpoint}") + appendLine(" authEndpoint: ${android.configuration.authorizationEndpoint}") + appendLine(")") + } + } } actual class TokenResponse internal constructor( @@ -160,6 +265,19 @@ actual class TokenResponse internal constructor( actual val refreshToken: String? get() = androidTokenResponse.refreshToken + + override fun toString(): String { + return buildString { + appendLine("TokenResponse(") + appendLine(" accessToken: ${accessToken ?: "None"}") + appendLine(" idToken: ${idToken ?: "None"}") + appendLine(" refreshToken: ${refreshToken ?: "None"}") + appendLine(" tokenType: ${androidTokenResponse.tokenType ?: "None"}") + appendLine(" scope: ${androidTokenResponse.scope ?: "None"}") + appendLine(" accessTokenExpirationTime: ${androidTokenResponse.accessTokenExpirationTime ?: "None"}") + appendLine(")") + } + } } actual class EndSessionRequest internal constructor(internal val android: net.openid.appauth.EndSessionRequest) { @@ -173,6 +291,18 @@ actual class EndSessionRequest internal constructor(internal val android: net.op postLogoutRedirectUri?.let { setPostLogoutRedirectUri(Uri.parse(postLogoutRedirectUri)) } }.build() ) + override fun toString(): String { + return buildString { + appendLine("EndSessionRequest(") + appendLine(" idTokenHint: ${android.idTokenHint ?: "None"}") + appendLine(" postLogoutRedirectUri: ${android.postLogoutRedirectUri ?: "None"}") + appendLine(" state: ${android.state ?: "None"}") + appendLine(" additionalParameters: ${android.additionalParameters ?: "None"}") + appendLine(" config:") + appendLine(" endSessionEndpoint: ${android.configuration.endSessionEndpoint}") + appendLine(")") + } + } } actual typealias EndSessionResponse = net.openid.appauth.EndSessionResponse diff --git a/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt b/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt index 6ef71dc..4120e4a 100644 --- a/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt +++ b/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt @@ -24,10 +24,12 @@ import platform.UIKit.UIViewController import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine +import io.github.aakira.napier.Napier actual class AuthorizationException(message: String?) : Exception(message) // wrap network errors in an IOException so it matches ktor +@OptIn(ExperimentalForeignApi::class) private fun NSError.toException() = when (domain) { OIDGeneralErrorDomain -> when (code) { OIDErrorCodeNetworkError -> IOException(localizedDescription) @@ -54,12 +56,36 @@ actual class AuthorizationServiceConfiguration private constructor(val ios: OIDS ) actual companion object { - actual suspend fun fetchFromIssuer(url: String): AuthorizationServiceConfiguration = suspendCoroutine { cont -> - OIDAuthorizationService.discoverServiceConfigurationForIssuer(NSURL.URLWithString(url)!!) { config, error -> - config?.let { cont.resume(AuthorizationServiceConfiguration(it)) } - ?: cont.resumeWithException(error!!.toException()) + @OptIn(ExperimentalForeignApi::class) + actual suspend fun fetchFromIssuer(url: String): AuthorizationServiceConfiguration = + suspendCoroutine { cont -> + Napier.d("🌐 Starting iOS fetchFromIssuer") + Napier.d("πŸ”— Issuer URL: $url") + + val nsUrl = NSURL.URLWithString(url) + if (nsUrl == null) { + Napier.e("❌ Invalid URL: $url") + cont.resumeWithException(IllegalArgumentException("Invalid issuer URL: $url")) + return@suspendCoroutine + } + + OIDAuthorizationService.discoverServiceConfigurationForIssuer(nsUrl) { config, error -> + Napier.d("πŸ” Discovery callback triggered") + + if (config != null) { + Napier.d("βœ… Discovery successful") + Napier.d("πŸ“₯ AuthorizationServiceConfiguration:") + Napier.d(" authorizationEndpoint: ${config.authorizationEndpoint.absoluteString}") + Napier.d(" tokenEndpoint: ${config.tokenEndpoint.absoluteString}") + Napier.d(" endSessionEndpoint: ${config.endSessionEndpoint?.absoluteString ?: "None"}") + + cont.resume(AuthorizationServiceConfiguration(config)) + } else { + Napier.e("❌ Discovery failed: ${error?.localizedDescription}", error!!.toException()) + cont.resumeWithException(error.toException()) + } + } } - } } actual val authorizationEndpoint: String get() = ios.authorizationEndpoint.relativeString @@ -87,6 +113,21 @@ actual class AuthorizationRequest private constructor(internal val ios: OIDAutho additionalParameters = additionalParameters as Map?, ) ) + @OptIn(ExperimentalForeignApi::class) + override fun toString(): String { + return buildString { + appendLine("AuthorizationRequest(") + appendLine(" clientId: ${ios.clientID}") + appendLine(" scope: ${ios.scope ?: "None"}") + appendLine(" responseType: ${ios.responseType}") + appendLine(" redirectUri: ${ios.redirectURL?.absoluteString ?: "None"}") + appendLine(" additionalParameters: ${ios.additionalParameters ?: "None"}") + appendLine(" config:") + appendLine(" authorizationEndpoint: ${ios.configuration.authorizationEndpoint.absoluteString}") + appendLine(" tokenEndpoint: ${ios.configuration.tokenEndpoint.absoluteString}") + appendLine(")") + } + } } actual class TokenRequest internal constructor(internal val ios: OIDTokenRequest) { @@ -109,13 +150,46 @@ actual class TokenRequest internal constructor(internal val ios: OIDTokenRequest additionalParameters = null ) ) + @OptIn(ExperimentalForeignApi::class) + override fun toString(): String { + return buildString { + appendLine("TokenRequest(") + appendLine(" clientId: ${ios.clientID ?: "None"}") + appendLine(" grantType: ${ios.grantType}") + appendLine(" scope: ${ios.scope ?: "None"}") + appendLine(" refreshToken: ${ios.refreshToken ?: "None"}") + appendLine(" redirectUri: ${ios.redirectURL?.absoluteString ?: "None"}") + appendLine(" additionalParameters: ${ios.additionalParameters ?: "None"}") + appendLine(" config:") + appendLine(" tokenEndpoint: ${ios.configuration.tokenEndpoint.absoluteString}") + appendLine(" authorizationEndpoint: ${ios.configuration.authorizationEndpoint.absoluteString}") + appendLine(")") + } + } } - +@OptIn(ExperimentalForeignApi::class) actual class AuthorizationResponse internal constructor(internal val ios: OIDAuthorizationResponse) { actual val authorizationCode: String? get() = ios.authorizationCode actual val idToken: String? get() = ios.idToken actual val scope get() = ios.scope actual fun createTokenExchangeRequest() = TokenRequest(ios.tokenExchangeRequest()!!) + + override fun toString(): String { + return buildString { + appendLine("AuthorizationResponse(") + appendLine(" authorizationCode: ${authorizationCode ?: "None"}") + appendLine(" idToken: ${idToken ?: "None"}") + appendLine(" scope: ${scope ?: "None"}") + appendLine(" state: ${ios.state ?: "None"}") + appendLine(" redirectUri: ${ios.request?.redirectURL?.absoluteString ?: "None"}") + appendLine(" clientId: ${ios.request?.clientID ?: "None"}") + appendLine(" responseType: ${ios.request?.responseType ?: "None"}") + appendLine(" config:") + appendLine(" authorizationEndpoint: ${ios.request?.configuration?.authorizationEndpoint?.absoluteString ?: "None"}") + appendLine(" tokenEndpoint: ${ios.request?.configuration?.tokenEndpoint?.absoluteString ?: "None"}") + appendLine(")") + } + } } actual class EndSessionRequest internal constructor(internal val ios: OIDEndSessionRequest) { @@ -146,6 +220,7 @@ actual typealias EndSessionResponse = OIDEndSessionResponse actual typealias AuthorizationServiceContext = UIViewController +@OptIn(ExperimentalForeignApi::class) actual class AuthorizationService actual constructor(private val context: () -> AuthorizationServiceContext) { private var session: OIDExternalUserAgentSessionProtocol? = null @@ -155,39 +230,83 @@ actual class AuthorizationService actual constructor(private val context: () -> actual suspend fun performAuthorizationRequest(request: AuthorizationRequest): AuthorizationResponse = withContext(Dispatchers.Main) { + Napier.d("πŸ” Starting iOS performAuthorizationRequest") + Napier.d("πŸ“€ AuthorizationRequest:\n$request") + suspendCoroutine { cont -> + val viewController = context() + Napier.d("🧭 Presenting authorization from context: ${viewController::class.simpleName}") + session = OIDAuthorizationService.presentAuthorizationRequest( request.ios, - OIDExternalUserAgentIOS(context()) + OIDExternalUserAgentIOS(viewController) ) { response, error -> + Napier.d("πŸ” Authorization callback triggered") session = null - response?.let { cont.resume(AuthorizationResponse(it)) } - ?: cont.resumeWithException(error!!.toException()) + + if (response != null) { + Napier.d("βœ… Authorization successful") + Napier.d("πŸ“₯ AuthorizationResponse:\n$response") + cont.resume(AuthorizationResponse(response)) + } else { + Napier.e("❌ Authorization failed: ${error?.localizedDescription}", error!!.toException()) + cont.resumeWithException(error.toException()) + } } } } + actual suspend fun performEndSessionRequest(request: EndSessionRequest): EndSessionResponse = withContext(Dispatchers.Main) { + Napier.d("πŸ” Starting iOS performEndSessionRequest") + Napier.d("πŸ“€ EndSessionRequest:\n$request") + suspendCoroutine { cont -> + val viewController = context() + Napier.d("🧭 Presenting end session from context: ${viewController::class.simpleName}") + session = OIDAuthorizationService.presentEndSessionRequest( request.ios, - OIDExternalUserAgentIOS(context()) + OIDExternalUserAgentIOS(viewController) ) { response, error -> + Napier.d("πŸ” End session callback triggered") session = null - response?.let { cont.resume(it) } - ?: cont.resumeWithException(error!!.toException()) + + if (response != null) { + Napier.d("βœ… End session completed successfully") + Napier.d("πŸ“₯ EndSessionResponse: $response") + cont.resume(response) + } else { + Napier.e("❌ End session failed: ${error?.localizedDescription}", error!!.toException()) + cont.resumeWithException(error.toException()) + } } } } + actual suspend fun performTokenRequest(request: TokenRequest): TokenResponse = withContext(Dispatchers.Main) { + Napier.d("πŸ” Starting iOS performTokenRequest") + Napier.d("πŸ“€ TokenRequest:\n$request") + suspendCoroutine { cont -> + Napier.d("πŸ“‘ Performing token request via OIDAuthorizationService") + OIDAuthorizationService.performTokenRequest(request.ios) { response, error -> - response?.let { cont.resume(TokenResponse(it)) } - ?: cont.resumeWithException(error!!.toException()) + Napier.d("πŸ” Token request callback triggered") + + if (response != null) { + Napier.d("βœ… Token request successful") + Napier.d("πŸ“₯ TokenResponse: ${TokenResponse(response)}") + cont.resume(TokenResponse(response)) + } else { + Napier.e("❌ Token request failed: ${error?.localizedDescription}", error!!.toException()) + cont.resumeWithException(error.toException()) + } } } } + } From 38b24bdaa45eb0b62859232f3a8ea289066b4a8e Mon Sep 17 00:00:00 2001 From: trykov Date: Mon, 9 Jun 2025 11:52:07 +0300 Subject: [PATCH 11/19] version bump --- README.md | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f03613b..e5b536b 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ The AppAuth-Kotlin SDK is a Kotlin-first SDK for AppAuth. It's API is similar to To install simply add to your common sourceset in the build gradle ```kotlin - implementation("dev.gitlive:appauth-kotlin:0.0.5") + implementation("dev.gitlive:appauth-kotlin:0.0.6") ``` Perform a gradle refresh and you should then be able to import the app auth files. diff --git a/gradle.properties b/gradle.properties index ba19fb8..334ae4a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,7 +19,7 @@ gpr.user=yet300 gpr.key=todo MODULE_PACKAGE_NAME=dev.gitlive -MODULE_VERSION_NUMBER=0.0.5 +MODULE_VERSION_NUMBER=0.0.6 MODULE_NAME=appauth-kotlin OPEN_SOURCE_REPO=https://oss.sonatype.org/service/local/staging/deploy/maven2/ From c8f837ef3e092bdcee0321bfd603f667bd0be2d6 Mon Sep 17 00:00:00 2001 From: yet Date: Wed, 18 Jun 2025 09:45:13 +0400 Subject: [PATCH 12/19] Implemented performEndSessionRequest and added revocationEndpoint The `performEndSessionRequest` function now correctly handles end session requests across iOS, Android, and JS platforms. The `EndSessionResponse` typealias has been removed as the function no longer returns a specific response object. Additionally, a `revocationEndpoint` property has been added to `AuthorizationServiceConfiguration` to support token revocation. A new function `performTokenRevocationRequest` has been added to `AuthorizationService` for this purpose, with platform-specific implementations pending. --- .../kotlin/dev/gitlive/appauth/androidMain.kt | 9 +++------ .../kotlin/dev/gitlive/appauth/commonMain.kt | 4 ++-- src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt | 12 ++++++++---- src/jsMain/kotlin/dev/gitlive/appauth/jsMain.kt | 10 +++++++--- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt b/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt index e2dae3d..ea07480 100644 --- a/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt +++ b/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt @@ -71,7 +71,7 @@ actual class AuthorizationService private constructor(private val android: net.o } } - actual suspend fun performEndSessionRequest(request: EndSessionRequest): EndSessionResponse { + actual suspend fun performEndSessionRequest(request: EndSessionRequest) { // if a previous request is still pending then wait for it to finish // Show the request details Napier.d("πŸ“€ Starting EndSessionRequest:\n$request") @@ -94,10 +94,7 @@ actual class AuthorizationService private constructor(private val android: net.o val intent = response.await() Napier.d("βœ… End session response received") - val parsedResponse = EndSessionResponse.fromIntent(intent!!) - Napier.d("πŸ“₯ Parsed EndSessionResponse:\n$parsedResponse") - - parsedResponse!! + return } catch (e: Exception) { Napier.e("❌ End session failed", e) throw e @@ -305,4 +302,4 @@ actual class EndSessionRequest internal constructor(internal val android: net.op } } -actual typealias EndSessionResponse = net.openid.appauth.EndSessionResponse + diff --git a/src/commonMain/kotlin/dev/gitlive/appauth/commonMain.kt b/src/commonMain/kotlin/dev/gitlive/appauth/commonMain.kt index 01e0237..56ebd1d 100644 --- a/src/commonMain/kotlin/dev/gitlive/appauth/commonMain.kt +++ b/src/commonMain/kotlin/dev/gitlive/appauth/commonMain.kt @@ -6,7 +6,7 @@ expect class AuthorizationServiceContext expect class AuthorizationService(context: () -> AuthorizationServiceContext) { suspend fun performAuthorizationRequest(request: AuthorizationRequest): AuthorizationResponse - suspend fun performEndSessionRequest(request: EndSessionRequest): EndSessionResponse + suspend fun performEndSessionRequest(request: EndSessionRequest) suspend fun performTokenRequest(request: TokenRequest): TokenResponse } @@ -61,4 +61,4 @@ expect class EndSessionRequest( postLogoutRedirectUri: String? = null ) -expect class EndSessionResponse + diff --git a/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt b/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt index 4120e4a..1aeac0e 100644 --- a/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt +++ b/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt @@ -44,7 +44,8 @@ actual class AuthorizationServiceConfiguration private constructor(val ios: OIDS authorizationEndpoint: String, tokenEndpoint: String, registrationEndpoint: String?, - endSessionEndpoint: String? + endSessionEndpoint: String?, + revocationEndpoint: String? ) : this( OIDServiceConfiguration( NSURL.URLWithString(authorizationEndpoint)!!, @@ -92,6 +93,7 @@ actual class AuthorizationServiceConfiguration private constructor(val ios: OIDS actual val tokenEndpoint: String get() = ios.tokenEndpoint.relativeString actual val registrationEndpoint: String? get() = ios.registrationEndpoint?.relativeString actual val endSessionEndpoint: String? get() = ios.endSessionEndpoint?.relativeString + actual val revocationEndpoint: String? get() = ios.revocationEndpoint?.absoluteString } actual class AuthorizationRequest private constructor(internal val ios: OIDAuthorizationRequest) { @@ -216,7 +218,6 @@ actual class TokenResponse internal constructor(internal val ios: OIDTokenRespon actual val refreshToken: String? get() = ios.refreshToken } -actual typealias EndSessionResponse = OIDEndSessionResponse actual typealias AuthorizationServiceContext = UIViewController @@ -257,7 +258,7 @@ actual class AuthorizationService actual constructor(private val context: () -> } - actual suspend fun performEndSessionRequest(request: EndSessionRequest): EndSessionResponse = + actual suspend fun performEndSessionRequest(request: EndSessionRequest) = withContext(Dispatchers.Main) { Napier.d("πŸ” Starting iOS performEndSessionRequest") Napier.d("πŸ“€ EndSessionRequest:\n$request") @@ -276,7 +277,7 @@ actual class AuthorizationService actual constructor(private val context: () -> if (response != null) { Napier.d("βœ… End session completed successfully") Napier.d("πŸ“₯ EndSessionResponse: $response") - cont.resume(response) + cont.resume(Unit) } else { Napier.e("❌ End session failed: ${error?.localizedDescription}", error!!.toException()) cont.resumeWithException(error.toException()) @@ -309,4 +310,7 @@ actual class AuthorizationService actual constructor(private val context: () -> } } + actual suspend fun performTokenRevocationRequest(request: TokenRevocationRequest) { + + } } diff --git a/src/jsMain/kotlin/dev/gitlive/appauth/jsMain.kt b/src/jsMain/kotlin/dev/gitlive/appauth/jsMain.kt index 1d9432d..35b0cce 100644 --- a/src/jsMain/kotlin/dev/gitlive/appauth/jsMain.kt +++ b/src/jsMain/kotlin/dev/gitlive/appauth/jsMain.kt @@ -5,7 +5,8 @@ actual class AuthorizationServiceConfiguration actual constructor( authorizationEndpoint: String, tokenEndpoint: String, registrationEndpoint: String?, - endSessionEndpoint: String? + endSessionEndpoint: String?, + revocationEndpoint: String? ) { actual companion object { actual suspend fun fetchFromIssuer(url: String): AuthorizationServiceConfiguration { @@ -72,7 +73,11 @@ actual class AuthorizationService actual constructor(context: () -> Authorizatio TODO("Not yet implemented") } - actual suspend fun performEndSessionRequest(request: EndSessionRequest): EndSessionResponse { + actual suspend fun performTokenRevocationRequest(request: TokenRevocationRequest){ + TODO("Not yet implemented") + } + + actual suspend fun performEndSessionRequest(request: EndSessionRequest) { TODO("Not yet implemented") } } @@ -83,4 +88,3 @@ actual class EndSessionRequest actual constructor( postLogoutRedirectUri: String? ) -actual class EndSessionResponse From 793ce91d2b0ee2aa6ebc05385335d73790e5867f Mon Sep 17 00:00:00 2001 From: yet Date: Wed, 18 Jun 2025 09:46:28 +0400 Subject: [PATCH 13/19] Update gradle.properties Incremented MODULE_VERSION_NUMBER from 0.0.5 to 0.0.8 and updated gpr.key. --- README.md | 2 +- gradle.properties | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f03613b..d4a4310 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ The AppAuth-Kotlin SDK is a Kotlin-first SDK for AppAuth. It's API is similar to To install simply add to your common sourceset in the build gradle ```kotlin - implementation("dev.gitlive:appauth-kotlin:0.0.5") + implementation("dev.gitlive:appauth-kotlin:0.0.8") ``` Perform a gradle refresh and you should then be able to import the app auth files. diff --git a/gradle.properties b/gradle.properties index ba19fb8..11b8dfc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,10 +16,10 @@ signing.keyId="" signing.password="" gpr.user=yet300 -gpr.key=todo +gpr.key=KEY MODULE_PACKAGE_NAME=dev.gitlive -MODULE_VERSION_NUMBER=0.0.5 +MODULE_VERSION_NUMBER=0.0.8 MODULE_NAME=appauth-kotlin OPEN_SOURCE_REPO=https://oss.sonatype.org/service/local/staging/deploy/maven2/ From 4929901921389dd35c512e082b522a77747769ad Mon Sep 17 00:00:00 2001 From: yet Date: Wed, 18 Jun 2025 09:50:44 +0400 Subject: [PATCH 14/19] Update version to 0.0.8 --- README.md | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f03613b..d4a4310 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ The AppAuth-Kotlin SDK is a Kotlin-first SDK for AppAuth. It's API is similar to To install simply add to your common sourceset in the build gradle ```kotlin - implementation("dev.gitlive:appauth-kotlin:0.0.5") + implementation("dev.gitlive:appauth-kotlin:0.0.8") ``` Perform a gradle refresh and you should then be able to import the app auth files. diff --git a/gradle.properties b/gradle.properties index ba19fb8..bded46b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,7 +19,7 @@ gpr.user=yet300 gpr.key=todo MODULE_PACKAGE_NAME=dev.gitlive -MODULE_VERSION_NUMBER=0.0.5 +MODULE_VERSION_NUMBER=0.0.8 MODULE_NAME=appauth-kotlin OPEN_SOURCE_REPO=https://oss.sonatype.org/service/local/staging/deploy/maven2/ From 464c04d90df44eed13bbbcbd580e888d8e53d380 Mon Sep 17 00:00:00 2001 From: yet Date: Wed, 18 Jun 2025 14:22:43 +0400 Subject: [PATCH 15/19] Fix: Add missing additional parameters for end session request This commit adds the `additionalParameters` field to the `EndSessionRequest` class, which was previously missing. This allows for passing additional parameters during the end session request, aligning the implementation with the common interface. Additionally, this commit removes the `revocationEndpoint` from `AuthorizationServiceConfiguration` and the `performTokenRevocationRequest` function from `AuthorizationService` as token revocation is not currently a supported feature in this library. --- .../kotlin/dev/gitlive/appauth/androidMain.kt | 8 +++++--- src/commonMain/kotlin/dev/gitlive/appauth/commonMain.kt | 3 ++- src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt | 9 ++------- src/jsMain/kotlin/dev/gitlive/appauth/jsMain.kt | 8 ++------ 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt b/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt index ea07480..5a70c66 100644 --- a/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt +++ b/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt @@ -6,6 +6,7 @@ import android.net.Uri import androidx.activity.result.ActivityResultCaller import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult +import io.github.aakira.napier.Napier import kotlinx.coroutines.CompletableDeferred import kotlinx.io.IOException import net.openid.appauth.AuthorizationException @@ -13,7 +14,6 @@ import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine import net.openid.appauth.TokenResponse as AndroidTokenResponse -import io.github.aakira.napier.Napier actual typealias AuthorizationException = AuthorizationException @@ -139,7 +139,7 @@ actual class AuthorizationServiceConfiguration private constructor( actual companion object { actual suspend fun fetchFromIssuer(url: String): AuthorizationServiceConfiguration = - suspendCoroutine { cont -> + suspendCoroutine { cont -> Napier.d("🌐 Starting fetchFromIssuer") Napier.d("πŸ”— Issuer URL: $url") @@ -281,11 +281,13 @@ actual class EndSessionRequest internal constructor(internal val android: net.op actual constructor( config: AuthorizationServiceConfiguration, idTokenHint: String?, - postLogoutRedirectUri: String? + postLogoutRedirectUri: String?, + additionalParameters: Map?, ) : this( net.openid.appauth.EndSessionRequest.Builder(config.android).apply { idTokenHint?.let { setIdTokenHint(it) } postLogoutRedirectUri?.let { setPostLogoutRedirectUri(Uri.parse(postLogoutRedirectUri)) } + setAdditionalParameters(additionalParameters) }.build() ) override fun toString(): String { diff --git a/src/commonMain/kotlin/dev/gitlive/appauth/commonMain.kt b/src/commonMain/kotlin/dev/gitlive/appauth/commonMain.kt index 56ebd1d..9df09fe 100644 --- a/src/commonMain/kotlin/dev/gitlive/appauth/commonMain.kt +++ b/src/commonMain/kotlin/dev/gitlive/appauth/commonMain.kt @@ -58,7 +58,8 @@ expect class TokenResponse { expect class EndSessionRequest( config: AuthorizationServiceConfiguration, idTokenHint: String? = null, - postLogoutRedirectUri: String? = null + postLogoutRedirectUri: String? = null, + additionalParameters: Map? = null, ) diff --git a/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt b/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt index 1aeac0e..6cef16c 100644 --- a/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt +++ b/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt @@ -45,7 +45,6 @@ actual class AuthorizationServiceConfiguration private constructor(val ios: OIDS tokenEndpoint: String, registrationEndpoint: String?, endSessionEndpoint: String?, - revocationEndpoint: String? ) : this( OIDServiceConfiguration( NSURL.URLWithString(authorizationEndpoint)!!, @@ -93,7 +92,6 @@ actual class AuthorizationServiceConfiguration private constructor(val ios: OIDS actual val tokenEndpoint: String get() = ios.tokenEndpoint.relativeString actual val registrationEndpoint: String? get() = ios.registrationEndpoint?.relativeString actual val endSessionEndpoint: String? get() = ios.endSessionEndpoint?.relativeString - actual val revocationEndpoint: String? get() = ios.revocationEndpoint?.absoluteString } actual class AuthorizationRequest private constructor(internal val ios: OIDAuthorizationRequest) { @@ -199,6 +197,7 @@ actual class EndSessionRequest internal constructor(internal val ios: OIDEndSess config: AuthorizationServiceConfiguration, idTokenHint: String?, postLogoutRedirectUri: String?, + additionalParameters: Map?, ) : this( OIDEndSessionRequest( configuration = config.ios, @@ -207,7 +206,7 @@ actual class EndSessionRequest internal constructor(internal val ios: OIDEndSess NSURL.URLWithString(uri) ?: throw IllegalArgumentException("Invalid postLogoutRedirectUri: $uri") } ?: NSURL.URLWithString(postLogoutRedirectUri ?: "")!!, - additionalParameters = null + additionalParameters = additionalParameters?.mapValues { it.value as Any? } ) ) } @@ -309,8 +308,4 @@ actual class AuthorizationService actual constructor(private val context: () -> } } } - - actual suspend fun performTokenRevocationRequest(request: TokenRevocationRequest) { - - } } diff --git a/src/jsMain/kotlin/dev/gitlive/appauth/jsMain.kt b/src/jsMain/kotlin/dev/gitlive/appauth/jsMain.kt index 35b0cce..8e60f85 100644 --- a/src/jsMain/kotlin/dev/gitlive/appauth/jsMain.kt +++ b/src/jsMain/kotlin/dev/gitlive/appauth/jsMain.kt @@ -6,7 +6,6 @@ actual class AuthorizationServiceConfiguration actual constructor( tokenEndpoint: String, registrationEndpoint: String?, endSessionEndpoint: String?, - revocationEndpoint: String? ) { actual companion object { actual suspend fun fetchFromIssuer(url: String): AuthorizationServiceConfiguration { @@ -73,10 +72,6 @@ actual class AuthorizationService actual constructor(context: () -> Authorizatio TODO("Not yet implemented") } - actual suspend fun performTokenRevocationRequest(request: TokenRevocationRequest){ - TODO("Not yet implemented") - } - actual suspend fun performEndSessionRequest(request: EndSessionRequest) { TODO("Not yet implemented") } @@ -85,6 +80,7 @@ actual class AuthorizationService actual constructor(context: () -> Authorizatio actual class EndSessionRequest actual constructor( config: AuthorizationServiceConfiguration, idTokenHint: String?, - postLogoutRedirectUri: String? + postLogoutRedirectUri: String?, + additionalParameters: Map? ) From 8edbc7f33e86fd8dcde3f1a6ee5e732b0ff1bc19 Mon Sep 17 00:00:00 2001 From: yet Date: Wed, 18 Jun 2025 18:40:33 +0400 Subject: [PATCH 16/19] Update gradle.properties --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index bded46b..841aa19 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,7 +19,7 @@ gpr.user=yet300 gpr.key=todo MODULE_PACKAGE_NAME=dev.gitlive -MODULE_VERSION_NUMBER=0.0.8 +MODULE_VERSION_NUMBER=0.0.9 MODULE_NAME=appauth-kotlin OPEN_SOURCE_REPO=https://oss.sonatype.org/service/local/staging/deploy/maven2/ From b6cc95775385a20c9841c5766719a3d5f9966389 Mon Sep 17 00:00:00 2001 From: yet Date: Wed, 18 Jun 2025 22:24:05 +0400 Subject: [PATCH 17/19] feat: Add support for token revocation This commit introduces the ability to revoke OAuth tokens. Key changes: - Added `revocationEndpoint` to `AuthorizationServiceConfiguration` to store the token revocation URL. - Implemented `performRevokeTokenRequest` in `AuthorizationService` for both Android and iOS platforms. - On Android, uses `HttpURLConnection` for the revocation request. - On iOS, uses `NSURLSession` for the revocation request. - Added `RevokeTokenRequest` class to encapsulate parameters for the revocation request. - Updated `fetchFromIssuer` to attempt to discover the `revocation_endpoint` from the OpenID Connect discovery document. --- .../kotlin/dev/gitlive/appauth/androidMain.kt | 149 +++++++++++++++--- .../kotlin/dev/gitlive/appauth/commonMain.kt | 9 ++ .../kotlin/dev/gitlive/appauth/iosMain.kt | 94 ++++++++++- .../kotlin/dev/gitlive/appauth/jsMain.kt | 12 ++ 4 files changed, 233 insertions(+), 31 deletions(-) diff --git a/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt b/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt index 5a70c66..98caecc 100644 --- a/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt +++ b/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt @@ -3,13 +3,23 @@ package dev.gitlive.appauth import android.content.ContextWrapper import android.content.Intent import android.net.Uri +import android.util.Base64 import androidx.activity.result.ActivityResultCaller import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult import io.github.aakira.napier.Napier import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import kotlinx.io.IOException import net.openid.appauth.AuthorizationException +import org.json.JSONException +import org.json.JSONObject +import java.io.BufferedWriter +import java.io.OutputStreamWriter +import java.net.HttpURLConnection +import java.net.URL +import java.net.URLEncoder import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine @@ -106,57 +116,138 @@ actual class AuthorizationService private constructor(private val android: net.o Napier.d("πŸ” Starting performTokenRequest") Napier.d("πŸ“€ TokenRequest:\n$request") - android.performTokenRequest(request.android) { response, ex -> - if (response != null) { - Napier.d("βœ… Token response received") - Napier.d("πŸ“₯ Parsed TokenResponse:\n$response") - cont.resume(TokenResponse(response)) - } else { - Napier.e("❌ Token request failed", ex) - cont.resumeWithException(ex!!.wrapIfNecessary()) + android.performTokenRequest(request.android) { response, ex -> + if (response != null) { + Napier.d("βœ… Token response received") + Napier.d("πŸ“₯ Parsed TokenResponse:\n$response") + cont.resume(TokenResponse(response)) + } else { + Napier.e("❌ Token request failed", ex) + cont.resumeWithException(ex!!.wrapIfNecessary()) + } } } + + actual suspend fun performRevokeTokenRequest(request: RevokeTokenRequest) = + withContext(Dispatchers.IO) { + val endpoint = request.config.revocationEndpoint + ?: throw IllegalStateException("Revocation endpoint not found in configuration.") + + var connection: HttpURLConnection? = null + try { + Napier.d("Performing token revocation via native HttpURLConnection to $endpoint") + connection = URL(endpoint).openConnection() as HttpURLConnection + connection.requestMethod = "POST" + connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded") + connection.doOutput = true + + val params = mutableMapOf("token" to request.token) + if (request.clientSecret == null) { + params["client_id"] = request.clientId + } else { + val credentials = "${request.clientId}:${request.clientSecret}" + val authHeader = + "Basic ${Base64.encodeToString(credentials.toByteArray(), Base64.NO_WRAP)}" + connection.setRequestProperty("Authorization", authHeader) + } + + val writer = BufferedWriter(OutputStreamWriter(connection.outputStream, "UTF-8")) + writer.write(getPostDataString(params)) + writer.flush() + writer.close() + + val responseCode = connection.responseCode + Napier.d("Revocation response code: $responseCode") + + + if (responseCode >= 400) { + val errorStream = connection.errorStream?.bufferedReader()?.readText() + throw AuthorizationException.fromTemplate( + AuthorizationException.GeneralErrors.SERVER_ERROR, + Exception("Revocation failed with code $responseCode: $errorStream") + ) + } + + } catch (e: Exception) { + Napier.e("Revocation request failed", e) + throw e + } finally { + connection?.disconnect() + } + } + + private fun getPostDataString(params: Map): String { + return params.entries.joinToString("&") { (key, value) -> + "${URLEncoder.encode(key, "UTF-8")}=${URLEncoder.encode(value, "UTF-8")}" + } } + } actual class AuthorizationServiceConfiguration private constructor( - val android: net.openid.appauth.AuthorizationServiceConfiguration + val android: net.openid.appauth.AuthorizationServiceConfiguration, + actual val revocationEndpoint: String? ) { actual constructor( authorizationEndpoint: String, tokenEndpoint: String, registrationEndpoint: String?, - endSessionEndpoint: String? + endSessionEndpoint: String?, + revocationEndpoint: String? ) : this( net.openid.appauth.AuthorizationServiceConfiguration( Uri.parse(authorizationEndpoint), Uri.parse(tokenEndpoint), registrationEndpoint?.let { Uri.parse(it) }, endSessionEndpoint?.let { Uri.parse(it) }, - ) + ), + revocationEndpoint ) actual companion object { actual suspend fun fetchFromIssuer(url: String): AuthorizationServiceConfiguration = suspendCoroutine { cont -> - Napier.d("🌐 Starting fetchFromIssuer") - Napier.d("πŸ”— Issuer URL: $url") - - net.openid.appauth.AuthorizationServiceConfiguration - .fetchFromIssuer(Uri.parse(url)) { serviceConfiguration, ex -> - if (serviceConfiguration != null) { - Napier.d("βœ… Fetched AuthorizationServiceConfiguration:") - Napier.d(" authorizationEndpoint: ${serviceConfiguration.authorizationEndpoint}") - Napier.d(" tokenEndpoint: ${serviceConfiguration.tokenEndpoint}") - Napier.d(" endSessionEndpoint: ${serviceConfiguration.endSessionEndpoint ?: "None"}") - cont.resume(AuthorizationServiceConfiguration(serviceConfiguration)) - } else { - Napier.e("❌ Failed to fetch configuration from issuer", ex) - cont.resumeWithException(ex!!.wrapIfNecessary()) + Napier.d("🌐 Starting custom fetchFromIssuer for Android") + net.openid.appauth.AuthorizationServiceConfiguration.fetchFromIssuer( + Uri.parse(url) + ) { serviceConfiguration, ex -> + if (ex != null) { + Napier.e("❌ Failed to fetch base configuration", ex) + cont.resumeWithException(ex.wrapIfNecessary()) + return@fetchFromIssuer + } + if (serviceConfiguration == null) { + Napier.e("❌ Fetched configuration is null") + cont.resumeWithException(IllegalStateException("Configuration is null")) + return@fetchFromIssuer + } + + var revocationEndpoint: String? = null + try { + val discoveryDocJson: JSONObject? = serviceConfiguration.discoveryDoc?.docJson + if (discoveryDocJson != null && discoveryDocJson.has("revocation_endpoint")) { + // ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅ΠΌ, Ρ‡Ρ‚ΠΎ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ Π½Π΅ null, ΠΏΡ€Π΅ΠΆΠ΄Π΅ Ρ‡Π΅ΠΌ Π΅Π³ΠΎ ΠΏΠΎΠ»ΡƒΡ‡Π°Ρ‚ΡŒ + if (!discoveryDocJson.isNull("revocation_endpoint")) { + revocationEndpoint = discoveryDocJson.getString("revocation_endpoint") + Napier.i("βœ… Found revocation_endpoint: $revocationEndpoint") + } } + } catch (jsonEx: JSONException) { + Napier.w( + "Could not parse revocation_endpoint from discovery document", + jsonEx + ) } - } + + cont.resume( + AuthorizationServiceConfiguration( + android = serviceConfiguration, + revocationEndpoint = revocationEndpoint + ) + ) + } + } } actual val authorizationEndpoint get() = android.authorizationEndpoint.toString() @@ -304,4 +395,10 @@ actual class EndSessionRequest internal constructor(internal val android: net.op } } +actual class RevokeTokenRequest actual constructor( + val config: AuthorizationServiceConfiguration, + val token: String, + val clientId: String, + val clientSecret: String? +) diff --git a/src/commonMain/kotlin/dev/gitlive/appauth/commonMain.kt b/src/commonMain/kotlin/dev/gitlive/appauth/commonMain.kt index 9df09fe..a302326 100644 --- a/src/commonMain/kotlin/dev/gitlive/appauth/commonMain.kt +++ b/src/commonMain/kotlin/dev/gitlive/appauth/commonMain.kt @@ -8,6 +8,7 @@ expect class AuthorizationService(context: () -> AuthorizationServiceContext) { suspend fun performAuthorizationRequest(request: AuthorizationRequest): AuthorizationResponse suspend fun performEndSessionRequest(request: EndSessionRequest) suspend fun performTokenRequest(request: TokenRequest): TokenResponse + suspend fun performRevokeTokenRequest(request: RevokeTokenRequest) } expect class AuthorizationServiceConfiguration( @@ -15,11 +16,13 @@ expect class AuthorizationServiceConfiguration( tokenEndpoint: String, registrationEndpoint: String? = null, endSessionEndpoint: String? = null, + revocationEndpoint: String? = null ) { val authorizationEndpoint: String val tokenEndpoint: String val registrationEndpoint: String? val endSessionEndpoint: String? + val revocationEndpoint: String? companion object { suspend fun fetchFromIssuer(url: String): AuthorizationServiceConfiguration @@ -63,3 +66,9 @@ expect class EndSessionRequest( ) +expect class RevokeTokenRequest( + config: AuthorizationServiceConfiguration, + token: String, + clientId: String, + clientSecret: String? = null +) \ No newline at end of file diff --git a/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt b/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt index 6cef16c..3650535 100644 --- a/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt +++ b/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt @@ -6,7 +6,6 @@ import cocoapods.AppAuth.OIDAuthorizationRequest import cocoapods.AppAuth.OIDAuthorizationResponse import cocoapods.AppAuth.OIDAuthorizationService import cocoapods.AppAuth.OIDEndSessionRequest -import cocoapods.AppAuth.OIDEndSessionResponse import cocoapods.AppAuth.OIDErrorCodeNetworkError import cocoapods.AppAuth.OIDExternalUserAgentIOS import cocoapods.AppAuth.OIDExternalUserAgentSessionProtocol @@ -14,17 +13,21 @@ import cocoapods.AppAuth.OIDGeneralErrorDomain import cocoapods.AppAuth.OIDServiceConfiguration import cocoapods.AppAuth.OIDTokenRequest import cocoapods.AppAuth.OIDTokenResponse +import io.github.aakira.napier.Napier import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.io.IOException +import platform.Foundation.* import platform.Foundation.NSError +import platform.Foundation.NSHTTPURLResponse +import platform.Foundation.NSMutableURLRequest +import platform.Foundation.NSString import platform.Foundation.NSURL import platform.UIKit.UIViewController import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine -import io.github.aakira.napier.Napier actual class AuthorizationException(message: String?) : Exception(message) @@ -38,13 +41,17 @@ private fun NSError.toException() = when (domain) { else -> AuthorizationException(localizedDescription) } -actual class AuthorizationServiceConfiguration private constructor(val ios: OIDServiceConfiguration) { +actual class AuthorizationServiceConfiguration private constructor( + val ios: OIDServiceConfiguration, + actual val revocationEndpoint: String? +) { actual constructor( authorizationEndpoint: String, tokenEndpoint: String, registrationEndpoint: String?, endSessionEndpoint: String?, + revocationEndpoint: String? ) : this( OIDServiceConfiguration( NSURL.URLWithString(authorizationEndpoint)!!, @@ -52,7 +59,8 @@ actual class AuthorizationServiceConfiguration private constructor(val ios: OIDS null, registrationEndpoint?.let { NSURL.URLWithString(it) }, endSessionEndpoint?.let { NSURL.URLWithString(it) } - ) + ), + revocationEndpoint ) actual companion object { @@ -79,7 +87,20 @@ actual class AuthorizationServiceConfiguration private constructor(val ios: OIDS Napier.d(" tokenEndpoint: ${config.tokenEndpoint.absoluteString}") Napier.d(" endSessionEndpoint: ${config.endSessionEndpoint?.absoluteString ?: "None"}") - cont.resume(AuthorizationServiceConfiguration(config)) + var revocationEndpoint: String? = null + try { + val discoveryDoc = config.discoveryDocument() + if (discoveryDoc is Map<*, *>) { + revocationEndpoint = discoveryDoc["revocation_endpoint"] as? String + if (revocationEndpoint != null) { + Napier.i("βœ… Found revocation_endpoint: $revocationEndpoint") + } + } + } catch (e: Exception) { + Napier.w("Could not parse revocation_endpoint", e) + } + + cont.resume(AuthorizationServiceConfiguration(config, revocationEndpoint)) } else { Napier.e("❌ Discovery failed: ${error?.localizedDescription}", error!!.toException()) cont.resumeWithException(error.toException()) @@ -308,4 +329,67 @@ actual class AuthorizationService actual constructor(private val context: () -> } } } + + actual suspend fun performRevokeTokenRequest(request: RevokeTokenRequest) { + val endpoint = request.config.revocationEndpoint + ?: throw AuthorizationException("Revocation endpoint not found in configuration.") + + return suspendCoroutine { continuation -> + Napier.d("Performing token revocation via native URLSession to $endpoint") + + val url = NSURL(string = endpoint) + val urlRequest = NSMutableURLRequest(uRL = url) + + urlRequest.setHTTPMethod("POST") + + urlRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField = "Content-Type") + + var bodyString = "token=${request.token.urlEncoded()}" + if (request.clientSecret == null) { + bodyString += "&client_id=${request.clientId.urlEncoded()}" + } else { + val credentials = "${request.clientId}:${request.clientSecret}" + val authHeader = "Basic ${credentials.base64Encoded()}" + urlRequest.setValue(authHeader, forHTTPHeaderField = "Authorization") + } + + urlRequest.setHTTPBody((bodyString as NSString).dataUsingEncoding(NSUTF8StringEncoding)) + + val task = NSURLSession.sharedSession.dataTaskWithRequest(urlRequest) { data, response, error -> + if (error != null) { + continuation.resumeWithException(error.toException()) + return@dataTaskWithRequest + } + + val httpResponse = response as? NSHTTPURLResponse + val statusCode = httpResponse?.statusCode?.toInt() ?: 0 + Napier.d("Revocation response code: $statusCode") + + if (statusCode >= 400) { + val errorBody = data?.let { NSString.create(it, NSUTF8StringEncoding) } + continuation.resumeWithException(AuthorizationException("Revocation failed with code $statusCode: $errorBody")) + } else { + continuation.resume(Unit) + } + } + task.resume() + } + } + + private fun String.urlEncoded(): String { + return this.replace("=", "%3D").replace("+", "%2B").replace("/", "%2F") + } +} + +@OptIn(ExperimentalForeignApi::class) +internal fun String.base64Encoded(): String { + val data = (this as NSString).dataUsingEncoding(NSUTF8StringEncoding) + return data?.base64EncodedStringWithOptions(0u) ?: "" } + +actual class RevokeTokenRequest actual constructor( + val config: AuthorizationServiceConfiguration, + val token: String, + val clientId: String, + val clientSecret: String? +) \ No newline at end of file diff --git a/src/jsMain/kotlin/dev/gitlive/appauth/jsMain.kt b/src/jsMain/kotlin/dev/gitlive/appauth/jsMain.kt index 8e60f85..22eec59 100644 --- a/src/jsMain/kotlin/dev/gitlive/appauth/jsMain.kt +++ b/src/jsMain/kotlin/dev/gitlive/appauth/jsMain.kt @@ -6,6 +6,7 @@ actual class AuthorizationServiceConfiguration actual constructor( tokenEndpoint: String, registrationEndpoint: String?, endSessionEndpoint: String?, + revocationEndpoint: String? ) { actual companion object { actual suspend fun fetchFromIssuer(url: String): AuthorizationServiceConfiguration { @@ -21,6 +22,8 @@ actual class AuthorizationServiceConfiguration actual constructor( get() = TODO("Not yet implemented") actual val endSessionEndpoint: String? get() = TODO("Not yet implemented") + actual val revocationEndpoint: String? + get() = TODO("Not yet implemented") } actual class AuthorizationException : Exception() @@ -75,6 +78,9 @@ actual class AuthorizationService actual constructor(context: () -> Authorizatio actual suspend fun performEndSessionRequest(request: EndSessionRequest) { TODO("Not yet implemented") } + actual suspend fun performRevokeTokenRequest(request: RevokeTokenRequest) { + TODO("Not yet implemented") + } } actual class EndSessionRequest actual constructor( @@ -84,3 +90,9 @@ actual class EndSessionRequest actual constructor( additionalParameters: Map? ) +actual class RevokeTokenRequest actual constructor( + val config: AuthorizationServiceConfiguration, + val token: String, + val clientId: String, + val clientSecret: String? +) \ No newline at end of file From 202674f7131114f9eb901a546d18bf038ec085c6 Mon Sep 17 00:00:00 2001 From: yet Date: Wed, 18 Jun 2025 22:24:52 +0400 Subject: [PATCH 18/19] Update version to 0.1.1 --- README.md | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d4a4310..a1d7d98 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ The AppAuth-Kotlin SDK is a Kotlin-first SDK for AppAuth. It's API is similar to To install simply add to your common sourceset in the build gradle ```kotlin - implementation("dev.gitlive:appauth-kotlin:0.0.8") + implementation("dev.gitlive:appauth-kotlin:0.1.1") ``` Perform a gradle refresh and you should then be able to import the app auth files. diff --git a/gradle.properties b/gradle.properties index 841aa19..ef92ebe 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,7 +19,7 @@ gpr.user=yet300 gpr.key=todo MODULE_PACKAGE_NAME=dev.gitlive -MODULE_VERSION_NUMBER=0.0.9 +MODULE_VERSION_NUMBER=0.1.1 MODULE_NAME=appauth-kotlin OPEN_SOURCE_REPO=https://oss.sonatype.org/service/local/staging/deploy/maven2/ From 8e244e1b312bc26fbe2e48e0814484d445d768a9 Mon Sep 17 00:00:00 2001 From: yet Date: Wed, 18 Jun 2025 22:55:09 +0400 Subject: [PATCH 19/19] Refactor: Move platform-specific classes to separate files This commit refactors the codebase by moving platform-specific classes (Android and iOS) into their respective dedicated files. This improves code organization and maintainability. Additionally, the module version number has been incremented to 0.1.2. --- gradle.properties | 2 +- .../gitlive/appauth/AuthorizationRequest.kt | 38 ++ .../gitlive/appauth/AuthorizationResponse.kt | 23 ++ .../gitlive/appauth/AuthorizationService.kt | 172 +++++++++ .../AuthorizationServiceConfiguration.kt | 88 +++++ .../dev/gitlive/appauth/TokenRequest.kt | 30 ++ .../kotlin/dev/gitlive/appauth/androidMain.kt | 333 ----------------- .../kotlin/dev/gitlive/appauth/commonMain.kt | 64 ++++ .../gitlive/appauth/AuthorizationRequest.kt | 44 +++ .../gitlive/appauth/AuthorizationResponse.kt | 29 ++ .../gitlive/appauth/AuthorizationService.kt | 187 ++++++++++ .../AuthorizationServiceConfiguration.kt | 86 +++++ .../dev/gitlive/appauth/TokenRequest.kt | 44 +++ .../kotlin/dev/gitlive/appauth/iosMain.kt | 336 +----------------- 14 files changed, 807 insertions(+), 669 deletions(-) create mode 100644 src/androidMain/kotlin/dev/gitlive/appauth/AuthorizationRequest.kt create mode 100644 src/androidMain/kotlin/dev/gitlive/appauth/AuthorizationResponse.kt create mode 100644 src/androidMain/kotlin/dev/gitlive/appauth/AuthorizationService.kt create mode 100644 src/androidMain/kotlin/dev/gitlive/appauth/AuthorizationServiceConfiguration.kt create mode 100644 src/androidMain/kotlin/dev/gitlive/appauth/TokenRequest.kt create mode 100644 src/iosMain/kotlin/dev/gitlive/appauth/AuthorizationRequest.kt create mode 100644 src/iosMain/kotlin/dev/gitlive/appauth/AuthorizationResponse.kt create mode 100644 src/iosMain/kotlin/dev/gitlive/appauth/AuthorizationService.kt create mode 100644 src/iosMain/kotlin/dev/gitlive/appauth/AuthorizationServiceConfiguration.kt create mode 100644 src/iosMain/kotlin/dev/gitlive/appauth/TokenRequest.kt diff --git a/gradle.properties b/gradle.properties index ef92ebe..ad01308 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,7 +19,7 @@ gpr.user=yet300 gpr.key=todo MODULE_PACKAGE_NAME=dev.gitlive -MODULE_VERSION_NUMBER=0.1.1 +MODULE_VERSION_NUMBER=0.1.2 MODULE_NAME=appauth-kotlin OPEN_SOURCE_REPO=https://oss.sonatype.org/service/local/staging/deploy/maven2/ diff --git a/src/androidMain/kotlin/dev/gitlive/appauth/AuthorizationRequest.kt b/src/androidMain/kotlin/dev/gitlive/appauth/AuthorizationRequest.kt new file mode 100644 index 0000000..e71c32f --- /dev/null +++ b/src/androidMain/kotlin/dev/gitlive/appauth/AuthorizationRequest.kt @@ -0,0 +1,38 @@ +package dev.gitlive.appauth + +import android.net.Uri + +actual class AuthorizationRequest private constructor(internal val android: net.openid.appauth.AuthorizationRequest) { + actual constructor( + config: AuthorizationServiceConfiguration, + clientId: String, + scopes: List, + responseType: String, + redirectUri: String, + additionalParameters: Map? + ) : this( + net.openid.appauth.AuthorizationRequest.Builder( + config.android, + clientId, + responseType, + Uri.parse(redirectUri), + ) + .setAdditionalParameters(additionalParameters) + .setScopes(scopes) + .build() + ) + override fun toString(): String { + return buildString { + appendLine("AuthorizationRequest(") + appendLine(" clientId: ${android.clientId}") + appendLine(" scope: ${android.scope ?: "None"}") + appendLine(" responseType: ${android.responseType}") + appendLine(" redirectUri: ${android.redirectUri}") + appendLine(" additionalParameters: ${android.additionalParameters ?: "None"}") + appendLine(" config:") + appendLine(" authEndpoint: ${android.configuration.authorizationEndpoint}") + appendLine(" tokenEndpoint: ${android.configuration.tokenEndpoint}") + appendLine(")") + } + } +} \ No newline at end of file diff --git a/src/androidMain/kotlin/dev/gitlive/appauth/AuthorizationResponse.kt b/src/androidMain/kotlin/dev/gitlive/appauth/AuthorizationResponse.kt new file mode 100644 index 0000000..1eb86be --- /dev/null +++ b/src/androidMain/kotlin/dev/gitlive/appauth/AuthorizationResponse.kt @@ -0,0 +1,23 @@ +package dev.gitlive.appauth + +actual class AuthorizationResponse internal constructor(private val android: net.openid.appauth.AuthorizationResponse) { + actual fun createTokenExchangeRequest() = TokenRequest(android.createTokenExchangeRequest()) + actual val idToken get() = android.idToken + actual val scope get() = android.scope + actual val authorizationCode get() = android.authorizationCode + + override fun toString(): String { + return buildString { + appendLine("AuthorizationResponse(") + appendLine(" authorizationCode: ${authorizationCode ?: "None"}") + appendLine(" idToken: ${idToken ?: "None"}") + appendLine(" scope: ${scope ?: "None"}") + appendLine(" state: ${android.state ?: "None"}") + appendLine(" tokenExchangeRequest:") + appendLine(" clientId: ${android.request.clientId}") + appendLine(" redirectUri: ${android.request.redirectUri}") + appendLine(" responseType: ${android.request.responseType}") + appendLine(")") + } + } +} \ No newline at end of file diff --git a/src/androidMain/kotlin/dev/gitlive/appauth/AuthorizationService.kt b/src/androidMain/kotlin/dev/gitlive/appauth/AuthorizationService.kt new file mode 100644 index 0000000..159eaec --- /dev/null +++ b/src/androidMain/kotlin/dev/gitlive/appauth/AuthorizationService.kt @@ -0,0 +1,172 @@ +package dev.gitlive.appauth + +import android.content.Intent +import android.util.Base64 +import androidx.activity.result.ActivityResultCaller +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult +import io.github.aakira.napier.Napier +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import net.openid.appauth.AuthorizationException +import java.io.BufferedWriter +import java.io.OutputStreamWriter +import java.net.HttpURLConnection +import java.net.URL +import java.net.URLEncoder +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + + +actual class AuthorizationService private constructor(private val android: net.openid.appauth.AuthorizationService) { + actual constructor(context: () -> AuthorizationServiceContext) : this( + net.openid.appauth.AuthorizationService( + context() + ) + ) + + fun bind(activityOrFragment: ActivityResultCaller) { + launcher = activityOrFragment + .registerForActivityResult(StartActivityForResult()) { result -> + result.data + ?.let { AuthorizationException.fromIntent(it) } + ?.let { response.completeExceptionally(it.wrapIfNecessary()) } + ?: response.complete(result.data) + } + } + + private var response = CompletableDeferred(value = null) + + private lateinit var launcher: ActivityResultLauncher + + + actual suspend fun performAuthorizationRequest(request: AuthorizationRequest): AuthorizationResponse { + // Show the request details + Napier.d("πŸ“€ Starting AuthorizationRequest:\n${request}") + // if a previous request is still pending then wait for it to finish + response.runCatching { + Napier.d("⏳ Waiting for previous authorization request to complete...") + await() + } + response = CompletableDeferred() + Napier.d("πŸš€ Launching authorization intent") + launcher.launch(android.getAuthorizationRequestIntent(request.android)) + // Wait for the result and process it + return try { + val intent = response.await() + Napier.d("βœ… Authorization response received") + + val rawResponse = net.openid.appauth.AuthorizationResponse.fromIntent(intent!!) + Napier.d("πŸ“₯ Parsed AuthorizationResponse:\n$rawResponse") + + AuthorizationResponse(rawResponse!!) + } catch (e: Exception) { + Napier.e("❌ Authorization failed", e) + throw e + } + } + + actual suspend fun performEndSessionRequest(request: EndSessionRequest) { + // if a previous request is still pending then wait for it to finish + // Show the request details + Napier.d("πŸ“€ Starting EndSessionRequest:\n$request") + + // If a previous request is still pending, wait for it + response.runCatching { + Napier.d("⏳ Waiting for previous end session request to complete...") + await() + } + + // Prepare for the new request + response = CompletableDeferred() + + // Launch the logout intent + Napier.d("πŸš€ Launching end session intent") + launcher.launch(android.getEndSessionRequestIntent(request.android)) + + // Await the result and parse the response + return try { + val intent = response.await() + Napier.d("βœ… End session response received") + + return + } catch (e: Exception) { + Napier.e("❌ End session failed", e) + throw e + } + } + + actual suspend fun performTokenRequest(request: TokenRequest): TokenResponse = + suspendCoroutine { cont -> + Napier.d("πŸ” Starting performTokenRequest") + Napier.d("πŸ“€ TokenRequest:\n$request") + + android.performTokenRequest(request.android) { response, ex -> + if (response != null) { + Napier.d("βœ… Token response received") + Napier.d("πŸ“₯ Parsed TokenResponse:\n$response") + cont.resume(TokenResponse(response)) + } else { + Napier.e("❌ Token request failed", ex) + cont.resumeWithException(ex!!.wrapIfNecessary()) + } + } + } + + actual suspend fun performRevokeTokenRequest(request: RevokeTokenRequest) = + withContext(Dispatchers.IO) { + val endpoint = request.config.revocationEndpoint + ?: throw IllegalStateException("Revocation endpoint not found in configuration.") + + var connection: HttpURLConnection? = null + try { + Napier.d("Performing token revocation via native HttpURLConnection to $endpoint") + connection = URL(endpoint).openConnection() as HttpURLConnection + connection.requestMethod = "POST" + connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded") + connection.doOutput = true + + val params = mutableMapOf("token" to request.token) + if (request.clientSecret == null) { + params["client_id"] = request.clientId + } else { + val credentials = "${request.clientId}:${request.clientSecret}" + val authHeader = + "Basic ${Base64.encodeToString(credentials.toByteArray(), Base64.NO_WRAP)}" + connection.setRequestProperty("Authorization", authHeader) + } + + val writer = BufferedWriter(OutputStreamWriter(connection.outputStream, "UTF-8")) + writer.write(getPostDataString(params)) + writer.flush() + writer.close() + + val responseCode = connection.responseCode + Napier.d("Revocation response code: $responseCode") + + + if (responseCode >= 400) { + val errorStream = connection.errorStream?.bufferedReader()?.readText() + throw AuthorizationException.fromTemplate( + AuthorizationException.GeneralErrors.SERVER_ERROR, + Exception("Revocation failed with code $responseCode: $errorStream") + ) + } + + } catch (e: Exception) { + Napier.e("Revocation request failed", e) + throw e + } finally { + connection?.disconnect() + } + } + + private fun getPostDataString(params: Map): String { + return params.entries.joinToString("&") { (key, value) -> + "${URLEncoder.encode(key, "UTF-8")}=${URLEncoder.encode(value, "UTF-8")}" + } + } + +} \ No newline at end of file diff --git a/src/androidMain/kotlin/dev/gitlive/appauth/AuthorizationServiceConfiguration.kt b/src/androidMain/kotlin/dev/gitlive/appauth/AuthorizationServiceConfiguration.kt new file mode 100644 index 0000000..218dfad --- /dev/null +++ b/src/androidMain/kotlin/dev/gitlive/appauth/AuthorizationServiceConfiguration.kt @@ -0,0 +1,88 @@ +package dev.gitlive.appauth + +import android.net.Uri +import io.github.aakira.napier.Napier +import net.openid.appauth.AuthorizationException +import org.json.JSONException +import org.json.JSONObject +import java.io.IOException +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +// wrap network errors in an IOException so it matches ktor +internal fun net.openid.appauth.AuthorizationException.wrapIfNecessary() = + takeUnless { it == AuthorizationException.GeneralErrors.NETWORK_ERROR } + ?: IOException(message, this) + +actual class AuthorizationServiceConfiguration private constructor( + val android: net.openid.appauth.AuthorizationServiceConfiguration, + actual val revocationEndpoint: String? +) { + + actual constructor( + authorizationEndpoint: String, + tokenEndpoint: String, + registrationEndpoint: String?, + endSessionEndpoint: String?, + revocationEndpoint: String? + ) : this( + net.openid.appauth.AuthorizationServiceConfiguration( + Uri.parse(authorizationEndpoint), + Uri.parse(tokenEndpoint), + registrationEndpoint?.let { Uri.parse(it) }, + endSessionEndpoint?.let { Uri.parse(it) }, + ), + revocationEndpoint + ) + + actual companion object { + actual suspend fun fetchFromIssuer(url: String): AuthorizationServiceConfiguration = + suspendCoroutine { cont -> + Napier.d("🌐 Starting custom fetchFromIssuer for Android") + net.openid.appauth.AuthorizationServiceConfiguration.fetchFromIssuer( + Uri.parse(url) + ) { serviceConfiguration, ex -> + if (ex != null) { + Napier.e("❌ Failed to fetch base configuration", ex) + cont.resumeWithException(ex.wrapIfNecessary()) + return@fetchFromIssuer + } + if (serviceConfiguration == null) { + Napier.e("❌ Fetched configuration is null") + cont.resumeWithException(IllegalStateException("Configuration is null")) + return@fetchFromIssuer + } + + var revocationEndpoint: String? = null + try { + val discoveryDocJson: JSONObject? = serviceConfiguration.discoveryDoc?.docJson + if (discoveryDocJson != null && discoveryDocJson.has("revocation_endpoint")) { + // ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅ΠΌ, Ρ‡Ρ‚ΠΎ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ Π½Π΅ null, ΠΏΡ€Π΅ΠΆΠ΄Π΅ Ρ‡Π΅ΠΌ Π΅Π³ΠΎ ΠΏΠΎΠ»ΡƒΡ‡Π°Ρ‚ΡŒ + if (!discoveryDocJson.isNull("revocation_endpoint")) { + revocationEndpoint = discoveryDocJson.getString("revocation_endpoint") + Napier.i("βœ… Found revocation_endpoint: $revocationEndpoint") + } + } + } catch (jsonEx: JSONException) { + Napier.w( + "Could not parse revocation_endpoint from discovery document", + jsonEx + ) + } + + cont.resume( + AuthorizationServiceConfiguration( + android = serviceConfiguration, + revocationEndpoint = revocationEndpoint + ) + ) + } + } + } + + actual val authorizationEndpoint get() = android.authorizationEndpoint.toString() + actual val tokenEndpoint get() = android.tokenEndpoint.toString() + actual val registrationEndpoint get() = android.registrationEndpoint?.toString() + actual val endSessionEndpoint get() = android.endSessionEndpoint?.toString() +} \ No newline at end of file diff --git a/src/androidMain/kotlin/dev/gitlive/appauth/TokenRequest.kt b/src/androidMain/kotlin/dev/gitlive/appauth/TokenRequest.kt new file mode 100644 index 0000000..62a3c92 --- /dev/null +++ b/src/androidMain/kotlin/dev/gitlive/appauth/TokenRequest.kt @@ -0,0 +1,30 @@ +package dev.gitlive.appauth + +actual class TokenRequest internal constructor(internal val android: net.openid.appauth.TokenRequest) { + actual constructor( + config: AuthorizationServiceConfiguration, + clientId: String, + grantType: String, + refreshToken: String? + ) : this( + net.openid.appauth.TokenRequest.Builder(config.android, clientId).apply { + setGrantType(grantType) + refreshToken?.let { setRefreshToken(it) } + }.build() + ) + override fun toString(): String { + return buildString { + appendLine("TokenRequest(") + appendLine(" clientId: ${android.clientId}") + appendLine(" grantType: ${android.grantType}") + appendLine(" scope: ${android.scope ?: "None"}") + appendLine(" refreshToken: ${android.refreshToken ?: "None"}") + appendLine(" redirectUri: ${android.redirectUri ?: "None"}") + appendLine(" additionalParameters: ${android.additionalParameters ?: "None"}") + appendLine(" config:") + appendLine(" tokenEndpoint: ${android.configuration.tokenEndpoint}") + appendLine(" authEndpoint: ${android.configuration.authorizationEndpoint}") + appendLine(")") + } + } +} \ No newline at end of file diff --git a/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt b/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt index 98caecc..3e1e445 100644 --- a/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt +++ b/src/androidMain/kotlin/dev/gitlive/appauth/androidMain.kt @@ -1,347 +1,14 @@ package dev.gitlive.appauth import android.content.ContextWrapper -import android.content.Intent import android.net.Uri -import android.util.Base64 -import androidx.activity.result.ActivityResultCaller -import androidx.activity.result.ActivityResultLauncher -import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult -import io.github.aakira.napier.Napier -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import kotlinx.io.IOException import net.openid.appauth.AuthorizationException -import org.json.JSONException -import org.json.JSONObject -import java.io.BufferedWriter -import java.io.OutputStreamWriter -import java.net.HttpURLConnection -import java.net.URL -import java.net.URLEncoder -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine import net.openid.appauth.TokenResponse as AndroidTokenResponse actual typealias AuthorizationException = AuthorizationException -// wrap network errors in an IOException so it matches ktor -private fun AuthorizationException.wrapIfNecessary() = - takeUnless { it == AuthorizationException.GeneralErrors.NETWORK_ERROR } - ?: IOException(message, this) - actual typealias AuthorizationServiceContext = ContextWrapper -actual class AuthorizationService private constructor(private val android: net.openid.appauth.AuthorizationService) { - actual constructor(context: () -> AuthorizationServiceContext) : this( - net.openid.appauth.AuthorizationService( - context() - ) - ) - - fun bind(activityOrFragment: ActivityResultCaller) { - launcher = activityOrFragment - .registerForActivityResult(StartActivityForResult()) { result -> - result.data - ?.let { AuthorizationException.fromIntent(it) } - ?.let { response.completeExceptionally(it.wrapIfNecessary()) } - ?: response.complete(result.data) - } - } - - private var response = CompletableDeferred(value = null) - - private lateinit var launcher: ActivityResultLauncher - - actual suspend fun performAuthorizationRequest(request: AuthorizationRequest): AuthorizationResponse { - // Show the request details - Napier.d("πŸ“€ Starting AuthorizationRequest:\n${request}") - // if a previous request is still pending then wait for it to finish - response.runCatching { - Napier.d("⏳ Waiting for previous authorization request to complete...") - await() - } - response = CompletableDeferred() - Napier.d("πŸš€ Launching authorization intent") - launcher.launch(android.getAuthorizationRequestIntent(request.android)) - // Wait for the result and process it - return try { - val intent = response.await() - Napier.d("βœ… Authorization response received") - - val rawResponse = net.openid.appauth.AuthorizationResponse.fromIntent(intent!!) - Napier.d("πŸ“₯ Parsed AuthorizationResponse:\n$rawResponse") - - AuthorizationResponse(rawResponse!!) - } catch (e: Exception) { - Napier.e("❌ Authorization failed", e) - throw e - } - } - - actual suspend fun performEndSessionRequest(request: EndSessionRequest) { - // if a previous request is still pending then wait for it to finish - // Show the request details - Napier.d("πŸ“€ Starting EndSessionRequest:\n$request") - - // If a previous request is still pending, wait for it - response.runCatching { - Napier.d("⏳ Waiting for previous end session request to complete...") - await() - } - - // Prepare for the new request - response = CompletableDeferred() - - // Launch the logout intent - Napier.d("πŸš€ Launching end session intent") - launcher.launch(android.getEndSessionRequestIntent(request.android)) - - // Await the result and parse the response - return try { - val intent = response.await() - Napier.d("βœ… End session response received") - - return - } catch (e: Exception) { - Napier.e("❌ End session failed", e) - throw e - } - } - - actual suspend fun performTokenRequest(request: TokenRequest): TokenResponse = - suspendCoroutine { cont -> - Napier.d("πŸ” Starting performTokenRequest") - Napier.d("πŸ“€ TokenRequest:\n$request") - - android.performTokenRequest(request.android) { response, ex -> - if (response != null) { - Napier.d("βœ… Token response received") - Napier.d("πŸ“₯ Parsed TokenResponse:\n$response") - cont.resume(TokenResponse(response)) - } else { - Napier.e("❌ Token request failed", ex) - cont.resumeWithException(ex!!.wrapIfNecessary()) - } - } - } - - actual suspend fun performRevokeTokenRequest(request: RevokeTokenRequest) = - withContext(Dispatchers.IO) { - val endpoint = request.config.revocationEndpoint - ?: throw IllegalStateException("Revocation endpoint not found in configuration.") - - var connection: HttpURLConnection? = null - try { - Napier.d("Performing token revocation via native HttpURLConnection to $endpoint") - connection = URL(endpoint).openConnection() as HttpURLConnection - connection.requestMethod = "POST" - connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded") - connection.doOutput = true - - val params = mutableMapOf("token" to request.token) - if (request.clientSecret == null) { - params["client_id"] = request.clientId - } else { - val credentials = "${request.clientId}:${request.clientSecret}" - val authHeader = - "Basic ${Base64.encodeToString(credentials.toByteArray(), Base64.NO_WRAP)}" - connection.setRequestProperty("Authorization", authHeader) - } - - val writer = BufferedWriter(OutputStreamWriter(connection.outputStream, "UTF-8")) - writer.write(getPostDataString(params)) - writer.flush() - writer.close() - - val responseCode = connection.responseCode - Napier.d("Revocation response code: $responseCode") - - - if (responseCode >= 400) { - val errorStream = connection.errorStream?.bufferedReader()?.readText() - throw AuthorizationException.fromTemplate( - AuthorizationException.GeneralErrors.SERVER_ERROR, - Exception("Revocation failed with code $responseCode: $errorStream") - ) - } - - } catch (e: Exception) { - Napier.e("Revocation request failed", e) - throw e - } finally { - connection?.disconnect() - } - } - - private fun getPostDataString(params: Map): String { - return params.entries.joinToString("&") { (key, value) -> - "${URLEncoder.encode(key, "UTF-8")}=${URLEncoder.encode(value, "UTF-8")}" - } - } - -} - -actual class AuthorizationServiceConfiguration private constructor( - val android: net.openid.appauth.AuthorizationServiceConfiguration, - actual val revocationEndpoint: String? -) { - - actual constructor( - authorizationEndpoint: String, - tokenEndpoint: String, - registrationEndpoint: String?, - endSessionEndpoint: String?, - revocationEndpoint: String? - ) : this( - net.openid.appauth.AuthorizationServiceConfiguration( - Uri.parse(authorizationEndpoint), - Uri.parse(tokenEndpoint), - registrationEndpoint?.let { Uri.parse(it) }, - endSessionEndpoint?.let { Uri.parse(it) }, - ), - revocationEndpoint - ) - - actual companion object { - actual suspend fun fetchFromIssuer(url: String): AuthorizationServiceConfiguration = - suspendCoroutine { cont -> - Napier.d("🌐 Starting custom fetchFromIssuer for Android") - net.openid.appauth.AuthorizationServiceConfiguration.fetchFromIssuer( - Uri.parse(url) - ) { serviceConfiguration, ex -> - if (ex != null) { - Napier.e("❌ Failed to fetch base configuration", ex) - cont.resumeWithException(ex.wrapIfNecessary()) - return@fetchFromIssuer - } - if (serviceConfiguration == null) { - Napier.e("❌ Fetched configuration is null") - cont.resumeWithException(IllegalStateException("Configuration is null")) - return@fetchFromIssuer - } - - var revocationEndpoint: String? = null - try { - val discoveryDocJson: JSONObject? = serviceConfiguration.discoveryDoc?.docJson - if (discoveryDocJson != null && discoveryDocJson.has("revocation_endpoint")) { - // ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅ΠΌ, Ρ‡Ρ‚ΠΎ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ Π½Π΅ null, ΠΏΡ€Π΅ΠΆΠ΄Π΅ Ρ‡Π΅ΠΌ Π΅Π³ΠΎ ΠΏΠΎΠ»ΡƒΡ‡Π°Ρ‚ΡŒ - if (!discoveryDocJson.isNull("revocation_endpoint")) { - revocationEndpoint = discoveryDocJson.getString("revocation_endpoint") - Napier.i("βœ… Found revocation_endpoint: $revocationEndpoint") - } - } - } catch (jsonEx: JSONException) { - Napier.w( - "Could not parse revocation_endpoint from discovery document", - jsonEx - ) - } - - cont.resume( - AuthorizationServiceConfiguration( - android = serviceConfiguration, - revocationEndpoint = revocationEndpoint - ) - ) - } - } - } - - actual val authorizationEndpoint get() = android.authorizationEndpoint.toString() - actual val tokenEndpoint get() = android.tokenEndpoint.toString() - actual val registrationEndpoint get() = android.registrationEndpoint?.toString() - actual val endSessionEndpoint get() = android.endSessionEndpoint?.toString() -} - -actual class AuthorizationRequest private constructor(internal val android: net.openid.appauth.AuthorizationRequest) { - actual constructor( - config: AuthorizationServiceConfiguration, - clientId: String, - scopes: List, - responseType: String, - redirectUri: String, - additionalParameters: Map? - ) : this( - net.openid.appauth.AuthorizationRequest.Builder( - config.android, - clientId, - responseType, - Uri.parse(redirectUri), - ) - .setAdditionalParameters(additionalParameters) - .setScopes(scopes) - .build() - ) - override fun toString(): String { - return buildString { - appendLine("AuthorizationRequest(") - appendLine(" clientId: ${android.clientId}") - appendLine(" scope: ${android.scope ?: "None"}") - appendLine(" responseType: ${android.responseType}") - appendLine(" redirectUri: ${android.redirectUri}") - appendLine(" additionalParameters: ${android.additionalParameters ?: "None"}") - appendLine(" config:") - appendLine(" authEndpoint: ${android.configuration.authorizationEndpoint}") - appendLine(" tokenEndpoint: ${android.configuration.tokenEndpoint}") - appendLine(")") - } - } -} - -actual class AuthorizationResponse internal constructor(private val android: net.openid.appauth.AuthorizationResponse) { - actual fun createTokenExchangeRequest() = TokenRequest(android.createTokenExchangeRequest()) - actual val idToken get() = android.idToken - actual val scope get() = android.scope - actual val authorizationCode get() = android.authorizationCode - - override fun toString(): String { - return buildString { - appendLine("AuthorizationResponse(") - appendLine(" authorizationCode: ${authorizationCode ?: "None"}") - appendLine(" idToken: ${idToken ?: "None"}") - appendLine(" scope: ${scope ?: "None"}") - appendLine(" state: ${android.state ?: "None"}") - appendLine(" tokenExchangeRequest:") - appendLine(" clientId: ${android.request.clientId}") - appendLine(" redirectUri: ${android.request.redirectUri}") - appendLine(" responseType: ${android.request.responseType}") - appendLine(")") - } - } -} - -actual class TokenRequest internal constructor(internal val android: net.openid.appauth.TokenRequest) { - actual constructor( - config: AuthorizationServiceConfiguration, - clientId: String, - grantType: String, - refreshToken: String? - ) : this( - net.openid.appauth.TokenRequest.Builder(config.android, clientId).apply { - setGrantType(grantType) - refreshToken?.let { setRefreshToken(it) } - }.build() - ) - override fun toString(): String { - return buildString { - appendLine("TokenRequest(") - appendLine(" clientId: ${android.clientId}") - appendLine(" grantType: ${android.grantType}") - appendLine(" scope: ${android.scope ?: "None"}") - appendLine(" refreshToken: ${android.refreshToken ?: "None"}") - appendLine(" redirectUri: ${android.redirectUri ?: "None"}") - appendLine(" additionalParameters: ${android.additionalParameters ?: "None"}") - appendLine(" config:") - appendLine(" tokenEndpoint: ${android.configuration.tokenEndpoint}") - appendLine(" authEndpoint: ${android.configuration.authorizationEndpoint}") - appendLine(")") - } - } -} - actual class TokenResponse internal constructor( private val androidTokenResponse: AndroidTokenResponse ) { diff --git a/src/commonMain/kotlin/dev/gitlive/appauth/commonMain.kt b/src/commonMain/kotlin/dev/gitlive/appauth/commonMain.kt index a302326..c84e7f5 100644 --- a/src/commonMain/kotlin/dev/gitlive/appauth/commonMain.kt +++ b/src/commonMain/kotlin/dev/gitlive/appauth/commonMain.kt @@ -4,13 +4,60 @@ expect class AuthorizationException : Exception expect class AuthorizationServiceContext +/** + * A service that handles the authorization flow. + * It provides methods for performing authorization, token exchange, and session management. + * This class is a KMP wrapper around the native AppAuth libraries. + */ expect class AuthorizationService(context: () -> AuthorizationServiceContext) { + /** + * Performs an authorization request to the authorization server. + * This typically involves opening a browser or a custom tab for the user to sign in. + * + * @param request The [AuthorizationRequest] containing all details for the authorization flow. + * @return An [AuthorizationResponse] containing the authorization code and other details. + * @throws AuthorizationException if the authorization flow fails. + */ suspend fun performAuthorizationRequest(request: AuthorizationRequest): AuthorizationResponse + + /** + * Performs an end-session request to the authorization server to log the user out. + * This follows the OIDC RP-Initiated Logout specification. + * + * @param request The [EndSessionRequest] containing details for the logout flow. + * @throws AuthorizationException if the end-session flow fails. + */ suspend fun performEndSessionRequest(request: EndSessionRequest) + + /** + * Performs a token request to exchange an authorization code or refresh token for new tokens. + * + * @param request The [TokenRequest] containing the grant type and relevant codes/tokens. + * @return A [TokenResponse] containing the new access, refresh, and ID tokens. + * @throws AuthorizationException if the token exchange fails. + */ suspend fun performTokenRequest(request: TokenRequest): TokenResponse + + + /** + * Performs a token revocation request as per RFC 7009. + * This directly communicates with the revocation endpoint to invalidate a token. + * + * @param request The [RevokeTokenRequest] containing the token to be revoked. + * @throws AuthorizationException if the revocation fails. + */ suspend fun performRevokeTokenRequest(request: RevokeTokenRequest) } +/** + * Represents the configuration of an authorization service, including all necessary endpoints. + * + * @property authorizationEndpoint The URL of the authorization endpoint. + * @property tokenEndpoint The URL of the token endpoint. + * @property registrationEndpoint The URL of the dynamic client registration endpoint, if available. + * @property endSessionEndpoint The URL of the end-session (logout) endpoint, if available. + * @property revocationEndpoint The URL of the token revocation endpoint (RFC 7009), if available. + */ expect class AuthorizationServiceConfiguration( authorizationEndpoint: String, tokenEndpoint: String, @@ -25,6 +72,14 @@ expect class AuthorizationServiceConfiguration( val revocationEndpoint: String? companion object { + /** + * Fetches the service configuration from an OIDC discovery document. + * + * @param url The issuer URL of the authorization server. The discovery document is typically + * found at `[url]/.well-known/openid-configuration`. + * @return A new [AuthorizationServiceConfiguration] instance. + * @throws Exception if the discovery document cannot be fetched or parsed. + */ suspend fun fetchFromIssuer(url: String): AuthorizationServiceConfiguration } } @@ -66,6 +121,15 @@ expect class EndSessionRequest( ) +/** + * Encapsulates a request to revoke a token at the token revocation endpoint. + * + * @param config The service configuration, which should contain the `revocationEndpoint`. + * @param token The refresh or access token to be revoked. + * @param clientId The client ID of the application. + * @param clientSecret The client secret, if the client is confidential. If provided, it will be + * used for Basic HTTP authentication. If null, the client is treated as public. + */ expect class RevokeTokenRequest( config: AuthorizationServiceConfiguration, token: String, diff --git a/src/iosMain/kotlin/dev/gitlive/appauth/AuthorizationRequest.kt b/src/iosMain/kotlin/dev/gitlive/appauth/AuthorizationRequest.kt new file mode 100644 index 0000000..40afc49 --- /dev/null +++ b/src/iosMain/kotlin/dev/gitlive/appauth/AuthorizationRequest.kt @@ -0,0 +1,44 @@ +@file:OptIn(ExperimentalForeignApi::class) + +package dev.gitlive.appauth + +import cocoapods.AppAuth.OIDAuthorizationRequest +import kotlinx.cinterop.ExperimentalForeignApi +import platform.Foundation.NSURL + +actual class AuthorizationRequest private constructor(internal val ios: OIDAuthorizationRequest) { + + actual constructor( + config: AuthorizationServiceConfiguration, + clientId: String, + scopes: List, + responseType: String, + redirectUri: String, + additionalParameters: Map? + ) : this( + OIDAuthorizationRequest( + configuration = config.ios, + clientId = clientId, + scopes = scopes, + redirectURL = NSURL.URLWithString(redirectUri)!!, + responseType = responseType, + additionalParameters = additionalParameters as Map?, + ) + ) + + @OptIn(ExperimentalForeignApi::class) + override fun toString(): String { + return buildString { + appendLine("AuthorizationRequest(") + appendLine(" clientId: ${ios.clientID}") + appendLine(" scope: ${ios.scope ?: "None"}") + appendLine(" responseType: ${ios.responseType}") + appendLine(" redirectUri: ${ios.redirectURL?.absoluteString ?: "None"}") + appendLine(" additionalParameters: ${ios.additionalParameters ?: "None"}") + appendLine(" config:") + appendLine(" authorizationEndpoint: ${ios.configuration.authorizationEndpoint.absoluteString}") + appendLine(" tokenEndpoint: ${ios.configuration.tokenEndpoint.absoluteString}") + appendLine(")") + } + } +} \ No newline at end of file diff --git a/src/iosMain/kotlin/dev/gitlive/appauth/AuthorizationResponse.kt b/src/iosMain/kotlin/dev/gitlive/appauth/AuthorizationResponse.kt new file mode 100644 index 0000000..dacb8ae --- /dev/null +++ b/src/iosMain/kotlin/dev/gitlive/appauth/AuthorizationResponse.kt @@ -0,0 +1,29 @@ +package dev.gitlive.appauth + +import cocoapods.AppAuth.OIDAuthorizationResponse +import kotlinx.cinterop.ExperimentalForeignApi + +@OptIn(ExperimentalForeignApi::class) +actual class AuthorizationResponse internal constructor(internal val ios: OIDAuthorizationResponse) { + actual val authorizationCode: String? get() = ios.authorizationCode + actual val idToken: String? get() = ios.idToken + actual val scope get() = ios.scope + actual fun createTokenExchangeRequest() = TokenRequest(ios.tokenExchangeRequest()!!) + + override fun toString(): String { + return buildString { + appendLine("AuthorizationResponse(") + appendLine(" authorizationCode: ${authorizationCode ?: "None"}") + appendLine(" idToken: ${idToken ?: "None"}") + appendLine(" scope: ${scope ?: "None"}") + appendLine(" state: ${ios.state ?: "None"}") + appendLine(" redirectUri: ${ios.request?.redirectURL?.absoluteString ?: "None"}") + appendLine(" clientId: ${ios.request?.clientID ?: "None"}") + appendLine(" responseType: ${ios.request?.responseType ?: "None"}") + appendLine(" config:") + appendLine(" authorizationEndpoint: ${ios.request?.configuration?.authorizationEndpoint?.absoluteString ?: "None"}") + appendLine(" tokenEndpoint: ${ios.request?.configuration?.tokenEndpoint?.absoluteString ?: "None"}") + appendLine(")") + } + } +} \ No newline at end of file diff --git a/src/iosMain/kotlin/dev/gitlive/appauth/AuthorizationService.kt b/src/iosMain/kotlin/dev/gitlive/appauth/AuthorizationService.kt new file mode 100644 index 0000000..051b662 --- /dev/null +++ b/src/iosMain/kotlin/dev/gitlive/appauth/AuthorizationService.kt @@ -0,0 +1,187 @@ +package dev.gitlive.appauth + +import cocoapods.AppAuth.OIDAuthorizationService +import cocoapods.AppAuth.OIDExternalUserAgentIOS +import cocoapods.AppAuth.OIDExternalUserAgentSessionProtocol +import io.github.aakira.napier.Napier +import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import platform.Foundation.NSHTTPURLResponse +import platform.Foundation.NSMutableURLRequest +import platform.Foundation.NSString +import platform.Foundation.NSURL +import platform.Foundation.NSURLSession +import platform.Foundation.NSUTF8StringEncoding +import platform.Foundation.base64EncodedStringWithOptions +import platform.Foundation.create +import platform.Foundation.dataTaskWithRequest +import platform.Foundation.dataUsingEncoding +import platform.Foundation.setHTTPBody +import platform.Foundation.setHTTPMethod +import platform.Foundation.setValue +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + + + +@OptIn(ExperimentalForeignApi::class) +internal fun String.base64Encoded(): String { + val data = (this as NSString).dataUsingEncoding(NSUTF8StringEncoding) + return data?.base64EncodedStringWithOptions(0u) ?: "" +} + + +@OptIn(ExperimentalForeignApi::class) +actual class AuthorizationService actual constructor(private val context: () -> AuthorizationServiceContext) { + + private var session: OIDExternalUserAgentSessionProtocol? = null + + fun resumeExternalUserAgentFlow(url: NSURL): Boolean = + session?.resumeExternalUserAgentFlowWithURL(url) == true + + actual suspend fun performAuthorizationRequest(request: AuthorizationRequest): AuthorizationResponse = + withContext(Dispatchers.Main) { + Napier.d("πŸ” Starting iOS performAuthorizationRequest") + Napier.d("πŸ“€ AuthorizationRequest:\n$request") + + suspendCoroutine { cont -> + val viewController = context() + Napier.d("🧭 Presenting authorization from context: ${viewController::class.simpleName}") + + session = OIDAuthorizationService.presentAuthorizationRequest( + request.ios, + OIDExternalUserAgentIOS(viewController) + ) { response, error -> + Napier.d("πŸ” Authorization callback triggered") + session = null + + if (response != null) { + Napier.d("βœ… Authorization successful") + Napier.d("πŸ“₯ AuthorizationResponse:\n$response") + cont.resume(AuthorizationResponse(response)) + } else { + Napier.e( + "❌ Authorization failed: ${error?.localizedDescription}", + error!!.toException() + ) + cont.resumeWithException(error.toException()) + } + } + } + } + + + actual suspend fun performEndSessionRequest(request: EndSessionRequest) = + withContext(Dispatchers.Main) { + Napier.d("πŸ” Starting iOS performEndSessionRequest") + Napier.d("πŸ“€ EndSessionRequest:\n$request") + + suspendCoroutine { cont -> + val viewController = context() + Napier.d("🧭 Presenting end session from context: ${viewController::class.simpleName}") + + session = OIDAuthorizationService.presentEndSessionRequest( + request.ios, + OIDExternalUserAgentIOS(viewController) + ) { response, error -> + Napier.d("πŸ” End session callback triggered") + session = null + + if (response != null) { + Napier.d("βœ… End session completed successfully") + Napier.d("πŸ“₯ EndSessionResponse: $response") + cont.resume(Unit) + } else { + Napier.e( + "❌ End session failed: ${error?.localizedDescription}", + error!!.toException() + ) + cont.resumeWithException(error.toException()) + } + } + } + } + + + actual suspend fun performTokenRequest(request: TokenRequest): TokenResponse = + withContext(Dispatchers.Main) { + Napier.d("πŸ” Starting iOS performTokenRequest") + Napier.d("πŸ“€ TokenRequest:\n$request") + + suspendCoroutine { cont -> + Napier.d("πŸ“‘ Performing token request via OIDAuthorizationService") + + OIDAuthorizationService.performTokenRequest(request.ios) { response, error -> + Napier.d("πŸ” Token request callback triggered") + + if (response != null) { + Napier.d("βœ… Token request successful") + Napier.d("πŸ“₯ TokenResponse: ${TokenResponse(response)}") + cont.resume(TokenResponse(response)) + } else { + Napier.e( + "❌ Token request failed: ${error?.localizedDescription}", + error!!.toException() + ) + cont.resumeWithException(error.toException()) + } + } + } + } + + actual suspend fun performRevokeTokenRequest(request: RevokeTokenRequest) { + val endpoint = request.config.revocationEndpoint + ?: throw AuthorizationException("Revocation endpoint not found in configuration.") + + return suspendCoroutine { continuation -> + Napier.d("Performing token revocation via native URLSession to $endpoint") + + val url = NSURL(string = endpoint) + val urlRequest = NSMutableURLRequest(uRL = url) + + urlRequest.setHTTPMethod("POST") + + urlRequest.setValue( + "application/x-www-form-urlencoded", + forHTTPHeaderField = "Content-Type" + ) + + var bodyString = "token=${request.token.urlEncoded()}" + if (request.clientSecret == null) { + bodyString += "&client_id=${request.clientId.urlEncoded()}" + } else { + val credentials = "${request.clientId}:${request.clientSecret}" + val authHeader = "Basic ${credentials.base64Encoded()}" + urlRequest.setValue(authHeader, forHTTPHeaderField = "Authorization") + } + + urlRequest.setHTTPBody((bodyString as NSString).dataUsingEncoding(NSUTF8StringEncoding)) + + val task = + NSURLSession.sharedSession.dataTaskWithRequest(urlRequest) { data, response, error -> + if (error != null) { + continuation.resumeWithException(error.toException()) + return@dataTaskWithRequest + } + + val httpResponse = response as? NSHTTPURLResponse + val statusCode = httpResponse?.statusCode?.toInt() ?: 0 + Napier.d("Revocation response code: $statusCode") + + if (statusCode >= 400) { + val errorBody = data?.let { NSString.create(it, NSUTF8StringEncoding) } + continuation.resumeWithException(AuthorizationException("Revocation failed with code $statusCode: $errorBody")) + } else { + continuation.resume(Unit) + } + } + task.resume() + } + } + + private fun String.urlEncoded(): String { + return this.replace("=", "%3D").replace("+", "%2B").replace("/", "%2F") + } +} \ No newline at end of file diff --git a/src/iosMain/kotlin/dev/gitlive/appauth/AuthorizationServiceConfiguration.kt b/src/iosMain/kotlin/dev/gitlive/appauth/AuthorizationServiceConfiguration.kt new file mode 100644 index 0000000..94ff039 --- /dev/null +++ b/src/iosMain/kotlin/dev/gitlive/appauth/AuthorizationServiceConfiguration.kt @@ -0,0 +1,86 @@ +package dev.gitlive.appauth + +import cocoapods.AppAuth.OIDAuthorizationService +import cocoapods.AppAuth.OIDServiceConfiguration +import io.github.aakira.napier.Napier +import kotlinx.cinterop.ExperimentalForeignApi +import platform.Foundation.NSURL +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + + +@OptIn(ExperimentalForeignApi::class) +actual class AuthorizationServiceConfiguration private constructor( + val ios: OIDServiceConfiguration, + actual val revocationEndpoint: String? +) { + + actual constructor( + authorizationEndpoint: String, + tokenEndpoint: String, + registrationEndpoint: String?, + endSessionEndpoint: String?, + revocationEndpoint: String? + ) : this( + OIDServiceConfiguration( + NSURL.URLWithString(authorizationEndpoint)!!, + NSURL.URLWithString(tokenEndpoint)!!, + null, + registrationEndpoint?.let { NSURL.URLWithString(it) }, + endSessionEndpoint?.let { NSURL.URLWithString(it) } + ), + revocationEndpoint + ) + + actual companion object { + @OptIn(ExperimentalForeignApi::class) + actual suspend fun fetchFromIssuer(url: String): AuthorizationServiceConfiguration = + suspendCoroutine { cont -> + Napier.d("🌐 Starting iOS fetchFromIssuer") + Napier.d("πŸ”— Issuer URL: $url") + + val nsUrl = NSURL.URLWithString(url) + if (nsUrl == null) { + Napier.e("❌ Invalid URL: $url") + cont.resumeWithException(IllegalArgumentException("Invalid issuer URL: $url")) + return@suspendCoroutine + } + + OIDAuthorizationService.discoverServiceConfigurationForIssuer(nsUrl) { config, error -> + Napier.d("πŸ” Discovery callback triggered") + + if (config != null) { + Napier.d("βœ… Discovery successful") + Napier.d("πŸ“₯ AuthorizationServiceConfiguration:") + Napier.d(" authorizationEndpoint: ${config.authorizationEndpoint.absoluteString}") + Napier.d(" tokenEndpoint: ${config.tokenEndpoint.absoluteString}") + Napier.d(" endSessionEndpoint: ${config.endSessionEndpoint?.absoluteString ?: "None"}") + + var revocationEndpoint: String? = null + try { + val discoveryDoc = config.discoveryDocument() + if (discoveryDoc is Map<*, *>) { + revocationEndpoint = discoveryDoc["revocation_endpoint"] as? String + if (revocationEndpoint != null) { + Napier.i("βœ… Found revocation_endpoint: $revocationEndpoint") + } + } + } catch (e: Exception) { + Napier.w("Could not parse revocation_endpoint", e) + } + + cont.resume(AuthorizationServiceConfiguration(config, revocationEndpoint)) + } else { + Napier.e("❌ Discovery failed: ${error?.localizedDescription}", error!!.toException()) + cont.resumeWithException(error.toException()) + } + } + } + } + + actual val authorizationEndpoint: String get() = ios.authorizationEndpoint.relativeString + actual val tokenEndpoint: String get() = ios.tokenEndpoint.relativeString + actual val registrationEndpoint: String? get() = ios.registrationEndpoint?.relativeString + actual val endSessionEndpoint: String? get() = ios.endSessionEndpoint?.relativeString +} diff --git a/src/iosMain/kotlin/dev/gitlive/appauth/TokenRequest.kt b/src/iosMain/kotlin/dev/gitlive/appauth/TokenRequest.kt new file mode 100644 index 0000000..74b2c1e --- /dev/null +++ b/src/iosMain/kotlin/dev/gitlive/appauth/TokenRequest.kt @@ -0,0 +1,44 @@ +@file:OptIn(ExperimentalForeignApi::class) + +package dev.gitlive.appauth + +import cocoapods.AppAuth.OIDTokenRequest +import kotlinx.cinterop.ExperimentalForeignApi + +actual class TokenRequest internal constructor(internal val ios: OIDTokenRequest) { + actual constructor( + config: AuthorizationServiceConfiguration, + clientId: String, + grantType: String, + refreshToken: String? + ) : this( + OIDTokenRequest( + configuration = config.ios, + grantType = grantType, + authorizationCode = null, + redirectURL = null, + clientID = clientId, + clientSecret = null, + scope = null, + refreshToken = refreshToken, + codeVerifier = null, + additionalParameters = null + ) + ) + @OptIn(ExperimentalForeignApi::class) + override fun toString(): String { + return buildString { + appendLine("TokenRequest(") + appendLine(" clientId: ${ios.clientID ?: "None"}") + appendLine(" grantType: ${ios.grantType}") + appendLine(" scope: ${ios.scope ?: "None"}") + appendLine(" refreshToken: ${ios.refreshToken ?: "None"}") + appendLine(" redirectUri: ${ios.redirectURL?.absoluteString ?: "None"}") + appendLine(" additionalParameters: ${ios.additionalParameters ?: "None"}") + appendLine(" config:") + appendLine(" tokenEndpoint: ${ios.configuration.tokenEndpoint.absoluteString}") + appendLine(" authorizationEndpoint: ${ios.configuration.authorizationEndpoint.absoluteString}") + appendLine(")") + } + } +} \ No newline at end of file diff --git a/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt b/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt index 3650535..44b9925 100644 --- a/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt +++ b/src/iosMain/kotlin/dev/gitlive/appauth/iosMain.kt @@ -2,38 +2,21 @@ package dev.gitlive.appauth -import cocoapods.AppAuth.OIDAuthorizationRequest -import cocoapods.AppAuth.OIDAuthorizationResponse -import cocoapods.AppAuth.OIDAuthorizationService import cocoapods.AppAuth.OIDEndSessionRequest import cocoapods.AppAuth.OIDErrorCodeNetworkError -import cocoapods.AppAuth.OIDExternalUserAgentIOS -import cocoapods.AppAuth.OIDExternalUserAgentSessionProtocol import cocoapods.AppAuth.OIDGeneralErrorDomain -import cocoapods.AppAuth.OIDServiceConfiguration -import cocoapods.AppAuth.OIDTokenRequest import cocoapods.AppAuth.OIDTokenResponse -import io.github.aakira.napier.Napier import kotlinx.cinterop.ExperimentalForeignApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext import kotlinx.io.IOException -import platform.Foundation.* import platform.Foundation.NSError -import platform.Foundation.NSHTTPURLResponse -import platform.Foundation.NSMutableURLRequest -import platform.Foundation.NSString import platform.Foundation.NSURL import platform.UIKit.UIViewController -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine actual class AuthorizationException(message: String?) : Exception(message) // wrap network errors in an IOException so it matches ktor @OptIn(ExperimentalForeignApi::class) -private fun NSError.toException() = when (domain) { +internal fun NSError.toException() = when (domain) { OIDGeneralErrorDomain -> when (code) { OIDErrorCodeNetworkError -> IOException(localizedDescription) else -> AuthorizationException(localizedDescription) @@ -41,178 +24,6 @@ private fun NSError.toException() = when (domain) { else -> AuthorizationException(localizedDescription) } -actual class AuthorizationServiceConfiguration private constructor( - val ios: OIDServiceConfiguration, - actual val revocationEndpoint: String? -) { - - actual constructor( - authorizationEndpoint: String, - tokenEndpoint: String, - registrationEndpoint: String?, - endSessionEndpoint: String?, - revocationEndpoint: String? - ) : this( - OIDServiceConfiguration( - NSURL.URLWithString(authorizationEndpoint)!!, - NSURL.URLWithString(tokenEndpoint)!!, - null, - registrationEndpoint?.let { NSURL.URLWithString(it) }, - endSessionEndpoint?.let { NSURL.URLWithString(it) } - ), - revocationEndpoint - ) - - actual companion object { - @OptIn(ExperimentalForeignApi::class) - actual suspend fun fetchFromIssuer(url: String): AuthorizationServiceConfiguration = - suspendCoroutine { cont -> - Napier.d("🌐 Starting iOS fetchFromIssuer") - Napier.d("πŸ”— Issuer URL: $url") - - val nsUrl = NSURL.URLWithString(url) - if (nsUrl == null) { - Napier.e("❌ Invalid URL: $url") - cont.resumeWithException(IllegalArgumentException("Invalid issuer URL: $url")) - return@suspendCoroutine - } - - OIDAuthorizationService.discoverServiceConfigurationForIssuer(nsUrl) { config, error -> - Napier.d("πŸ” Discovery callback triggered") - - if (config != null) { - Napier.d("βœ… Discovery successful") - Napier.d("πŸ“₯ AuthorizationServiceConfiguration:") - Napier.d(" authorizationEndpoint: ${config.authorizationEndpoint.absoluteString}") - Napier.d(" tokenEndpoint: ${config.tokenEndpoint.absoluteString}") - Napier.d(" endSessionEndpoint: ${config.endSessionEndpoint?.absoluteString ?: "None"}") - - var revocationEndpoint: String? = null - try { - val discoveryDoc = config.discoveryDocument() - if (discoveryDoc is Map<*, *>) { - revocationEndpoint = discoveryDoc["revocation_endpoint"] as? String - if (revocationEndpoint != null) { - Napier.i("βœ… Found revocation_endpoint: $revocationEndpoint") - } - } - } catch (e: Exception) { - Napier.w("Could not parse revocation_endpoint", e) - } - - cont.resume(AuthorizationServiceConfiguration(config, revocationEndpoint)) - } else { - Napier.e("❌ Discovery failed: ${error?.localizedDescription}", error!!.toException()) - cont.resumeWithException(error.toException()) - } - } - } - } - - actual val authorizationEndpoint: String get() = ios.authorizationEndpoint.relativeString - actual val tokenEndpoint: String get() = ios.tokenEndpoint.relativeString - actual val registrationEndpoint: String? get() = ios.registrationEndpoint?.relativeString - actual val endSessionEndpoint: String? get() = ios.endSessionEndpoint?.relativeString -} - -actual class AuthorizationRequest private constructor(internal val ios: OIDAuthorizationRequest) { - - actual constructor( - config: AuthorizationServiceConfiguration, - clientId: String, - scopes: List, - responseType: String, - redirectUri: String, - additionalParameters: Map? - ) : this( - OIDAuthorizationRequest( - configuration = config.ios, - clientId = clientId, - scopes = scopes, - redirectURL = NSURL.URLWithString(redirectUri)!!, - responseType = responseType, - additionalParameters = additionalParameters as Map?, - ) - ) - @OptIn(ExperimentalForeignApi::class) - override fun toString(): String { - return buildString { - appendLine("AuthorizationRequest(") - appendLine(" clientId: ${ios.clientID}") - appendLine(" scope: ${ios.scope ?: "None"}") - appendLine(" responseType: ${ios.responseType}") - appendLine(" redirectUri: ${ios.redirectURL?.absoluteString ?: "None"}") - appendLine(" additionalParameters: ${ios.additionalParameters ?: "None"}") - appendLine(" config:") - appendLine(" authorizationEndpoint: ${ios.configuration.authorizationEndpoint.absoluteString}") - appendLine(" tokenEndpoint: ${ios.configuration.tokenEndpoint.absoluteString}") - appendLine(")") - } - } -} - -actual class TokenRequest internal constructor(internal val ios: OIDTokenRequest) { - actual constructor( - config: AuthorizationServiceConfiguration, - clientId: String, - grantType: String, - refreshToken: String? - ) : this( - OIDTokenRequest( - configuration = config.ios, - grantType = grantType, - authorizationCode = null, - redirectURL = null, - clientID = clientId, - clientSecret = null, - scope = null, - refreshToken = refreshToken, - codeVerifier = null, - additionalParameters = null - ) - ) - @OptIn(ExperimentalForeignApi::class) - override fun toString(): String { - return buildString { - appendLine("TokenRequest(") - appendLine(" clientId: ${ios.clientID ?: "None"}") - appendLine(" grantType: ${ios.grantType}") - appendLine(" scope: ${ios.scope ?: "None"}") - appendLine(" refreshToken: ${ios.refreshToken ?: "None"}") - appendLine(" redirectUri: ${ios.redirectURL?.absoluteString ?: "None"}") - appendLine(" additionalParameters: ${ios.additionalParameters ?: "None"}") - appendLine(" config:") - appendLine(" tokenEndpoint: ${ios.configuration.tokenEndpoint.absoluteString}") - appendLine(" authorizationEndpoint: ${ios.configuration.authorizationEndpoint.absoluteString}") - appendLine(")") - } - } -} -@OptIn(ExperimentalForeignApi::class) -actual class AuthorizationResponse internal constructor(internal val ios: OIDAuthorizationResponse) { - actual val authorizationCode: String? get() = ios.authorizationCode - actual val idToken: String? get() = ios.idToken - actual val scope get() = ios.scope - actual fun createTokenExchangeRequest() = TokenRequest(ios.tokenExchangeRequest()!!) - - override fun toString(): String { - return buildString { - appendLine("AuthorizationResponse(") - appendLine(" authorizationCode: ${authorizationCode ?: "None"}") - appendLine(" idToken: ${idToken ?: "None"}") - appendLine(" scope: ${scope ?: "None"}") - appendLine(" state: ${ios.state ?: "None"}") - appendLine(" redirectUri: ${ios.request?.redirectURL?.absoluteString ?: "None"}") - appendLine(" clientId: ${ios.request?.clientID ?: "None"}") - appendLine(" responseType: ${ios.request?.responseType ?: "None"}") - appendLine(" config:") - appendLine(" authorizationEndpoint: ${ios.request?.configuration?.authorizationEndpoint?.absoluteString ?: "None"}") - appendLine(" tokenEndpoint: ${ios.request?.configuration?.tokenEndpoint?.absoluteString ?: "None"}") - appendLine(")") - } - } -} - actual class EndSessionRequest internal constructor(internal val ios: OIDEndSessionRequest) { actual constructor( config: AuthorizationServiceConfiguration, @@ -241,151 +52,6 @@ actual class TokenResponse internal constructor(internal val ios: OIDTokenRespon actual typealias AuthorizationServiceContext = UIViewController -@OptIn(ExperimentalForeignApi::class) -actual class AuthorizationService actual constructor(private val context: () -> AuthorizationServiceContext) { - - private var session: OIDExternalUserAgentSessionProtocol? = null - - fun resumeExternalUserAgentFlow(url: NSURL): Boolean = - session?.resumeExternalUserAgentFlowWithURL(url) == true - - actual suspend fun performAuthorizationRequest(request: AuthorizationRequest): AuthorizationResponse = - withContext(Dispatchers.Main) { - Napier.d("πŸ” Starting iOS performAuthorizationRequest") - Napier.d("πŸ“€ AuthorizationRequest:\n$request") - - suspendCoroutine { cont -> - val viewController = context() - Napier.d("🧭 Presenting authorization from context: ${viewController::class.simpleName}") - - session = OIDAuthorizationService.presentAuthorizationRequest( - request.ios, - OIDExternalUserAgentIOS(viewController) - ) { response, error -> - Napier.d("πŸ” Authorization callback triggered") - session = null - - if (response != null) { - Napier.d("βœ… Authorization successful") - Napier.d("πŸ“₯ AuthorizationResponse:\n$response") - cont.resume(AuthorizationResponse(response)) - } else { - Napier.e("❌ Authorization failed: ${error?.localizedDescription}", error!!.toException()) - cont.resumeWithException(error.toException()) - } - } - } - } - - - actual suspend fun performEndSessionRequest(request: EndSessionRequest) = - withContext(Dispatchers.Main) { - Napier.d("πŸ” Starting iOS performEndSessionRequest") - Napier.d("πŸ“€ EndSessionRequest:\n$request") - - suspendCoroutine { cont -> - val viewController = context() - Napier.d("🧭 Presenting end session from context: ${viewController::class.simpleName}") - - session = OIDAuthorizationService.presentEndSessionRequest( - request.ios, - OIDExternalUserAgentIOS(viewController) - ) { response, error -> - Napier.d("πŸ” End session callback triggered") - session = null - - if (response != null) { - Napier.d("βœ… End session completed successfully") - Napier.d("πŸ“₯ EndSessionResponse: $response") - cont.resume(Unit) - } else { - Napier.e("❌ End session failed: ${error?.localizedDescription}", error!!.toException()) - cont.resumeWithException(error.toException()) - } - } - } - } - - - actual suspend fun performTokenRequest(request: TokenRequest): TokenResponse = - withContext(Dispatchers.Main) { - Napier.d("πŸ” Starting iOS performTokenRequest") - Napier.d("πŸ“€ TokenRequest:\n$request") - - suspendCoroutine { cont -> - Napier.d("πŸ“‘ Performing token request via OIDAuthorizationService") - - OIDAuthorizationService.performTokenRequest(request.ios) { response, error -> - Napier.d("πŸ” Token request callback triggered") - - if (response != null) { - Napier.d("βœ… Token request successful") - Napier.d("πŸ“₯ TokenResponse: ${TokenResponse(response)}") - cont.resume(TokenResponse(response)) - } else { - Napier.e("❌ Token request failed: ${error?.localizedDescription}", error!!.toException()) - cont.resumeWithException(error.toException()) - } - } - } - } - - actual suspend fun performRevokeTokenRequest(request: RevokeTokenRequest) { - val endpoint = request.config.revocationEndpoint - ?: throw AuthorizationException("Revocation endpoint not found in configuration.") - - return suspendCoroutine { continuation -> - Napier.d("Performing token revocation via native URLSession to $endpoint") - - val url = NSURL(string = endpoint) - val urlRequest = NSMutableURLRequest(uRL = url) - - urlRequest.setHTTPMethod("POST") - - urlRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField = "Content-Type") - - var bodyString = "token=${request.token.urlEncoded()}" - if (request.clientSecret == null) { - bodyString += "&client_id=${request.clientId.urlEncoded()}" - } else { - val credentials = "${request.clientId}:${request.clientSecret}" - val authHeader = "Basic ${credentials.base64Encoded()}" - urlRequest.setValue(authHeader, forHTTPHeaderField = "Authorization") - } - - urlRequest.setHTTPBody((bodyString as NSString).dataUsingEncoding(NSUTF8StringEncoding)) - - val task = NSURLSession.sharedSession.dataTaskWithRequest(urlRequest) { data, response, error -> - if (error != null) { - continuation.resumeWithException(error.toException()) - return@dataTaskWithRequest - } - - val httpResponse = response as? NSHTTPURLResponse - val statusCode = httpResponse?.statusCode?.toInt() ?: 0 - Napier.d("Revocation response code: $statusCode") - - if (statusCode >= 400) { - val errorBody = data?.let { NSString.create(it, NSUTF8StringEncoding) } - continuation.resumeWithException(AuthorizationException("Revocation failed with code $statusCode: $errorBody")) - } else { - continuation.resume(Unit) - } - } - task.resume() - } - } - - private fun String.urlEncoded(): String { - return this.replace("=", "%3D").replace("+", "%2B").replace("/", "%2F") - } -} - -@OptIn(ExperimentalForeignApi::class) -internal fun String.base64Encoded(): String { - val data = (this as NSString).dataUsingEncoding(NSUTF8StringEncoding) - return data?.base64EncodedStringWithOptions(0u) ?: "" -} actual class RevokeTokenRequest actual constructor( val config: AuthorizationServiceConfiguration,