const Note = require('lib/models/Note.js'); const Alarm = require('lib/models/Alarm.js'); class AlarmService { static setDriver(v) { this.driver_ = v; } static driver() { if (!this.driver_) throw new Error('AlarmService driver not set!'); return this.driver_; } static setLogger(v) { this.logger_ = v; } static logger() { return this.logger_; } static setInAppNotificationHandler(v) { this.inAppNotificationHandler_ = v; if (this.driver_.setInAppNotificationHandler) this.driver_.setInAppNotificationHandler(v); } static async garbageCollect() { this.logger().info('Garbage collecting alarms...'); // Delete alarms that have already been triggered await Alarm.deleteExpiredAlarms(); // Delete alarms that correspond to non-existent notes const alarmIds = await Alarm.alarmIdsWithoutNotes(); for (let i = 0; i < alarmIds.length; i++) { this.logger().info('Clearing notification for non-existing note. Alarm ' + alarmIds[i]); await this.driver().clearNotification(alarmIds[i]); } await Alarm.batchDelete(alarmIds); } // When passing a note, make sure it has all the required properties // (better to pass a complete note or else just the ID) static async updateNoteNotification(noteOrId, isDeleted = false) { try { let note = null; let noteId = null; if (typeof noteOrId === 'object') { note = noteOrId; noteId = note.id; } else { note = await Note.load(noteOrId); noteId = note ? note.id : null; } if (!note && !isDeleted) return; const driver = this.driver(); let alarm = noteId ? await Alarm.byNoteId(noteId) : null; let clearAlarm = false; if (isDeleted || !Note.needAlarm(note) || (alarm && alarm.trigger_time !== note.todo_due)) { clearAlarm = !!alarm; } if (!clearAlarm && alarm) { // Alarm already exists and set at the right time // For persistent notifications (those that stay active after the app has been closed, like on mobile), if we have // an alarm object we can be sure that the notification has already been set, so there's nothing to do. // For non-persistent notifications however we need to check that the notification has been set because, for example, // if the app has just started the notifications need to be set again. so we do this below. if (!driver.hasPersistentNotifications() && !driver.notificationIsSet(alarm.id)) { const notification = await Alarm.makeNotification(alarm, note); this.logger().info('Scheduling (non-persistent) notification for note ' + note.id, notification); driver.scheduleNotification(notification); } return; } if (clearAlarm) { this.logger().info('Clearing notification for note ' + noteId); await driver.clearNotification(alarm.id); await Alarm.delete(alarm.id); } if (isDeleted || !Note.needAlarm(note)) return; await Alarm.save({ note_id: note.id, trigger_time: note.todo_due, }); // Reload alarm to get its ID alarm = await Alarm.byNoteId(note.id); const notification = await Alarm.makeNotification(alarm, note); this.logger().info('Scheduling notification for note ' + note.id, notification); await driver.scheduleNotification(notification); } catch (error) { this.logger().error('Could not update notification', error); } } static async updateAllNotifications() { this.logger().info('Updating all notifications...'); await this.garbageCollect(); const dueNotes = await Note.dueNotes(); for (let i = 0; i < dueNotes.length; i++) { await this.updateNoteNotification(dueNotes[i]); } } } module.exports = AlarmService;