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.
This commit is contained in:
parent
a71e6d2012
commit
320daf2f67
1 changed files with 1656 additions and 90 deletions
|
|
@ -3,17 +3,28 @@ import type {
|
|||
ResourceSubscriptionsState,
|
||||
ResourceSubscriptionsStateEventMap,
|
||||
} from "../mcp/state/resourceSubscriptionsState.js";
|
||||
import type { InspectorResourceSubscription } from "../mcp/types.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[]. When the state is null (no active
|
||||
* server), returns an empty array.
|
||||
* 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,
|
||||
|
|
@ -21,13 +32,19 @@ export function useResourceSubscriptions(
|
|||
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,
|
||||
|
|
@ -36,11 +53,21 @@ export function useResourceSubscriptions(
|
|||
) => {
|
||||
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 };
|
||||
return { subscriptions, streamState };
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue