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.
131 lines
4.7 KiB
Kotlin
131 lines
4.7 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.BugReportID
|
|
import com.tailscale.ipn.ui.model.Ipn
|
|
import com.tailscale.ipn.ui.model.IpnLocal
|
|
import com.tailscale.ipn.ui.model.IpnState
|
|
import kotlinx.coroutines.CoroutineScope
|
|
import kotlinx.coroutines.flow.MutableStateFlow
|
|
import kotlinx.coroutines.flow.StateFlow
|
|
import kotlinx.coroutines.flow.first
|
|
import kotlinx.coroutines.launch
|
|
import java.util.concurrent.ConcurrentHashMap
|
|
|
|
typealias StatusResponseHandler = (Result<IpnState.Status>) -> Unit
|
|
typealias BugReportIdHandler = (Result<BugReportID>) -> Unit
|
|
typealias PrefsHandler = (Result<Ipn.Prefs>) -> Unit
|
|
|
|
class LocalApiClient(private val scope: CoroutineScope) {
|
|
|
|
companion object {
|
|
private val _isReady = MutableStateFlow(false)
|
|
val isReady: StateFlow<Boolean> = _isReady
|
|
|
|
// Called by the backend when the localAPI is ready to accept requests.
|
|
@JvmStatic
|
|
@Suppress("unused")
|
|
fun onReady() {
|
|
_isReady.value = true
|
|
Log.d("LocalApiClient", "LocalApiClient is ready")
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
private external fun doRequest(request: String, method: String, body: ByteArray?, cookie: String)
|
|
|
|
private fun <T> executeRequest(request: LocalAPIRequest<T>) {
|
|
scope.launch {
|
|
isReady.first { it }
|
|
Log.d("LocalApiClient", "Executing request:${request.method}:${request.path}")
|
|
requests[request.cookie] = request
|
|
doRequest(request.path, request.method, request.body, request.cookie)
|
|
}
|
|
}
|
|
|
|
// This is called from the JNI layer to publish localAPIResponses. This should execute on the
|
|
// same thread that called doRequest.
|
|
@Suppress("unused")
|
|
fun onResponse(response: ByteArray, cookie: String) {
|
|
requests.remove(cookie)?.let { request ->
|
|
Log.d("LocalApiClient", "Reponse for request:${request.path} cookie:${request.cookie}")
|
|
// The response handler will invoked internally by the request parser
|
|
request.parser(response)
|
|
} ?: { 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 = ConcurrentHashMap<String, LocalAPIRequest<*>>()
|
|
|
|
// localapi Invocations
|
|
|
|
fun getStatus(responseHandler: StatusResponseHandler) {
|
|
val req = LocalAPIRequest.status(responseHandler)
|
|
executeRequest(req)
|
|
}
|
|
|
|
fun getBugReportId(responseHandler: BugReportIdHandler) {
|
|
val req = LocalAPIRequest.bugReportId(responseHandler)
|
|
executeRequest(req)
|
|
}
|
|
|
|
fun getPrefs(responseHandler: PrefsHandler) {
|
|
val req = LocalAPIRequest.prefs(responseHandler)
|
|
executeRequest(req)
|
|
}
|
|
|
|
fun getProfiles(responseHandler: (Result<List<IpnLocal.LoginProfile>>) -> Unit) {
|
|
val req = LocalAPIRequest.profiles(responseHandler)
|
|
executeRequest(req)
|
|
}
|
|
|
|
fun getCurrentProfile(responseHandler: (Result<IpnLocal.LoginProfile>) -> Unit) {
|
|
val req = LocalAPIRequest.currentProfile(responseHandler)
|
|
executeRequest(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
|
|
init {
|
|
Log.d("LocalApiClient", "LocalApiClient created")
|
|
}
|
|
}
|