Bridge: advertise node capabilities
This commit is contained in:
@@ -3,6 +3,15 @@ import Foundation
|
||||
import Network
|
||||
import OSLog
|
||||
|
||||
struct BridgeNodeInfo: Sendable {
|
||||
var nodeId: String
|
||||
var displayName: String?
|
||||
var platform: String?
|
||||
var version: String?
|
||||
var remoteAddress: String?
|
||||
var caps: [String]?
|
||||
}
|
||||
|
||||
actor BridgeConnectionHandler {
|
||||
private let connection: NWConnection
|
||||
private let logger: Logger
|
||||
@@ -38,7 +47,7 @@ actor BridgeConnectionHandler {
|
||||
var serverName: String
|
||||
var resolveAuth: @Sendable (BridgeHello) async -> AuthResult
|
||||
var handlePair: @Sendable (BridgePairRequest) async -> PairResult
|
||||
var onAuthenticated: (@Sendable (String) async -> Void)?
|
||||
var onAuthenticated: (@Sendable (BridgeNodeInfo) async -> Void)?
|
||||
var onEvent: (@Sendable (String, BridgeEventFrame) async -> Void)?
|
||||
var onRequest: (@Sendable (String, BridgeRPCRequest) async -> BridgeRPCResponse)?
|
||||
}
|
||||
@@ -46,7 +55,7 @@ actor BridgeConnectionHandler {
|
||||
func run(
|
||||
resolveAuth: @escaping @Sendable (BridgeHello) async -> AuthResult,
|
||||
handlePair: @escaping @Sendable (BridgePairRequest) async -> PairResult,
|
||||
onAuthenticated: (@Sendable (String) async -> Void)? = nil,
|
||||
onAuthenticated: (@Sendable (BridgeNodeInfo) async -> Void)? = nil,
|
||||
onDisconnected: (@Sendable (String) async -> Void)? = nil,
|
||||
onEvent: (@Sendable (String, BridgeEventFrame) async -> Void)? = nil,
|
||||
onRequest: (@Sendable (String, BridgeRPCRequest) async -> BridgeRPCResponse)? = nil) async
|
||||
@@ -125,11 +134,19 @@ actor BridgeConnectionHandler {
|
||||
{
|
||||
do {
|
||||
let hello = try self.decoder.decode(BridgeHello.self, from: data)
|
||||
self.nodeId = hello.nodeId
|
||||
let nodeId = hello.nodeId.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
self.nodeId = nodeId
|
||||
let result = await context.resolveAuth(hello)
|
||||
await self.handleAuthResult(result, serverName: context.serverName)
|
||||
if case .ok = result, let nodeId = self.nodeId {
|
||||
await context.onAuthenticated?(nodeId)
|
||||
if case .ok = result {
|
||||
await context.onAuthenticated?(
|
||||
BridgeNodeInfo(
|
||||
nodeId: nodeId,
|
||||
displayName: hello.displayName,
|
||||
platform: hello.platform,
|
||||
version: hello.version,
|
||||
remoteAddress: self.remoteAddressString(),
|
||||
caps: hello.caps))
|
||||
}
|
||||
} catch {
|
||||
await self.sendError(code: "INVALID_REQUEST", message: error.localizedDescription)
|
||||
@@ -142,18 +159,27 @@ actor BridgeConnectionHandler {
|
||||
{
|
||||
do {
|
||||
let req = try self.decoder.decode(BridgePairRequest.self, from: data)
|
||||
self.nodeId = req.nodeId
|
||||
let nodeId = req.nodeId.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
self.nodeId = nodeId
|
||||
let enriched = BridgePairRequest(
|
||||
type: req.type,
|
||||
nodeId: req.nodeId,
|
||||
nodeId: nodeId,
|
||||
displayName: req.displayName,
|
||||
platform: req.platform,
|
||||
version: req.version,
|
||||
caps: req.caps,
|
||||
remoteAddress: self.remoteAddressString())
|
||||
let result = await context.handlePair(enriched)
|
||||
await self.handlePairResult(result, serverName: context.serverName)
|
||||
if case .ok = result, let nodeId = self.nodeId {
|
||||
await context.onAuthenticated?(nodeId)
|
||||
if case .ok = result {
|
||||
await context.onAuthenticated?(
|
||||
BridgeNodeInfo(
|
||||
nodeId: nodeId,
|
||||
displayName: enriched.displayName,
|
||||
platform: enriched.platform,
|
||||
version: enriched.version,
|
||||
remoteAddress: enriched.remoteAddress,
|
||||
caps: enriched.caps))
|
||||
}
|
||||
} catch {
|
||||
await self.sendError(code: "INVALID_REQUEST", message: error.localizedDescription)
|
||||
|
||||
@@ -13,6 +13,7 @@ actor BridgeServer {
|
||||
private var isRunning = false
|
||||
private var store: PairedNodesStore?
|
||||
private var connections: [String: BridgeConnectionHandler] = [:]
|
||||
private var nodeInfoById: [String: BridgeNodeInfo] = [:]
|
||||
private var presenceTasks: [String: Task<Void, Never>] = [:]
|
||||
private var chatSubscriptions: [String: Set<String>] = [:]
|
||||
private var gatewayPushTask: Task<Void, Never>?
|
||||
@@ -73,7 +74,7 @@ actor BridgeServer {
|
||||
}
|
||||
|
||||
private func handle(connection: NWConnection) async {
|
||||
let handler = BridgeConnectionHandler(connection: connection, logger: self.logger)
|
||||
let handler = BridgeConnectionHandler(connection: connection, logger: self.logger)
|
||||
await handler.run(
|
||||
resolveAuth: { [weak self] hello in
|
||||
await self?.authorize(hello: hello) ?? .error(code: "UNAVAILABLE", message: "bridge unavailable")
|
||||
@@ -81,8 +82,8 @@ actor BridgeServer {
|
||||
handlePair: { [weak self] request in
|
||||
await self?.pair(request: request) ?? .error(code: "UNAVAILABLE", message: "bridge unavailable")
|
||||
},
|
||||
onAuthenticated: { [weak self] nodeId in
|
||||
await self?.registerConnection(handler: handler, nodeId: nodeId)
|
||||
onAuthenticated: { [weak self] node in
|
||||
await self?.registerConnection(handler: handler, node: node)
|
||||
},
|
||||
onDisconnected: { [weak self] nodeId in
|
||||
await self?.unregisterConnection(nodeId: nodeId)
|
||||
@@ -112,10 +113,22 @@ actor BridgeServer {
|
||||
Array(self.connections.keys).sorted()
|
||||
}
|
||||
|
||||
private func registerConnection(handler: BridgeConnectionHandler, nodeId: String) async {
|
||||
self.connections[nodeId] = handler
|
||||
await self.beaconPresence(nodeId: nodeId, reason: "connect")
|
||||
self.startPresenceTask(nodeId: nodeId)
|
||||
func connectedNodes() -> [BridgeNodeInfo] {
|
||||
self.nodeInfoById.values.sorted { a, b in
|
||||
(a.displayName ?? a.nodeId) < (b.displayName ?? b.nodeId)
|
||||
}
|
||||
}
|
||||
|
||||
func pairedNodes() async -> [PairedNode] {
|
||||
guard let store = self.store else { return [] }
|
||||
return await store.all()
|
||||
}
|
||||
|
||||
private func registerConnection(handler: BridgeConnectionHandler, node: BridgeNodeInfo) async {
|
||||
self.connections[node.nodeId] = handler
|
||||
self.nodeInfoById[node.nodeId] = node
|
||||
await self.beaconPresence(nodeId: node.nodeId, reason: "connect")
|
||||
self.startPresenceTask(nodeId: node.nodeId)
|
||||
self.ensureGatewayPushTask()
|
||||
}
|
||||
|
||||
@@ -123,6 +136,7 @@ actor BridgeServer {
|
||||
await self.beaconPresence(nodeId: nodeId, reason: "disconnect")
|
||||
self.stopPresenceTask(nodeId: nodeId)
|
||||
self.connections.removeValue(forKey: nodeId)
|
||||
self.nodeInfoById.removeValue(forKey: nodeId)
|
||||
self.chatSubscriptions[nodeId] = nil
|
||||
self.stopGatewayPushTaskIfIdle()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user