feat(companion): add mobile runtime skeleton shell templates
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
package flynn.companion
|
||||
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.ScheduledFuture
|
||||
import java.util.concurrent.TimeUnit
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
interface CompanionTransport {
|
||||
fun connect(url: String, token: String?)
|
||||
fun disconnect()
|
||||
fun send(rawMessage: String)
|
||||
}
|
||||
|
||||
data class CompanionStatusPayload(
|
||||
val platform: String,
|
||||
val appVersion: String? = null,
|
||||
val statusText: String? = null,
|
||||
val batteryPct: Double? = null
|
||||
)
|
||||
|
||||
data class CompanionLocationPayload(
|
||||
val latitude: Double,
|
||||
val longitude: Double,
|
||||
val source: String = "device"
|
||||
)
|
||||
|
||||
class AndroidCompanionRuntime(
|
||||
private val bootstrap: CompanionBootstrap,
|
||||
private val transport: CompanionTransport
|
||||
) {
|
||||
private var nextRequestId: Int = 1
|
||||
private val scheduler = Executors.newSingleThreadScheduledExecutor()
|
||||
private var heartbeatFuture: ScheduledFuture<*>? = null
|
||||
|
||||
fun connect() {
|
||||
transport.connect(bootstrap.gateway.url, bootstrap.gateway.token)
|
||||
}
|
||||
|
||||
fun disconnect() {
|
||||
heartbeatFuture?.cancel(true)
|
||||
heartbeatFuture = null
|
||||
transport.disconnect()
|
||||
}
|
||||
|
||||
fun registerNode() {
|
||||
val params = JSONObject()
|
||||
.put("nodeId", bootstrap.node.nodeId)
|
||||
.put("role", bootstrap.node.role)
|
||||
.put("platform", bootstrap.node.platform)
|
||||
.put("capabilities", JSONArray(bootstrap.node.capabilities))
|
||||
sendRpc("node.register", params)
|
||||
}
|
||||
|
||||
fun publishStatus(status: CompanionStatusPayload) {
|
||||
val statusJson = JSONObject()
|
||||
.put("platform", status.platform)
|
||||
status.appVersion?.let { statusJson.put("appVersion", it) }
|
||||
status.statusText?.let { statusJson.put("statusText", it) }
|
||||
status.batteryPct?.let { statusJson.put("batteryPct", it) }
|
||||
sendRpc(
|
||||
"node.status.set",
|
||||
JSONObject()
|
||||
.put("nodeId", bootstrap.node.nodeId)
|
||||
.put("status", statusJson)
|
||||
)
|
||||
}
|
||||
|
||||
fun publishLocation(location: CompanionLocationPayload) {
|
||||
sendRpc(
|
||||
"node.location.set",
|
||||
JSONObject()
|
||||
.put("nodeId", bootstrap.node.nodeId)
|
||||
.put(
|
||||
"location",
|
||||
JSONObject()
|
||||
.put("latitude", location.latitude)
|
||||
.put("longitude", location.longitude)
|
||||
.put("source", location.source)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun registerPushToken(token: String, topic: String? = null) {
|
||||
val params = JSONObject()
|
||||
.put("nodeId", bootstrap.node.nodeId)
|
||||
.put("provider", "fcm")
|
||||
.put("token", token)
|
||||
topic?.let { params.put("topic", it) }
|
||||
sendRpc("node.push_token.set", params)
|
||||
}
|
||||
|
||||
fun sendHandoff(message: String, sessionId: String? = null) {
|
||||
val params = JSONObject()
|
||||
.put("message", message)
|
||||
.put("awaitResponse", true)
|
||||
sessionId?.let { params.put("sessionId", it) }
|
||||
sendRpc("agent.send", params)
|
||||
}
|
||||
|
||||
fun startHeartbeatLoop(statusFactory: () -> CompanionStatusPayload) {
|
||||
heartbeatFuture?.cancel(true)
|
||||
val intervalSeconds = bootstrap.runtime.heartbeatSeconds.coerceAtLeast(1).toLong()
|
||||
heartbeatFuture = scheduler.scheduleAtFixedRate(
|
||||
{ publishStatus(statusFactory()) },
|
||||
0L,
|
||||
intervalSeconds,
|
||||
TimeUnit.SECONDS
|
||||
)
|
||||
}
|
||||
|
||||
private fun sendRpc(method: String, params: JSONObject) {
|
||||
val request = JSONObject()
|
||||
.put("jsonrpc", "2.0")
|
||||
.put("id", nextRequestId++)
|
||||
.put("method", method)
|
||||
.put("params", params)
|
||||
transport.send(request.toString())
|
||||
}
|
||||
}
|
||||
|
||||
// Generated for node: android-reference-shell (android)
|
||||
Reference in New Issue
Block a user