2024-08-13 10:26:23 +03:00
|
|
|
import { t, Trans } from '@lingui/macro';
|
2023-09-09 09:09:50 +03:00
|
|
|
import { memo } from 'preact/compat';
|
2023-09-02 13:19:09 +03:00
|
|
|
import { useEffect, useRef, useState } from 'preact/hooks';
|
2024-04-15 19:09:53 +03:00
|
|
|
import { useHotkeys } from 'react-hotkeys-hook';
|
2023-09-02 13:19:09 +03:00
|
|
|
|
|
|
|
import { api } from '../utils/api';
|
2023-10-31 15:50:27 +03:00
|
|
|
import showToast from '../utils/show-toast';
|
2023-09-02 13:19:09 +03:00
|
|
|
import states, { saveStatus } from '../utils/states';
|
|
|
|
import useInterval from '../utils/useInterval';
|
|
|
|
import usePageVisibility from '../utils/usePageVisibility';
|
|
|
|
|
2023-11-01 17:26:21 +03:00
|
|
|
const STREAMING_TIMEOUT = 1000 * 3; // 3 seconds
|
2024-08-04 05:09:33 +03:00
|
|
|
const POLL_INTERVAL = 20_000; // 20 seconds
|
2023-11-01 17:26:21 +03:00
|
|
|
|
2023-09-09 09:09:50 +03:00
|
|
|
export default memo(function BackgroundService({ isLoggedIn }) {
|
2023-09-02 13:19:09 +03:00
|
|
|
// Notifications service
|
|
|
|
// - WebSocket to receive notifications when page is visible
|
|
|
|
const [visible, setVisible] = useState(true);
|
2024-08-27 19:20:44 +03:00
|
|
|
const visibleTimeout = useRef();
|
|
|
|
usePageVisibility((visible) => {
|
|
|
|
clearTimeout(visibleTimeout.current);
|
|
|
|
if (visible) {
|
|
|
|
setVisible(true);
|
|
|
|
} else {
|
|
|
|
visibleTimeout.current = setTimeout(() => {
|
|
|
|
setVisible(false);
|
|
|
|
}, POLL_INTERVAL);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-11-01 16:31:43 +03:00
|
|
|
const checkLatestNotification = async (masto, instance, skipCheckMarkers) => {
|
|
|
|
if (states.notificationsLast) {
|
|
|
|
const notificationsIterator = masto.v1.notifications.list({
|
|
|
|
limit: 1,
|
|
|
|
sinceId: states.notificationsLast.id,
|
|
|
|
});
|
|
|
|
const { value: notifications } = await notificationsIterator.next();
|
|
|
|
if (notifications?.length) {
|
|
|
|
if (skipCheckMarkers) {
|
|
|
|
states.notificationsShowNew = true;
|
|
|
|
} else {
|
|
|
|
let lastReadId;
|
|
|
|
try {
|
|
|
|
const markers = await masto.v1.markers.fetch({
|
|
|
|
timeline: 'notifications',
|
|
|
|
});
|
|
|
|
lastReadId = markers?.notifications?.lastReadId;
|
|
|
|
} catch (e) {}
|
|
|
|
if (lastReadId) {
|
|
|
|
states.notificationsShowNew = notifications[0].id !== lastReadId;
|
|
|
|
} else {
|
|
|
|
states.notificationsShowNew = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-10-30 15:45:30 +03:00
|
|
|
useEffect(() => {
|
|
|
|
let sub;
|
2024-08-04 05:06:55 +03:00
|
|
|
let streamTimeout;
|
2023-11-01 16:31:43 +03:00
|
|
|
let pollNotifications;
|
2023-10-30 15:45:30 +03:00
|
|
|
if (isLoggedIn && visible) {
|
|
|
|
const { masto, streaming, instance } = api();
|
|
|
|
(async () => {
|
|
|
|
// 1. Get the latest notification
|
2023-11-01 16:31:43 +03:00
|
|
|
await checkLatestNotification(masto, instance);
|
2023-09-02 13:19:09 +03:00
|
|
|
|
2023-11-01 16:31:43 +03:00
|
|
|
let hasStreaming = false;
|
2023-10-30 15:45:30 +03:00
|
|
|
// 2. Start streaming
|
|
|
|
if (streaming) {
|
2024-08-04 05:06:55 +03:00
|
|
|
streamTimeout = setTimeout(() => {
|
2023-11-01 17:26:21 +03:00
|
|
|
(async () => {
|
|
|
|
try {
|
|
|
|
hasStreaming = true;
|
|
|
|
sub = streaming.user.notification.subscribe();
|
|
|
|
console.log('🎏 Streaming notification', sub);
|
|
|
|
for await (const entry of sub) {
|
|
|
|
if (!sub) break;
|
|
|
|
if (!visible) break;
|
|
|
|
console.log('🔔🔔 Notification entry', entry);
|
|
|
|
if (entry.event === 'notification') {
|
|
|
|
console.log('🔔🔔 Notification', entry);
|
|
|
|
saveStatus(entry.payload, instance, {
|
|
|
|
skipThreading: true,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
states.notificationsShowNew = true;
|
|
|
|
}
|
2023-12-12 03:34:06 +03:00
|
|
|
console.log('💥 Streaming notification loop STOPPED');
|
2023-11-01 17:26:21 +03:00
|
|
|
} catch (e) {
|
|
|
|
hasStreaming = false;
|
|
|
|
console.error(e);
|
2023-11-01 16:31:43 +03:00
|
|
|
}
|
|
|
|
|
2023-11-01 17:26:21 +03:00
|
|
|
if (!hasStreaming) {
|
|
|
|
console.log('🎏 Streaming failed, fallback to polling');
|
|
|
|
pollNotifications = setInterval(() => {
|
|
|
|
checkLatestNotification(masto, instance, true);
|
|
|
|
}, POLL_INTERVAL);
|
|
|
|
}
|
|
|
|
})();
|
|
|
|
}, STREAMING_TIMEOUT);
|
2023-11-01 16:31:43 +03:00
|
|
|
}
|
2023-10-30 15:45:30 +03:00
|
|
|
})();
|
2023-09-02 13:19:09 +03:00
|
|
|
}
|
|
|
|
return () => {
|
2023-10-30 15:45:30 +03:00
|
|
|
sub?.unsubscribe?.();
|
|
|
|
sub = null;
|
2024-08-04 05:06:55 +03:00
|
|
|
clearTimeout(streamTimeout);
|
2023-11-01 16:31:43 +03:00
|
|
|
clearInterval(pollNotifications);
|
2023-09-02 13:19:09 +03:00
|
|
|
};
|
|
|
|
}, [visible, isLoggedIn]);
|
|
|
|
|
|
|
|
// Check for updates service
|
|
|
|
const lastCheckDate = useRef();
|
|
|
|
const checkForUpdates = () => {
|
|
|
|
lastCheckDate.current = Date.now();
|
|
|
|
console.log('✨ Check app update');
|
|
|
|
fetch('./version.json')
|
|
|
|
.then((r) => r.json())
|
|
|
|
.then((info) => {
|
|
|
|
if (info) states.appVersion = info;
|
|
|
|
})
|
|
|
|
.catch((e) => {
|
|
|
|
console.error(e);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
useInterval(checkForUpdates, visible && 1000 * 60 * 30); // 30 minutes
|
|
|
|
usePageVisibility((visible) => {
|
|
|
|
if (visible) {
|
|
|
|
if (!lastCheckDate.current) {
|
|
|
|
checkForUpdates();
|
|
|
|
} else {
|
|
|
|
const diff = Date.now() - lastCheckDate.current;
|
|
|
|
if (diff > 1000 * 60 * 60) {
|
|
|
|
// 1 hour
|
|
|
|
checkForUpdates();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-10-31 15:50:27 +03:00
|
|
|
// Global keyboard shortcuts "service"
|
|
|
|
useHotkeys('shift+alt+k', () => {
|
|
|
|
const currentCloakMode = states.settings.cloakMode;
|
|
|
|
states.settings.cloakMode = !currentCloakMode;
|
|
|
|
showToast({
|
2024-08-13 10:26:23 +03:00
|
|
|
text: currentCloakMode ? t`Cloak mode disabled` : t`Cloak mode enabled`,
|
2023-10-31 15:50:27 +03:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2023-09-02 13:19:09 +03:00
|
|
|
return null;
|
2023-09-09 09:09:50 +03:00
|
|
|
});
|