-
Notifications
You must be signed in to change notification settings - Fork 3.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(providers): improve expo response management and multi device token #4508
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,7 @@ import { | |
SelectIntegration, | ||
CompileTemplate, | ||
CompileTemplateCommand, | ||
IPushHandler, | ||
PushFactory, | ||
GetNovuProviderCredentials, | ||
} from '@novu/application-generic'; | ||
|
@@ -148,21 +149,26 @@ export class SendMessagePush extends SendMessageBase { | |
await this.sendSelectedIntegrationExecution(command.job, integration); | ||
|
||
const overrides = command.overrides[integration.providerId] || {}; | ||
|
||
const result = await this.sendMessage( | ||
subscriber, | ||
integration, | ||
deviceTokens, | ||
title, | ||
content, | ||
command, | ||
command.payload, | ||
overrides, | ||
stepData | ||
); | ||
|
||
if (!result) { | ||
integrationsWithErrors++; | ||
const target = (overrides as { deviceTokens?: string[] }).deviceTokens || deviceTokens; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Extracting the |
||
|
||
const message = await this.createMessage(command, integration, title, content, target, overrides); | ||
|
||
for (const deviceToken of target) { | ||
const result = await this.sendMessage( | ||
command, | ||
message, | ||
subscriber, | ||
integration, | ||
deviceToken, | ||
title, | ||
content, | ||
overrides, | ||
stepData | ||
); | ||
|
||
if (!result) { | ||
integrationsWithErrors++; | ||
} | ||
} | ||
} | ||
|
||
|
@@ -273,59 +279,24 @@ export class SendMessagePush extends SendMessageBase { | |
} | ||
|
||
private async sendMessage( | ||
command: SendMessageCommand, | ||
message: MessageEntity, | ||
subscriber: IPushOptions['subscriber'], | ||
integration: IntegrationEntity, | ||
target: string[], | ||
deviceToken: string, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now we will miss the functionalities of sending multiple tokens at the same time in exchange of better execution details without doing breaking changes in the providers. |
||
title: string, | ||
content: string, | ||
command: SendMessageCommand, | ||
payload: object, | ||
overrides: object, | ||
step: IPushOptions['step'] | ||
): Promise<boolean> { | ||
const message: MessageEntity = await this.messageRepository.create({ | ||
_notificationId: command.notificationId, | ||
_environmentId: command.environmentId, | ||
_organizationId: command.organizationId, | ||
_subscriberId: command._subscriberId, | ||
_templateId: command._templateId, | ||
_messageTemplateId: command.step?.template?._id, | ||
channel: ChannelTypeEnum.PUSH, | ||
transactionId: command.transactionId, | ||
deviceTokens: target, | ||
content: this.storeContent() ? content : null, | ||
title, | ||
payload: payload as never, | ||
overrides: overrides as never, | ||
providerId: integration.providerId, | ||
_jobId: command.jobId, | ||
}); | ||
|
||
await this.createExecutionDetails.execute( | ||
CreateExecutionDetailsCommand.create({ | ||
...CreateExecutionDetailsCommand.getDetailsFromJob(command.job), | ||
detail: `${DetailEnum.MESSAGE_CREATED}: ${integration.providerId}`, | ||
source: ExecutionDetailsSourceEnum.INTERNAL, | ||
status: ExecutionDetailsStatusEnum.PENDING, | ||
messageId: message._id, | ||
isTest: false, | ||
isRetry: false, | ||
raw: this.storeContent() ? JSON.stringify(content) : null, | ||
}) | ||
); | ||
|
||
try { | ||
const pushFactory = new PushFactory(); | ||
const pushHandler = pushFactory.getHandler(integration); | ||
if (!pushHandler) { | ||
throw new PlatformException(`Push handler for provider ${integration.providerId} is not found`); | ||
} | ||
const pushHandler = this.getIntegrationHandler(integration); | ||
|
||
const result = await pushHandler.send({ | ||
target: (overrides as { deviceTokens?: string[] }).deviceTokens || target, | ||
target: [deviceToken], | ||
title, | ||
content, | ||
payload, | ||
payload: command.payload, | ||
overrides, | ||
subscriber, | ||
step, | ||
|
@@ -340,7 +311,7 @@ export class SendMessagePush extends SendMessageBase { | |
status: ExecutionDetailsStatusEnum.SUCCESS, | ||
isTest: false, | ||
isRetry: false, | ||
raw: JSON.stringify(result), | ||
raw: JSON.stringify({ result, deviceToken }), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We add the device token in the execution detail raw information to help users to identify which ones succeeded or failed, though it can be arguable if we shouldn't as identifies the device of a user. We need to be careful with them. Some providers treat the device token as not PII, like for example OneSignal: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @scopsy is it good to proceed? |
||
}) | ||
); | ||
|
||
|
@@ -363,4 +334,58 @@ export class SendMessagePush extends SendMessageBase { | |
return false; | ||
} | ||
} | ||
|
||
private async createMessage( | ||
command: SendMessageCommand, | ||
integration: IntegrationEntity, | ||
title: string, | ||
content: string, | ||
deviceTokens: string[], | ||
overrides: object | ||
): Promise<MessageEntity> { | ||
const message = await this.messageRepository.create({ | ||
_notificationId: command.notificationId, | ||
_environmentId: command.environmentId, | ||
_organizationId: command.organizationId, | ||
_subscriberId: command._subscriberId, | ||
_templateId: command._templateId, | ||
_messageTemplateId: command.step?.template?._id, | ||
channel: ChannelTypeEnum.PUSH, | ||
transactionId: command.transactionId, | ||
deviceTokens, | ||
content: this.storeContent() ? content : null, | ||
title, | ||
payload: command.payload as never, | ||
overrides: overrides as never, | ||
providerId: integration.providerId, | ||
_jobId: command.jobId, | ||
}); | ||
|
||
await this.createExecutionDetails.execute( | ||
CreateExecutionDetailsCommand.create({ | ||
...CreateExecutionDetailsCommand.getDetailsFromJob(command.job), | ||
detail: `${DetailEnum.MESSAGE_CREATED}: ${integration.providerId}`, | ||
source: ExecutionDetailsSourceEnum.INTERNAL, | ||
status: ExecutionDetailsStatusEnum.PENDING, | ||
messageId: message._id, | ||
isTest: false, | ||
isRetry: false, | ||
raw: this.storeContent() ? JSON.stringify(content) : null, | ||
}) | ||
); | ||
|
||
return message; | ||
} | ||
|
||
private getIntegrationHandler(integration): IPushHandler { | ||
const pushFactory = new PushFactory(); | ||
const pushHandler = pushFactory.getHandler(integration); | ||
|
||
if (!pushHandler) { | ||
const message = `Push handler for provider ${integration.providerId} is not found`; | ||
throw new PlatformException(message); | ||
} | ||
|
||
return pushHandler; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,7 +17,7 @@ export class PushFactory implements IPushFactory { | |
new PushWebhookHandler(), | ||
]; | ||
|
||
getHandler(integration: IntegrationEntity) { | ||
getHandler(integration: IntegrationEntity): IPushHandler { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Type wasn't being infered by TypeScript. +1 for team explicit here. |
||
const handler = | ||
this.handlers.find((handlerItem) => | ||
handlerItem.canHandle(integration.providerId, integration.channel) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also taking advantage of cleaning the function arguments.