|  |  |  | import _ from 'lodash'; | 
					
						
							|  |  |  | import moment from 'moment'; | 
					
						
							|  |  |  | import { MessageModel } from '../models/message'; | 
					
						
							|  |  |  | import { messageExpired } from '../state/ducks/conversations'; | 
					
						
							|  |  |  | import { TimerOptionsArray } from '../state/ducks/timerOptions'; | 
					
						
							|  |  |  | import { LocalizerKeys } from '../types/LocalizerKeys'; | 
					
						
							|  |  |  | import { initWallClockListener } from './wallClockListener'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import { Data } from '../data/data'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async function destroyExpiredMessages() { | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     window.log.info('destroyExpiredMessages: Loading messages...'); | 
					
						
							|  |  |  |     const messages = await Data.getExpiredMessages(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     await Promise.all( | 
					
						
							|  |  |  |       messages.map(async (message: MessageModel) => { | 
					
						
							|  |  |  |         window.log.info('Message expired', { | 
					
						
							|  |  |  |           sentAt: message.get('sent_at'), | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // We delete after the trigger to allow the conversation time to process
 | 
					
						
							|  |  |  |         //   the expiration before the message is removed from the database.
 | 
					
						
							|  |  |  |         await Data.removeMessage(message.id); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // trigger the expiration of the message on the redux itself.
 | 
					
						
							|  |  |  |         window.inboxStore?.dispatch( | 
					
						
							|  |  |  |           messageExpired({ | 
					
						
							|  |  |  |             conversationKey: message.attributes.conversationId, | 
					
						
							|  |  |  |             messageId: message.id, | 
					
						
							|  |  |  |           }) | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const conversation = message.getConversation(); | 
					
						
							|  |  |  |         if (conversation) { | 
					
						
							|  |  |  |           await conversation.onExpired(message); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } catch (error) { | 
					
						
							|  |  |  |     window.log.error( | 
					
						
							|  |  |  |       'destroyExpiredMessages: Error deleting expired messages', | 
					
						
							|  |  |  |       error && error.stack ? error.stack : error | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   window.log.info('destroyExpiredMessages: complete'); | 
					
						
							|  |  |  |   void checkExpiringMessages(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | let timeout: NodeJS.Timeout | undefined; | 
					
						
							|  |  |  | async function checkExpiringMessages() { | 
					
						
							|  |  |  |   // Look up the next expiring message and set a timer to destroy it
 | 
					
						
							|  |  |  |   const messages = await Data.getNextExpiringMessage(); | 
					
						
							|  |  |  |   const next = messages.at(0); | 
					
						
							|  |  |  |   if (!next) { | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const expiresAt = next.get('expires_at'); | 
					
						
							|  |  |  |   if (!expiresAt) { | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   window.log.info('next message expires', new Date(expiresAt).toISOString()); | 
					
						
							|  |  |  |   window.log.info('next message expires in ', (expiresAt - Date.now()) / 1000); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   let wait = expiresAt - Date.now(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // In the past
 | 
					
						
							|  |  |  |   if (wait < 0) { | 
					
						
							|  |  |  |     wait = 0; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Too far in the future, since it's limited to a 32-bit value
 | 
					
						
							|  |  |  |   if (wait > 2147483647) { | 
					
						
							|  |  |  |     wait = 2147483647; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (timeout) { | 
					
						
							|  |  |  |     global.clearTimeout(timeout); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   timeout = global.setTimeout(destroyExpiredMessages, wait); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | const throttledCheckExpiringMessages = _.throttle(checkExpiringMessages, 1000); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | let isInit = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const initExpiringMessageListener = () => { | 
					
						
							|  |  |  |   if (isInit) { | 
					
						
							|  |  |  |     throw new Error('expiring messages listener is already init'); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void checkExpiringMessages(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   initWallClockListener(throttledCheckExpiringMessages); | 
					
						
							|  |  |  |   isInit = true; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const updateExpiringMessagesCheck = () => { | 
					
						
							|  |  |  |   void throttledCheckExpiringMessages(); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function getTimerOptionName(time: number, unit: moment.DurationInputArg2) { | 
					
						
							|  |  |  |   return ( | 
					
						
							|  |  |  |     window.i18n(['timerOption', time, unit].join('_') as LocalizerKeys) || | 
					
						
							|  |  |  |     moment.duration(time, unit).humanize() | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | function getTimerOptionAbbreviated(time: number, unit: string) { | 
					
						
							|  |  |  |   return window.i18n(['timerOption', time, unit, 'abbreviated'].join('_') as LocalizerKeys); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const timerOptionsDurations: Array<{ | 
					
						
							|  |  |  |   time: number; | 
					
						
							|  |  |  |   unit: moment.DurationInputArg2; | 
					
						
							|  |  |  |   seconds: number; | 
					
						
							|  |  |  | }> = [ | 
					
						
							|  |  |  |   { time: 0, unit: 'seconds' as moment.DurationInputArg2 }, | 
					
						
							|  |  |  |   { time: 5, unit: 'seconds' as moment.DurationInputArg2 }, | 
					
						
							|  |  |  |   { time: 10, unit: 'seconds' as moment.DurationInputArg2 }, | 
					
						
							|  |  |  |   { time: 30, unit: 'seconds' as moment.DurationInputArg2 }, | 
					
						
							|  |  |  |   { time: 1, unit: 'minute' as moment.DurationInputArg2 }, | 
					
						
							|  |  |  |   { time: 5, unit: 'minutes' as moment.DurationInputArg2 }, | 
					
						
							|  |  |  |   { time: 30, unit: 'minutes' as moment.DurationInputArg2 }, | 
					
						
							|  |  |  |   { time: 1, unit: 'hour' as moment.DurationInputArg2 }, | 
					
						
							|  |  |  |   { time: 6, unit: 'hours' as moment.DurationInputArg2 }, | 
					
						
							|  |  |  |   { time: 12, unit: 'hours' as moment.DurationInputArg2 }, | 
					
						
							|  |  |  |   { time: 1, unit: 'day' as moment.DurationInputArg2 }, | 
					
						
							|  |  |  |   { time: 1, unit: 'week' as moment.DurationInputArg2 }, | 
					
						
							|  |  |  | ].map(o => { | 
					
						
							|  |  |  |   const duration = moment.duration(o.time, o.unit); // 5, 'seconds'
 | 
					
						
							|  |  |  |   return { | 
					
						
							|  |  |  |     time: o.time, | 
					
						
							|  |  |  |     unit: o.unit, | 
					
						
							|  |  |  |     seconds: duration.asSeconds(), | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function getName(seconds = 0) { | 
					
						
							|  |  |  |   const o = timerOptionsDurations.find(m => m.seconds === seconds); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (o) { | 
					
						
							|  |  |  |     return getTimerOptionName(o.time, o.unit); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return [seconds, 'seconds'].join(' '); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | function getAbbreviated(seconds = 0) { | 
					
						
							|  |  |  |   const o = timerOptionsDurations.find(m => m.seconds === seconds); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (o) { | 
					
						
							|  |  |  |     return getTimerOptionAbbreviated(o.time, o.unit); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return [seconds, 's'].join(''); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function getTimerSecondsWithName(): TimerOptionsArray { | 
					
						
							|  |  |  |   return timerOptionsDurations.map(t => { | 
					
						
							|  |  |  |     return { name: getName(t.seconds), value: t.seconds }; | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export const ExpirationTimerOptions = { | 
					
						
							|  |  |  |   getName, | 
					
						
							|  |  |  |   getAbbreviated, | 
					
						
							|  |  |  |   updateExpiringMessagesCheck, | 
					
						
							|  |  |  |   initExpiringMessageListener, | 
					
						
							|  |  |  |   getTimerSecondsWithName, | 
					
						
							|  |  |  | }; |