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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -377,8 +377,9 @@ function WebhookEventsDialog({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
void loadEvents();
|
||||
dialogRef.current?.open();
|
||||
const id = requestAnimationFrame(() => void loadEvents());
|
||||
return () => cancelAnimationFrame(id);
|
||||
}, [loadEvents, dialogRef]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user