feat(desktop): report current user online clients
This commit is contained in:
@@ -84,8 +84,10 @@ type RotateDesktopClientResponse struct {
|
||||
}
|
||||
|
||||
type HeartbeatDesktopClientResponse struct {
|
||||
Client DesktopClientView `json:"client"`
|
||||
ServerTime time.Time `json:"server_time"`
|
||||
Client DesktopClientView `json:"client"`
|
||||
ServerTime time.Time `json:"server_time"`
|
||||
OnlineClientCount int `json:"online_client_count"`
|
||||
CurrentUserOnlineClientCount int `json:"current_user_online_client_count"`
|
||||
}
|
||||
|
||||
type OfflineDesktopClientResponse struct {
|
||||
@@ -128,7 +130,7 @@ func (s *DesktopClientService) Register(ctx context.Context, actor auth.Actor, r
|
||||
return nil, response.ErrInternal(50072, "desktop_client_register_failed", "failed to persist desktop client")
|
||||
}
|
||||
|
||||
markDesktopClientPresent(ctx, s.redis, client.ID)
|
||||
markDesktopClientPresentForUser(ctx, s.redis, client.ID, client.TenantID, client.WorkspaceID, client.UserID)
|
||||
|
||||
return &RegisterDesktopClientResponse{
|
||||
ClientID: client.ID.String(),
|
||||
@@ -160,7 +162,7 @@ func (s *DesktopClientService) Rotate(ctx context.Context, client *repository.De
|
||||
return nil, response.ErrInternal(50074, "desktop_client_rotate_failed", "failed to rotate desktop client token")
|
||||
}
|
||||
|
||||
markDesktopClientPresent(ctx, s.redis, updated.ID)
|
||||
markDesktopClientPresentForUser(ctx, s.redis, updated.ID, updated.TenantID, updated.WorkspaceID, updated.UserID)
|
||||
|
||||
return &RotateDesktopClientResponse{
|
||||
ClientToken: rawToken,
|
||||
@@ -190,8 +192,12 @@ func (s *DesktopClientService) Heartbeat(ctx context.Context, client *repository
|
||||
return nil, response.ErrInternal(50075, "desktop_client_heartbeat_failed", "failed to persist desktop client heartbeat")
|
||||
}
|
||||
|
||||
markDesktopClientPresent(ctx, s.redis, updated.ID)
|
||||
markDesktopClientPresentForUser(ctx, s.redis, updated.ID, updated.TenantID, updated.WorkspaceID, updated.UserID)
|
||||
markDesktopAccountsPresent(ctx, s.redis, updated.ID, parseDesktopAccountIDs(req.AccountIDs))
|
||||
onlineClientCount, err := s.currentUserOnlineClientCount(ctx, updated)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if req.Startup && s.taskSvc != nil {
|
||||
if err := s.taskSvc.recoverClientTasksOnStartup(ctx, updated); err != nil {
|
||||
@@ -200,8 +206,10 @@ func (s *DesktopClientService) Heartbeat(ctx context.Context, client *repository
|
||||
}
|
||||
|
||||
return &HeartbeatDesktopClientResponse{
|
||||
Client: buildDesktopClientView(updated),
|
||||
ServerTime: time.Now().UTC(),
|
||||
Client: buildDesktopClientView(updated),
|
||||
ServerTime: time.Now().UTC(),
|
||||
OnlineClientCount: onlineClientCount,
|
||||
CurrentUserOnlineClientCount: onlineClientCount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -218,7 +226,7 @@ func (s *DesktopClientService) Revoke(ctx context.Context, client *repository.De
|
||||
return nil, response.ErrInternal(50076, "desktop_client_revoke_failed", "failed to revoke desktop client")
|
||||
}
|
||||
|
||||
clearDesktopClientPresenceState(ctx, s.redis, updated.ID)
|
||||
clearDesktopClientPresenceStateForUser(ctx, s.redis, updated.ID, updated.TenantID, updated.WorkspaceID, updated.UserID)
|
||||
if s.taskSvc != nil {
|
||||
if err := s.taskSvc.recoverClientTasksOnDisconnect(ctx, updated); err != nil {
|
||||
return nil, err
|
||||
@@ -234,7 +242,7 @@ func (s *DesktopClientService) Offline(ctx context.Context, client *repository.D
|
||||
return nil, response.ErrUnauthorized(40108, "desktop_client_missing", "desktop client context is required")
|
||||
}
|
||||
|
||||
clearDesktopClientPresenceState(ctx, s.redis, client.ID)
|
||||
clearDesktopClientPresenceStateForUser(ctx, s.redis, client.ID, client.TenantID, client.WorkspaceID, client.UserID)
|
||||
if s.taskSvc != nil {
|
||||
if err := s.taskSvc.recoverClientTasksOnDisconnect(ctx, client); err != nil {
|
||||
return nil, err
|
||||
@@ -246,6 +254,45 @@ func (s *DesktopClientService) Offline(ctx context.Context, client *repository.D
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *DesktopClientService) currentUserOnlineClientCount(ctx context.Context, client *repository.DesktopClient) (int, error) {
|
||||
if count, ok := loadDesktopUserOnlineClientCount(ctx, s.redis, client.TenantID, client.WorkspaceID, client.UserID, time.Now().UTC()); ok {
|
||||
return count, nil
|
||||
}
|
||||
|
||||
clients, err := s.repo.ListByUser(ctx, client.TenantID, client.WorkspaceID, client.UserID)
|
||||
if err != nil {
|
||||
return 0, response.ErrInternal(50077, "desktop_client_online_count_failed", "failed to inspect online desktop clients")
|
||||
}
|
||||
|
||||
return countOnlineDesktopClients(ctx, s.redis, clients, time.Now().UTC()), nil
|
||||
}
|
||||
|
||||
func countOnlineDesktopClients(ctx context.Context, redis *goredis.Client, clients []repository.DesktopClient, now time.Time) int {
|
||||
if len(clients) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
clientIDs := make([]uuid.UUID, 0, len(clients))
|
||||
for _, client := range clients {
|
||||
if client.ID == uuid.Nil || client.RevokedAt != nil {
|
||||
continue
|
||||
}
|
||||
clientIDs = append(clientIDs, client.ID)
|
||||
}
|
||||
presence := loadDesktopClientPresence(ctx, redis, clientIDs)
|
||||
|
||||
onlineCount := 0
|
||||
for _, client := range clients {
|
||||
if client.ID == uuid.Nil || client.RevokedAt != nil {
|
||||
continue
|
||||
}
|
||||
if resolveDesktopClientOnline(&client, presence[client.ID], presence != nil, now) {
|
||||
onlineCount++
|
||||
}
|
||||
}
|
||||
return onlineCount
|
||||
}
|
||||
|
||||
func newDesktopClientToken() (string, []byte, error) {
|
||||
raw := make([]byte, 32)
|
||||
if _, err := rand.Read(raw); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user