Ctrl K
ComunidadGuías…

src/ui/session.js

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

Archivo original ↗Preguntar a KiBot
const WIZARD_SESSION_PATTERN = /^[A-Za-z0-9_-]{43}$/u;
const WIZARD_ROUTES = new Set(["/", "/assistant", "/local", "/supergrok-vps", "/claude-vps", "/local-model-vps", "/hosting"]);
const fragmentSessionWindows = new WeakSet();

const pendingBrowserTransfer = globalThis.__relmioWizardSessionReady;
if (pendingBrowserTransfer && typeof pendingBrowserTransfer.then === "function") {
  try { await pendingBrowserTransfer; } catch { /* The bootstrap fails closed. */ }
  try { delete globalThis.__relmioWizardSessionReady; } catch {
    globalThis.__relmioWizardSessionReady = null;
  }
}

function validWizardSession(value) {
  return typeof value === "string" && WIZARD_SESSION_PATTERN.test(value);
}

function cleanWizardLocation(location) {
  return `${location.pathname}${location.hash ?? ""}`;
}

function preserveFragmentSession(browserWindow, session) {
  if (
    !validWizardSession(session) ||
    !browserWindow ||
    (typeof browserWindow !== "object" && typeof browserWindow !== "function") ||
    typeof browserWindow.addEventListener !== "function" ||
    fragmentSessionWindows.has(browserWindow)
  ) return;

  fragmentSessionWindows.add(browserWindow);
  const documentPath = browserWindow.location.pathname;
  // Navigation carries the private capability through history before reloading.
  // Back/Forward can restore that same-document entry with the old route's DOM.
  browserWindow.addEventListener("popstate", () => {
    if (browserWindow.location.pathname !== documentPath) {
      browserWindow.location.reload();
    }
  });
  browserWindow.addEventListener("pageshow", (event) => {
    if (event.persisted) browserWindow.location.reload();
  });
  browserWindow.addEventListener("hashchange", () => {
    browserWindow.history.replaceState(
      { relmioWizardSession: session },
      "",
      cleanWizardLocation(browserWindow.location),
    );
  });
}

export function readWizardSession(browserWindow = window) {
  const { history, location } = browserWindow;
  const historySession = validWizardSession(
    history.state?.relmioWizardSession,
  )
    ? history.state.relmioWizardSession
    : null;
  const session = historySession;
  const cleanLocation = cleanWizardLocation(location);

  history.replaceState(
    session ? { relmioWizardSession: session } : null,
    "",
    cleanLocation,
  );
  preserveFragmentSession(browserWindow, session);
  return session;
}

export function bindWizardNavigation(
  link,
  path,
  session,
  browserWindow = window,
) {
  if (!link?.setAttribute || !link?.addEventListener) {
    throw new TypeError("A wizard navigation link is required.");
  }
  if (!WIZARD_ROUTES.has(path)) {
    throw new TypeError("The wizard navigation route is invalid.");
  }

  link.setAttribute("href", path);
  link.addEventListener("click", (event) => {
    const target = link.getAttribute?.("target") || link.target;
    const modified =
      event.button !== 0 ||
      event.altKey ||
      event.ctrlKey ||
      event.metaKey ||
      event.shiftKey;

    if (
      event.defaultPrevented ||
      modified ||
      (target && target.toLowerCase() !== "_self") ||
      !validWizardSession(session)
    ) {
      return;
    }

    event.preventDefault();
    browserWindow.history.pushState(
      { relmioWizardSession: session },
      "",
      path,
    );
    browserWindow.location.reload();
  });
}

// The failure a wizard API reported, with the fields error help reads. `recovery`
// is left out on purpose: that table is ChatGPT sign-in advice.
export function wizardApiError(result, status, fallback = "The operation could not be completed.") {
  const error = new Error(typeof result?.error === "string" && result.error ? result.error : fallback);
  error.status = status;
  for (const key of ["code", "param"]) {
    if (typeof result?.[key] === "string" && result[key] && result[key].length <= 128) error[key] = result[key];
  }
  for (const flag of ["remoteOutcomeUnknown", "retryBlocked"]) {
    if (result?.[flag] === true) error[flag] = true;
  }
  return error;
}

const copiedTimers = new WeakMap();

// Copy feedback: marks the button with data-copied for a moment, and the kit
// shows a check. The accessible name never changes; the caller announces the
// copy through its polite status line.
export function flashCopied(button) {
  button.toggleAttribute("data-copied", true);
  clearTimeout(copiedTimers.get(button));
  copiedTimers.set(button, setTimeout(() => button.removeAttribute("data-copied"), 1_800));
}

// Runs a poll tick now, or once when the tab is shown again if it is hidden,
// so a background tab sends no requests.
export function whenVisible(tick, page = document) {
  if (!page.hidden) {
    tick();
    return;
  }
  page.addEventListener("visibilitychange", function shown() {
    if (page.hidden) return;
    page.removeEventListener("visibilitychange", shown);
    tick();
  });
}