import { Listener, NotificationEvent, NotificationLookupArguments, NotificationRepository, ScheduledNotification } from "./NotificationRepository"; import { TupleKey } from "../types/TupleKey"; type DeviceIdSecondsThresholdAssociation = { [key: string]: number }; export class InMemoryNotificationRepository implements NotificationRepository { /** * An object of device ID arrays to deliver notifications to. * The key should be a combination of the shuttle ID and * stop ID, which can be generated using `TupleKey`. * The value is a dictionary of the device ID to the stored seconds threshold. * @private */ private deviceIdsToDeliverTo: { [key: string]: DeviceIdSecondsThresholdAssociation } = {} private listeners: Listener[] = []; constructor() { this.getAllNotificationsForShuttleAndStopId = this.getAllNotificationsForShuttleAndStopId.bind(this); this.getSecondsThresholdForNotificationIfExists = this.getSecondsThresholdForNotificationIfExists.bind(this); this.deleteNotificationIfExists = this.deleteNotificationIfExists.bind(this); this.addOrUpdateNotification = this.addOrUpdateNotification.bind(this); this.isNotificationScheduled = this.isNotificationScheduled.bind(this); this.subscribeToNotificationChanges = this.subscribeToNotificationChanges.bind(this); this.unsubscribeFromNotificationChanges = this.unsubscribeFromNotificationChanges.bind(this); } async getAllNotificationsForShuttleAndStopId(shuttleId: string, stopId: string) { const tuple = new TupleKey(shuttleId, stopId); if (this.deviceIdsToDeliverTo[tuple.toString()] === undefined) { return []; } return Object.keys(this.deviceIdsToDeliverTo[tuple.toString()]) .map((deviceId) => { return { shuttleId, stopId, deviceId, secondsThreshold: this.deviceIdsToDeliverTo[tuple.toString()][deviceId] } }); } async getSecondsThresholdForNotificationIfExists({ shuttleId, stopId, deviceId }: NotificationLookupArguments) { const tuple = new TupleKey(shuttleId, stopId); if (this.deviceIdsToDeliverTo[tuple.toString()] === undefined) { return null; } return this.deviceIdsToDeliverTo[tuple.toString()][deviceId]; } async isNotificationScheduled(lookupArguments: NotificationLookupArguments): Promise { const threshold = await this.getSecondsThresholdForNotificationIfExists(lookupArguments); return threshold !== null; } async addOrUpdateNotification({ shuttleId, stopId, deviceId, secondsThreshold }: ScheduledNotification) { const tuple = new TupleKey(shuttleId, stopId); if (this.deviceIdsToDeliverTo[tuple.toString()] === undefined) { this.deviceIdsToDeliverTo[tuple.toString()] = {}; } this.deviceIdsToDeliverTo[tuple.toString()][deviceId] = secondsThreshold; this.listeners.forEach((listener: Listener) => { const event: NotificationEvent = { event: 'addOrUpdate', notification: { shuttleId, stopId, deviceId, secondsThreshold }, } listener(event); }) } async deleteNotificationIfExists({ deviceId, shuttleId, stopId }: NotificationLookupArguments) { const tupleKey = new TupleKey(shuttleId, stopId); if ( this.deviceIdsToDeliverTo[tupleKey.toString()] === undefined || !(deviceId in this.deviceIdsToDeliverTo[tupleKey.toString()]) ) { return; } const secondsThreshold = this.deviceIdsToDeliverTo[tupleKey.toString()][deviceId]; delete this.deviceIdsToDeliverTo[tupleKey.toString()][deviceId]; if (Object.keys(this.deviceIdsToDeliverTo[tupleKey.toString()]).length === 0) { // no more device IDs remaining for this key combination delete this.deviceIdsToDeliverTo[tupleKey.toString()]; } this.listeners.forEach((listener) => { const event: NotificationEvent = { event: 'delete', notification: { deviceId, shuttleId, stopId, secondsThreshold } } listener(event); }) } public subscribeToNotificationChanges(listener: Listener): void { const index = this.listeners.findIndex((existingListener) => existingListener == listener); if (index < 0) { this.listeners.push(listener); } } public unsubscribeFromNotificationChanges(listener: Listener): void { const index = this.listeners.findIndex((existingListener) => existingListener == listener); if (index >= 0) { this.listeners.splice(index, 1); } } }