import { ERROR_MESSAGES } from '@koh/common' ; import { BadRequestException , Injectable } from '@nestjs/common' ; import { ConfigService } from '@nestjs/config' ; import { DeepPartial } from 'typeorm' ; import * as webPush from 'web-push' ; import { UserModel } from '../profile/user.entity' ; import { DesktopNotifModel } from './desktop-notif.entity' ; import { PhoneNotifModel } from './phone-notif.entity' ; import { TwilioService } from './twilio/twilio.service' ; export const NotifMsgs = { phone : { WRONG_MESSAGE : 'Please respond with either YES or NO. Text STOP at any time to stop receiving text messages' , COULD_NOT_FIND_NUMBER : 'Could not find an Office Hours account with your phone number.' , UNREGISTER : "You've unregistered from text notifications for Khoury Office Hours. Feel free to re-register any time through the website" , DUPLICATE : "You've already been verified to receive text notifications from Khoury Office Hours!" , OK : 'Thank you for verifying your number with Khoury Office Hours! You are now signed up for text notifications!' , } , queue : { ALERT_BUTTON : "The TA could't reach you, please have Microsoft Teams open and confirm you are back!" , THIRD_PLACE : `You're 3rd in the queue. Be ready for a TA to call you soon!` , TA_HIT_HELPED : ( taName ) : string => `${ taName } is coming to help you!` , REMOVED : `You've been removed from the queue. Please return to the app for more information.` , } , ta : { STUDENT_JOINED_EMPTY_QUEUE : 'A student has joined your (previously empty) queue!' , } , } ; @ Injectable ( ) export class NotificationService { desktopPublicKey : string ; constructor ( private configService , private twilioService ) { webPush . setVapidDetails ( this . configService . get ( 'EMAIL' ) , this . configService . get ( 'PUBLICKEY' ) , this . configService . get ( 'PRIVATEKEY' ) ) ; this . desktopPublicKey = this . configService . get ( 'PUBLICKEY' ) ; } async registerDesktop ( info ) { let dn = await DesktopNotifModel . findOne ( { where : { userId : info . userId , endpoint : info . endpoint } , } ) ; if ( ! dn ) { dn = await DesktopNotifModel . create ( info ) . save ( ) ; await dn . reload ( ) ; } return dn ; } async registerPhone ( phoneNumber , user ) { const fullNumber = await this . twilioService . getFullPhoneNumber ( phoneNumber ) ; if ( ! fullNumber ) { throw new BadRequestException ( ERROR_MESSAGES . notificationService . registerPhone ) ; } let phoneNotifModel = await PhoneNotifModel . findOne ( { userId : user . id , } ) ; if ( phoneNotifModel ) { if ( phoneNotifModel . phoneNumber === fullNumber ) { return ; } else { phoneNotifModel . phoneNumber = fullNumber ; phoneNotifModel . verified = false ; await phoneNotifModel . save ( ) ; } } else { phoneNotifModel = await PhoneNotifModel . create ( { phoneNumber : fullNumber , userId : user . id , verified : false , } ) . save ( ) ; user . phoneNotif = phoneNotifModel ; } await this . notifyPhone ( phoneNotifModel , "You've signed up for phone notifications for Khoury Office Hours. To verify your number, please respond to this message with YES. To unsubscribe, respond to this message with NO or STOP" , true ) ; } async notifyUser ( userId , message ) { const notifModelsOfUser = await UserModel . findOne ( { where : { id : userId , } , relations : [ 'desktopNotifs' , 'phoneNotif' ] , } ) ; if ( notifModelsOfUser . desktopNotifsEnabled ) { await Promise . all ( notifModelsOfUser . desktopNotifs . map ( async ( nm ) => this . notifyDesktop ( nm , message ) ) ) ; } if ( notifModelsOfUser . phoneNotif && notifModelsOfUser . phoneNotifsEnabled ) { this . notifyPhone ( notifModelsOfUser . phoneNotif , message , false ) ; } } async notifyDesktop ( nm , message ) { try { await webPush . sendNotification ( { endpoint : nm . endpoint , keys : { p256dh : nm . p256dh , auth : nm . auth , } , } , message ) ; } catch ( error ) { await DesktopNotifModel . remove ( nm ) ; } } async notifyPhone ( pn , message , force ) { if ( force || pn . verified ) { try { await this . twilioService . sendSMS ( pn . phoneNumber , message ) ; } catch ( error ) { console . error ( 'problem sending message' , error ) ; } } } async verifyPhone ( phoneNumber , message ) { const phoneNotif = await PhoneNotifModel . findOne ( { where : { phoneNumber : phoneNumber } , } ) ; if ( ! phoneNotif ) { return NotifMsgs . phone . COULD_NOT_FIND_NUMBER ; } else if ( message !== 'YES' && message !== 'NO' && message !== 'STOP' ) { return NotifMsgs . phone . WRONG_MESSAGE ; } else if ( message === 'NO' || message === 'STOP' ) { await PhoneNotifModel . delete ( phoneNotif ) ; return NotifMsgs . phone . UNREGISTER ; } else if ( phoneNotif . verified ) { return NotifMsgs . phone . DUPLICATE ; } else { phoneNotif . verified = true ; await phoneNotif . save ( ) ; return NotifMsgs . phone . OK ; } } }