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.
tailscale-android/android_legacy/src/main/java/com/tailscale/ipn/ui/localapi/LocalAPIClient.kt

150 lines
5.2 KiB
Kotlin

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package com.tailscale.ipn.ui.localapi
import android.util.Log
import com.tailscale.ipn.ui.model.*
import kotlinx.coroutines.*
// A response from the echo endpoint.
typealias StatusResponseHandler = (Result<IpnState.Status>) -> Unit
typealias BugReportIdHandler = (Result<BugReportID>) -> Unit
typealias PrefsHandler = (Result<Ipn.Prefs>) -> Unit
class LocalApiClient {
constructor() {
Log.d("LocalApiClient", "LocalApiClient created")
}
// Perform a request to the local API in the go backend. This is
// the primary JNI method for servicing a localAPI call. This
// is GUARANTEED to call back into onResponse with the response
// from the backend with a matching cookie.
// @see cmd/localapiclient/localapishim.go
//
// request: The path to the localAPI endpoint.
// method: The HTTP method to use.
// body: The body of the request.
// cookie: A unique identifier for this request. This is used map responses to
// the corresponding request. Cookies must be unique for each request.
external fun doRequest(request: String, method: String, body: String, cookie: String)
fun <T> executeRequest(request: LocalAPIRequest<T>) {
Log.d("LocalApiClient", "Executing request:${request.method}:${request.path}")
addRequest(request)
// The jni handler will treat the empty string in the body as null.
val body = request.body ?: ""
doRequest(request.path, request.method, body, request.cookie)
}
// This is called from the JNI layer to publish localAPIResponses. This should execute on the
// same thread that called doRequest.
fun onResponse(response: String, cookie: String) {
val request = requests[cookie]
if (request != null) {
Log.d("LocalApiClient", "Reponse for request:${request.path} cookie:${request.cookie}")
// The response handler will invoked internally by the request parser
request.parser(response)
removeRequest(cookie)
} else {
Log.e("LocalApiClient", "Received response for unknown request: ${cookie}")
}
}
// Tracks in-flight requests and their callback handlers by cookie. This should
// always be manipulated via the addRequest and removeRequest methods.
private var requests = HashMap<String, LocalAPIRequest<*>>()
private var requestLock = Any()
fun addRequest(request: LocalAPIRequest<*>) {
synchronized(requestLock) { requests[request.cookie] = request }
}
fun removeRequest(cookie: String) {
synchronized(requestLock) { requests.remove(cookie) }
}
// localapi Invocations
fun getStatus(responseHandler: StatusResponseHandler) {
val req = LocalAPIRequest.status(responseHandler)
executeRequest<IpnState.Status>(req)
}
fun getBugReportId(responseHandler: BugReportIdHandler) {
val req = LocalAPIRequest.bugReportId(responseHandler)
executeRequest<BugReportID>(req)
}
fun getPrefs(responseHandler: PrefsHandler) {
val req = LocalAPIRequest.prefs(responseHandler)
executeRequest<Ipn.Prefs>(req)
}
// (jonathan) TODO: A (likely) exhaustive list of localapi endpoints required for
// a fully functioning client. This is a work in progress and will be updated
// See: corp/xcode/Shared/LocalAPIClient.swift for the various verbs, parameters,
// and body contents for each endpoint. Endpoints are defined in LocalAPIEndpoint
//
// fetchFileTargets
// sendFiles
// getWaitingFiles
// recieveWaitingFile
// inidicateFileRecieved
// debug
// debugLog
// uploadClientMetrics
// start
// startLoginInteractive
// logout
// profiles
// currentProfile
// addProfile
// switchProfile
// deleteProfile
// tailnetLocalStatus
// signNode
// verifyDeepling
// ping
// setTailFSFileServerAddress
// Run some tests to validate the APIs work before we have anything
// that calls them. This runs after a short delay to avoid not-ready
// errors
// (jonathan) TODO: Do we need some kind of "onReady" callback?
// (jonathan) TODO: Remove these we're further along
fun runAPITests() = runBlocking {
delay(5000L)
getStatus { result ->
if (result.failed) {
Log.e("LocalApiClient", "Error getting status: ${result.error}")
} else {
val status = result.success
Log.d("LocalApiClient", "Got status: ${status}")
}
}
getBugReportId { result ->
if (result.failed) {
Log.e("LocalApiClient", "Error getting bug report id: ${result.error}")
} else {
val bugReportId = result.success
Log.d("LocalApiClient", "Got bug report id: ${bugReportId}")
}
}
getPrefs { result ->
if (result.failed) {
Log.e("LocalApiClient", "Error getting prefs: ${result.error}")
} else {
val prefs = result.success
Log.d("LocalApiClient", "Got prefs: ${prefs}")
}
}
}
}