AkurAI Build
Menu

Public repository / 4f0b1d374fab4075f2b552b5f250bca5f28afee9

AkurAI-Mail / src/lib/webmail/sync.js

Latest change 10d19fe0941c186ec244d1f0e2af6b19200dcb0c - OLI-248: pure webmail logic as plain ES modules with JSDoc types; inline icon module by Ólafur Búi Ólafsson

/** @typedef {"offline" | "auth-expired" | "server-error"} SyncErrorKind */
/** Pure helpers behind the webmail sync status and error banner. */

export const SYNC_ERROR_MESSAGES = {
  offline: "You're offline. Showing the last synced messages.",
  "auth-expired": "Your session expired. Sign in again to resume syncing.",
  "server-error": "The mail server had a problem. Showing the last synced messages.",
};

export const RETRY_BASE_MS = 2000;
export const RETRY_MAX_MS = 60000;

/**
 * Exponential backoff: 2s, 4s, 8s ... capped at one minute.
 * @param {number} attempt
 */
export function retryDelay(attempt) {
  return Math.min(RETRY_BASE_MS * 2 ** attempt, RETRY_MAX_MS);
}

/**
 * @param {unknown} error
 * @param {{ status: number }} [response]
 * @param {any} [online]
 * @returns {SyncErrorKind}
 */
export function classifySyncError(error, response, online = typeof navigator === "undefined" ? true : navigator.onLine) {
  if (response?.status === 401) return "auth-expired";
  if (online === false || error instanceof TypeError) return "offline";
  return "server-error";
}

/**
 * "Synced just now", "Synced 12s ago", "Synced 3m ago", "Synced 2h ago".
 * @param {number | null} lastSyncAt
 * @param {number} [now]
 */
export function syncedLabel(lastSyncAt, now = Date.now()) {
  if (!lastSyncAt) return "Never synced";
  const seconds = Math.max(0, Math.round((now - lastSyncAt) / 1000));
  if (seconds < 5) return "Synced just now";
  if (seconds < 60) return `Synced ${seconds}s ago`;
  const minutes = Math.round(seconds / 60);
  if (minutes < 60) return `Synced ${minutes}m ago`;
  return `Synced ${Math.round(minutes / 60)}h ago`;
}