feat: add platform OAuth implementations (Android, iOS, Desktop)
- AndroidOAuthClient: ACTION_VIEW + OAuthCallbackActivity (myice://callback) - IosOAuthClient: ASWebAuthenticationSession with presentation context provider - DesktopOAuthClient: local HTTP server + system browser - AndroidManifest: register OAuthCallbackActivity intent filter - Align Kotlin to 2.3.20 (required by Compose MP 1.11.1 klibs) and compileSdk to 36 (required by activity-compose 1.13.0); fix missing Text import and stray brace in placeholder iOS/Android entry points
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
package ch.parano.myice
|
||||
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.ui.window.ComposeUIViewController
|
||||
|
||||
fun MainViewController() = ComposeUIViewController {
|
||||
Text("MyIce")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package ch.parano.myice.auth
|
||||
|
||||
import kotlinx.cinterop.ExperimentalForeignApi
|
||||
import platform.AuthenticationServices.ASWebAuthenticationPresentationContextProvidingProtocol
|
||||
import platform.AuthenticationServices.ASWebAuthenticationSession
|
||||
import platform.Foundation.NSError
|
||||
import platform.Foundation.NSURL
|
||||
import platform.UIKit.UIApplication
|
||||
import platform.UIKit.UIWindow
|
||||
import platform.UIKit.UIWindowScene
|
||||
import platform.darwin.NSObject
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
@OptIn(ExperimentalForeignApi::class)
|
||||
class IosOAuthClient : OAuthClient {
|
||||
|
||||
override fun redirectUri(): String = "myice://callback"
|
||||
|
||||
override suspend fun authenticate(loginUrl: String): String? = suspendCoroutine { continuation ->
|
||||
val nsUrl = NSURL.URLWithString(loginUrl) ?: run {
|
||||
continuation.resume(null)
|
||||
return@suspendCoroutine
|
||||
}
|
||||
|
||||
val session = ASWebAuthenticationSession(
|
||||
nsUrl,
|
||||
"myice",
|
||||
completionHandler = { callbackURL: NSURL?, error: NSError? ->
|
||||
if (error != null || callbackURL == null) {
|
||||
continuation.resume(null)
|
||||
} else {
|
||||
val urlStr = callbackURL.absoluteString ?: ""
|
||||
val fragmentIndex = urlStr.indexOf("#")
|
||||
if (fragmentIndex < 0) {
|
||||
continuation.resume(null)
|
||||
} else {
|
||||
val fragment = urlStr.substring(fragmentIndex + 1)
|
||||
val params = fragment.split("&").associate { pair ->
|
||||
val idx = pair.indexOf("=")
|
||||
if (idx >= 0) pair.substring(0, idx) to pair.substring(idx + 1)
|
||||
else pair to ""
|
||||
}
|
||||
continuation.resume(params["access_token"])
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
session.presentationContextProvider = object : NSObject(),
|
||||
ASWebAuthenticationPresentationContextProvidingProtocol {
|
||||
override fun presentationAnchorForWebAuthenticationSession(
|
||||
session: ASWebAuthenticationSession
|
||||
): UIWindow {
|
||||
return keyWindow()
|
||||
}
|
||||
}
|
||||
|
||||
session.start()
|
||||
}
|
||||
|
||||
private fun keyWindow(): UIWindow {
|
||||
val scenes = UIApplication.sharedApplication.connectedScenes
|
||||
val windowScene = scenes.firstOrNull { it is UIWindowScene } as? UIWindowScene
|
||||
return (windowScene?.windows?.firstOrNull() as? UIWindow) ?: UIWindow()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user