Ctrl K
ComunidadGuías…

shared/utils/sessions.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { addMonths } from "date-fns";
import isUUID from "validator/lib/isUUID";
import { z } from "zod";

/** Details of a workspace recorded in the sessions cookie. */
export interface Session {
  name: string;
  logoUrl: string | null;
  url: string;
  expiresAt?: string;
  lastUsedAt?: string;
}

/** Workspace sessions indexed by team ID. */
export type Sessions = Record<string, Session>;

/**
 * Parses a decoded sessions cookie, discarding malformed entries.
 *
 * @param value the decoded cookie value.
 * @returns the valid session entries.
 */
export function parseSessionsCookie(value: string): Sessions {
  try {
    const parsed = sessionsSchema.safeParse(JSON.parse(value));
    if (!parsed.success) {
      return {};
    }

    const sessions: Sessions = {};
    for (const [id, value] of Object.entries(parsed.data)) {
      if (!isUUID(id)) {
        continue;
      }

      const session = sessionSchema.safeParse(value);
      if (session.success) {
        sessions[id] = session.data;
      }
    }
    return sessions;
  } catch (_err) {
    return {};
  }
}

/**
 * Checks whether a remembered workspace has an active session.
 * Legacy entries without an expiry are treated as signed in until reconciled.
 *
 * @param session the remembered workspace session.
 * @returns whether the session has not expired.
 */
export function isSessionActive(session: Session): boolean {
  return (
    session.expiresAt === undefined ||
    Date.parse(session.expiresAt) > Date.now()
  );
}

/**
 * Selects active sessions without removing remembered workspaces from storage.
 *
 * @param sessions the sessions to filter.
 * @returns the active sessions.
 */
export function getActiveSessions(sessions: Sessions): Sessions {
  return Object.fromEntries(
    Object.entries(sessions).filter(([, session]) => isSessionActive(session))
  );
}

/**
 * Keeps workspaces used in the last three months, dating legacy entries on first read.
 *
 * @param sessions the remembered workspace sessions.
 * @returns the recently used workspaces.
 */
export function getRememberedSessions(sessions: Sessions): Sessions {
  const now = new Date();
  return Object.fromEntries(
    Object.entries(sessions)
      .filter(
        ([, session]) =>
          session.lastUsedAt === undefined ||
          addMonths(new Date(session.lastUsedAt), 3).getTime() > now.getTime()
      )
      .map(([id, session]) => [
        id,
        { ...session, lastUsedAt: session.lastUsedAt ?? now.toISOString() },
      ])
  );
}

/**
 * Finds when the last remembered workspace reaches three months of inactivity.
 *
 * @param sessions the remembered workspace sessions.
 * @returns the cookie expiry, or undefined when there are no entries.
 */
export function getSessionsCookieExpiry(sessions: Sessions): Date | undefined {
  const expiresAt = Math.max(
    0,
    ...Object.values(getRememberedSessions(sessions)).map((session) =>
      addMonths(new Date(session.lastUsedAt ?? Date.now()), 3).getTime()
    )
  );
  return expiresAt ? new Date(expiresAt) : undefined;
}

const sessionSchema = z.object({
  name: z.string(),
  logoUrl: z.string().nullable(),
  url: z.string(),
  expiresAt: z.string().optional(),
  lastUsedAt: z.string().optional(),
});

const sessionsSchema = z.record(z.string(), z.unknown());