mirror of
https://github.com/abhinavxd/libredesk.git
synced 2026-11-01 08:59:46 +00:00
723c0e84ba
Opening a conversation used to mark it read even when the agent never scrolled to the new messages or the tab was in the background. Reply notifications in the bell also stayed unread after the agent read the reply. Now the browser tells the server which message the agent saw. It only does this when the end of the thread is on screen and the tab has focus. The server moves the agent's last seen time forward to that message and never back. A reply notification counts as read when the agent's last seen time is at or after the reply. "Mark as unread" marks the old reply notifications read first, so the bell stays the same. Reply emails and pushes wait a little before sending. They are skipped if the agent already read the message or can no longer see the conversation.
71 lines
2.4 KiB
Go
71 lines
2.4 KiB
Go
package notifier
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"slices"
|
|
|
|
"github.com/abhinavxd/libredesk/internal/authz"
|
|
"github.com/abhinavxd/libredesk/internal/notification/models"
|
|
umodels "github.com/abhinavxd/libredesk/internal/user/models"
|
|
"github.com/volatiletech/null/v9"
|
|
)
|
|
|
|
type NotificationAgentStore interface {
|
|
GetAgentCachedOrLoad(int) (umodels.User, error)
|
|
}
|
|
|
|
type DeliveryChecker interface {
|
|
ShouldDeliver(int, models.NotificationReference, models.NotificationChannel) (bool, error)
|
|
}
|
|
|
|
type ReplyDeliveryChecker struct {
|
|
notifications *UserNotificationManager
|
|
agents NotificationAgentStore
|
|
prefs NotificationPreferenceStore
|
|
}
|
|
|
|
type replyDeliveryState struct {
|
|
AssignedUserID null.Int `db:"assigned_user_id"`
|
|
AssignedTeamID null.Int `db:"assigned_team_id"`
|
|
Participating bool `db:"participating"`
|
|
Seen bool `db:"seen"`
|
|
}
|
|
|
|
func NewReplyDeliveryChecker(notifications *UserNotificationManager, agents NotificationAgentStore, prefs NotificationPreferenceStore) *ReplyDeliveryChecker {
|
|
return &ReplyDeliveryChecker{notifications: notifications, agents: agents, prefs: prefs}
|
|
}
|
|
|
|
func (c *ReplyDeliveryChecker) ShouldDeliver(userID int, ref models.NotificationReference, channel models.NotificationChannel) (bool, error) {
|
|
if !models.IsReply(ref.Type) {
|
|
return true, nil
|
|
}
|
|
agent, err := c.agents.GetAgentCachedOrLoad(userID)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
enabled, err := c.prefs.EnabledChannels([]int{userID}, ref.Type)
|
|
if err != nil || !slices.Contains(enabled[userID], channel) {
|
|
return false, err
|
|
}
|
|
var state replyDeliveryState
|
|
if err := c.notifications.q.ReplyDeliveryState.Get(&state, userID, ref.ConversationID, ref.MessageID, ref.NotificationID); err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
return !state.Seen && CanReceiveReply(agent, ref.Type, state.AssignedUserID, state.AssignedTeamID, state.Participating), nil
|
|
}
|
|
|
|
// CanReceiveReply reports whether agent should get a reply notification of type nType.
|
|
func CanReceiveReply(agent umodels.User, nType models.NotificationType, assignedUserID, assignedTeamID null.Int, participating bool) bool {
|
|
if !agent.Enabled || !authz.CanReadAssignment(agent, assignedUserID, assignedTeamID) {
|
|
return false
|
|
}
|
|
if nType == models.NotificationTypeNewReplyParticipating {
|
|
return participating && assignedUserID.Int != agent.ID
|
|
}
|
|
return assignedUserID.Valid && assignedUserID.Int == agent.ID
|
|
}
|