fluxer/fluxer_app/src/stores/StatusExpiryStore.tsx
2026-02-17 12:22:36 +00:00

165 lines
4.0 KiB
TypeScript

/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import * as UserSettingsActionCreators from '@app/actions/UserSettingsActionCreators';
import {Logger} from '@app/lib/Logger';
import AccountManager from '@app/stores/AccountManager';
import UserSettingsStore from '@app/stores/UserSettingsStore';
import type {StatusType} from '@fluxer/constants/src/StatusConstants';
import {StatusTypes} from '@fluxer/constants/src/StatusConstants';
import {makeAutoObservable, reaction} from 'mobx';
class StatusExpiryStore {
private logger = new Logger('StatusExpiryStore');
activeUserId: string | null = null;
private timer: ReturnType<typeof setTimeout> | null = null;
constructor() {
makeAutoObservable(this);
reaction(
() => AccountManager.currentUserId,
(userId) => {
this.handleActiveUserChange(userId);
},
{fireImmediately: true},
);
reaction(
() => ({
statusResetsAt: UserSettingsStore.statusResetsAt,
statusResetsTo: UserSettingsStore.statusResetsTo,
status: UserSettingsStore.status,
}),
() => {
this.scheduleTimer();
},
);
}
get activeExpiresAt(): number | null {
const resetsAt = UserSettingsStore.statusResetsAt;
if (!resetsAt) {
return null;
}
return new Date(resetsAt).getTime();
}
get activeFallbackStatus(): StatusType {
const resetsTo = UserSettingsStore.statusResetsTo;
if (!resetsTo) {
return StatusTypes.ONLINE;
}
switch (resetsTo) {
case 'online':
return StatusTypes.ONLINE;
case 'idle':
return StatusTypes.IDLE;
case 'dnd':
return StatusTypes.DND;
case 'invisible':
return StatusTypes.INVISIBLE;
default:
return StatusTypes.ONLINE;
}
}
setActiveStatusExpiry({
status,
durationMs,
fallbackStatus = StatusTypes.ONLINE,
}: {
status: StatusType;
durationMs: number | null;
fallbackStatus?: StatusType;
}): void {
if (durationMs === null) {
this.clearStatusExpiry();
UserSettingsActionCreators.update({
status,
statusResetsAt: null,
statusResetsTo: null,
}).catch((error) => {
this.logger.error('Failed to update status:', error);
});
return;
}
const expiresAt = new Date(Date.now() + durationMs);
UserSettingsActionCreators.update({
status,
statusResetsAt: expiresAt.toISOString(),
statusResetsTo: fallbackStatus,
}).catch((error) => {
this.logger.error('Failed to update status with expiry:', error);
});
}
clearStatusExpiry(): void {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
}
handleActiveUserChange(userId: string | null): void {
this.activeUserId = userId;
this.scheduleTimer();
}
private scheduleTimer(): void {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
const expiresAt = this.activeExpiresAt;
if (!expiresAt) {
return;
}
const delay = expiresAt - Date.now();
if (delay <= 0) {
void this.handleExpiry();
return;
}
this.timer = setTimeout(() => {
void this.handleExpiry();
}, delay);
}
private async handleExpiry(): Promise<void> {
const fallbackStatus = this.activeFallbackStatus;
try {
await UserSettingsActionCreators.update({
status: fallbackStatus,
statusResetsAt: null,
statusResetsTo: null,
});
} catch (error) {
this.logger.error('Failed to revert status after expiry:', error);
}
}
}
export default new StatusExpiryStore();