-
Notifications
You must be signed in to change notification settings - Fork 4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
75 changed files
with
1,444 additions
and
95 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
24 changes: 24 additions & 0 deletions
24
apps/api/src/app/events/services/push-service/handlers/base.handler.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { IPushOptions, IPushProvider } from '@novu/stateless'; | ||
import { ChannelTypeEnum } from '@novu/shared'; | ||
import { ICredentials } from '@novu/dal'; | ||
import { IPushHandler } from '../interfaces'; | ||
|
||
export abstract class BasePushHandler implements IPushHandler { | ||
protected provider: IPushProvider; | ||
|
||
protected constructor(private providerId: string, private channelType: string) {} | ||
|
||
canHandle(providerId: string, channelType: ChannelTypeEnum) { | ||
return providerId === this.providerId && channelType === this.channelType; | ||
} | ||
|
||
async send(options: IPushOptions) { | ||
if (process.env.NODE_ENV === 'test') { | ||
throw new Error('Currently 3rd-party packages test are not support on test env'); | ||
} | ||
|
||
return await this.provider.sendMessage(options); | ||
} | ||
|
||
abstract buildProvider(credentials: ICredentials); | ||
} |
16 changes: 16 additions & 0 deletions
16
apps/api/src/app/events/services/push-service/handlers/fcm.handler.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { ChannelTypeEnum } from '@novu/shared'; | ||
import { FcmPushProvider } from '@novu/fcm'; | ||
import { BasePushHandler } from './base.handler'; | ||
import { ICredentials } from '@novu/dal'; | ||
|
||
export class FCMHandler extends BasePushHandler { | ||
constructor() { | ||
super('fcm', ChannelTypeEnum.PUSH); | ||
} | ||
|
||
buildProvider(credentials: ICredentials) { | ||
this.provider = new FcmPushProvider({ | ||
secretKey: credentials.secretKey, | ||
}); | ||
} | ||
} |
1 change: 1 addition & 0 deletions
1
apps/api/src/app/events/services/push-service/handlers/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './fcm.handler'; |
2 changes: 2 additions & 0 deletions
2
apps/api/src/app/events/services/push-service/interfaces/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './push.factory.interface'; | ||
export * from './push.handler.interface'; |
6 changes: 6 additions & 0 deletions
6
apps/api/src/app/events/services/push-service/interfaces/push.factory.interface.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { IntegrationEntity } from '@novu/dal'; | ||
import { IPushHandler } from './push.handler.interface'; | ||
|
||
export interface IPushFactory { | ||
getHandler(integration: IntegrationEntity): IPushHandler; | ||
} |
11 changes: 11 additions & 0 deletions
11
apps/api/src/app/events/services/push-service/interfaces/push.handler.interface.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { IPushOptions, ISendMessageSuccessResponse } from '@novu/stateless'; | ||
import { ICredentials } from '@novu/dal'; | ||
import { ChannelTypeEnum } from '@novu/shared'; | ||
|
||
export interface IPushHandler { | ||
canHandle(providerId: string, channelType: ChannelTypeEnum); | ||
|
||
buildProvider(credentials: ICredentials); | ||
|
||
send(smsOptions: IPushOptions): Promise<ISendMessageSuccessResponse>; | ||
} |
22 changes: 22 additions & 0 deletions
22
apps/api/src/app/events/services/push-service/push.factory.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { IntegrationEntity } from '@novu/dal'; | ||
import { IPushFactory, IPushHandler } from './interfaces'; | ||
import { FCMHandler } from './handlers'; | ||
|
||
export class PushFactory implements IPushFactory { | ||
handlers: IPushHandler[] = [new FCMHandler()]; | ||
|
||
getHandler(integration: IntegrationEntity): IPushHandler { | ||
try { | ||
const handler = | ||
this.handlers.find((handlerItem) => handlerItem.canHandle(integration.providerId, integration.channel)) ?? null; | ||
|
||
if (!handler) return null; | ||
|
||
handler.buildProvider(integration.credentials); | ||
|
||
return handler; | ||
} catch (error) { | ||
throw new Error(`Could not build push handler id: ${integration._id}, error: ${error}`); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
196 changes: 196 additions & 0 deletions
196
apps/api/src/app/events/usecases/send-message/send-message-push.usecase.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
import { | ||
IntegrationRepository, | ||
MessageRepository, | ||
NotificationStepEntity, | ||
NotificationRepository, | ||
SubscriberEntity, | ||
SubscriberRepository, | ||
NotificationEntity, | ||
MessageEntity, | ||
} from '@novu/dal'; | ||
import { ChannelTypeEnum, LogCodeEnum, LogStatusEnum } from '@novu/shared'; | ||
import * as Sentry from '@sentry/node'; | ||
import { ContentService } from '../../../shared/helpers/content.service'; | ||
import { CreateLog } from '../../../logs/usecases/create-log/create-log.usecase'; | ||
import { CreateLogCommand } from '../../../logs/usecases/create-log/create-log.command'; | ||
import { PushFactory } from '../../services/push-service/push.factory'; | ||
import { SendMessageCommand } from './send-message.command'; | ||
import { SendMessageType } from './send-message-type.usecase'; | ||
|
||
@Injectable() | ||
export class SendMessagePush extends SendMessageType { | ||
private pushFactory = new PushFactory(); | ||
|
||
constructor( | ||
private subscriberRepository: SubscriberRepository, | ||
private notificationRepository: NotificationRepository, | ||
protected messageRepository: MessageRepository, | ||
protected createLogUsecase: CreateLog, | ||
private integrationRepository: IntegrationRepository | ||
) { | ||
super(messageRepository, createLogUsecase); | ||
} | ||
|
||
public async execute(command: SendMessageCommand) { | ||
Sentry.addBreadcrumb({ | ||
message: 'Sending Push', | ||
}); | ||
const pushChannel: NotificationStepEntity = command.step; | ||
const notification = await this.notificationRepository.findById(command.notificationId); | ||
const subscriber: SubscriberEntity = await this.subscriberRepository.findOne({ | ||
_environmentId: command.environmentId, | ||
_id: command.subscriberId, | ||
}); | ||
const contentService = new ContentService(); | ||
const messageVariables = contentService.buildMessageVariables(command.payload, subscriber); | ||
const content = contentService.replaceVariables(pushChannel.template.content as string, messageVariables); | ||
const title = contentService.replaceVariables(pushChannel.template.title as string, messageVariables); | ||
const notificationIdentifiers = command.payload.notificationIdentifiers || subscriber.notificationIdentifiers; | ||
|
||
const messagePayload = Object.assign({}, command.payload); | ||
delete messagePayload.attachments; | ||
|
||
const message: MessageEntity = await this.messageRepository.create({ | ||
_notificationId: notification._id, | ||
_environmentId: command.environmentId, | ||
_organizationId: command.organizationId, | ||
_subscriberId: command.subscriberId, | ||
_templateId: notification._templateId, | ||
_messageTemplateId: pushChannel.template._id, | ||
channel: ChannelTypeEnum.PUSH, | ||
transactionId: command.transactionId, | ||
notificationIdentifiers, | ||
content, | ||
title, | ||
payload: messagePayload, | ||
}); | ||
|
||
const integration = await this.integrationRepository.findOne({ | ||
_environmentId: command.environmentId, | ||
channel: ChannelTypeEnum.PUSH, | ||
active: true, | ||
}); | ||
|
||
if (notificationIdentifiers && integration) { | ||
await this.sendMessage( | ||
integration, | ||
notificationIdentifiers, | ||
title, | ||
content, | ||
message, | ||
command, | ||
notification, | ||
command.payload | ||
); | ||
|
||
return; | ||
} | ||
|
||
await this.sendErrors(notificationIdentifiers, integration, message, command, notification); | ||
} | ||
|
||
private async sendErrors( | ||
phone, | ||
integration, | ||
message: MessageEntity, | ||
command: SendMessageCommand, | ||
notification: NotificationEntity | ||
) { | ||
if (!phone) { | ||
await this.createLogUsecase.execute( | ||
CreateLogCommand.create({ | ||
transactionId: command.transactionId, | ||
status: LogStatusEnum.ERROR, | ||
environmentId: command.environmentId, | ||
organizationId: command.organizationId, | ||
text: 'Subscriber does not have active phone', | ||
userId: command.userId, | ||
subscriberId: command.subscriberId, | ||
code: LogCodeEnum.SUBSCRIBER_MISSING_PHONE, | ||
templateId: notification._templateId, | ||
raw: { | ||
payload: command.payload, | ||
triggerIdentifier: command.identifier, | ||
}, | ||
}) | ||
); | ||
await this.messageRepository.updateMessageStatus( | ||
message._id, | ||
'warning', | ||
null, | ||
'no_subscriber_phone', | ||
'Subscriber does not have active phone' | ||
); | ||
} | ||
if (!integration) { | ||
await this.sendErrorStatus( | ||
message, | ||
'warning', | ||
'push_missing_integration_error', | ||
'Subscriber does not have an active push integration', | ||
command, | ||
notification, | ||
LogCodeEnum.MISSING_PUSH_INTEGRATION | ||
); | ||
} | ||
if (!integration?.credentials?.from) { | ||
await this.sendErrorStatus( | ||
message, | ||
'warning', | ||
'no_integration_from_phone', | ||
'Integration does not have from phone configured', | ||
command, | ||
notification, | ||
LogCodeEnum.MISSING_PUSH_PROVIDER | ||
); | ||
} | ||
} | ||
|
||
private async sendMessage( | ||
integration, | ||
target: string, | ||
title: string, | ||
content: string, | ||
message: MessageEntity, | ||
command: SendMessageCommand, | ||
notification: NotificationEntity, | ||
payload: object | ||
) { | ||
try { | ||
const pushHandler = this.pushFactory.getHandler(integration); | ||
|
||
await pushHandler.send({ | ||
target, | ||
title, | ||
content, | ||
payload, | ||
}); | ||
} catch (e) { | ||
await this.createLogUsecase.execute( | ||
CreateLogCommand.create({ | ||
transactionId: command.transactionId, | ||
status: LogStatusEnum.ERROR, | ||
environmentId: command.environmentId, | ||
organizationId: command.organizationId, | ||
text: e.message || e.name || 'Un-expect Push provider error', | ||
userId: command.userId, | ||
code: LogCodeEnum.PUSH_ERROR, | ||
templateId: notification._templateId, | ||
raw: { | ||
payload: command.payload, | ||
triggerIdentifier: command.identifier, | ||
}, | ||
}) | ||
); | ||
|
||
await this.messageRepository.updateMessageStatus( | ||
message._id, | ||
'error', | ||
e, | ||
'unexpected_push_error', | ||
e.message || e.name || 'Un-expect Push provider error' | ||
); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.