mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
116 lines
3.2 KiB
TypeScript
116 lines
3.2 KiB
TypeScript
import type {Theme} from '@emotion/react';
|
|
import {createStore} from 'reflux';
|
|
|
|
import {defined} from 'sentry/utils';
|
|
import localStorage from 'sentry/utils/localStorage';
|
|
|
|
import type {CommonStoreDefinition} from './types';
|
|
|
|
type Alert = {
|
|
message: React.ReactNode;
|
|
type: keyof Theme['alert'];
|
|
expireAfter?: number;
|
|
id?: string;
|
|
key?: number;
|
|
neverExpire?: boolean;
|
|
noDuplicates?: boolean;
|
|
onClose?: () => void;
|
|
opaque?: boolean;
|
|
url?: string;
|
|
};
|
|
|
|
interface InternalAlertStoreDefinition {
|
|
alerts: Alert[];
|
|
count: number;
|
|
}
|
|
interface AlertStoreDefinition
|
|
extends CommonStoreDefinition<Alert[]>,
|
|
InternalAlertStoreDefinition {
|
|
addAlert(alert: Alert): void;
|
|
closeAlert(alert: Alert, duration?: number): void;
|
|
init(): void;
|
|
}
|
|
|
|
const storeConfig: AlertStoreDefinition = {
|
|
alerts: [],
|
|
count: 0,
|
|
|
|
init() {
|
|
// XXX: Do not use `this.listenTo` in this store. We avoid usage of reflux
|
|
// listeners due to their leaky nature in tests.
|
|
|
|
this.alerts = [];
|
|
this.count = 0;
|
|
},
|
|
|
|
addAlert(alert) {
|
|
const alertAlreadyExists = this.alerts.some(a => a.id === alert.id);
|
|
if (alertAlreadyExists && alert.noDuplicates) {
|
|
return;
|
|
}
|
|
|
|
if (defined(alert.id)) {
|
|
const mutedData = localStorage.getItem('alerts:muted');
|
|
if (typeof mutedData === 'string' && mutedData.length) {
|
|
const expirations: Record<string, number> = JSON.parse(mutedData);
|
|
|
|
// Remove any objects that have passed their mute duration.
|
|
const now = Math.floor(new Date().valueOf() / 1000);
|
|
for (const key in expirations) {
|
|
if (expirations.hasOwnProperty(key) && expirations[key] < now) {
|
|
delete expirations[key];
|
|
}
|
|
}
|
|
localStorage.setItem('alerts:muted', JSON.stringify(expirations));
|
|
|
|
if (expirations.hasOwnProperty(alert.id)) {
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
if (!defined(alert.expireAfter)) {
|
|
alert.expireAfter = 5000;
|
|
}
|
|
}
|
|
|
|
if (alert.expireAfter && !alert.neverExpire) {
|
|
window.setTimeout(() => {
|
|
this.closeAlert(alert);
|
|
}, alert.expireAfter);
|
|
}
|
|
|
|
alert.key = this.count++;
|
|
|
|
// intentionally recreate array via concat because of Reflux
|
|
// "bug" where React components are given same reference to tracked
|
|
// data objects, and don't *see* that values have changed
|
|
this.alerts = this.alerts.concat([alert]);
|
|
this.trigger(this.alerts);
|
|
},
|
|
|
|
closeAlert(alert, duration = 60 * 60 * 7 * 24) {
|
|
if (defined(alert.id) && defined(duration)) {
|
|
const expiry = Math.floor(new Date().valueOf() / 1000) + duration;
|
|
const mutedData = localStorage.getItem('alerts:muted');
|
|
|
|
let expirations: Record<string, number> = {};
|
|
if (typeof mutedData === 'string' && expirations.length) {
|
|
expirations = JSON.parse(mutedData);
|
|
}
|
|
expirations[alert.id] = expiry;
|
|
localStorage.setItem('alerts:muted', JSON.stringify(expirations));
|
|
}
|
|
|
|
// TODO(dcramer): we need some animations here for closing alerts
|
|
this.alerts = this.alerts.filter(item => alert !== item);
|
|
this.trigger(this.alerts);
|
|
},
|
|
|
|
getState() {
|
|
return this.alerts;
|
|
},
|
|
};
|
|
|
|
const AlertStore = createStore(storeConfig);
|
|
export default AlertStore;
|