Use same lock values as mobile clients
[ProtonMail-WebClient.git] / packages / shared / lib / calendar / alarms.ts
blob206e7840601cd9ae032a851ad8d3f53a2fdc5079
1 import truncate from '@proton/utils/truncate';
2 import uniqueBy from '@proton/utils/uniqueBy';
4 import { MINUTE } from '../constants';
5 import { convertUTCDateTimeToZone, fromUTCDate, getTimezoneOffset, toUTCDate } from '../date/timezone';
6 import { omit } from '../helpers/object';
7 import type {
8     NotificationModel,
9     VcalDurationValue,
10     VcalValarmRelativeComponent,
11     VcalVeventComponent,
12 } from '../interfaces/calendar';
13 import getAlarmMessageText from './alarms/getAlarmMessageText';
14 import { getValarmTrigger } from './alarms/getValarmTrigger';
15 import { normalizeDurationToUnit } from './alarms/trigger';
16 import { NOTIFICATION_TYPE_API, NOTIFICATION_UNITS, NOTIFICATION_WHEN } from './constants';
17 import { getDisplayTitle } from './helper';
18 import { getMillisecondsFromTriggerString } from './vcal';
19 import { propertyToUTCDate } from './vcalConverter';
20 import { getIsAllDay } from './veventHelper';
22 /**
23  * Given a raw event, (optionally) its starting date, the date now and a timezone id,
24  * generate a notification message for the event
25  */
26 interface Arguments {
27     component: VcalVeventComponent;
28     start?: Date;
29     now: Date;
30     tzid: string;
31     formatOptions: any;
33 export const getAlarmMessage = ({ component, start, now, tzid, formatOptions }: Arguments) => {
34     const { dtstart, summary } = component;
35     const title = truncate(getDisplayTitle(summary?.value), 100);
36     const utcStartDate = start || propertyToUTCDate(dtstart);
38     // To determine if the event is happening in timezoned today, tomorrow, this month or this year,
39     // we pass fake UTC dates to the getAlarmMessage helper
40     const startFakeUTCDate = toUTCDate(convertUTCDateTimeToZone(fromUTCDate(utcStartDate), tzid));
41     const nowFakeUTCDate = toUTCDate(convertUTCDateTimeToZone(fromUTCDate(now), tzid));
42     const isAllDay = getIsAllDay(component);
44     return getAlarmMessageText({
45         title,
46         isAllDay,
47         startFakeUTCDate,
48         nowFakeUTCDate,
49         formatOptions,
50     });
53 /**
54  * Given the UNIX timestamp for the occurrence of an alarm, and the trigger string of this alarm,
55  * return the timestamp in milliseconds the occurrence of the corresponding event.
56  * The computation must take into account possible DST shifts
57  */
58 interface Params {
59     Occurrence: number;
60     Trigger: string;
61     tzid: string;
63 export const getNextEventTime = ({ Occurrence, Trigger, tzid }: Params) => {
64     const alarmTime = Occurrence * 1000;
65     const eventTime = alarmTime - getMillisecondsFromTriggerString(Trigger);
66     const offsetAlarmTime = getTimezoneOffset(new Date(alarmTime), tzid).offset;
67     const offsetEventTime = getTimezoneOffset(new Date(eventTime), tzid).offset;
68     const offsetDifference = offsetAlarmTime - offsetEventTime;
69     // correct eventTime in case we jumped across an odd number of DST changes
70     return eventTime - offsetDifference * MINUTE;
73 /**
74  * Filter out future notifications
75  */
76 export const filterFutureNotifications = (notifications: NotificationModel[]) => {
77     return notifications.filter(({ when, value }) => {
78         if (when === NOTIFICATION_WHEN.BEFORE) {
79             return true;
80         }
81         return value === 0;
82     });
85 export const sortNotificationsByAscendingTrigger = (notifications: NotificationModel[]) =>
86     [...notifications].sort((a: NotificationModel, b: NotificationModel) => {
87         const triggerA = getValarmTrigger(a);
88         const triggerB = getValarmTrigger(b);
89         const triggerAMinutes =
90             normalizeDurationToUnit(triggerA, NOTIFICATION_UNITS.MINUTE) * (triggerA.isNegative ? -1 : 1);
91         const triggerBMinutes =
92             normalizeDurationToUnit(triggerB, NOTIFICATION_UNITS.MINUTE) * (triggerB.isNegative ? -1 : 1);
94         return triggerAMinutes - triggerBMinutes;
95     });
97 const sortNotificationsByAscendingValue = (a: NotificationModel, b: NotificationModel) =>
98     (a.value || 0) - (b.value || 0);
100 const uniqueNotificationComparator = (notification: NotificationModel) => {
101     const trigger = getValarmTrigger(notification);
103     return `${notification.type}-${
104         normalizeDurationToUnit(trigger, NOTIFICATION_UNITS.MINUTE) * (trigger.isNegative ? -1 : 1)
105     }`;
108 export const dedupeNotifications = (notifications: NotificationModel[]) => {
109     const sortedNotifications = [...notifications].sort(sortNotificationsByAscendingValue);
111     return uniqueBy(sortedNotifications, uniqueNotificationComparator);
114 const getSmallestNonZeroNumericValueFromDurationValue = (object: VcalDurationValue) =>
115     Math.min(...Object.values(omit(object, ['isNegative'])).filter(Boolean));
117 const sortAlarmsByAscendingTriggerValue = (a: VcalValarmRelativeComponent, b: VcalValarmRelativeComponent) => {
118     const aMin = getSmallestNonZeroNumericValueFromDurationValue(a.trigger.value);
119     const bMin = getSmallestNonZeroNumericValueFromDurationValue(b.trigger.value);
121     return aMin - bMin;
124 const uniqueAlarmComparator = (alarm: VcalValarmRelativeComponent) => {
125     const triggerValue = alarm.trigger.value;
126     const isTriggerNegative = 'isNegative' in triggerValue && triggerValue.isNegative;
128     return `${alarm.action.value}-${
129         normalizeDurationToUnit(triggerValue, NOTIFICATION_UNITS.MINUTE) * (isTriggerNegative ? -1 : 1)
130     }`;
134  * ATTENTION
135  * This function will deduplicate alarms with any type of relative trigger,
136  * but if you expect it to pick the nicest triggers (i.e. 2 days instead of 1 day and 24 hours)
137  * you must pass normalized triggers
138  */
139 export const dedupeAlarmsWithNormalizedTriggers = (alarms: VcalValarmRelativeComponent[]) => {
140     const sortedAlarms = [...alarms].sort(sortAlarmsByAscendingTriggerValue);
142     return uniqueBy(sortedAlarms, uniqueAlarmComparator);
145 export const isEmailNotification = ({ type }: NotificationModel) => type === NOTIFICATION_TYPE_API.EMAIL;