Factor out hook for receiving messages from the extension

This commit is contained in:
Asger F
2024-11-12 15:25:52 +01:00
parent 5969c6f5d3
commit c9fbafb919
8 changed files with 45 additions and 117 deletions

View File

@@ -0,0 +1,27 @@
import { useEffect } from "react";
/**
* Invokes the given callback when a message is received from the extension.
*/
export function useMessageFromExtension<T>(
onEvent: (event: T) => void,
onEventDependencies: unknown[],
): void {
useEffect(() => {
const listener = (evt: MessageEvent) => {
if (evt.origin === window.origin) {
onEvent(evt.data as T);
} else {
// sanitize origin
const origin = evt.origin.replace(/\n|\r/g, "");
console.error(`Invalid event origin ${origin}`);
}
};
window.addEventListener("message", listener);
return () => {
window.removeEventListener("message", listener);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, onEventDependencies);
}

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, useRef } from "react";
import { useState, useRef } from "react";
import { styled } from "styled-components";
import type {
@@ -16,6 +16,7 @@ import CompareTable from "./CompareTable";
import "../results/resultsView.css";
import { assertNever } from "../../common/helpers-pure";
import { useMessageFromExtension } from "../common/useMessageFromExtension";
const Header = styled.div`
display: flex;
@@ -50,10 +51,7 @@ export function Compare(_: Record<string, never>): React.JSX.Element {
comparison?.result &&
(comparison.result.to.length || comparison.result.from.length);
useEffect(() => {
const listener = (evt: MessageEvent) => {
if (evt.origin === window.origin) {
const msg: ToCompareViewMessage = evt.data;
useMessageFromExtension<ToCompareViewMessage>((msg) => {
switch (msg.t) {
case "setComparisonQueryInfo":
setQueryInfo(msg);
@@ -148,17 +146,6 @@ export function Compare(_: Record<string, never>): React.JSX.Element {
default:
assertNever(msg);
}
} else {
// sanitize origin
const origin = evt.origin.replace(/\n|\r/g, "");
console.error(`Invalid event origin ${origin}`);
}
};
window.addEventListener("message", listener);
return () => {
window.removeEventListener("message", listener);
};
}, []);
if (!queryInfo || !comparison) {

View File

@@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
import type { ToDataFlowPathsMessage } from "../../common/interface-types";
import type { DataFlowPaths as DataFlowPathsDomainModel } from "../../variant-analysis/shared/data-flow-paths";
import { DataFlowPaths } from "./DataFlowPaths";
import { useMessageFromExtension } from "../common/useMessageFromExtension";
export type DataFlowPathsViewProps = {
dataFlowPaths?: DataFlowPathsDomainModel;
@@ -14,28 +15,12 @@ export function DataFlowPathsView({
DataFlowPathsDomainModel | undefined
>(initialDataFlowPaths);
useEffect(() => {
const listener = (evt: MessageEvent) => {
if (evt.origin === window.origin) {
const msg: ToDataFlowPathsMessage = evt.data;
if (msg.t === "setDataFlowPaths") {
useMessageFromExtension<ToDataFlowPathsMessage>((msg) => {
setDataFlowPaths(msg.dataFlowPaths);
// Scroll to the top of the page when we're rendering
// new data flow paths.
window.scrollTo(0, 0);
}
} else {
// sanitize origin
const origin = evt.origin.replace(/\n|\r/g, "");
console.error(`Invalid event origin ${origin}`);
}
};
window.addEventListener("message", listener);
return () => {
window.removeEventListener("message", listener);
};
}, []);
if (!dataFlowPaths) {

View File

@@ -12,6 +12,7 @@ import { NoMethodSelected } from "./NoMethodSelected";
import type { MethodModelingPanelViewState } from "../../model-editor/shared/view-state";
import { MethodAlreadyModeled } from "./MethodAlreadyModeled";
import { defaultModelConfig } from "../../model-editor/languages";
import { useMessageFromExtension } from "../common/useMessageFromExtension";
type Props = {
initialViewState?: MethodModelingPanelViewState;
@@ -36,10 +37,7 @@ export function MethodModelingView({
[modeledMethods, isMethodModified],
);
useEffect(() => {
const listener = (evt: MessageEvent) => {
if (evt.origin === window.origin) {
const msg: ToMethodModelingMessage = evt.data;
useMessageFromExtension<ToMethodModelingMessage>((msg) => {
switch (msg.t) {
case "setMethodModelingPanelViewState":
setViewState(msg.viewState);
@@ -66,17 +64,6 @@ export function MethodModelingView({
default:
assertNever(msg);
}
} else {
// sanitize origin
const origin = evt.origin.replace(/\n|\r/g, "");
console.error(`Invalid event origin ${origin}`);
}
};
window.addEventListener("message", listener);
return () => {
window.removeEventListener("message", listener);
};
}, []);
if (!inModelingMode || !viewState?.language) {

View File

@@ -18,6 +18,7 @@ import {
} from "../../model-editor/shared/model-alerts-filter-sort";
import type { ModelAlertsFilterSortState } from "../../model-editor/shared/model-alerts-filter-sort";
import type { ModeledMethod } from "../../model-editor/modeled-method";
import { useMessageFromExtension } from "../common/useMessageFromExtension";
type Props = {
initialViewState?: ModelAlertsViewState;
@@ -67,10 +68,7 @@ export function ModelAlerts({
null,
);
useEffect(() => {
const listener = (evt: MessageEvent) => {
if (evt.origin === window.origin) {
const msg: ToModelAlertsMessage = evt.data;
useMessageFromExtension<ToModelAlertsMessage>((msg) => {
switch (msg.t) {
case "setModelAlertsViewState": {
setViewState(msg.viewState);
@@ -97,17 +95,6 @@ export function ModelAlerts({
break;
}
}
} else {
// sanitize origin
const origin = evt.origin.replace(/\n|\r/g, "");
console.error(`Invalid event origin ${origin}`);
}
};
window.addEventListener("message", listener);
return () => {
window.removeEventListener("message", listener);
};
}, []);
const modelAlerts = useMemo(() => {

View File

@@ -21,6 +21,7 @@ import { INITIAL_HIDE_MODELED_METHODS_VALUE } from "../../model-editor/shared/hi
import type { AccessPathSuggestionOptions } from "../../model-editor/suggestions";
import type { ModelEvaluationRunState } from "../../model-editor/shared/model-evaluation-run-state";
import { ModelEvaluation } from "./ModelEvaluation";
import { useMessageFromExtension } from "../common/useMessageFromExtension";
const LoadingContainer = styled.div`
text-align: center;
@@ -129,10 +130,7 @@ export function ModelEditor({
AccessPathSuggestionOptions | undefined
>(undefined);
useEffect(() => {
const listener = (evt: MessageEvent) => {
if (evt.origin === window.origin) {
const msg: ToModelEditorMessage = evt.data;
useMessageFromExtension<ToModelEditorMessage>((msg) => {
switch (msg.t) {
case "setModelEditorViewState":
setViewState(msg.viewState);
@@ -159,17 +157,6 @@ export function ModelEditor({
default:
assertNever(msg);
}
} else {
// sanitize origin
const origin = evt.origin.replace(/\n|\r/g, "");
console.error(`Invalid event origin ${origin}`);
}
};
window.addEventListener("message", listener);
return () => {
window.removeEventListener("message", listener);
};
}, []);
useEffect(() => {

View File

@@ -16,11 +16,12 @@ import {
DEFAULT_USER_SETTINGS,
GRAPH_TABLE_NAME,
} from "../../common/interface-types";
import { useMessageFromExtension } from "../common/useMessageFromExtension";
import { ResultTables } from "./ResultTables";
import { onNavigation } from "./navigation";
import "./resultsView.css";
import { useCallback, useEffect, useState } from "react";
import { useCallback, useState } from "react";
/**
* ResultsApp.tsx
@@ -113,8 +114,8 @@ export function ResultsApp() {
[],
);
const handleMessage = useCallback(
(msg: IntoResultsViewMsg): void => {
useMessageFromExtension<IntoResultsViewMsg>(
(msg) => {
switch (msg.t) {
case "setUserSettings":
setUserSettings(msg.userSettings);
@@ -189,26 +190,6 @@ export function ResultsApp() {
[updateStateWithNewResultsInfo],
);
const vscodeMessageHandler = useCallback(
(evt: MessageEvent) => {
// sanitize origin
const origin = evt.origin.replace(/\n|\r/g, "");
if (evt.origin === window.origin) {
handleMessage(evt.data as IntoResultsViewMsg);
} else {
console.error(`Invalid event origin ${origin}`);
}
},
[handleMessage],
);
useEffect(() => {
window.addEventListener("message", vscodeMessageHandler);
return () => {
window.removeEventListener("message", vscodeMessageHandler);
};
}, [vscodeMessageHandler]);
const { displayedResults, nextResultsInfo, isExpectingResultsUpdate } = state;
if (
displayedResults.results !== null &&

View File

@@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from "react";
import { useCallback, useState } from "react";
import type {
VariantAnalysis as VariantAnalysisDomainModel,
@@ -13,6 +13,7 @@ import type { ToVariantAnalysisMessage } from "../../common/interface-types";
import { vscode } from "../vscode-api";
import { defaultFilterSortState } from "../../variant-analysis/shared/variant-analysis-filter-sort";
import { sendTelemetry, useTelemetryOnChange } from "../common/telemetry";
import { useMessageFromExtension } from "../common/useMessageFromExtension";
export type VariantAnalysisProps = {
variantAnalysis?: VariantAnalysisDomainModel;
@@ -77,10 +78,7 @@ export function VariantAnalysis({
debounceTimeoutMillis: 1000,
});
useEffect(() => {
const listener = (evt: MessageEvent) => {
if (evt.origin === window.origin) {
const msg: ToVariantAnalysisMessage = evt.data;
useMessageFromExtension<ToVariantAnalysisMessage>((msg) => {
if (msg.t === "setVariantAnalysis") {
setVariantAnalysis(msg.variantAnalysis);
vscode.setState({
@@ -109,17 +107,6 @@ export function VariantAnalysis({
];
});
}
} else {
// sanitize origin
const origin = evt.origin.replace(/\n|\r/g, "");
console.error(`Invalid event origin ${origin}`);
}
};
window.addEventListener("message", listener);
return () => {
window.removeEventListener("message", listener);
};
}, []);
const copyRepositoryList = useCallback(() => {