chore: `useAutoupdate` (#35466)

pull/35551/head^2
Júlia Jaeger Foresti 10 months ago committed by GitHub
parent e3ea1d8923
commit 20fef4d2a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 30
      apps/meteor/client/components/AutoupdateToastMessage.tsx
  2. 54
      apps/meteor/client/hooks/useAutoupdate.spec.ts
  3. 32
      apps/meteor/client/hooks/useAutoupdate.tsx
  4. 56
      apps/meteor/client/hooks/useIdleDetection.ts
  5. 2
      apps/meteor/client/views/root/AppLayout.tsx
  6. 4
      apps/meteor/packages/autoupdate/autoupdate_client.js
  7. 6
      packages/i18n/src/locales/en.i18n.json

@ -0,0 +1,30 @@
import { css } from '@rocket.chat/css-in-js';
import { Box, Button } from '@rocket.chat/fuselage';
import { useTranslation } from 'react-i18next';
import { useIdleDetection } from '../hooks/useIdleDetection';
export const AutoupdateToastMessage = () => {
const { t } = useTranslation();
useIdleDetection(
() => {
window.location.reload();
},
{ awayOnWindowBlur: true },
);
return (
<Box
display='flex'
alignItems='center'
className={css`
gap: 8px;
`}
>
{t('An_update_is_available')}
<Button primary small onClick={() => window.location.reload()}>
{t('Reload_to_update')}
</Button>
</Box>
);
};

@ -0,0 +1,54 @@
import { useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import { renderHook } from '@testing-library/react';
import { useAutoupdate } from './useAutoupdate';
jest.mock('@rocket.chat/ui-contexts', () => ({
useToastMessageDispatch: jest.fn(() => jest.fn()),
}));
jest.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));
describe('useAutoupdate', () => {
it('should add event listener to document on mount', () => {
const addEventListenerSpy = jest.spyOn(document, 'addEventListener');
renderHook(() => useAutoupdate());
expect(addEventListenerSpy).toHaveBeenCalled();
expect(addEventListenerSpy).toHaveBeenCalledWith('client_changed', expect.any(Function));
});
it('should remove event listener on unmount', () => {
const removeEventListenerSpy = jest.spyOn(document, 'removeEventListener');
const { unmount } = renderHook(() => useAutoupdate());
unmount();
expect(removeEventListenerSpy).toHaveBeenCalled();
expect(removeEventListenerSpy).toHaveBeenCalledWith('client_changed', expect.any(Function));
});
it('should call toast function when client_changed event is fired', () => {
const toastMock = jest.fn();
(useToastMessageDispatch as jest.Mock).mockImplementation(() => toastMock);
renderHook(() => useAutoupdate());
const event = new Event('client_changed');
document.dispatchEvent(event);
expect(toastMock).toHaveBeenCalledTimes(1);
expect(toastMock).toHaveBeenCalledWith({
type: 'info',
message: expect.anything(),
options: { isPersistent: true },
});
});
it('should not call toast function when client_changed event is not fired', () => {
const toastMock = jest.fn();
(useToastMessageDispatch as jest.Mock).mockImplementation(() => toastMock);
renderHook(() => useAutoupdate());
expect(toastMock).not.toHaveBeenCalled();
});
});

@ -0,0 +1,32 @@
import { useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { AutoupdateToastMessage } from '../components/AutoupdateToastMessage';
export const useAutoupdate = () => {
const toast = useToastMessageDispatch();
const { t } = useTranslation();
const isDevMode = process.env.NODE_ENV === 'development';
useEffect(() => {
const fn = () => {
// To test this feature locally, comment the if statement below
if (isDevMode) {
window.location.reload();
return;
}
toast({
type: 'info',
options: { isPersistent: true },
message: <AutoupdateToastMessage />,
});
};
document.addEventListener('client_changed', fn);
return () => {
document.removeEventListener('client_changed', fn);
};
}, [isDevMode, t, toast]);
};

@ -0,0 +1,56 @@
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import { useEffect } from 'react';
const events = ['mousemove', 'mousedown', 'touchend', 'touchstart', 'keypress'];
/**
* useIdleDetection is a custom hook that triggers a callback function when the user is detected to be idle.
* The idle state is determined based on the absence of certain user interactions for a specified time period.
*
* @param callback - The callback function to be called when the user is detected to be idle.
* @param options - An optional configuration object.
* @param options.time - The time in milliseconds to consider the user idle. Defaults to 600000 ms (10 minutes).
* @param options.awayOnWindowBlur - A boolean flag to trigger the callback when the window loses focus. Defaults to false.
*
*/
export const useIdleDetection = (callback: () => void, { time = 600000, awayOnWindowBlur = false } = {}) => {
const stableCallback = useEffectEvent(callback);
useEffect(() => {
let interval: ReturnType<typeof setTimeout>;
const handleIdle = () => {
clearTimeout(interval);
interval = setTimeout(() => {
document.dispatchEvent(new Event('idle'));
}, time);
};
handleIdle();
events.forEach((key) => document.addEventListener(key, handleIdle));
return () => {
clearTimeout(interval);
events.forEach((key) => document.removeEventListener(key, handleIdle));
};
}, [stableCallback, time]);
useEffect(() => {
if (!awayOnWindowBlur) {
return;
}
window.addEventListener('blur', stableCallback);
return () => {
window.removeEventListener('blur', stableCallback);
};
}, [awayOnWindowBlur, stableCallback]);
useEffect(() => {
document.addEventListener('idle', stableCallback);
return () => {
document.removeEventListener('idle', stableCallback);
};
}, [stableCallback]);
};

@ -20,6 +20,7 @@ import { useLivechatEnterprise } from '../../../app/livechat-enterprise/hooks/us
import { useNextcloud } from '../../../app/nextcloud/client/useNextcloud';
import { useTokenPassAuth } from '../../../app/tokenpass/client/hooks/useTokenPassAuth';
import { useAnalyticsEventTracking } from '../../hooks/useAnalyticsEventTracking';
import { useAutoupdate } from '../../hooks/useAutoupdate';
import { useLoadRoomForAllowedAnonymousRead } from '../../hooks/useLoadRoomForAllowedAnonymousRead';
import { useNotifyUser } from '../../hooks/useNotifyUser';
import { appLayout } from '../../lib/appLayout';
@ -57,6 +58,7 @@ const AppLayout = () => {
useOTRMessaging();
useUpdateVideoConfUser();
useStoreCookiesOnLogin();
useAutoupdate();
const layout = useSyncExternalStore(appLayout.subscribe, appLayout.getSnapshot);

@ -117,9 +117,7 @@ Autoupdate._retrySubscription = () => {
doc.versionNonRefreshable,
`Page will reload in ${reloadDelayInSeconds} seconds`,
);
setTimeout(() => {
Package.reload.Reload._reload();
}, reloadDelayInSeconds * 1000);
document.dispatchEvent(new Event('client_changed'));
}
return;
}

@ -6787,5 +6787,7 @@
"Recent": "Recent",
"On_All_Contacts": "On All Contacts",
"Once": "Once",
"__unreadTitle__from__roomTitle__": "{{unreadTitle}} from {{roomTitle}}"
}
"__unreadTitle__from__roomTitle__": "{{unreadTitle}} from {{roomTitle}}",
"An_update_is_available": "An update is available",
"Reload_to_update": "Reload to update"
}

Loading…
Cancel
Save