feat: implement background sync service for Matrix integration

This commit is contained in:
2026-04-07 20:36:25 +10:00
parent 08b6dce4ee
commit 70f7d41d9a
9 changed files with 522 additions and 2 deletions

View File

@@ -36,6 +36,11 @@ import {
setupNotificationTapListener,
} from '../../utils/tauri';
import { setPaarrotNavigate, initPaarrotAPI } from '../../paarrot-api';
import {
startBackgroundSync,
stopBackgroundSync,
setAppForegroundState,
} from '../../utils/backgroundSync';
/**
* Applies the selected emoji style font to the document.
@@ -413,6 +418,31 @@ function MessageNotifications() {
return null;
}
/**
* Starts the native Android background sync service on login, keeps it
* informed of foreground state so it doesn't double-fire notifications,
* and stops it cleanly on unmount (logout).
* Only active on Android Capacitor builds.
*/
function BackgroundSyncSetup() {
const mx = useMatrixClient();
useEffect(() => {
startBackgroundSync(mx);
const onVisibility = () => setAppForegroundState(!document.hidden);
document.addEventListener('visibilitychange', onVisibility);
setAppForegroundState(!document.hidden);
return () => {
document.removeEventListener('visibilitychange', onVisibility);
stopBackgroundSync();
};
}, [mx]);
return null;
}
/**
* Initializes the Paarrot API for Electron integration
* Registers the navigate function and sets up IPC handlers
@@ -475,6 +505,7 @@ export function ClientNonUIFeatures({ children }: ClientNonUIFeaturesProps) {
<FaviconUpdater />
<InviteNotifications />
<MessageNotifications />
<BackgroundSyncSetup />
<PaarrotAPIInitializer />
<TaskbarFlashStopper />
{children}

View File

@@ -0,0 +1,91 @@
import { Capacitor, registerPlugin } from '@capacitor/core';
import type { MatrixClient } from 'matrix-js-sdk';
interface MatrixBackgroundSyncPlugin {
/** Start the background sync service with the given Matrix credentials. */
start(options: {
homeserverUrl: string;
accessToken: string;
userId: string;
deviceId: string;
}): Promise<void>;
/** Stop the background sync service and clear persisted credentials. */
stop(): Promise<void>;
/**
* Notify the service whether the app UI is visible.
* When foreground is true, the service suppresses native notifications
* because the JS layer handles them via LocalNotifications.
*/
setAppForeground(options: { foreground: boolean }): Promise<void>;
/** Returns whether the sync service is currently running. */
getStatus(): Promise<{ running: boolean }>;
}
const MatrixBackgroundSync = registerPlugin<MatrixBackgroundSyncPlugin>('MatrixBackgroundSync');
/** Returns true when the current platform is Android Capacitor. */
export const isBackgroundSyncSupported = (): boolean =>
Capacitor.isNativePlatform() && Capacitor.getPlatform() === 'android';
/**
* Starts the native Matrix background sync service.
* Reads credentials directly from the active MatrixClient.
* Safe to call on every login — the service is idempotent.
* @param mx Authenticated Matrix client
*/
export const startBackgroundSync = async (mx: MatrixClient): Promise<void> => {
if (!isBackgroundSyncSupported()) return;
const homeserverUrl = mx.getHomeserverUrl();
const accessToken = mx.getAccessToken();
const userId = mx.getUserId();
const deviceId = mx.getDeviceId();
if (!homeserverUrl || !accessToken || !userId) {
console.warn('[BackgroundSync] Missing credentials, not starting');
return;
}
try {
await MatrixBackgroundSync.start({
homeserverUrl,
accessToken,
userId,
deviceId: deviceId ?? '',
});
console.log('[BackgroundSync] Service started');
} catch (err) {
console.warn('[BackgroundSync] Failed to start:', err);
}
};
/**
* Stops the native Matrix background sync service.
* Call on logout so the service stops and credentials are wiped.
*/
export const stopBackgroundSync = async (): Promise<void> => {
if (!isBackgroundSyncSupported()) return;
try {
await MatrixBackgroundSync.stop();
console.log('[BackgroundSync] Service stopped');
} catch (err) {
console.warn('[BackgroundSync] Failed to stop:', err);
}
};
/**
* Tells the native service whether the app UI is in the foreground.
* Call when document visibility changes so the service can suppress
* duplicate notifications while the JS layer is active.
* @param foreground true if the WebView UI is currently visible
*/
export const setAppForegroundState = async (foreground: boolean): Promise<void> => {
if (!isBackgroundSyncSupported()) return;
try {
await MatrixBackgroundSync.setAppForeground({ foreground });
} catch (err) {
console.warn('[BackgroundSync] setAppForeground failed:', err);
}
};