You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
129 lines
4.3 KiB
Kotlin
129 lines
4.3 KiB
Kotlin
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package com.tailscale.ipn.ui.localapi
|
|
|
|
import com.tailscale.ipn.ui.model.*
|
|
import kotlinx.serialization.decodeFromString
|
|
import kotlinx.serialization.json.Json
|
|
|
|
enum class LocalAPIEndpoint(val rawValue: String) {
|
|
Debug("debug"),
|
|
Debug_Log("debug-log"),
|
|
BugReport("bugreport"),
|
|
Prefs("prefs"),
|
|
FileTargets("file-targets"),
|
|
UploadMetrics("upload-client-metrics"),
|
|
Start("start"),
|
|
LoginInteractive("login-interactive"),
|
|
ResetAuth("reset-auth"),
|
|
Logout("logout"),
|
|
Profiles("profiles"),
|
|
ProfilesCurrent("profiles/current"),
|
|
Status("status"),
|
|
TKAStatus("tka/status"),
|
|
TKASitng("tka/sign"),
|
|
TKAVerifyDeeplink("tka/verify-deeplink"),
|
|
Ping("ping"),
|
|
Files("files"),
|
|
FilePut("file-put"),
|
|
TailFSServerAddress("tailfs/fileserver-address");
|
|
|
|
val prefix = "/localapi/v0/"
|
|
|
|
fun path(): String {
|
|
return prefix + rawValue
|
|
}
|
|
}
|
|
|
|
// Potential local and upstream errors. Error handling in localapi in the go layer
|
|
// is inconsistent but different clients already deal with that inconsistency so
|
|
// 'fixing' it will likely break other things.
|
|
//
|
|
// For now, anything that isn't an { error: "message" } will be passed along
|
|
// as UNPARSEABLE_RESPONSE. We can add additional error translation in the parseError
|
|
// method as needed.
|
|
//
|
|
// (jonathan) TODO: Audit local API for all of the possible error results and clean
|
|
// it up if possible.
|
|
enum class APIErrorVals(val rawValue: String) {
|
|
UNPARSEABLE_RESPONSE("Unparseable localAPI response"),
|
|
NOT_READY("Not Ready");
|
|
|
|
fun toError(): Error {
|
|
return Error(rawValue)
|
|
}
|
|
}
|
|
|
|
class LocalAPIRequest<T>(
|
|
val path: String,
|
|
val method: String,
|
|
val body: String? = null,
|
|
val responseHandler: (Result<T>) -> Unit,
|
|
val parser: (String) -> Unit,
|
|
) {
|
|
companion object {
|
|
val cookieLock = Any()
|
|
var cookieCounter: Int = 0
|
|
val decoder = Json { ignoreUnknownKeys = true }
|
|
|
|
fun getCookie(): String {
|
|
synchronized(cookieLock) {
|
|
cookieCounter += 1
|
|
return cookieCounter.toString()
|
|
}
|
|
}
|
|
|
|
fun status(responseHandler: StatusResponseHandler): LocalAPIRequest<IpnState.Status> {
|
|
val path = LocalAPIEndpoint.Status.path()
|
|
return LocalAPIRequest<IpnState.Status>(path, "GET", null, responseHandler) { resp ->
|
|
responseHandler(decode<IpnState.Status>(resp))
|
|
}
|
|
}
|
|
|
|
fun bugReportId(responseHandler: BugReportIdHandler): LocalAPIRequest<BugReportID> {
|
|
val path = LocalAPIEndpoint.BugReport.path()
|
|
return LocalAPIRequest<BugReportID>(path, "POST", null, responseHandler) { resp ->
|
|
responseHandler(parseString(resp))
|
|
}
|
|
}
|
|
|
|
fun prefs(responseHandler: PrefsHandler): LocalAPIRequest<Ipn.Prefs> {
|
|
val path = LocalAPIEndpoint.Prefs.path()
|
|
return LocalAPIRequest<Ipn.Prefs>(path, "GET", null, responseHandler) { resp ->
|
|
responseHandler(decode<Ipn.Prefs>(resp))
|
|
}
|
|
}
|
|
|
|
// Check if the response was a generic error
|
|
fun parseError(respData: String): Error {
|
|
try {
|
|
val err = Json.decodeFromString<Errors.GenericError>(respData)
|
|
return Error(err.error)
|
|
} catch (e: Exception) {
|
|
return Error(APIErrorVals.UNPARSEABLE_RESPONSE.toError())
|
|
}
|
|
}
|
|
|
|
// Handles responses that are raw strings. Returns an error result if the string
|
|
// is empty
|
|
fun parseString(respData: String): Result<String> {
|
|
return if (respData.length > 0) Result(respData)
|
|
else Result(APIErrorVals.UNPARSEABLE_RESPONSE.toError())
|
|
}
|
|
|
|
// Attempt to decode the response into the expected type. If that fails, then try
|
|
// parsing as an error.
|
|
inline fun <reified T> decode(respData: String): Result<T> {
|
|
try {
|
|
val message = decoder.decodeFromString<T>(respData)
|
|
return Result(message)
|
|
} catch (e: Exception) {
|
|
return Result(parseError(respData))
|
|
}
|
|
}
|
|
}
|
|
|
|
val cookie: String = getCookie()
|
|
}
|