@oxyhq/services
Version:
Reusable OxyHQ module to handle authentication, user management, karma system, device-based session management and more 🚀
180 lines (159 loc) • 6.24 kB
JavaScript
"use strict";
/**
* Session management utilities
*
* Provides consistent session normalization, deduplication, and sorting
* to ensure sessions are always displayed in a predictable order.
*/
/**
* Normalize a session to ensure all required fields are present
*/
export function normalizeSession(session) {
const now = new Date().toISOString();
return {
sessionId: session.sessionId,
deviceId: session.deviceId || '',
expiresAt: session.expiresAt || now,
lastActive: session.lastActive || now,
userId: session.userId || ''
};
}
/**
* Compare two sessions for equality
*/
export function sessionsEqual(a, b) {
return a.sessionId === b.sessionId;
}
/**
* Sort sessions by lastActive (most recent first), then by sessionId for stability
*/
export function sortSessions(sessions) {
return [...sessions].sort((a, b) => {
// Sort by lastActive descending (most recent first)
const timeA = new Date(a.lastActive).getTime();
const timeB = new Date(b.lastActive).getTime();
if (timeA !== timeB) {
return timeB - timeA; // Descending order
}
// If lastActive is the same, sort by sessionId for stability
return a.sessionId.localeCompare(b.sessionId);
});
}
/**
* Deduplicate sessions by sessionId, keeping the most recent version
*/
export function deduplicateSessions(sessions) {
const sessionMap = new Map();
for (const session of sessions) {
const existing = sessionMap.get(session.sessionId);
if (!existing) {
sessionMap.set(session.sessionId, session);
} else {
// Keep the one with more recent lastActive
const existingTime = new Date(existing.lastActive).getTime();
const currentTime = new Date(session.lastActive).getTime();
if (currentTime > existingTime) {
sessionMap.set(session.sessionId, session);
}
}
}
return Array.from(sessionMap.values());
}
/**
* Deduplicate sessions by userId, keeping only one session per user
* Priority: 1) Active session (if provided), 2) Most recent session
* This prevents showing duplicate accounts for the same user
*/
export function deduplicateSessionsByUserId(sessions, activeSessionId) {
if (!sessions.length) return [];
const userSessionMap = new Map();
for (const session of sessions) {
if (!session.userId) continue; // Skip sessions without userId
const existing = userSessionMap.get(session.userId);
if (!existing) {
userSessionMap.set(session.userId, session);
} else {
// Prioritize active session
const isCurrentActive = activeSessionId && session.sessionId === activeSessionId;
const isExistingActive = activeSessionId && existing.sessionId === activeSessionId;
if (isCurrentActive && !isExistingActive) {
userSessionMap.set(session.userId, session);
} else if (!isCurrentActive && isExistingActive) {
// Keep existing (active) session
continue;
} else {
// Neither is active, keep the one with more recent lastActive
const existingTime = new Date(existing.lastActive).getTime();
const currentTime = new Date(session.lastActive).getTime();
if (currentTime > existingTime) {
userSessionMap.set(session.userId, session);
}
}
}
}
return Array.from(userSessionMap.values());
}
/**
* Normalize, deduplicate, and sort sessions
* This ensures consistent session ordering across the application
*
* @param sessions - Array of sessions to normalize
* @param activeSessionId - Optional active session ID to prioritize
* @param deduplicateByUserId - If true, deduplicate by userId (one account per user). Default: true
*/
export function normalizeAndSortSessions(sessions, activeSessionId, deduplicateByUserId = true) {
if (!sessions.length) return [];
// Normalize all sessions
const normalized = sessions.map(normalizeSession);
// First deduplicate by sessionId (exact duplicates)
const deduplicatedBySessionId = deduplicateSessions(normalized);
// Then deduplicate by userId if requested (one account per user)
const finalSessions = deduplicateByUserId ? deduplicateSessionsByUserId(deduplicatedBySessionId, activeSessionId) : deduplicatedBySessionId;
// Sort consistently
return sortSessions(finalSessions);
}
/**
* Merge two session arrays, prioritizing newer data
* Returns normalized, deduplicated, and sorted sessions
*
* @param existing - Existing sessions array
* @param incoming - New sessions to merge in
* @param activeSessionId - Optional active session ID to prioritize
* @param deduplicateByUserId - If true, deduplicate by userId (one account per user). Default: true
*/
export function mergeSessions(existing, incoming, activeSessionId, deduplicateByUserId = true) {
if (!existing.length && !incoming.length) return [];
if (!existing.length) return normalizeAndSortSessions(incoming, activeSessionId, deduplicateByUserId);
if (!incoming.length) return normalizeAndSortSessions(existing, activeSessionId, deduplicateByUserId);
// Normalize both arrays
const normalizedExisting = existing.map(normalizeSession);
const normalizedIncoming = incoming.map(normalizeSession);
// Create a map with existing sessions (by sessionId)
const sessionMap = new Map();
// Add existing sessions first
for (const session of normalizedExisting) {
sessionMap.set(session.sessionId, session);
}
// Merge incoming sessions - backend data always replaces existing
for (const session of normalizedIncoming) {
sessionMap.set(session.sessionId, session);
}
// Convert to array
const merged = Array.from(sessionMap.values());
// Apply userId deduplication if requested
const finalSessions = deduplicateByUserId ? deduplicateSessionsByUserId(merged, activeSessionId) : merged;
// Sort consistently
return sortSessions(finalSessions);
}
/**
* Check if two session arrays are equal (same sessionIds in same order)
*/
export function sessionsArraysEqual(a, b) {
if (a.length !== b.length) {
return false;
}
const sortedA = sortSessions(a);
const sortedB = sortSessions(b);
return sortedA.every((session, index) => sessionsEqual(session, sortedB[index]));
}
//# sourceMappingURL=sessionUtils.js.map