92 lines
3.7 KiB
TypeScript
92 lines
3.7 KiB
TypeScript
// Bridge to the native PollingPlugin (see
|
|
// android/app/src/main/java/chat/vojo/app/PollingPlugin.java).
|
|
//
|
|
// Drives the WorkManager-based /notifications polling fallback used on
|
|
// networks where FCM (mtalk.google.com:5228) is blocked. JS owns the
|
|
// credential + room-name cache lifecycle; native owns the periodic fetch
|
|
// and notification rendering.
|
|
//
|
|
// Web has no analogue: the Service Worker already wakes for push without
|
|
// needing a periodic poll, and browsers don't expose a 15-minute periodic
|
|
// background API anyway. The web fallback is a no-op.
|
|
|
|
import { registerPlugin } from '@capacitor/core';
|
|
import { isAndroidPlatform } from '../utils/capacitor';
|
|
|
|
export type RoomMetadataMap = Record<
|
|
string,
|
|
| string
|
|
| {
|
|
name: string;
|
|
isDirect: boolean;
|
|
isEncrypted: boolean;
|
|
// MXC URL for the room's avatar (or, for a DM with no explicit room
|
|
// avatar, the other member's avatar). Optional — empty for rooms
|
|
// without an avatar configured. Java side resolves via auth-media
|
|
// v1.11 + LRU bitmap cache.
|
|
avatarMxc?: string;
|
|
}
|
|
>;
|
|
|
|
/**
|
|
* user_id → MXC avatar URL. Bridged to the Java side so the FCM /
|
|
* WorkManager renderers can attach IconCompat icons to per-sender Person
|
|
* objects in MessagingStyle (and to the self-Person anchor). Senders not
|
|
* in this map fall through to Android's default initials/blank circle.
|
|
*/
|
|
export type UserAvatarsMap = Record<string, string>;
|
|
|
|
interface PollingPluginIface {
|
|
saveSession(opts: { accessToken: string; homeserverUrl: string; userId?: string }): Promise<void>;
|
|
clearSession(): Promise<void>;
|
|
// Tolerant of both legacy (`roomId: "Display"`) and new structured shape
|
|
// (`roomId: { name, isDirect, isEncrypted, avatarMxc }`) — the Java side
|
|
// decides the message channel (DM vs group) and resolves the room large
|
|
// icon based on the structured value when present.
|
|
saveRoomNames(opts: { names: RoomMetadataMap }): Promise<void>;
|
|
saveUserAvatars(opts: { avatars: UserAvatarsMap }): Promise<void>;
|
|
schedule(opts: { intervalMinutes: number }): Promise<void>;
|
|
cancel(): Promise<void>;
|
|
dismissRoom(opts: { roomId: string }): Promise<void>;
|
|
}
|
|
|
|
const noopPlugin: PollingPluginIface = {
|
|
saveSession: async () => undefined,
|
|
clearSession: async () => undefined,
|
|
saveRoomNames: async () => undefined,
|
|
saveUserAvatars: async () => undefined,
|
|
schedule: async () => undefined,
|
|
cancel: async () => undefined,
|
|
dismissRoom: async () => undefined,
|
|
};
|
|
|
|
const plugin = registerPlugin<PollingPluginIface>('Polling', {
|
|
web: noopPlugin,
|
|
});
|
|
|
|
const guard = async <T>(fn: () => Promise<T>, fallback: T): Promise<T> => {
|
|
if (!isAndroidPlatform()) return fallback;
|
|
try {
|
|
return await fn();
|
|
} catch (err) {
|
|
// Old APK installed before the plugin shipped, or transient bridge
|
|
// error. Swallow — polling is a best-effort backup channel, not a
|
|
// hard dependency on the foreground push lifecycle.
|
|
// eslint-disable-next-line no-console
|
|
console.warn('[polling] native call failed:', err);
|
|
return fallback;
|
|
}
|
|
};
|
|
|
|
export const polling = {
|
|
saveSession: (opts: { accessToken: string; homeserverUrl: string; userId?: string }) =>
|
|
guard(() => plugin.saveSession(opts), undefined),
|
|
clearSession: () => guard(() => plugin.clearSession(), undefined),
|
|
saveRoomNames: (names: RoomMetadataMap) =>
|
|
guard(() => plugin.saveRoomNames({ names }), undefined),
|
|
saveUserAvatars: (avatars: UserAvatarsMap) =>
|
|
guard(() => plugin.saveUserAvatars({ avatars }), undefined),
|
|
schedule: (intervalMinutes = 15) => guard(() => plugin.schedule({ intervalMinutes }), undefined),
|
|
cancel: () => guard(() => plugin.cancel(), undefined),
|
|
dismissRoom: (roomId: string) => guard(() => plugin.dismissRoom({ roomId }), undefined),
|
|
};
|