revite/src/context/revoltjs/SyncManager.tsx

127 lines
3.9 KiB
TypeScript

/**
* 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<string, number>) {
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
);