* [#449] added debounce for websocket updates * [#449] changed behavior to have disableable debounce + changed debounced messages data storage to be handled by websocket Gate
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import { useAppDispatch, useAppSelector } from "@/app/hooks";
|
||||
import { AppDispatch } from "@/app/store";
|
||||
import { useSelectedCalendars } from "@/utils/storage/useSelectedCalendars";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import type { WebSocketWithCleanup } from "./connection";
|
||||
import { closeWebSocketConnection } from "./connection/lifecycle/closeWebSocketConnection";
|
||||
import { establishWebSocketConnection } from "./connection/lifecycle/establishWebSocketConnection";
|
||||
import { updateCalendars } from "./messaging";
|
||||
import { updateCalendars } from "./messaging/updateCalendars";
|
||||
import { syncCalendarRegistrations } from "./operations";
|
||||
|
||||
export function WebSocketGate() {
|
||||
@@ -24,9 +25,25 @@ export function WebSocketGate() {
|
||||
useAppSelector((state) => state?.calendars?.templist) ?? {}
|
||||
);
|
||||
|
||||
const calendarsToRefreshRef = useRef<Map<string, any>>(new Map());
|
||||
const calendarsToHideRef = useRef<Set<string>>(new Set());
|
||||
const debouncedUpdateFnRef = useRef<
|
||||
((dispatch: AppDispatch) => void) | undefined
|
||||
>();
|
||||
const currentDebouncePeriodRef = useRef<number | undefined>();
|
||||
|
||||
const onMessage = useCallback(
|
||||
(message: unknown) => {
|
||||
updateCalendars(message, dispatch);
|
||||
const accumulators = {
|
||||
calendarsToRefresh: calendarsToRefreshRef.current,
|
||||
calendarsToHide: calendarsToHideRef.current,
|
||||
debouncedUpdateFn: debouncedUpdateFnRef.current,
|
||||
currentDebouncePeriod: currentDebouncePeriodRef.current,
|
||||
};
|
||||
updateCalendars(message, dispatch, accumulators);
|
||||
// Persist any mutations back to refs
|
||||
debouncedUpdateFnRef.current = accumulators.debouncedUpdateFn;
|
||||
currentDebouncePeriodRef.current = accumulators.currentDebouncePeriod;
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { AppDispatch } from "@/app/store";
|
||||
|
||||
export interface UpdateCalendarsAccumulators {
|
||||
calendarsToRefresh: Map<string, any>;
|
||||
calendarsToHide: Set<string>;
|
||||
debouncedUpdateFn?: (dispatch: AppDispatch) => void;
|
||||
currentDebouncePeriod?: number;
|
||||
}
|
||||
@@ -3,42 +3,157 @@ import { store } from "@/app/store";
|
||||
import { refreshCalendarWithSyncToken } from "@/features/Calendars/services";
|
||||
import { findCalendarById, getDisplayedCalendarRange } from "@/utils";
|
||||
import { setSelectedCalendars } from "@/utils/storage/setSelectedCalendars";
|
||||
import { debounce } from "lodash";
|
||||
import { parseCalendarPath } from "./parseCalendarPath";
|
||||
import { parseMessage } from "./parseMessage";
|
||||
import { UpdateCalendarsAccumulators } from "./type/UpdateCalendarsAccumulators";
|
||||
|
||||
export function updateCalendars(message: unknown, dispatch: AppDispatch) {
|
||||
const currentRange = getDisplayedCalendarRange();
|
||||
const DEFAULT_DEBOUNCE_MS = 0;
|
||||
|
||||
function createDebouncedUpdate(
|
||||
debouncePeriodMs: number,
|
||||
getCalendarsToRefresh: () => Map<string, any>,
|
||||
getCalendarsToHide: () => Set<string>
|
||||
) {
|
||||
return debounce(
|
||||
(dispatch: AppDispatch) => {
|
||||
const currentRange = getDisplayedCalendarRange();
|
||||
|
||||
// Snapshot state
|
||||
const calendarsToProcess = new Map(getCalendarsToRefresh());
|
||||
const calendarsToHideSnapshot = new Set(getCalendarsToHide());
|
||||
|
||||
// Clear accumulators
|
||||
getCalendarsToRefresh().clear();
|
||||
getCalendarsToHide().clear();
|
||||
|
||||
try {
|
||||
processCalendarsToRefresh(dispatch, currentRange, calendarsToProcess);
|
||||
processCalendarsToHide(calendarsToHideSnapshot);
|
||||
} catch (error) {
|
||||
console.warn("Error processing accumulated calendar updates:", error);
|
||||
}
|
||||
},
|
||||
debouncePeriodMs,
|
||||
{ leading: true, trailing: true }
|
||||
);
|
||||
}
|
||||
|
||||
export function updateCalendars(
|
||||
message: unknown,
|
||||
dispatch: AppDispatch,
|
||||
accumulators: UpdateCalendarsAccumulators
|
||||
) {
|
||||
const state = store.getState();
|
||||
const { calendarsToRefresh, calendarsToHide } = parseMessage(message);
|
||||
calendarsToRefresh.forEach((calendarPath) => {
|
||||
|
||||
// Accumulate
|
||||
accumulateCalendarsToRefresh(
|
||||
state,
|
||||
calendarsToRefresh,
|
||||
accumulators.calendarsToRefresh
|
||||
);
|
||||
accumulateCalendarsToHide(calendarsToHide, accumulators.calendarsToHide);
|
||||
|
||||
const debouncePeriod =
|
||||
(window as any).WS_DEBOUNCE_PERIOD_MS ?? DEFAULT_DEBOUNCE_MS;
|
||||
|
||||
if (debouncePeriod > 0) {
|
||||
if (
|
||||
!accumulators.debouncedUpdateFn ||
|
||||
accumulators.currentDebouncePeriod !== debouncePeriod
|
||||
) {
|
||||
accumulators.debouncedUpdateFn = createDebouncedUpdate(
|
||||
debouncePeriod,
|
||||
() => accumulators.calendarsToRefresh,
|
||||
() => accumulators.calendarsToHide
|
||||
);
|
||||
accumulators.currentDebouncePeriod = debouncePeriod;
|
||||
}
|
||||
accumulators.debouncedUpdateFn(dispatch);
|
||||
return;
|
||||
}
|
||||
|
||||
// Immediate processing if debounce disabled
|
||||
const currentRange = getDisplayedCalendarRange();
|
||||
const calendarsToProcess = new Map(accumulators.calendarsToRefresh);
|
||||
const calendarsToHideSnapshot = new Set(accumulators.calendarsToHide);
|
||||
|
||||
accumulators.calendarsToRefresh.clear();
|
||||
accumulators.calendarsToHide.clear();
|
||||
|
||||
try {
|
||||
processCalendarsToRefresh(dispatch, currentRange, calendarsToProcess);
|
||||
processCalendarsToHide(calendarsToHideSnapshot);
|
||||
} catch (error) {
|
||||
console.warn("Error processing calendar updates:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
function accumulateCalendarsToRefresh(
|
||||
state: ReturnType<typeof store.getState>,
|
||||
calendarPaths: Set<string>,
|
||||
calendarsToRefreshMap: Map<string, any>
|
||||
) {
|
||||
calendarPaths.forEach((calendarPath) => {
|
||||
const calendarId = parseCalendarPath(calendarPath);
|
||||
if (!calendarId) {
|
||||
console.warn("Invalid calendar path received:", calendarPath);
|
||||
return;
|
||||
}
|
||||
const calendar = findCalendarById(state, calendarId);
|
||||
if (calendar) {
|
||||
dispatch(
|
||||
refreshCalendarWithSyncToken({
|
||||
calendar: calendar.calendar,
|
||||
calType: calendar.type,
|
||||
calendarRange: currentRange,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
if (!calendar) {
|
||||
console.warn("Calendar not found for id:", calendarId);
|
||||
return;
|
||||
}
|
||||
calendarsToRefreshMap.set(calendarId, calendar);
|
||||
});
|
||||
}
|
||||
|
||||
function accumulateCalendarsToHide(
|
||||
calendarPaths: Set<string>,
|
||||
calendarsToHideSet: Set<string>
|
||||
) {
|
||||
calendarPaths.forEach((calendarPath) => {
|
||||
const calendarId = parseCalendarPath(calendarPath);
|
||||
if (calendarId) {
|
||||
calendarsToHideSet.add(calendarId);
|
||||
}
|
||||
});
|
||||
const currentSelectedCalendars = JSON.parse(
|
||||
localStorage.getItem("selectedCalendars") ?? "[]"
|
||||
) as string[];
|
||||
}
|
||||
|
||||
const calendarIdsToHide = [...calendarsToHide]
|
||||
.map(parseCalendarPath)
|
||||
.filter((id): id is string => Boolean(id));
|
||||
function processCalendarsToRefresh(
|
||||
dispatch: AppDispatch,
|
||||
currentRange: { start: Date; end: Date },
|
||||
calendarsMap: Map<string, any>
|
||||
) {
|
||||
calendarsMap.forEach((calendar) => {
|
||||
dispatch(
|
||||
refreshCalendarWithSyncToken({
|
||||
calendar: calendar.calendar,
|
||||
calType: calendar.type,
|
||||
calendarRange: currentRange,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function processCalendarsToHide(calendarsToHideSnapshot: Set<string>) {
|
||||
if (calendarsToHideSnapshot.size === 0) return;
|
||||
|
||||
let currentSelectedCalendars: string[];
|
||||
|
||||
try {
|
||||
const stored = localStorage.getItem("selectedCalendars") ?? "[]";
|
||||
currentSelectedCalendars = JSON.parse(stored);
|
||||
} catch (error) {
|
||||
console.warn("Failed to parse selectedCalendars from localStorage:", error);
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedSelectedCalendars = currentSelectedCalendars.filter(
|
||||
(id) => !calendarIdsToHide.includes(id)
|
||||
(id) => !calendarsToHideSnapshot.has(id)
|
||||
);
|
||||
|
||||
setSelectedCalendars(updatedSelectedCalendars);
|
||||
|
||||
Reference in New Issue
Block a user