refactor: split up core into multiple modules
This commit is contained in:
parent
2725342c3f
commit
0d84411f14
38 changed files with 344 additions and 149 deletions
65
api/gateway/build.gradle.kts
Normal file
65
api/gateway/build.gradle.kts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.androidLibrary)
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
androidTarget {
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.JVM_11)
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation(project(":api:shared"))
|
||||
|
||||
implementation(libs.kermit)
|
||||
implementation(libs.ktor.client.core)
|
||||
implementation(libs.ktor.client.content.negotiation)
|
||||
implementation(libs.ktor.client.websockets)
|
||||
implementation(libs.ktor.serialization.kotlinx.json)
|
||||
}
|
||||
commonTest.dependencies {
|
||||
implementation(libs.kotlin.test)
|
||||
}
|
||||
jvmMain.dependencies {
|
||||
implementation(libs.ktor.client.okhttp)
|
||||
}
|
||||
androidMain.dependencies {
|
||||
implementation(libs.ktor.client.okhttp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring(libs.desugar)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "moe.lava.neon.api.gateway"
|
||||
compileSdk = libs.versions.android.compileSdk.get().toInt()
|
||||
|
||||
defaultConfig {
|
||||
minSdk = libs.versions.android.minSdk.get().toInt()
|
||||
}
|
||||
packaging {
|
||||
resources {
|
||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
getByName("release") {
|
||||
isMinifyEnabled = false
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package moe.lava.neon.core.api.gateway
|
||||
package moe.lava.neon.api.gateway
|
||||
|
||||
@Suppress("unused")
|
||||
object Capability {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package moe.lava.neon.core.api.gateway
|
||||
package moe.lava.neon.api.gateway
|
||||
|
||||
import io.ktor.websocket.CloseReason
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package moe.lava.neon.core.api.gateway
|
||||
package moe.lava.neon.api.gateway
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -6,15 +6,15 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import moe.lava.neon.core.api.gateway.handlers.EventHandlers
|
||||
import moe.lava.neon.core.repository.AuthRepository
|
||||
import moe.lava.neon.api.gateway.handlers.Handler
|
||||
import kotlin.math.pow
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class GatewayHandler(
|
||||
private val auth: AuthRepository,
|
||||
private val eventHandlers: EventHandlers,
|
||||
) {
|
||||
typealias EventHandlers = Map<KClass<out Event.Dispatch>, MutableList<Handler<in Event.Dispatch>>>
|
||||
|
||||
class GatewayHandler {
|
||||
private val eventHandlers: EventHandlers = mutableMapOf()
|
||||
private val logger = Logger.withTag("neon.core.api.gateway/handler")
|
||||
private val scope = CoroutineScope(Dispatchers.IO)
|
||||
private var session: GatewaySession? = null
|
||||
|
|
@ -23,13 +23,11 @@ class GatewayHandler(
|
|||
private var retryAttempts: Int = 0
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
suspend fun connect() {
|
||||
suspend fun connect(token: String) {
|
||||
if (session != null) {
|
||||
logger.w(Throwable()) { "Attempted to connect, but client already connected, ignoring..." }
|
||||
return
|
||||
}
|
||||
val token = auth.token
|
||||
?: throw IllegalStateException("Tried to connect to gateway with no token")
|
||||
|
||||
session = GatewaySession.start(
|
||||
token = token,
|
||||
|
|
@ -59,7 +57,7 @@ class GatewayHandler(
|
|||
logger.d { "Reconnecting in ${dur.inWholeMilliseconds}ms" }
|
||||
delay(dur)
|
||||
retryAttempts += 1
|
||||
res = runCatching { connect() }
|
||||
res = runCatching { connect(token) }
|
||||
res.exceptionOrNull()?.let {
|
||||
logger.e(it) { "Reconnect failed" }
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package moe.lava.neon.core.api.gateway
|
||||
package moe.lava.neon.api.gateway
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import io.ktor.client.HttpClient
|
||||
|
|
@ -22,15 +22,14 @@ import kotlinx.coroutines.flow.onCompletion
|
|||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.lava.neon.core.api.ApiConstants
|
||||
import moe.lava.neon.core.api.ApiConstants.json
|
||||
import moe.lava.neon.core.api.gateway.handlers.EventHandlers
|
||||
import moe.lava.neon.api.ApiConstants
|
||||
import moe.lava.neon.api.ApiConstants.json
|
||||
import kotlin.random.Random
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
private val logger = Logger.withTag("neon.core.api.gateway/session")
|
||||
|
||||
class GatewaySession private constructor(
|
||||
internal class GatewaySession private constructor(
|
||||
private var ws: DefaultClientWebSocketSession,
|
||||
private val token: String,
|
||||
private val handlers: EventHandlers,
|
||||
|
|
@ -100,19 +99,28 @@ class GatewaySession private constructor(
|
|||
|
||||
private suspend fun handlePayload(payload: Payload.Incoming<*>) {
|
||||
logger.d { payload.toString() }
|
||||
when (val event = payload.d) {
|
||||
val event = payload.d
|
||||
when (event) {
|
||||
is Event.Heartbeat -> handleHeartbeat()
|
||||
is Event.Reconnect -> close(GatewayCloseReason.ServerReconnect)
|
||||
is Event.InvalidSession -> close(GatewayCloseReason.InvalidSession(event.resumable))
|
||||
is Event.Hello -> handleHello(event)
|
||||
is Event.HeartbeatAck -> { missedHeartbeats -= 1 }
|
||||
|
||||
is Event.Ready -> handlers.ready.handle(event) {
|
||||
resumeProps = it
|
||||
is Event.Ready -> {
|
||||
resumeProps = ResumeProperties(
|
||||
sessionId = event.sessionId,
|
||||
resumeGatewayUrl = event.resumeGatewayUrl,
|
||||
lastSequence = 0,
|
||||
)
|
||||
onSuccess()
|
||||
}
|
||||
is Event.Resumed -> onSuccess()
|
||||
}
|
||||
if (event is Event.Dispatch) {
|
||||
val eventHandlers = handlers[event::class] ?: return
|
||||
eventHandlers.forEach { it.handle(event) }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleUnknownPayload(payload: Payload.Unknown) {
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
package moe.lava.neon.core.api.gateway
|
||||
package moe.lava.neon.api.gateway
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import moe.lava.neon.core.api.ApiConstants
|
||||
import moe.lava.neon.core.api.structures.User
|
||||
import moe.lava.neon.api.ApiConstants
|
||||
import moe.lava.neon.api.objects.User
|
||||
|
||||
sealed interface Payload {
|
||||
val op: Int
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package moe.lava.neon.core.api.gateway
|
||||
package moe.lava.neon.api.gateway
|
||||
|
||||
data class ResumeProperties(
|
||||
internal data class ResumeProperties(
|
||||
val sessionId: String,
|
||||
val resumeGatewayUrl: String,
|
||||
val lastSequence: Int,
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
package moe.lava.neon.core.api.gateway
|
||||
package moe.lava.neon.api.gateway
|
||||
|
||||
import kotlinx.serialization.json.JsonNull
|
||||
import kotlinx.serialization.json.decodeFromJsonElement
|
||||
import moe.lava.neon.core.api.ApiConstants
|
||||
import moe.lava.neon.api.ApiConstants.json
|
||||
|
||||
private val json = ApiConstants.json
|
||||
|
||||
fun <T : Event.Outgoing> T.pack(): Payload.Outgoing<T> {
|
||||
internal fun <T : Event.Outgoing> T.pack(): Payload.Outgoing<T> {
|
||||
val opcode: Int = when (this) {
|
||||
is Event.Heartbeat -> 1
|
||||
is Event.Identify -> 2
|
||||
|
|
@ -16,7 +14,7 @@ fun <T : Event.Outgoing> T.pack(): Payload.Outgoing<T> {
|
|||
return Payload.Outgoing(op = opcode, d = this)
|
||||
}
|
||||
|
||||
fun Payload.Unknown.asIncoming() : Payload.WithSequence {
|
||||
internal fun Payload.Unknown.asIncoming() : Payload.WithSequence {
|
||||
return when (op) {
|
||||
0 -> when (t) {
|
||||
"READY" -> decode<Event.Ready>()
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package moe.lava.neon.api.gateway.handlers
|
||||
|
||||
import moe.lava.neon.api.gateway.Event
|
||||
|
||||
sealed interface Handler<T: Event.Dispatch> {
|
||||
suspend fun handle(event: T)
|
||||
}
|
||||
65
api/rest/build.gradle.kts
Normal file
65
api/rest/build.gradle.kts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.androidLibrary)
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
androidTarget {
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.JVM_11)
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation(project(":api:shared"))
|
||||
implementation(project(":common"))
|
||||
|
||||
implementation(libs.kermit)
|
||||
implementation(libs.ktor.client.core)
|
||||
implementation(libs.ktor.client.content.negotiation)
|
||||
implementation(libs.ktor.serialization.kotlinx.json)
|
||||
}
|
||||
commonTest.dependencies {
|
||||
implementation(libs.kotlin.test)
|
||||
}
|
||||
jvmMain.dependencies {
|
||||
implementation(libs.ktor.client.okhttp)
|
||||
}
|
||||
androidMain.dependencies {
|
||||
implementation(libs.ktor.client.okhttp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring(libs.desugar)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "moe.lava.neon.api.rest"
|
||||
compileSdk = libs.versions.android.compileSdk.get().toInt()
|
||||
|
||||
defaultConfig {
|
||||
minSdk = libs.versions.android.minSdk.get().toInt()
|
||||
}
|
||||
packaging {
|
||||
resources {
|
||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
getByName("release") {
|
||||
isMinifyEnabled = false
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package moe.lava.neon.core.api
|
||||
package moe.lava.neon.api
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import io.ktor.client.HttpClient
|
||||
|
|
@ -15,10 +15,8 @@ import io.ktor.http.userAgent
|
|||
import io.ktor.serialization.kotlinx.json.json
|
||||
import io.ktor.util.appendAll
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonNamingStrategy
|
||||
import moe.lava.neon.core.api.captcha.CaptchaRequest
|
||||
import moe.lava.neon.core.api.captcha.CaptchaResponse
|
||||
import moe.lava.neon.common.captcha.CaptchaRequest
|
||||
import moe.lava.neon.common.captcha.CaptchaResponse
|
||||
|
||||
class ApiClient {
|
||||
private val logger = Logger.withTag("neon.core.api/client")
|
||||
|
|
@ -81,20 +79,3 @@ class ApiClient {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
fun buildApiClient() = HttpClient {
|
||||
expectSuccess = true
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
namingStrategy = JsonNamingStrategy.SnakeCase
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
install(WebSockets)
|
||||
install(HttpCookies)
|
||||
defaultRequest {
|
||||
url("https://discord.com/api/v9/")
|
||||
headers.appendAll(ApiConstants.baseHeaders)
|
||||
}
|
||||
}
|
||||
50
api/shared/build.gradle.kts
Normal file
50
api/shared/build.gradle.kts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.androidLibrary)
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
androidTarget {
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.JVM_11)
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring(libs.desugar)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "moe.lava.neon.api"
|
||||
compileSdk = libs.versions.android.compileSdk.get().toInt()
|
||||
|
||||
defaultConfig {
|
||||
minSdk = libs.versions.android.minSdk.get().toInt()
|
||||
}
|
||||
packaging {
|
||||
resources {
|
||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
getByName("release") {
|
||||
isMinifyEnabled = false
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,14 @@
|
|||
package moe.lava.neon.core.api
|
||||
package moe.lava.neon.api
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import java.util.Locale
|
||||
|
||||
@Suppress("ConstantLocale")
|
||||
@SuppressLint("ConstantLocale")
|
||||
internal actual val platformSuperProps = PlatformProps(
|
||||
device = android.os.Build.DEVICE,
|
||||
device = Build.DEVICE,
|
||||
// TODO: this only outputs language but not country (e.g. en instead of en-AU)
|
||||
// .toLanguageTag() is close, but returns too much junk (e.g. en-AU-u-fw-mon)
|
||||
systemLocale = Locale.getDefault().language,
|
||||
osVersion = "${android.os.Build.VERSION.SDK_INT}",
|
||||
osVersion = "${Build.VERSION.SDK_INT}",
|
||||
)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package moe.lava.neon.core.api
|
||||
package moe.lava.neon.api
|
||||
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package moe.lava.neon.core.api.structures
|
||||
package moe.lava.neon.api.objects
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.builtins.LongAsStringSerializer
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package moe.lava.neon.core.api.structures
|
||||
package moe.lava.neon.api.objects
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
package moe.lava.neon.core.api
|
||||
package moe.lava.neon.api
|
||||
|
||||
import java.util.Locale
|
||||
|
||||
// TODO
|
||||
@Suppress("ConstantLocale")
|
||||
internal actual val platformSuperProps = PlatformProps(
|
||||
device = "",
|
||||
14
common/build.gradle.kts
Normal file
14
common/build.gradle.kts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
plugins {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation(libs.kotlinx.serialization.core)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package moe.lava.neon.core.api.captcha
|
||||
package moe.lava.neon.common.captcha
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package moe.lava.neon.core.api.captcha
|
||||
package moe.lava.neon.common.captcha
|
||||
|
||||
sealed class CaptchaResponse {
|
||||
data class Success(val token: String) : CaptchaResponse()
|
||||
|
|
@ -18,9 +18,10 @@ kotlin {
|
|||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation(project(":api:gateway"))
|
||||
implementation(project(":api:rest"))
|
||||
implementation(project(":common"))
|
||||
implementation(libs.ktor.client.core)
|
||||
implementation(libs.ktor.client.content.negotiation)
|
||||
implementation(libs.ktor.client.websockets)
|
||||
implementation(libs.ktor.serialization.kotlinx.json)
|
||||
|
||||
implementation(project.dependencies.platform(libs.koin.bom))
|
||||
|
|
@ -32,12 +33,6 @@ kotlin {
|
|||
commonTest.dependencies {
|
||||
implementation(libs.kotlin.test)
|
||||
}
|
||||
jvmMain.dependencies {
|
||||
implementation(libs.ktor.client.okhttp)
|
||||
}
|
||||
androidMain.dependencies {
|
||||
implementation(libs.ktor.client.okhttp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package moe.lava.neon.core
|
|||
import com.russhwolf.settings.Settings
|
||||
import com.russhwolf.settings.nullableString
|
||||
|
||||
class AppSettings {
|
||||
internal class AppSettings {
|
||||
private val settings = Settings()
|
||||
|
||||
var fingerprint by settings.nullableString()
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
package moe.lava.neon.core.api.gateway.handlers
|
||||
|
||||
import moe.lava.neon.core.api.gateway.Event
|
||||
|
||||
sealed interface Handler<T: Event.Incoming>
|
||||
|
||||
class EventHandlers(
|
||||
val ready: ReadyHandler
|
||||
)
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
package moe.lava.neon.core.api.gateway.handlers
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import moe.lava.neon.core.api.gateway.Event
|
||||
import moe.lava.neon.core.api.gateway.ResumeProperties
|
||||
|
||||
private val logger = Logger.withTag("neon.core.api.events/ready")
|
||||
|
||||
class ReadyHandler : Handler<Event.Ready> {
|
||||
fun handle(event: Event.Ready, updateResumeProps: (ResumeProperties) -> Unit) {
|
||||
logger.i { "Received payload $event" }
|
||||
updateResumeProps(ResumeProperties(
|
||||
sessionId = event.sessionId,
|
||||
resumeGatewayUrl = event.resumeGatewayUrl,
|
||||
lastSequence = 0,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
package moe.lava.neon.core.di
|
||||
|
||||
import moe.lava.neon.api.ApiClient
|
||||
import moe.lava.neon.api.gateway.GatewayHandler
|
||||
import moe.lava.neon.core.AppSettings
|
||||
import moe.lava.neon.core.api.ApiClient
|
||||
import moe.lava.neon.core.api.gateway.GatewayHandler
|
||||
import moe.lava.neon.core.api.gateway.handlers.EventHandlers
|
||||
import moe.lava.neon.core.api.gateway.handlers.ReadyHandler
|
||||
import moe.lava.neon.core.repository.AuthRepository
|
||||
import moe.lava.neon.core.repository.CaptchaRepository
|
||||
import moe.lava.neon.core.repository.GatewayRepository
|
||||
import moe.lava.neon.core.repository.UserRepository
|
||||
import org.koin.dsl.module
|
||||
import org.koin.plugin.module.dsl.single
|
||||
|
|
@ -15,10 +15,9 @@ val coreModule = module {
|
|||
single<AppSettings>()
|
||||
|
||||
single<AuthRepository>()
|
||||
single<CaptchaRepository>()
|
||||
single<GatewayRepository>()
|
||||
single<UserRepository>()
|
||||
|
||||
single<GatewayHandler>()
|
||||
|
||||
single<ReadyHandler>()
|
||||
single<EventHandlers>()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import io.ktor.client.request.setBody
|
|||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.contentType
|
||||
import kotlinx.serialization.Serializable
|
||||
import moe.lava.neon.api.ApiClient
|
||||
import moe.lava.neon.core.AppSettings
|
||||
import moe.lava.neon.core.api.ApiClient
|
||||
|
||||
@Serializable
|
||||
private data class ExperimentResponse(
|
||||
|
|
@ -43,16 +43,15 @@ sealed class AuthResponse {
|
|||
// data class MFARequested() : AuthResponse()
|
||||
}
|
||||
|
||||
class AuthRepository(
|
||||
class AuthRepository internal constructor(
|
||||
private val settings: AppSettings,
|
||||
private val api: ApiClient,
|
||||
) {
|
||||
private val logger = Logger.withTag("neon.core.repo/auth")
|
||||
var token by settings::token
|
||||
private set
|
||||
private var token by settings::token
|
||||
private var fingerprint by settings::fingerprint
|
||||
|
||||
var fingerprint by settings::fingerprint
|
||||
private set
|
||||
val loggedIn get() = token != null
|
||||
|
||||
suspend fun login(
|
||||
email: String,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
package moe.lava.neon.core.repository
|
||||
|
||||
import moe.lava.neon.api.ApiClient
|
||||
import moe.lava.neon.common.captcha.CaptchaRequest
|
||||
import moe.lava.neon.common.captcha.CaptchaResponse
|
||||
|
||||
class CaptchaRepository(
|
||||
private val api: ApiClient,
|
||||
) {
|
||||
fun setHandler(handler: suspend (CaptchaRequest) -> CaptchaResponse) = api.setCaptchaHandler(handler)
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package moe.lava.neon.core.repository
|
||||
|
||||
import moe.lava.neon.api.gateway.GatewayHandler
|
||||
import moe.lava.neon.core.AppSettings
|
||||
|
||||
class GatewayRepository internal constructor(
|
||||
private val gateway: GatewayHandler,
|
||||
private val settings: AppSettings,
|
||||
) {
|
||||
suspend fun start(): Result<Unit> = runCatching {
|
||||
val token = settings.token
|
||||
?: throw IllegalArgumentException("Tried to start gateway with no token")
|
||||
|
||||
gateway.connect(token)
|
||||
}
|
||||
|
||||
suspend fun pause() = runCatching { gateway.disconnect() }
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ koin-bom = "4.2.0-RC1"
|
|||
koin-plugin = "0.3.0"
|
||||
kotlin = "2.3.0"
|
||||
kotlinx-coroutines = "1.10.2"
|
||||
kotlinx-serialization = "1.10.0"
|
||||
ktor = "3.4.0"
|
||||
material3 = "1.11.0-alpha02"
|
||||
material3-adaptive = "1.3.0-alpha04"
|
||||
|
|
@ -64,6 +65,8 @@ koin-test = { module = "io.insert-koin:koin-test" }
|
|||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
||||
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
||||
kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
|
||||
kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinx-serialization" }
|
||||
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
|
||||
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
|
||||
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
|
||||
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
|
||||
|
|
|
|||
|
|
@ -33,5 +33,9 @@ plugins {
|
|||
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
|
||||
}
|
||||
|
||||
include(":api:gateway")
|
||||
include(":api:rest")
|
||||
include(":api:shared")
|
||||
include(":common")
|
||||
include(":core")
|
||||
include(":ui")
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ kotlin {
|
|||
implementation(libs.ktor.client.okhttp)
|
||||
}
|
||||
commonMain.dependencies {
|
||||
implementation(project(":common"))
|
||||
implementation(project(":core"))
|
||||
implementation(libs.compose.components.resources)
|
||||
implementation(libs.compose.foundation)
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ import com.hcaptcha.sdk.HCaptchaVerifyParams
|
|||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.lava.neon.core.api.ApiClient
|
||||
import moe.lava.neon.core.api.captcha.CaptchaResponse
|
||||
import moe.lava.neon.common.captcha.CaptchaRequest
|
||||
import moe.lava.neon.common.captcha.CaptchaResponse
|
||||
|
||||
private val logger = Logger.withTag("neon.ui.app/captcha")
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ private const val EXTRA_RESULT_TOKEN = "extra_result_token"
|
|||
private const val EXTRA_RESULT_ERROR = "extra_result_error"
|
||||
|
||||
@Composable
|
||||
actual fun CaptchaBinder(api: ApiClient) {
|
||||
actual fun getCaptchaHandler(): suspend (CaptchaRequest) -> CaptchaResponse {
|
||||
val context = LocalContext.current
|
||||
val queue = MutableSharedFlow<Pair<String, CaptchaResponse>>()
|
||||
val scope = rememberCoroutineScope()
|
||||
|
|
@ -67,7 +67,7 @@ actual fun CaptchaBinder(api: ApiClient) {
|
|||
}
|
||||
}
|
||||
|
||||
api.setCaptchaHandler { captcha ->
|
||||
return { captcha ->
|
||||
val intent = Intent(context, HCaptchaActivity::class.java).apply {
|
||||
putExtra(EXTRA_SITE_KEY, captcha.captchaSitekey)
|
||||
putExtra(EXTRA_RQ_DATA, captcha.captchaRqdata)
|
||||
|
|
@ -17,6 +17,7 @@ import kotlinx.serialization.Serializable
|
|||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.modules.polymorphic
|
||||
import moe.lava.neon.core.repository.AuthRepository
|
||||
import moe.lava.neon.core.repository.CaptchaRepository
|
||||
import moe.lava.neon.ui.screens.Login
|
||||
import moe.lava.neon.ui.screens.Sample
|
||||
import moe.lava.neon.ui.screens.chat.Chat
|
||||
|
|
@ -67,12 +68,15 @@ fun App() {
|
|||
}
|
||||
|
||||
val auth: AuthRepository = koinInject()
|
||||
CaptchaBinder(koinInject())
|
||||
val captcha: CaptchaRepository = koinInject()
|
||||
captcha.setHandler(getCaptchaHandler())
|
||||
|
||||
MaterialExpressiveTheme(
|
||||
colorScheme = getColorScheme(),
|
||||
motionScheme = MotionScheme.expressive(),
|
||||
) {
|
||||
val init = if (auth.token != null) Route.Sample else Route.Login
|
||||
val init = if (auth.loggedIn) Route.Sample else Route.Login
|
||||
// val backStack = rememberNavBackStack(config, init)
|
||||
val backStack = rememberNavBackStack(config, Route.Sample)
|
||||
val threePaneStrategy = rememberThreePaneSceneStrategy<NavKey>()
|
||||
NavDisplay(
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
package moe.lava.neon.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import moe.lava.neon.core.api.ApiClient
|
||||
|
||||
@Composable
|
||||
expect fun CaptchaBinder(api: ApiClient)
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package moe.lava.neon.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import moe.lava.neon.common.captcha.CaptchaRequest
|
||||
import moe.lava.neon.common.captcha.CaptchaResponse
|
||||
|
||||
@Composable
|
||||
expect fun getCaptchaHandler(): suspend (CaptchaRequest) -> CaptchaResponse
|
||||
|
|
@ -21,8 +21,8 @@ import androidx.lifecycle.ViewModel
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.lava.neon.core.api.gateway.GatewayHandler
|
||||
import moe.lava.neon.core.repository.AuthRepository
|
||||
import moe.lava.neon.core.repository.GatewayRepository
|
||||
import moe.lava.neon.resources.Res
|
||||
import moe.lava.neon.resources.compose_multiplatform
|
||||
import moe.lava.neon.ui.Greeting
|
||||
|
|
@ -84,26 +84,24 @@ fun Sample(
|
|||
|
||||
class SampleViewModel(
|
||||
private val auth: AuthRepository,
|
||||
private val gateway: GatewayHandler,
|
||||
private val gateway: GatewayRepository,
|
||||
) : ViewModel() {
|
||||
private val logger = Logger.withTag("neon.ui.screens/Sample")
|
||||
val token get() = auth.token
|
||||
|
||||
fun connect() {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
gateway.connect()
|
||||
} catch(e: Throwable) {
|
||||
logger.e(e) { "Failed to connect to gateway: ${e.stackTraceToString()}" }
|
||||
val exception = gateway.start().exceptionOrNull()
|
||||
if (exception != null) {
|
||||
logger.e(exception) { "Failed to connect to gateway: ${exception.stackTraceToString()}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
fun disconnect() {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
gateway.disconnect()
|
||||
} catch(e: Throwable) {
|
||||
logger.e(e) { "Failed to connect to gateway: ${e.stackTraceToString()}" }
|
||||
val exception = gateway.pause().exceptionOrNull()
|
||||
if (exception != null) {
|
||||
logger.e(exception) { "Failed to disconnect from gateway: ${exception.stackTraceToString()}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
package moe.lava.neon.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import moe.lava.neon.core.api.ApiClient
|
||||
import moe.lava.neon.core.api.captcha.CaptchaResponse
|
||||
|
||||
@Composable
|
||||
// TODO
|
||||
actual fun CaptchaBinder(api: ApiClient) {
|
||||
api.setCaptchaHandler {
|
||||
CaptchaResponse.Failed(NotImplementedError())
|
||||
}
|
||||
}
|
||||
13
ui/src/jvmMain/kotlin/moe/lava/neon/ui/CaptchaHandler.jvm.kt
Normal file
13
ui/src/jvmMain/kotlin/moe/lava/neon/ui/CaptchaHandler.jvm.kt
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
package moe.lava.neon.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import moe.lava.neon.common.captcha.CaptchaRequest
|
||||
import moe.lava.neon.common.captcha.CaptchaResponse
|
||||
|
||||
@Composable
|
||||
// TODO
|
||||
actual fun getCaptchaHandler(): suspend (CaptchaRequest) -> CaptchaResponse {
|
||||
return {
|
||||
CaptchaResponse.Failed(NotImplementedError())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue