inspector/core/react/useResourceSubscriptions.ts
Cliff Hall 320daf2f67
Resources: subscriptions/listen model on modern servers (era fork) (#1630) (#1730)
Fork resource subscriptions by protocol era: legacy keeps resources/subscribe; modern (2026-07-28) models subscriptions as a filter over one long-lived subscriptions/listen stream with reconnect-by-re-listen (capped backoff), an optimistic Connecting→Listening header badge, and a fix so the browser proxy no longer blocks listen() ~60s. Closes #1630.
2026-07-20 19:33:40 -04:00

73 lines
2.4 KiB
TypeScript

import { useState, useEffect } from "react";
import type {
ResourceSubscriptionsState,
ResourceSubscriptionsStateEventMap,
} from "../mcp/state/resourceSubscriptionsState.js";
import type {
InspectorResourceSubscription,
ResourceSubscriptionStreamState,
} from "../mcp/types.js";
import { INACTIVE_SUBSCRIPTION_STREAM_STATE } from "../mcp/types.js";
import type { TypedEventGeneric } from "../mcp/typedEventTarget.js";
export interface UseResourceSubscriptionsResult {
subscriptions: InspectorResourceSubscription[];
/**
* Modern-era (2026-07-28) `subscriptions/listen` stream state (#1630).
* `active: false` on the legacy era (and with no active server), so the
* Resources screen renders no stream chrome there.
*/
streamState: ResourceSubscriptionStreamState;
}
/**
* React hook that subscribes to ResourceSubscriptionsState and returns the
* current InspectorResourceSubscription[] plus the modern listen-stream state.
* When the state is null (no active server), returns an empty array and an
* inactive stream state.
*/
export function useResourceSubscriptions(
state: ResourceSubscriptionsState | null,
): UseResourceSubscriptionsResult {
const [subscriptions, setSubscriptions] = useState<
InspectorResourceSubscription[]
>(state?.getSubscriptions() ?? []);
const [streamState, setStreamState] =
useState<ResourceSubscriptionStreamState>(
state?.getStreamState() ?? INACTIVE_SUBSCRIPTION_STREAM_STATE,
);
useEffect(() => {
if (!state) {
setSubscriptions([]);
setStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
return;
}
setSubscriptions(state.getSubscriptions());
setStreamState(state.getStreamState());
const onSubscriptionsChange = (
event: TypedEventGeneric<
ResourceSubscriptionsStateEventMap,
"subscriptionsChange"
>,
) => {
setSubscriptions(event.detail);
};
const onStreamStateChange = (
event: TypedEventGeneric<
ResourceSubscriptionsStateEventMap,
"streamStateChange"
>,
) => {
setStreamState(event.detail);
};
state.addEventListener("subscriptionsChange", onSubscriptionsChange);
state.addEventListener("streamStateChange", onStreamStateChange);
return () => {
state.removeEventListener("subscriptionsChange", onSubscriptionsChange);
state.removeEventListener("streamStateChange", onStreamStateChange);
};
}, [state]);
return { subscriptions, streamState };
}