vojo/src/app/hooks/useIsBridgedRoom.ts

44 lines
1.9 KiB
TypeScript

import { useEffect, useState } from 'react';
import { EventTimeline, MatrixEvent, Room, RoomStateEvent } from 'matrix-js-sdk';
import { isBridgedRoom } from '../utils/room';
import { StateEvent } from '../../types/matrix/room';
// Reactive «is this a bridged room» — re-runs when an `m.bridge` /
// `uk.half-shot.bridge` state event lands so the call-button gate flips
// immediately when a bridge bot finishes provisioning. Without the
// subscription the gate would freeze at first render and a late bridge
// event would let the phone button stay visible on a Telegram puppet
// room until something else triggered a rerender — see
// dm_1x1_redesign.md §6.8b for why bridged 1:1 rooms must never expose
// the call surface.
//
// `useStateEvent(room, StateEvent.RoomBridge)` is not enough here —
// `m.bridge` is keyed by the bridge bot mxid (`state_key` ≠ ''), so the
// «empty key» helper would never see the event. We subscribe to the
// generic `RoomStateEvent.Events` stream and re-check on any matching
// type.
//
// The captured `state` keeps cleanup leak-safe; we don't try to follow a
// rare live-timeline state-container swap on the same `room` identity —
// in that corner case the gate would go stale until the next mount.
export const useIsBridgedRoom = (room: Room): boolean => {
const [value, setValue] = useState<boolean>(() => isBridgedRoom(room));
useEffect(() => {
const state = room.getLiveTimeline().getState(EventTimeline.FORWARDS);
setValue(isBridgedRoom(room));
if (!state) return undefined;
const handler = (event: MatrixEvent) => {
const type = event.getType();
if (type === StateEvent.RoomBridge || type === StateEvent.RoomBridgeUnstable) {
setValue(isBridgedRoom(room));
}
};
state.on(RoomStateEvent.Events, handler);
return () => {
state.removeListener(RoomStateEvent.Events, handler);
};
}, [room]);
return value;
};