Bump langsmith to 0.5.20 and override @langchain/classic to fix CVE

langsmith <=0.5.18 leaks streamed LLM output past redaction controls
(GHSA-rr7j-v2q5-chgv). The transitive pin via @n8n/ai-utilities held
@langchain/classic at 1.0.5 which required langsmith ^0.3.64, blocking
the upgrade. Add npm overrides for @langchain/classic (^1.0.27) and
langsmith (^0.5.19) to resolve all three langsmith advisories.

The lockfile refresh bumps eslint-plugin-react-hooks from 7.0.1 to
7.1.0 which enables the react-hooks/refs and set-state-in-effect
rules. Fix the resulting lint errors:

- PDFPreview (console + trust): read currentPage through a ref synced
  via useEffect, keeping the setState updater pure and ref access
  outside of render
- MeasuresPage: remove redundant categoryFilter state that mirrored
  urlCategory, use a ref to detect URL changes and trigger refetch
  as the single source of category refetches
- WebhooksSettingsPage: defer loadEvents via requestAnimationFrame
  to avoid synchronous setState within the effect body

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2026-04-17 15:08:13 +02:00
parent e1148f812e
commit 8ce429507b
6 changed files with 287 additions and 261 deletions

View File

@@ -21,7 +21,7 @@ import {
Spinner,
} from "@probo/ui";
import { IconMinusLarge } from "@probo/ui/src/Atoms/Icons/IconMinusLarge";
import { type ComponentProps, useRef, useState } from "react";
import { type ComponentProps, useCallback, useEffect, useRef, useState } from "react";
import { Document, Page, pdfjs } from "react-pdf";
import "react-pdf/dist/Page/TextLayer.css";
@@ -51,11 +51,17 @@ export function PDFPreview({ src, name }: { src: string; name?: string }) {
setScale(scale * factor);
};
const movePage = (direction: 1 | -1) => () => {
if (currentPage === 1 && direction === -1) {
const currentPageRef = useRef(currentPage);
useEffect(() => {
currentPageRef.current = currentPage;
}, [currentPage]);
const movePage = useCallback((direction: 1 | -1) => {
const prev = currentPageRef.current;
if (prev === 1 && direction === -1) {
return;
}
const newPage = currentPage + direction;
const newPage = prev + direction;
const page = documentRef.current?.pages.current[newPage - 1];
if (!page) {
return;
@@ -66,9 +72,9 @@ export function PDFPreview({ src, name }: { src: string; name?: string }) {
inline: "center",
});
setCurrentPage(newPage);
};
}, []);
const resolveCurrentPage = () => {
const resolveCurrentPage = useCallback(() => {
if (!wrapperRef.current) {
return;
}
@@ -81,10 +87,11 @@ export function PDFPreview({ src, name }: { src: string; name?: string }) {
for (let i = 0; i < pages.length; i++) {
const childRect = pages[i].getBoundingClientRect();
if (childRect.top <= parentMiddleY && childRect.bottom >= parentMiddleY) {
return setCurrentPage(i + 1);
setCurrentPage(i + 1);
return;
}
}
};
}, []);
const handleDownload = () => {
const link = document.createElement("a");
@@ -100,7 +107,7 @@ export function PDFPreview({ src, name }: { src: string; name?: string }) {
<div>{name}</div>
<div className="mx-auto flex gap-1 items-center">
<button
onClick={movePage(-1)}
onClick={() => movePage(-1)}
className={btnClass}
disabled={currentPage === 1}
>
@@ -112,7 +119,7 @@ export function PDFPreview({ src, name }: { src: string; name?: string }) {
/
{numPages}
</div>
<button onClick={movePage(1)} className={btnClass}>
<button onClick={() => movePage(1)} className={btnClass}>
<IconChevronRight size={16} />
</button>
</div>

View File

@@ -185,8 +185,6 @@ export default function MeasuresPage({ queryRef }: MeasuresPageProps) {
const [isPending, startTransition] = useTransition();
const [queryFilter, setQueryFilter] = useState<string | null>(null);
const [stateFilter, setStateFilter] = useState<MeasureState | null>(null);
const [categoryFilter, setCategoryFilter] = useState<string | null>(urlCategory);
const { data, loadNext, hasNext, isLoadingNext, refetch }
= usePaginationFragment<MeasuresPageRefetchQuery, MeasuresPageFragment$key>(
measuresPageFragment,
@@ -199,7 +197,7 @@ export default function MeasuresPage({ queryRef }: MeasuresPageProps) {
{
query: queryFilter,
state: stateFilter,
category: categoryFilter,
category: urlCategory,
...overrides,
},
{ fetchPolicy: "network-only" },
@@ -208,6 +206,7 @@ export default function MeasuresPage({ queryRef }: MeasuresPageProps) {
};
const initialUrlCategory = useRef(urlCategory);
const prevUrlCategory = useRef(urlCategory);
useEffect(() => {
if (initialUrlCategory.current) {
startTransition(() => {
@@ -224,12 +223,11 @@ export default function MeasuresPage({ queryRef }: MeasuresPageProps) {
}, [refetch, startTransition]);
useEffect(() => {
if (urlCategory !== categoryFilter) {
setCategoryFilter(urlCategory);
if (urlCategory !== prevUrlCategory.current) {
prevUrlCategory.current = urlCategory;
refetchFilters({ category: urlCategory });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [urlCategory]);
});
const handleQueryFilterChange = (value: string) => {
const newQuery = value === "" ? null : value;
@@ -245,7 +243,6 @@ export default function MeasuresPage({ queryRef }: MeasuresPageProps) {
const handleCategoryFilterChange = (value: string) => {
const newCategory = value === "ALL" ? null : value;
setCategoryFilter(newCategory);
setSearchParams((prev) => {
const next = new URLSearchParams(prev);
if (newCategory) {
@@ -255,13 +252,12 @@ export default function MeasuresPage({ queryRef }: MeasuresPageProps) {
}
return next;
}, { replace: true });
refetchFilters({ category: newCategory });
};
const currentFilter = {
query: queryFilter,
state: stateFilter,
category: categoryFilter,
category: urlCategory,
};
const connectionId = ConnectionHandler.getConnectionID(
@@ -274,7 +270,7 @@ export default function MeasuresPage({ queryRef }: MeasuresPageProps) {
MeasuresConnectionKey,
{ filter: { query: null, state: null, category: null } },
);
const hasActiveFilter = queryFilter || stateFilter || categoryFilter;
const hasActiveFilter = queryFilter || stateFilter || urlCategory;
const createConnectionIds = hasActiveFilter
? [allFiltersNullConnectionId, connectionId]
: [connectionId];
@@ -362,7 +358,7 @@ export default function MeasuresPage({ queryRef }: MeasuresPageProps) {
<Option value="NOT_APPLICABLE">{getMeasureStateLabel(__, "NOT_APPLICABLE")}</Option>
</Select>
<Select
value={categoryFilter ?? "ALL"}
value={urlCategory ?? "ALL"}
onValueChange={handleCategoryFilterChange}
>
<Option value="ALL">{__("All categories")}</Option>

View File

@@ -377,8 +377,9 @@ function WebhookEventsDialog({
);
useEffect(() => {
void loadEvents();
dialogRef.current?.open();
const id = requestAnimationFrame(() => void loadEvents());
return () => cancelAnimationFrame(id);
}, [loadEvents, dialogRef]);
return (

View File

@@ -24,7 +24,7 @@ import {
Spinner,
} from "@probo/ui";
import { IconMinusLarge } from "@probo/ui/src/Atoms/Icons/IconMinusLarge";
import { type ComponentProps, useRef, useState } from "react";
import { type ComponentProps, useCallback, useEffect, useRef, useState } from "react";
import { Document, Page, pdfjs } from "react-pdf";
// Worker for PDF.js
@@ -51,11 +51,17 @@ export function PDFPreview({ src, name }: { src: string; name?: string }) {
setScale(scale * factor);
};
const movePage = (direction: 1 | -1) => () => {
if (currentPage === 1 && direction === -1) {
const currentPageRef = useRef(currentPage);
useEffect(() => {
currentPageRef.current = currentPage;
}, [currentPage]);
const movePage = useCallback((direction: 1 | -1) => {
const prev = currentPageRef.current;
if (prev === 1 && direction === -1) {
return;
}
const newPage = currentPage + direction;
const newPage = prev + direction;
const page = documentRef.current?.pages.current[newPage - 1];
if (!page) {
return;
@@ -66,9 +72,9 @@ export function PDFPreview({ src, name }: { src: string; name?: string }) {
inline: "center",
});
setCurrentPage(newPage);
};
}, []);
const resolveCurrentPage = () => {
const resolveCurrentPage = useCallback(() => {
if (!wrapperRef.current) {
return;
}
@@ -81,10 +87,11 @@ export function PDFPreview({ src, name }: { src: string; name?: string }) {
for (let i = 0; i < pages.length; i++) {
const childRect = pages[i].getBoundingClientRect();
if (childRect.top <= parentMiddleY && childRect.bottom >= parentMiddleY) {
return setCurrentPage(i + 1);
setCurrentPage(i + 1);
return;
}
}
};
}, []);
const handleDownload = () => {
const link = document.createElement("a");
@@ -100,7 +107,7 @@ export function PDFPreview({ src, name }: { src: string; name?: string }) {
<div>{name}</div>
<div className="mx-auto flex gap-1 items-center">
<button
onClick={movePage(-1)}
onClick={() => movePage(-1)}
className={btnClass}
disabled={currentPage === 1}
>
@@ -112,7 +119,7 @@ export function PDFPreview({ src, name }: { src: string; name?: string }) {
/
{numPages}
</div>
<button onClick={movePage(1)} className={btnClass}>
<button onClick={() => movePage(1)} className={btnClass}>
<IconChevronRight size={16} />
</button>
</div>