mirror of
https://github.com/kerberos-io/agent.git
synced 2026-09-17 12:22:55 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
726d0722d9 | ||
|
|
d8f320b040 | ||
|
|
0131b87692 | ||
|
|
54e8198b65 | ||
|
|
3bfb68f950 |
@@ -20,96 +20,93 @@ func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configDirect
|
||||
|
||||
// This is legacy should be removed in future! Now everything
|
||||
// lives under the /api prefix.
|
||||
r.GET("/config", func(c *gin.Context) {
|
||||
r.GET("/config", authMiddleware.MiddlewareFunc(), func(c *gin.Context) {
|
||||
components.GetConfig(c, captureDevice, configuration, communication)
|
||||
})
|
||||
|
||||
// This is legacy should be removed in future! Now everything
|
||||
// lives under the /api prefix.
|
||||
r.POST("/config", func(c *gin.Context) {
|
||||
r.POST("/config", authMiddleware.MiddlewareFunc(), func(c *gin.Context) {
|
||||
components.UpdateConfig(c, configDirectory, configuration, communication)
|
||||
})
|
||||
|
||||
api := r.Group("/api")
|
||||
{
|
||||
// Public endpoints (no authentication required)
|
||||
api.POST("/login", authMiddleware.LoginHandler)
|
||||
|
||||
api.GET("/dashboard", func(c *gin.Context) {
|
||||
components.GetDashboard(c, configDirectory, configuration, communication)
|
||||
})
|
||||
|
||||
api.POST("/latest-events", func(c *gin.Context) {
|
||||
components.GetLatestEvents(c, configDirectory, configuration, communication)
|
||||
})
|
||||
|
||||
api.GET("/days", func(c *gin.Context) {
|
||||
components.GetDays(c, configDirectory, configuration, communication)
|
||||
})
|
||||
|
||||
api.GET("/config", func(c *gin.Context) {
|
||||
components.GetConfig(c, captureDevice, configuration, communication)
|
||||
})
|
||||
|
||||
api.POST("/config", func(c *gin.Context) {
|
||||
components.UpdateConfig(c, configDirectory, configuration, communication)
|
||||
})
|
||||
|
||||
// Will verify the hub settings.
|
||||
api.POST("/hub/verify", func(c *gin.Context) {
|
||||
cloud.VerifyHub(c)
|
||||
})
|
||||
|
||||
// Will verify the persistence settings.
|
||||
api.POST("/persistence/verify", func(c *gin.Context) {
|
||||
cloud.VerifyPersistence(c, configDirectory)
|
||||
})
|
||||
|
||||
// Will verify the secondary persistence settings.
|
||||
api.POST("/persistence/secondary/verify", func(c *gin.Context) {
|
||||
cloud.VerifySecondaryPersistence(c, configDirectory)
|
||||
})
|
||||
|
||||
// Camera specific methods. Doesn't require any authorization.
|
||||
// These are available for anyone, but require the agent, to reach
|
||||
// the camera.
|
||||
|
||||
api.POST("/camera/restart", func(c *gin.Context) {
|
||||
components.RestartAgent(c, communication)
|
||||
})
|
||||
|
||||
api.POST("/camera/stop", func(c *gin.Context) {
|
||||
components.StopAgent(c, communication)
|
||||
})
|
||||
|
||||
api.POST("/camera/record", func(c *gin.Context) {
|
||||
components.MakeRecording(c, communication)
|
||||
})
|
||||
|
||||
api.GET("/camera/snapshot/jpeg", func(c *gin.Context) {
|
||||
components.GetSnapshotRaw(c, captureDevice, configuration, communication)
|
||||
})
|
||||
|
||||
api.GET("/camera/snapshot/base64", func(c *gin.Context) {
|
||||
components.GetSnapshotBase64(c, captureDevice, configuration, communication)
|
||||
})
|
||||
|
||||
// Onvif specific methods. Doesn't require any authorization.
|
||||
// Will verify the current onvif settings.
|
||||
api.POST("/camera/onvif/verify", onvif.VerifyOnvifConnection)
|
||||
api.POST("/camera/onvif/login", LoginToOnvif)
|
||||
api.POST("/camera/onvif/capabilities", GetOnvifCapabilities)
|
||||
api.POST("/camera/onvif/presets", GetOnvifPresets)
|
||||
api.POST("/camera/onvif/gotopreset", GoToOnvifPreset)
|
||||
api.POST("/camera/onvif/pantilt", DoOnvifPanTilt)
|
||||
api.POST("/camera/onvif/zoom", DoOnvifZoom)
|
||||
api.POST("/camera/onvif/inputs", DoGetDigitalInputs)
|
||||
api.POST("/camera/onvif/outputs", DoGetRelayOutputs)
|
||||
api.POST("/camera/onvif/outputs/:output", DoTriggerRelayOutput)
|
||||
api.POST("/camera/verify/:streamType", capture.VerifyCamera)
|
||||
|
||||
// Secured endpoints..
|
||||
// Apply JWT authentication middleware.
|
||||
// All routes registered below this line require a valid JWT token.
|
||||
api.Use(authMiddleware.MiddlewareFunc())
|
||||
{
|
||||
api.GET("/dashboard", func(c *gin.Context) {
|
||||
components.GetDashboard(c, configDirectory, configuration, communication)
|
||||
})
|
||||
|
||||
api.POST("/latest-events", func(c *gin.Context) {
|
||||
components.GetLatestEvents(c, configDirectory, configuration, communication)
|
||||
})
|
||||
|
||||
api.GET("/days", func(c *gin.Context) {
|
||||
components.GetDays(c, configDirectory, configuration, communication)
|
||||
})
|
||||
|
||||
api.GET("/config", func(c *gin.Context) {
|
||||
components.GetConfig(c, captureDevice, configuration, communication)
|
||||
})
|
||||
|
||||
api.POST("/config", func(c *gin.Context) {
|
||||
components.UpdateConfig(c, configDirectory, configuration, communication)
|
||||
})
|
||||
|
||||
// Will verify the hub settings.
|
||||
api.POST("/hub/verify", func(c *gin.Context) {
|
||||
cloud.VerifyHub(c)
|
||||
})
|
||||
|
||||
// Will verify the persistence settings.
|
||||
api.POST("/persistence/verify", func(c *gin.Context) {
|
||||
cloud.VerifyPersistence(c, configDirectory)
|
||||
})
|
||||
|
||||
// Will verify the secondary persistence settings.
|
||||
api.POST("/persistence/secondary/verify", func(c *gin.Context) {
|
||||
cloud.VerifySecondaryPersistence(c, configDirectory)
|
||||
})
|
||||
|
||||
// Camera specific methods.
|
||||
api.POST("/camera/restart", func(c *gin.Context) {
|
||||
components.RestartAgent(c, communication)
|
||||
})
|
||||
|
||||
api.POST("/camera/stop", func(c *gin.Context) {
|
||||
components.StopAgent(c, communication)
|
||||
})
|
||||
|
||||
api.POST("/camera/record", func(c *gin.Context) {
|
||||
components.MakeRecording(c, communication)
|
||||
})
|
||||
|
||||
api.GET("/camera/snapshot/jpeg", func(c *gin.Context) {
|
||||
components.GetSnapshotRaw(c, captureDevice, configuration, communication)
|
||||
})
|
||||
|
||||
api.GET("/camera/snapshot/base64", func(c *gin.Context) {
|
||||
components.GetSnapshotBase64(c, captureDevice, configuration, communication)
|
||||
})
|
||||
|
||||
// Onvif specific methods.
|
||||
api.POST("/camera/onvif/verify", onvif.VerifyOnvifConnection)
|
||||
api.POST("/camera/onvif/login", LoginToOnvif)
|
||||
api.POST("/camera/onvif/capabilities", GetOnvifCapabilities)
|
||||
api.POST("/camera/onvif/presets", GetOnvifPresets)
|
||||
api.POST("/camera/onvif/gotopreset", GoToOnvifPreset)
|
||||
api.POST("/camera/onvif/pantilt", DoOnvifPanTilt)
|
||||
api.POST("/camera/onvif/zoom", DoOnvifZoom)
|
||||
api.POST("/camera/onvif/inputs", DoGetDigitalInputs)
|
||||
api.POST("/camera/onvif/outputs", DoGetRelayOutputs)
|
||||
api.POST("/camera/onvif/outputs/:output", DoTriggerRelayOutput)
|
||||
api.POST("/camera/verify/:streamType", capture.VerifyCamera)
|
||||
}
|
||||
}
|
||||
return api
|
||||
|
||||
@@ -29,9 +29,10 @@ const (
|
||||
rtcpBufferSize = 1500
|
||||
|
||||
// Timeouts and intervals
|
||||
keepAliveTimeout = 15 * time.Second
|
||||
defaultTimeout = 10 * time.Second
|
||||
maxLivePacketAge = 1500 * time.Millisecond
|
||||
keepAliveTimeout = 15 * time.Second
|
||||
defaultTimeout = 10 * time.Second
|
||||
maxLivePacketAge = 1500 * time.Millisecond
|
||||
disconnectGracePeriod = 5 * time.Second
|
||||
|
||||
// Track identifiers
|
||||
trackStreamID = "kerberos-stream"
|
||||
@@ -47,11 +48,13 @@ type ConnectionManager struct {
|
||||
|
||||
// peerConnectionWrapper wraps a peer connection with additional metadata
|
||||
type peerConnectionWrapper struct {
|
||||
conn *pionWebRTC.PeerConnection
|
||||
cancelCtx context.CancelFunc
|
||||
done chan struct{}
|
||||
closeOnce sync.Once
|
||||
connected atomic.Bool
|
||||
conn *pionWebRTC.PeerConnection
|
||||
cancelCtx context.CancelFunc
|
||||
done chan struct{}
|
||||
closeOnce sync.Once
|
||||
connected atomic.Bool
|
||||
disconnectMu sync.Mutex
|
||||
disconnectTimer *time.Timer
|
||||
}
|
||||
|
||||
var globalConnectionManager = NewConnectionManager()
|
||||
@@ -387,14 +390,61 @@ func InitializeWebRTCConnection(configuration *models.Configuration, communicati
|
||||
}()
|
||||
}
|
||||
|
||||
// Log ICE connection state changes for diagnostics
|
||||
peerConnection.OnICEConnectionStateChange(func(iceState pionWebRTC.ICEConnectionState) {
|
||||
log.Log.Info("webrtc.main.InitializeWebRTCConnection(): ICE connection state changed to: " + iceState.String() +
|
||||
" (session: " + handshake.SessionID + ")")
|
||||
})
|
||||
|
||||
peerConnection.OnConnectionStateChange(func(connectionState pionWebRTC.PeerConnectionState) {
|
||||
log.Log.Info("webrtc.main.InitializeWebRTCConnection(): connection state changed to: " + connectionState.String())
|
||||
log.Log.Info("webrtc.main.InitializeWebRTCConnection(): connection state changed to: " + connectionState.String() +
|
||||
" (session: " + handshake.SessionID + ")")
|
||||
|
||||
switch connectionState {
|
||||
case pionWebRTC.PeerConnectionStateDisconnected, pionWebRTC.PeerConnectionStateClosed, pionWebRTC.PeerConnectionStateFailed:
|
||||
case pionWebRTC.PeerConnectionStateDisconnected:
|
||||
// Disconnected is a transient state that can recover.
|
||||
// Start a grace period timer; if we don't recover, then cleanup.
|
||||
wrapper.disconnectMu.Lock()
|
||||
if wrapper.disconnectTimer == nil {
|
||||
log.Log.Info("webrtc.main.InitializeWebRTCConnection(): peer disconnected, waiting " +
|
||||
disconnectGracePeriod.String() + " for recovery (session: " + handshake.SessionID + ")")
|
||||
wrapper.disconnectTimer = time.AfterFunc(disconnectGracePeriod, func() {
|
||||
log.Log.Info("webrtc.main.InitializeWebRTCConnection(): disconnect grace period expired, closing connection (session: " + handshake.SessionID + ")")
|
||||
cleanupPeerConnection(sessionKey, wrapper)
|
||||
})
|
||||
}
|
||||
wrapper.disconnectMu.Unlock()
|
||||
|
||||
case pionWebRTC.PeerConnectionStateFailed:
|
||||
// Stop any pending disconnect timer
|
||||
wrapper.disconnectMu.Lock()
|
||||
if wrapper.disconnectTimer != nil {
|
||||
wrapper.disconnectTimer.Stop()
|
||||
wrapper.disconnectTimer = nil
|
||||
}
|
||||
wrapper.disconnectMu.Unlock()
|
||||
cleanupPeerConnection(sessionKey, wrapper)
|
||||
|
||||
case pionWebRTC.PeerConnectionStateClosed:
|
||||
// Stop any pending disconnect timer
|
||||
wrapper.disconnectMu.Lock()
|
||||
if wrapper.disconnectTimer != nil {
|
||||
wrapper.disconnectTimer.Stop()
|
||||
wrapper.disconnectTimer = nil
|
||||
}
|
||||
wrapper.disconnectMu.Unlock()
|
||||
cleanupPeerConnection(sessionKey, wrapper)
|
||||
|
||||
case pionWebRTC.PeerConnectionStateConnected:
|
||||
// Cancel any pending disconnect timer — connection recovered
|
||||
wrapper.disconnectMu.Lock()
|
||||
if wrapper.disconnectTimer != nil {
|
||||
wrapper.disconnectTimer.Stop()
|
||||
wrapper.disconnectTimer = nil
|
||||
log.Log.Info("webrtc.main.InitializeWebRTCConnection(): connection recovered from disconnected state (session: " + handshake.SessionID + ")")
|
||||
}
|
||||
wrapper.disconnectMu.Unlock()
|
||||
|
||||
if wrapper.connected.CompareAndSwap(false, true) {
|
||||
count := globalConnectionManager.IncrementPeerCount()
|
||||
log.Log.Info("webrtc.main.InitializeWebRTCConnection(): Peer connected. Active peers: " + strconv.FormatInt(count, 10))
|
||||
|
||||
Reference in New Issue
Block a user