fix: guard global ABSDK session state

This commit is contained in:
2026-08-23 07:05:43 +08:00
parent c2fb63e241
commit 0e9b7dfd4b
7 changed files with 80 additions and 11 deletions
@@ -208,12 +208,17 @@ class AbsdkBridge internal constructor(
private inline fun <T> withActiveSession(
profileId: ProviderProfileId,
block: () -> DomainResult<T>,
): DomainResult<T> = if (hasActiveSession(profileId)) {
block()
} else {
DomainResult.Failure(
DomainError.TokenInvalid(detail = "No active ABSDK session for this provider"),
)
): DomainResult<T> {
if (!hasActiveSession(profileId)) {
return DomainResult.Failure(
DomainError.TokenInvalid(detail = "No active ABSDK session for this provider"),
)
}
return block().also { result ->
if (result is DomainResult.Failure && result.error is DomainError.TokenInvalid) {
activeSession.value = null
}
}
}
private fun hasActiveSession(profileId: ProviderProfileId): Boolean =
@@ -299,10 +304,16 @@ class AbsdkBridge internal constructor(
private const val DISABLED_STATUS = "0"
private const val ENABLED_STATUS = "1"
fun create(): AbsdkBridge = AbsdkBridge(
client = VendorAbsdkClient.create(),
dispatcher = Dispatchers.IO,
)
@OptIn(CleartextAbsdkTransport::class)
private val processInstance: AbsdkBridge by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
AbsdkBridge(
client = VendorAbsdkClient.create(),
dispatcher = Dispatchers.IO,
)
}
@CleartextAbsdkTransport
fun create(): AbsdkBridge = processInstance
}
}
@@ -0,0 +1,22 @@
package com.flagship.abox.manager.absdk.bridge
/**
* Marks construction of the original ABSDK 0630 client, whose base endpoint is hardcoded to HTTP.
*
* Opting in acknowledges that transport confidentiality and integrity are not provided by the
* vendor JAR. App integration must remain disabled until the deployment has an approved transport
* control and the security limitation is presented to the responsible integrator.
*/
@RequiresOptIn(
message = "ABSDK 0630 uses a hardcoded cleartext HTTP transport. " +
"Do not enable it without an approved transport control.",
level = RequiresOptIn.Level.ERROR,
)
@Retention(AnnotationRetention.BINARY)
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.CONSTRUCTOR,
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY,
)
annotation class CleartextAbsdkTransport
@@ -4,6 +4,7 @@ import com.dhc.absdk.ABRet
import com.dhc.absdk.ABSDK
import com.dhc.absdk.IrInfo
@CleartextAbsdkTransport
internal class VendorAbsdkClient private constructor(
private val sdk: ABSDK,
) : AbsdkClient {
@@ -0,0 +1,12 @@
package com.flagship.abox.manager.absdk.bridge
import org.junit.Assert.assertSame
import org.junit.Test
@OptIn(CleartextAbsdkTransport::class)
class AbsdkFactoryTest {
@Test
fun productionFactoryReturnsOneProcessInstance() {
assertSame(AbsdkBridge.create(), AbsdkBridge.create())
}
}
@@ -91,6 +91,25 @@ class AbsdkSessionBridgeTest {
assertNull(bridge.observeSession(profile).first())
}
@Test
fun deviceTokenErrorClearsSessionAndBlocksFurtherCalls() = runTest {
val client = ScriptedAbsdkClient().apply {
socketStatusResponses = ArrayDeque(
listOf(ScriptedAbsdkClient.failure("10002", "expired")),
)
}
val bridge = bridge(client)
bridge.login(profile, credentials)
val first = bridge.getStatus(profile, DeviceName("socket"))
assertTrue((first as DomainResult.Failure).error is DomainError.TokenInvalid)
assertNull(bridge.observeSession(profile).first())
val second = bridge.getStatus(profile, DeviceName("socket"))
assertTrue((second as DomainResult.Failure).error is DomainError.TokenInvalid)
assertEquals(listOf("login:demo", "socket-status:socket"), client.calls)
}
private fun bridge(client: ScriptedAbsdkClient): AbsdkBridge =
AbsdkBridge(client, Dispatchers.Unconfined)
}