Files
coop-mobile/shared/src/commonMain/kotlin/su/reya/coop/blossom/BlossomClient.kt
T
2026-06-08 16:40:56 +07:00

84 lines
2.6 KiB
Kotlin

package su.reya.coop.blossom
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.header
import io.ktor.client.request.put
import io.ktor.client.request.setBody
import io.ktor.http.HeaderValue
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import io.ktor.utils.io.core.toByteArray
import okio.ByteString.Companion.toByteString
import rust.nostr.sdk.AsyncNostrSigner
import rust.nostr.sdk.EventBuilder
import rust.nostr.sdk.Timestamp
import kotlin.io.encoding.Base64
import kotlin.time.Duration
class BlossomClient(
val url: String,
val client: HttpClient,
) {
suspend fun upload(
file: ByteArray,
contentType: String? = null,
signer: AsyncNostrSigner? = null
): BlobDescriptor? {
val url = "$url/upload"
val hash = file.toByteString().sha256().hex()
val fileHashes = listOf(hash)
val res = client.put(url) {
// Set body
setBody(file)
// Set the content type if provided
contentType?.let {
header(HttpHeaders.ContentType, it)
}
signer?.let {
val defaultAuth = defaultAuth(
action = BlossomAuthorizationVerb.Upload,
defaultContent = "Blossom upload authorization",
defaultScope = BlossomAuthorizationScope.BlobSha256Hashes(fileHashes)
)
val authHeader = buildAuthHeader(it, defaultAuth)
header(HttpHeaders.Authorization, authHeader.value)
}
}
return when (res.status) {
HttpStatusCode.OK -> res.body<BlobDescriptor>()
else -> {
throw Exception("Failed to upload file: ${res.status}")
}
}
}
fun defaultAuth(
action: BlossomAuthorizationVerb,
defaultContent: String,
defaultScope: BlossomAuthorizationScope
): BlossomAuthorization {
val expiration = Timestamp.now().addDuration(Duration.parse("300s"))
return BlossomAuthorization(
content = defaultContent,
expiration = expiration,
action = action,
scope = defaultScope
)
}
suspend fun buildAuthHeader(
signer: AsyncNostrSigner,
authz: BlossomAuthorization
): HeaderValue {
val authEvent = EventBuilder.blossomAuth(authz).finalizeAsync(signer)
val encodedAuth = Base64.encode(authEvent.asJson().toByteArray())
val value = "Nostr $encodedAuth"
return HeaderValue(value)
}
}