@datum-cloud/activity-ui 0.6.0-dev.5b51180 → 0.6.0-dev.917556e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"useActivityFeed.d.ts","sourceRoot":"","sources":["../../src/hooks/useActivityFeed.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useActivityFeed.d.ts","sourceRoot":"","sources":["../../src/hooks/useActivityFeed.ts"],"names":[],"mappings":"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"}
package/dist/index.esm.js CHANGED
@@ -9784,6 +9784,14 @@ function ActionToggle({ value, onChange, className = '', disabled = false, }) {
9784
9784
 
9785
9785
  // Debounce delay for filter changes (ms)
9786
9786
  const FILTER_DEBOUNCE_MS$1 = 300;
9787
+ // Backoff for watch stream reconnects. Without this, a persistently failing
9788
+ // upstream gets re-opened on every render with no delay — see
9789
+ // https://github.com/datum-cloud/infra/issues/5060, where a single browser
9790
+ // tab reconnected roughly 2.5 times a minute against a stream that kept
9791
+ // aborting, driving the proxy's memory up until the process was OOMKilled.
9792
+ const RECONNECT_BASE_DELAY_MS = 1000;
9793
+ const RECONNECT_MAX_DELAY_MS = 30000;
9794
+ const MAX_RECONNECT_ATTEMPTS = 8;
9787
9795
  /**
9788
9796
  * Escape a value for safe interpolation into a CEL string literal.
9789
9797
  * Prevents injection via values containing backslashes or double quotes.
@@ -9878,6 +9886,12 @@ function useActivityFeed({ client, initialFilters = {}, initialTimeRange = { sta
9878
9886
  const resourceVersionRef = useRef();
9879
9887
  // Track the watch stop function
9880
9888
  const watchStopRef = useRef(null);
9889
+ // Consecutive watch failures since the last successful event, used to back
9890
+ // off reconnect attempts. Reset on any real event (including BOOKMARK) and
9891
+ // on a manual startStreaming() call.
9892
+ const reconnectAttemptsRef = useRef(0);
9893
+ // Pending scheduled reconnect, if any.
9894
+ const reconnectTimerRef = useRef(null);
9881
9895
  // Track whether streaming should restart after filter change
9882
9896
  const shouldRestartStreamingRef = useRef(false);
9883
9897
  // Track if we've done the initial load
@@ -9952,6 +9966,9 @@ function useActivityFeed({ client, initialFilters = {}, initialTimeRange = { sta
9952
9966
  console.error('Watch error:', event.object);
9953
9967
  return;
9954
9968
  }
9969
+ // A real event (including a BOOKMARK keep-alive) means the connection is
9970
+ // healthy — reset backoff so a later failure starts from the base delay.
9971
+ reconnectAttemptsRef.current = 0;
9955
9972
  if (event.type === 'BOOKMARK') {
9956
9973
  // Update resource version for resume capability
9957
9974
  if (event.object.metadata?.resourceVersion) {
@@ -10017,15 +10034,13 @@ function useActivityFeed({ client, initialFilters = {}, initialTimeRange = { sta
10017
10034
  setActivities((prev) => prev.filter((a) => a.metadata?.name !== event.object.metadata?.name));
10018
10035
  }
10019
10036
  }, [filters]);
10020
- // Start watching for real-time updates
10021
- const startStreaming = useCallback(() => {
10037
+ // Open the watch connection. Does not touch userPaused/backoff state —
10038
+ // callers (startStreaming, scheduleReconnect) own that.
10039
+ const connectWatch = useCallback(() => {
10022
10040
  if (watchStopRef.current) {
10023
10041
  // Already watching
10024
10042
  return;
10025
10043
  }
10026
- // Explicit start clears the user-paused flag so the auto-restart
10027
- // effects can keep the stream alive after subsequent filter changes.
10028
- setUserPaused(false);
10029
10044
  // Clear any previous watch error when starting a new stream
10030
10045
  setWatchError(null);
10031
10046
  const params = buildWatchParams();
@@ -10039,6 +10054,7 @@ function useActivityFeed({ client, initialFilters = {}, initialTimeRange = { sta
10039
10054
  setWatchError(streamError);
10040
10055
  setIsStreaming(false);
10041
10056
  watchStopRef.current = null;
10057
+ reconnectAttemptsRef.current += 1;
10042
10058
  },
10043
10059
  onClose: () => {
10044
10060
  setIsStreaming(false);
@@ -10049,11 +10065,50 @@ function useActivityFeed({ client, initialFilters = {}, initialTimeRange = { sta
10049
10065
  setIsStreaming(true);
10050
10066
  setNewActivitiesCount(0);
10051
10067
  }, [client, buildWatchParams, handleWatchEvent]);
10068
+ // Reconnect with exponential backoff. The first attempt (no prior
10069
+ // failures) connects immediately; each subsequent failure doubles the
10070
+ // delay up to RECONNECT_MAX_DELAY_MS. After MAX_RECONNECT_ATTEMPTS
10071
+ // consecutive failures, this stops retrying automatically — the watch
10072
+ // stays stopped (with watchError set) until the user calls
10073
+ // startStreaming() again, which resets the attempt count.
10074
+ const scheduleReconnect = useCallback(() => {
10075
+ if (reconnectTimerRef.current) {
10076
+ // A reconnect is already scheduled.
10077
+ return;
10078
+ }
10079
+ if (reconnectAttemptsRef.current >= MAX_RECONNECT_ATTEMPTS) {
10080
+ return;
10081
+ }
10082
+ const attempt = reconnectAttemptsRef.current;
10083
+ const delay = attempt === 0 ? 0 : Math.min(RECONNECT_MAX_DELAY_MS, RECONNECT_BASE_DELAY_MS * 2 ** (attempt - 1));
10084
+ reconnectTimerRef.current = setTimeout(() => {
10085
+ reconnectTimerRef.current = null;
10086
+ connectWatch();
10087
+ }, delay);
10088
+ }, [connectWatch]);
10089
+ // Start watching for real-time updates. This is the user/effect-facing
10090
+ // entry point: it resets backoff and any pending scheduled reconnect
10091
+ // before connecting, so a manual retry always starts from a clean slate.
10092
+ const startStreaming = useCallback(() => {
10093
+ // Explicit start clears the user-paused flag so the auto-restart
10094
+ // effects can keep the stream alive after subsequent filter changes.
10095
+ setUserPaused(false);
10096
+ reconnectAttemptsRef.current = 0;
10097
+ if (reconnectTimerRef.current) {
10098
+ clearTimeout(reconnectTimerRef.current);
10099
+ reconnectTimerRef.current = null;
10100
+ }
10101
+ connectWatch();
10102
+ }, [connectWatch]);
10052
10103
  // Stop watching. Marks the user as paused so the auto-start effects
10053
10104
  // below don't immediately re-open the stream this call just closed.
10054
10105
  const stopStreaming = useCallback(() => {
10055
10106
  setUserPaused(true);
10056
10107
  setIsStreaming(false);
10108
+ if (reconnectTimerRef.current) {
10109
+ clearTimeout(reconnectTimerRef.current);
10110
+ reconnectTimerRef.current = null;
10111
+ }
10057
10112
  if (watchStopRef.current) {
10058
10113
  watchStopRef.current();
10059
10114
  watchStopRef.current = null;
@@ -10216,9 +10271,10 @@ function useActivityFeed({ client, initialFilters = {}, initialTimeRange = { sta
10216
10271
  }
10217
10272
  };
10218
10273
  }, [filters, timeRange]); // eslint-disable-line react-hooks/exhaustive-deps
10219
- // Auto-start streaming after initial load when enabled. Skipped when
10220
- // the user has explicitly paused — otherwise clicking Pause would be
10221
- // immediately undone by this effect on the next render.
10274
+ // Auto-start streaming after initial load when enabled, and reconnect
10275
+ // (with backoff — see scheduleReconnect) whenever a connection drops.
10276
+ // Skipped when the user has explicitly paused — otherwise clicking Pause
10277
+ // would be immediately undone by this effect on the next render.
10222
10278
  useEffect(() => {
10223
10279
  if (enableStreaming &&
10224
10280
  autoStartStreaming &&
@@ -10226,9 +10282,9 @@ function useActivityFeed({ client, initialFilters = {}, initialTimeRange = { sta
10226
10282
  activities.length > 0 &&
10227
10283
  !isStreaming &&
10228
10284
  !isLoading) {
10229
- startStreaming();
10285
+ scheduleReconnect();
10230
10286
  }
10231
- }, [enableStreaming, autoStartStreaming, userPaused, activities.length, isStreaming, isLoading, startStreaming]);
10287
+ }, [enableStreaming, autoStartStreaming, userPaused, activities.length, isStreaming, isLoading, scheduleReconnect]);
10232
10288
  // Restart streaming after filter change refresh completes. Also skipped
10233
10289
  // when the user has paused — a filter change shouldn't silently resume
10234
10290
  // a stream the user intentionally stopped.
@@ -10240,15 +10296,18 @@ function useActivityFeed({ client, initialFilters = {}, initialTimeRange = { sta
10240
10296
  !isStreaming &&
10241
10297
  !isLoading) {
10242
10298
  shouldRestartStreamingRef.current = false;
10243
- startStreaming();
10299
+ scheduleReconnect();
10244
10300
  }
10245
- }, [enableStreaming, userPaused, activities.length, isStreaming, isLoading, startStreaming]);
10301
+ }, [enableStreaming, userPaused, activities.length, isStreaming, isLoading, scheduleReconnect]);
10246
10302
  // Cleanup on unmount
10247
10303
  useEffect(() => {
10248
10304
  return () => {
10249
10305
  if (watchStopRef.current) {
10250
10306
  watchStopRef.current();
10251
10307
  }
10308
+ if (reconnectTimerRef.current) {
10309
+ clearTimeout(reconnectTimerRef.current);
10310
+ }
10252
10311
  if (filterDebounceRef.current) {
10253
10312
  clearTimeout(filterDebounceRef.current);
10254
10313
  }