diff --git a/app/server/queues/notification.queue.ts b/app/server/queues/notification.queue.ts index ed8b236..d2ddda5 100644 --- a/app/server/queues/notification.queue.ts +++ b/app/server/queues/notification.queue.ts @@ -73,7 +73,7 @@ export async function addSendNotificationJobs(data: SendNotificationJobData[]) { export async function addFanoutNotificationJob(data: FanoutNotificationJobData) { const queue = getNotificationQueue() return queue.add('fanout-notification', data, { - jobId: `fanout:${data.notificationId}`, + jobId: `fanout-${data.notificationId}`, attempts: 5, backoff: { type: 'exponential', diff --git a/app/server/utils/notificationJobId.ts b/app/server/utils/notificationJobId.ts index c4b4a2d..dbc3e13 100644 --- a/app/server/utils/notificationJobId.ts +++ b/app/server/utils/notificationJobId.ts @@ -11,10 +11,13 @@ interface ChannelJobShape { to: string } +// BullMQ rejects custom job ids containing ':' (used as a Redis key separator), +// throwing "Custom Id cannot contain :". Use '-' instead. export function getSendNotificationJobId(data: DeviceJobShape | ChannelJobShape) { if (data.deliveryMode === 'device') { - return `notification:${data.notificationId}:device:${data.deviceId}` + return `notification-${data.notificationId}-device-${data.deviceId}` } - return `notification:${data.notificationId}:channel:${data.channelId}:${data.to}` + const to = data.to.replace(/:/g, '-') + return `notification-${data.notificationId}-channel-${data.channelId}-${to}` } diff --git a/app/tests/notification-queue.test.ts b/app/tests/notification-queue.test.ts index 75cd86a..33237f5 100644 --- a/app/tests/notification-queue.test.ts +++ b/app/tests/notification-queue.test.ts @@ -14,7 +14,7 @@ describe('getSendNotificationJobId', () => { title: 'Hello', body: 'World', }, - })).toBe('notification:n1:device:d1') + })).toBe('notification-n1-device-d1') }) it('builds deterministic ids for channel jobs', () => { @@ -29,6 +29,21 @@ describe('getSendNotificationJobId', () => { title: 'Hello', body: 'World', }, - })).toBe('notification:n1:channel:c1:foo@example.com') + })).toBe('notification-n1-channel-c1-foo@example.com') + }) + + it('sanitizes colons in channel "to" field (e.g. Web Push endpoints)', () => { + expect(getSendNotificationJobId({ + deliveryMode: 'channel', + notificationId: 'n1', + appId: 'a1', + channelId: 'c1', + to: 'https://fcm.googleapis.com/fcm/send/abc', + channelType: 'PUSH', + payload: { + title: 'Hello', + body: 'World', + }, + })).not.toContain(':') }) })