84 lines
2.2 KiB
TypeScript
84 lines
2.2 KiB
TypeScript
export type OperatorPresenceAction = "viewing" | "typing" | "left"
|
|
|
|
export type OperatorPresenceRecord = {
|
|
conversationId: number
|
|
userId: number
|
|
username: string
|
|
nickname: string
|
|
action: OperatorPresenceAction
|
|
at: number
|
|
}
|
|
|
|
export type OperatorPresencePayload = {
|
|
conversationId?: number
|
|
userId?: number
|
|
username?: string
|
|
nickname?: string
|
|
action?: string
|
|
at?: string | number
|
|
}
|
|
|
|
const DEFAULT_PRESENCE_TTL_MS = 15_000
|
|
|
|
export function purgeExpiredOperatorPresences(
|
|
currentList: OperatorPresenceRecord[],
|
|
nowMs?: number,
|
|
maxAgeMs = DEFAULT_PRESENCE_TTL_MS
|
|
): OperatorPresenceRecord[] {
|
|
const now = nowMs ?? Date.now()
|
|
return (currentList || []).filter((record) => now - record.at <= maxAgeMs)
|
|
}
|
|
|
|
export function updateOperatorPresenceList(
|
|
currentList: OperatorPresenceRecord[],
|
|
incoming: OperatorPresencePayload,
|
|
currentUserId: number,
|
|
nowMs?: number
|
|
): OperatorPresenceRecord[] {
|
|
if (!incoming || !incoming.conversationId || !incoming.userId) {
|
|
return currentList
|
|
}
|
|
if (incoming.userId === currentUserId) {
|
|
return currentList
|
|
}
|
|
|
|
const now = nowMs ?? Date.now()
|
|
const list = currentList || []
|
|
|
|
if (incoming.action === "left") {
|
|
const nextList = list.filter(
|
|
(item) =>
|
|
!(item.conversationId === incoming.conversationId && item.userId === incoming.userId)
|
|
)
|
|
return purgeExpiredOperatorPresences(nextList, now)
|
|
}
|
|
|
|
if (incoming.action === "viewing" || incoming.action === "typing") {
|
|
const existingIndex = list.findIndex(
|
|
(item) =>
|
|
item.conversationId === incoming.conversationId && item.userId === incoming.userId
|
|
)
|
|
const record: OperatorPresenceRecord = {
|
|
conversationId: incoming.conversationId,
|
|
userId: incoming.userId,
|
|
username: incoming.username ?? "",
|
|
nickname: incoming.nickname ?? "",
|
|
action: incoming.action,
|
|
at: now,
|
|
}
|
|
|
|
let nextList: OperatorPresenceRecord[]
|
|
if (existingIndex >= 0) {
|
|
nextList = [...list]
|
|
nextList[existingIndex] = record
|
|
} else {
|
|
nextList = [...list, record]
|
|
}
|
|
return purgeExpiredOperatorPresences(nextList, now)
|
|
}
|
|
|
|
return purgeExpiredOperatorPresences(list, now)
|
|
}
|
|
|
|
export const resolveOperatorPresence = updateOperatorPresenceList
|