Skip to content

Commit

Permalink
- Fixes caching bug of NIP-96 servers
Browse files Browse the repository at this point in the history
- Allow NIP-96 setups with relative URLs
- Displays error messages if the server has sent in the body
- Adds a test case for both
  • Loading branch information
vitorpamplona committed Aug 30, 2024
1 parent 388ccdb commit aeaddf7
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 8 deletions.
24 changes: 24 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,12 @@ class ImageUploadTesting {
testBase(Nip96MediaServers.ServerName("nostpic.com", "https://nostpic.com"))
}

@Test(expected = RuntimeException::class)
fun testSprovoostNl() =
runBlocking {
testBase(Nip96MediaServers.ServerName("sprovoost.nl", "https://img.sprovoost.nl/"))
}

@Test()
@Ignore("Not Working anymore")
fun testNostrOnch() =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.vitorpamplona.ammolite.service.HttpClientManager
import kotlinx.coroutines.CancellationException
import okhttp3.Request
import java.net.URI
import java.net.URL

object Nip96MediaServers {
val DEFAULT =
Expand Down Expand Up @@ -76,10 +78,26 @@ class Nip96Retriever {
val mediaTransformations: Map<MimeType, Array<String>> = emptyMap(),
)

fun parse(body: String): ServerInfo {
fun parse(
baseUrl: String,
body: String,
): ServerInfo {
val mapper =
jacksonObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
return mapper.readValue(body, ServerInfo::class.java)
val serverInfo = mapper.readValue(body, ServerInfo::class.java)

return serverInfo.copy(
apiUrl = makeAbsoluteIfRelativeUrl(baseUrl, serverInfo.apiUrl),
downloadUrl = serverInfo.downloadUrl?.let { makeAbsoluteIfRelativeUrl(baseUrl, it) },
delegatedToUrl = serverInfo.delegatedToUrl?.let { makeAbsoluteIfRelativeUrl(baseUrl, it) },
tosUrl = serverInfo.tosUrl?.let { makeAbsoluteIfRelativeUrl(baseUrl, it) },
plans =
serverInfo.plans.mapValues { u ->
u.value.copy(
url = u.value.url?.let { makeAbsoluteIfRelativeUrl(baseUrl, it) },
)
},
)
}

suspend fun loadInfo(baseUrl: String): ServerInfo {
Expand All @@ -98,7 +116,7 @@ class Nip96Retriever {
val body = it.body.string()
try {
if (it.isSuccessful) {
return parse(body)
return parse(baseUrl, body)
} else {
throw RuntimeException(
"Resulting Message from $baseUrl is an error: ${response.code} ${response.message}",
Expand All @@ -117,3 +135,18 @@ class Nip96Retriever {
typealias PlanName = String

typealias MimeType = String

fun makeAbsoluteIfRelativeUrl(
baseUrl: String,
potentialyRelativeUrl: String,
): String =
try {
val apiUrl = URI(potentialyRelativeUrl)
if (apiUrl.isAbsolute) {
potentialyRelativeUrl
} else {
URL(URL(baseUrl), potentialyRelativeUrl).toString()
}
} catch (e: Exception) {
potentialyRelativeUrl
}
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,26 @@ class Nip96Uploader(
}
}
} else {
val msg = response.body.string()

val errorMessage =
try {
val tree = jacksonObjectMapper().readTree(msg)
val status = tree.get("status")?.asText()
val message = tree.get("message")?.asText()
if (status == "error" && message != null) {
message
} else {
null
}
} catch (e: Exception) {
null
}

val explanation = HttpStatusMessages.resourceIdFor(response.code)
if (explanation != null) {
if (errorMessage != null) {
throw RuntimeException(stringRes(context, R.string.failed_to_upload_with_message, errorMessage))
} else if (explanation != null) {
throw RuntimeException(stringRes(context, R.string.failed_to_upload_with_message, stringRes(context, explanation)))
} else {
throw RuntimeException(stringRes(context, R.string.failed_to_upload_with_message, response.code))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1654,7 +1654,17 @@ fun ImageVideoDescription(
fileServers.map { TitleExplainer(it.server.name, it.server.baseUrl) }.toImmutableList()
}

var selectedServer by remember { mutableStateOf(ServerOption(defaultServer, false)) }
var selectedServer by remember {
mutableStateOf(
ServerOption(
fileServers
.firstOrNull { it.server == defaultServer }
?.server
?: fileServers[0].server,
false,
),
)
}
var message by remember { mutableStateOf("") }
var sensitiveContent by remember { mutableStateOf(false) }

Expand Down Expand Up @@ -1778,7 +1788,7 @@ fun ImageVideoDescription(
label = stringRes(id = R.string.file_server),
placeholder =
fileServers
.firstOrNull { it.server == accountViewModel.account.settings.defaultFileServer }
.firstOrNull { it.server == defaultServer }
?.server
?.name
?: fileServers[0].server.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ fun TextSpinner(
val focusRequester = remember { FocusRequester() }
val interactionSource = remember { MutableInteractionSource() }
var optionsShowing by remember { mutableStateOf(false) }
var currentText by remember { mutableStateOf(placeholder) }
var currentText by remember(placeholder) { mutableStateOf(placeholder) }

Box(
modifier = modifier,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,26 @@ import junit.framework.TestCase.assertEquals
import org.junit.Test

class Nip96Test {
val relativeUrlTest =
"""
{
"api_url": "/n96",
"download_url": "/",
"content_types": [
"image/*",
"video/*",
"audio/*"
],
"plans": {
"free": {
"name": "",
"is_nip98_required": true,
"max_byte_size": 5000000000
}
}
}
"""

val json =
"""
{
Expand Down Expand Up @@ -116,7 +136,7 @@ class Nip96Test {

@Test()
fun parseNostrBuild() {
val info = Nip96Retriever().parse(json)
val info = Nip96Retriever().parse("https://nostr.build", json)

assertEquals("https://nostr.build/api/v2/nip96/upload", info.apiUrl)
assertEquals("https://media.nostr.build", info.downloadUrl)
Expand All @@ -142,4 +162,14 @@ class Nip96Test {
assertEquals(26843545600L, info.plans["creator"]?.maxByteSize)
assertEquals(10737418240L, info.plans["professional"]?.maxByteSize)
}

@Test()
fun parseRelativeUrls() {
val info = Nip96Retriever().parse("https://test.com", relativeUrlTest)

assertEquals("https://test.com/n96", info.apiUrl)
assertEquals("https://test.com/", info.downloadUrl)
assertEquals(null, info.tosUrl)
assertEquals(null, info.delegatedToUrl)
}
}

0 comments on commit aeaddf7

Please sign in to comment.