18e574a765
A pane's tab shows the other people who have it open (pane-presence.js, in memory, fed by the /ws-pty attach; GET /api/lanes/:id/presence and the pane_presence broadcast to the owner and full collaborators) and says "Billed to <name>" for a pane a collaborator opened (startedBy on the run). The first real keystroke of each attach into a shared workspace's pane by anyone but its owner is audited as `type into <run id>` (mouse, focus and terminal replies to tmux do not count). The pty socket now pings, so a vanished browser is ended at the next re-check and leaves the presence list.
55 lines
1.8 KiB
TypeScript
55 lines
1.8 KiB
TypeScript
/**
|
|
* @file usePanePresence.ts
|
|
* @description Who has each of a workspace's panes open (server mode, workspace
|
|
* sharing phase 4): `GET /api/lanes/:id/presence` once, then every
|
|
* `pane_presence` frame for that workspace, and a re-read on a WebSocket
|
|
* reconnect (frames missed while away are never replayed). Local mode has
|
|
* one user and reads nothing. Returns `{runId: [{id, name}]}`.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { api } from "../lib/api";
|
|
import { eventBus } from "../lib/eventBus";
|
|
import { usePermissions } from "../components/AuthGate";
|
|
|
|
export type PanePeople = Record<string, { id: number; name: string }[]>;
|
|
|
|
export function usePanePresence(laneId: number | null | undefined): PanePeople {
|
|
const { local } = usePermissions();
|
|
const [panes, setPanes] = useState<PanePeople>({});
|
|
useEffect(() => {
|
|
setPanes({});
|
|
if (local || laneId == null) return;
|
|
let alive = true;
|
|
const load = () =>
|
|
Promise.resolve()
|
|
.then(() => api.lanes.presence(laneId))
|
|
.then((r) => alive && setPanes(r.panes))
|
|
.catch(() => {
|
|
/* presence is a nicety: no list, no chips */
|
|
});
|
|
void load();
|
|
const offMsg = eventBus.subscribe((msg) => {
|
|
if (msg.type !== "pane_presence") return;
|
|
const d = msg.data as { laneId?: number; runId?: string; users?: PanePeople[string] };
|
|
if (d.laneId !== laneId || !d.runId) return;
|
|
setPanes((prev) => {
|
|
const next = { ...prev };
|
|
if (d.users?.length) next[d.runId!] = d.users;
|
|
else delete next[d.runId!];
|
|
return next;
|
|
});
|
|
});
|
|
const offConn = eventBus.onConnection((up) => {
|
|
if (up) void load();
|
|
});
|
|
return () => {
|
|
alive = false;
|
|
offMsg();
|
|
offConn();
|
|
};
|
|
}, [laneId, local]);
|
|
return panes;
|
|
}
|