mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 06:05:24 +00:00
fix(session-sharing): replace local presentation after takeover (#1500)
This commit is contained in:
1 parent
fb28cd1f21
commit
f6ea218100
2 files changed
+113
No files matched your search
@@ -145,6 +145,7 @@ export function CollabRoomTab({
|
||||
const draftRef = useRef<PresentDraft | null>(null);
|
||||
draftRef.current = draft;
|
||||
const stageKeyRef = useRef<string | null>(null);
|
||||
const presenterRef = useRef<string | null>(null);
|
||||
const refreshSequence = useRef(0);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
@@ -153,6 +154,14 @@ export function CollabRoomTab({
|
||||
try {
|
||||
const nextDetail = await getCollabRoom(roomId);
|
||||
if (sequence !== refreshSequence.current) return;
|
||||
// Only discard a confirmed presentation that another member took over.
|
||||
if (
|
||||
presenterRef.current === nextDetail.me &&
|
||||
nextDetail.stage.presenterUserId !== nextDetail.me
|
||||
) {
|
||||
setDraft(null);
|
||||
}
|
||||
presenterRef.current = nextDetail.stage.presenterUserId;
|
||||
setDetail(nextDetail);
|
||||
setLoadError(null);
|
||||
// Presenting locally? The local session is the stage - don't join it.
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import ts from "typescript";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import roomSource from "../../src/frontend/CollabRoomTab.tsx?raw";
|
||||
|
||||
// Run the room's real refresh callback across successive server snapshots.
|
||||
const source = ts.createSourceFile(
|
||||
"CollabRoomTab.tsx",
|
||||
roomSource,
|
||||
ts.ScriptTarget.Latest,
|
||||
true,
|
||||
ts.ScriptKind.TSX,
|
||||
);
|
||||
let callback: string | undefined;
|
||||
function visit(node: ts.Node) {
|
||||
if (
|
||||
ts.isVariableDeclaration(node) &&
|
||||
node.name.getText(source) === "refresh" &&
|
||||
node.initializer &&
|
||||
ts.isCallExpression(node.initializer)
|
||||
) {
|
||||
callback = node.initializer.arguments[0].getText(source);
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
}
|
||||
visit(source);
|
||||
if (!callback) throw new Error("Missing room refresh callback");
|
||||
const refresh = new Function(
|
||||
"state",
|
||||
ts.transpileModule(
|
||||
`
|
||||
const { roomId, refreshSequence, presenterRef, stageKeyRef, draftRef,
|
||||
getCollabRoom, getCollabStage, setDetail, setLoadError, setDraft,
|
||||
setStage, getErrorMessage } = state;
|
||||
return (${callback})();
|
||||
`,
|
||||
{ compilerOptions: { target: ts.ScriptTarget.ES2022 } },
|
||||
).outputText,
|
||||
) as (state: ReturnType<typeof fixture>) => Promise<void>;
|
||||
|
||||
function snapshot(presenterUserId: string, shareId: string) {
|
||||
return {
|
||||
me: "alice",
|
||||
stage: { presenterUserId, shareId },
|
||||
controllerUserId: null,
|
||||
};
|
||||
}
|
||||
|
||||
function fixture() {
|
||||
const state = {
|
||||
roomId: "room",
|
||||
refreshSequence: { current: 0 },
|
||||
presenterRef: { current: null as string | null },
|
||||
stageKeyRef: { current: null as string | null },
|
||||
draftRef: { current: null as object | null },
|
||||
getCollabRoom: vi.fn(async () => snapshot("alice", "alice-share")),
|
||||
getCollabStage: vi.fn(async () => ({ stage: { shareId: "bob-share" } })),
|
||||
setDetail: vi.fn(),
|
||||
setLoadError: vi.fn(),
|
||||
setDraft: vi.fn((draft: object | null) => {
|
||||
state.draftRef.current = draft;
|
||||
}),
|
||||
setStage: vi.fn(),
|
||||
getErrorMessage: String,
|
||||
};
|
||||
return state;
|
||||
}
|
||||
|
||||
describe("meeting presenter takeover", () => {
|
||||
it("replaces the previous presenter's local session with the new stage", async () => {
|
||||
const state = fixture();
|
||||
state.draftRef.current = { protocol: "ssh", host: { id: 1 } };
|
||||
await refresh(state);
|
||||
state.getCollabRoom.mockResolvedValue(snapshot("bob", "bob-share"));
|
||||
await refresh(state);
|
||||
|
||||
expect(state.draftRef.current).toBeNull();
|
||||
expect(state.setStage).toHaveBeenCalledWith({ shareId: "bob-share" });
|
||||
expect(state.getCollabStage).toHaveBeenCalledWith("room");
|
||||
});
|
||||
|
||||
it("keeps a new connection being prepared to take over another presenter", async () => {
|
||||
const state = fixture();
|
||||
state.getCollabRoom.mockResolvedValue(snapshot("bob", "bob-share"));
|
||||
await refresh(state);
|
||||
const draft = { protocol: "ssh", host: { id: 2 } };
|
||||
state.draftRef.current = draft;
|
||||
await refresh(state);
|
||||
|
||||
expect(state.draftRef.current).toBe(draft);
|
||||
expect(state.setDraft).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps the local session during ordinary room refreshes", async () => {
|
||||
const state = fixture();
|
||||
const draft = { protocol: "ssh", host: { id: 1 } };
|
||||
state.draftRef.current = draft;
|
||||
await refresh(state);
|
||||
await refresh(state);
|
||||
|
||||
expect(state.draftRef.current).toBe(draft);
|
||||
expect(state.getCollabStage).not.toHaveBeenCalled();
|
||||
expect(state.setDraft).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user