/** * This file monitors changes to settings and syncs them to the server. */ import isEqual from "lodash.isequal"; import { Language } from "../Locale"; import { Sync } from "revolt.js/dist/api/objects"; import { useContext, useEffect } from "preact/hooks"; import { connectState } from "../../redux/connector"; import { WithDispatcher } from "../../redux/reducers"; import { Settings } from "../../redux/reducers/settings"; import { Notifications } from "../../redux/reducers/notifications"; import { AppContext, ClientStatus, StatusContext } from "./RevoltClient"; import { ClientboundNotification } from "revolt.js/dist/websocket/notifications"; import { DEFAULT_ENABLED_SYNC, SyncData, SyncKeys, SyncOptions } from "../../redux/reducers/sync"; type Props = WithDispatcher & { settings: Settings, locale: Language, sync: SyncOptions, notifications: Notifications }; var lastValues: { [key in SyncKeys]?: any } = { }; export function mapSync(packet: Sync.UserSettings, revision?: Record) { let update: { [key in SyncKeys]?: [ number, SyncData[key] ] } = {}; for (let key of Object.keys(packet)) { let [ timestamp, obj ] = packet[key]; if (timestamp < (revision ?? {})[key] ?? 0) { continue; } let object; if (obj[0] === '{') { object = JSON.parse(obj) } else { object = obj; } lastValues[key as SyncKeys] = object; update[key as SyncKeys] = [ timestamp, object ]; } return update; } function SyncManager(props: Props) { const client = useContext(AppContext); const status = useContext(StatusContext); useEffect(() => { if (status === ClientStatus.ONLINE) { client .syncFetchSettings(DEFAULT_ENABLED_SYNC.filter(x => !props.sync?.disabled?.includes(x))) .then(data => { props.dispatcher({ type: 'SYNC_UPDATE', update: mapSync(data) }); }); client .syncFetchUnreads() .then(unreads => props.dispatcher({ type: 'UNREADS_SET', unreads })); } }, [ status ]); function syncChange(key: SyncKeys, data: any) { let timestamp = + new Date(); props.dispatcher({ type: 'SYNC_SET_REVISION', key, timestamp }); client.syncSetSettings({ [key]: data }, timestamp); } let disabled = props.sync.disabled ?? []; for (let [key, object] of [ ['appearance', props.settings.appearance], ['theme', props.settings.theme], ['locale', props.locale], ['notifications', props.notifications] ] as [SyncKeys, any][]) { useEffect(() => { if (disabled.indexOf(key) === -1) { if (typeof lastValues[key] !== 'undefined') { if (!isEqual(lastValues[key], object)) { syncChange(key, object); } } } lastValues[key] = object; }, [ disabled, object ]); } useEffect(() => { function onPacket(packet: ClientboundNotification) { if (packet.type === 'UserSettingsUpdate') { let update: { [key in SyncKeys]?: [ number, SyncData[key] ] } = mapSync(packet.update, props.sync.revision); props.dispatcher({ type: 'SYNC_UPDATE', update }); } } client.addListener('packet', onPacket); return () => client.removeListener('packet', onPacket); }, [ disabled, props.sync ]); return null; } export default connectState( SyncManager, state => { return { settings: state.settings, locale: state.locale, sync: state.sync, notifications: state.notifications }; }, true );