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(() => 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; };