aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/apis/UrsaManager.kt
blob: cee6904952e645d21e1961ad8c9e67ce1cb1b556 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package moe.nea.firmament.apis

import java.net.URI
import java.net.http.HttpResponse
import java.time.Duration
import java.time.Instant
import java.util.OptionalLong
import java.util.UUID
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.future.await
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.withContext
import kotlinx.serialization.DeserializationStrategy
import kotlin.jvm.optionals.getOrNull
import net.minecraft.client.MinecraftClient
import moe.nea.firmament.Firmament
import moe.nea.firmament.util.net.HttpUtil

object UrsaManager {
	private data class Token(
		val validUntil: Instant,
		val token: String,
		val obtainedFrom: String,
	) {
		fun isValid(host: String) = Instant.now().plusSeconds(60) < validUntil && obtainedFrom == host
	}

	private var currentToken: Token? = null
	private val lock = Mutex()
	private fun getToken(host: String) = currentToken?.takeIf { it.isValid(host) }

	suspend fun <T> request(path: List<String>, bodyHandler: HttpResponse.BodyHandler<T>): T {
		var didLock = false
		try {
			val host = "ursa.notenoughupdates.org"
			var token = getToken(host)
			if (token == null) {
				lock.lock()
				didLock = true
				token = getToken(host)
			}
			var url = URI.create("https://$host")
			for (segment in path) {
				url = url.resolve(segment)
			}
			val request = HttpUtil.request(url)
			if (token == null) {
				withContext(Dispatchers.IO) {
					val mc = MinecraftClient.getInstance()
					val serverId = UUID.randomUUID().toString()
					mc.sessionService.joinServer(mc.session.uuidOrNull, mc.session.accessToken, serverId)
					request.header("x-ursa-username", mc.session.username)
					request.header("x-ursa-serverid", serverId)
				}
			} else {
				request.header("x-ursa-token", token.token)
			}
			val response = request.execute(bodyHandler)
				.await()
			val savedToken = response.headers().firstValue("x-ursa-token").getOrNull()
			if (savedToken != null) {
				val validUntil = response.headers().firstValueAsLong("x-ursa-expires").orNull()?.let { Instant.ofEpochMilli(it) }
					?: (Instant.now() + Duration.ofMinutes(55))
				currentToken = Token(validUntil, savedToken, host)
			}
			if (response.statusCode() != 200) {
				Firmament.logger.error("Failed to contact ursa minor: ${response.statusCode()}")
			}
			return response.body()
		} finally {
			if (didLock)
				lock.unlock()
		}
	}
}

private fun OptionalLong.orNull(): Long? {
	if (this.isPresent)return null
	return this.asLong
}