138 lines · 5.0 KB
import { HttpError } from "../errors";
import { agentUpdate } from "./room-agents";
import { canWrite, resolveEditorToken } from "./session";
import { CLOSE_DOWNGRADED, CLOSE_REAUTH, CLOSE_REVOKED } from "~/lib/editor-protocol";
import { awarenessFrame, base64, decodeAwareness, encodeAwareness, removalFrame, unbase64 } from "./room-awareness";

const MAX_CACHED_AWARENESS = 1200;
const MAX_CLIENT_ID = 2 ** 32;

/** Per-socket state; it survives hibernation, so it must stay small (attachments are capped at 2 KiB). */
export type Attachment = {
  token: string;
  userId: string;
  name: string;
  kind: "human" | "agent";
  writable: boolean;
  checkedAt: number;
  expiresAt: number;
  aw?: string;
  awClient?: number;
  awClock?: number;
};

export type RoomJoin = Omit<Attachment, "checkedAt" | "aw" | "awClient" | "awClock"> & {
  repoId: string;
  branch: string;
  head: string;
};

export type Participant = { id: string; name: string; kind: "human" | "agent"; path: string | null };

type AgentRow = Parameters<typeof agentUpdate>[0];

/**
 * Relays a cursor update with identity fields overwritten by the authenticated user, so nobody can
 * impersonate another participant. The last update is cached on the socket to replay to newcomers.
 */
export function sanitizeAwareness(
  attachment: Attachment,
  update: Uint8Array,