From 90e8ca4e976730694b3d1b299ebc831e28eafa47 Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Wed, 22 Jan 2025 19:25:49 +0700 Subject: [PATCH] UBERF-9230: Fix ses webpush (#7760) Signed-off-by: Andrey Sobolev --- .../notification-resources/src/index.ts | 29 +++++------- services/ses/pod-ses/src/index.ts | 6 ++- services/ses/pod-ses/src/main.ts | 47 +++++++++++++------ 3 files changed, 50 insertions(+), 32 deletions(-) diff --git a/server-plugins/notification-resources/src/index.ts b/server-plugins/notification-resources/src/index.ts index 622ee07482..9f5994e4e2 100644 --- a/server-plugins/notification-resources/src/index.ts +++ b/server-plugins/notification-resources/src/index.ts @@ -15,6 +15,7 @@ // import activity, { ActivityMessage, DocUpdateMessage } from '@hcengineering/activity' +import { Analytics } from '@hcengineering/analytics' import chunter, { ChatMessage } from '@hcengineering/chunter' import contact, { Employee, @@ -39,7 +40,6 @@ import core, { generateId, MeasureContext, MixinUpdate, - RateLimiter, Ref, RefTo, SortingOrder, @@ -82,7 +82,6 @@ import serverView from '@hcengineering/server-view' import { markupToText, stripTags } from '@hcengineering/text-core' import { encodeObjectURI } from '@hcengineering/view' import { workbenchId } from '@hcengineering/workbench' -import { Analytics } from '@hcengineering/analytics' import { Content, ContextsCache, ContextsCacheKey, NotifyParams, NotifyResult } from './types' import { @@ -92,6 +91,7 @@ import { getNotificationContent, getNotificationLink, getNotificationProviderControl, + getObjectSpace, getTextPresenter, getUsersInfo, isAllowed, @@ -103,8 +103,7 @@ import { replaceAll, toReceiverInfo, updateNotifyContextsSpace, - type NotificationProviderControl, - getObjectSpace + type NotificationProviderControl } from './utils' export function getPushCollaboratorTx ( @@ -602,13 +601,7 @@ export async function createPushNotification ( } } - const limiter = new RateLimiter(5) - for (const subscription of userSubscriptions) { - await limiter.add(async () => { - await sendPushToSubscription(sesURL, sesAuth, control, target, subscription, data) - }) - } - await limiter.waitProcessing() + void sendPushToSubscription(sesURL, sesAuth, control, target, userSubscriptions, data) } async function sendPushToSubscription ( @@ -616,11 +609,11 @@ async function sendPushToSubscription ( sesAuth: string | undefined, control: TriggerControl, targetUser: Ref, - subscription: PushSubscription, + subscriptions: PushSubscription[], data: PushData ): Promise { try { - const result: 'ok' | 'clear-push' = ( + const result: Ref[] = ( await ( await fetch(concatLink(sesURL, '/web-push'), { method: 'post', @@ -629,15 +622,17 @@ async function sendPushToSubscription ( ...(sesAuth != null ? { Authorization: `Bearer ${sesAuth}` } : {}) }, body: JSON.stringify({ - subscription, + subscriptions, data }) }) ).json() ).result - if (result === 'clear-push') { - const tx = control.txFactory.createTxRemoveDoc(subscription._class, subscription.space, subscription._id) - await control.apply(control.ctx, [tx]) + if (result.length > 0) { + const domain = control.hierarchy.findDomain(notification.class.PushSubscription) + if (domain !== undefined) { + await control.lowLevel.clean(control.ctx, domain, result) + } } } catch (err) { control.ctx.info('Cannot send push notification to', { user: targetUser, err }) diff --git a/services/ses/pod-ses/src/index.ts b/services/ses/pod-ses/src/index.ts index 22b45ddd13..c225f89a0f 100644 --- a/services/ses/pod-ses/src/index.ts +++ b/services/ses/pod-ses/src/index.ts @@ -15,4 +15,8 @@ import { main } from './main' -void main() +void main().catch((err) => { + if (err != null) { + console.error(err) + } +}) diff --git a/services/ses/pod-ses/src/main.ts b/services/ses/pod-ses/src/main.ts index 6e91af0cc2..3dd3ae0227 100644 --- a/services/ses/pod-ses/src/main.ts +++ b/services/ses/pod-ses/src/main.ts @@ -13,6 +13,7 @@ // limitations under the License. // +import type { Ref } from '@hcengineering/core' import { PushSubscription, type PushData } from '@hcengineering/notification' import type { Request, Response } from 'express' import webpush, { WebPushError } from 'web-push' @@ -22,25 +23,39 @@ import { SES } from './ses' import { Endpoint } from './types' const errorMessages = ['expired', 'Unregistered', 'No such subscription'] -async function sendPushToSubscription (subscription: PushSubscription, data: PushData): Promise<'ok' | 'clear-push'> { - try { - await webpush.sendNotification(subscription, JSON.stringify(data)) - } catch (err: any) { - if (err instanceof WebPushError) { - if (errorMessages.some((p) => JSON.stringify((err as WebPushError).body).includes(p))) { - return 'clear-push' +async function sendPushToSubscription ( + subscriptions: PushSubscription[], + data: PushData +): Promise[]> { + const result: Ref[] = [] + for (const subscription of subscriptions) { + try { + await webpush.sendNotification(subscription, JSON.stringify(data)) + } catch (err: any) { + if (err instanceof WebPushError) { + if (errorMessages.some((p) => JSON.stringify((err as WebPushError).body).includes(p))) { + result.push(subscription._id) + } } } } - return 'ok' + return result } export const main = async (): Promise => { const ses = new SES() console.log('SES service has been started') + let webpushInitDone = false if (config.PushPublicKey !== undefined && config.PushPrivateKey !== undefined) { - webpush.setVapidDetails(config.PushSubject ?? 'mailto:hey@huly.io', config.PushPublicKey, config.PushPublicKey) + try { + const subj = config.PushSubject ?? 'mailto:hey@huly.io' + console.log('Setting VAPID details', subj, config.PushPublicKey.length, config.PushPrivateKey.length) + webpush.setVapidDetails(config.PushSubject ?? 'mailto:hey@huly.io', config.PushPublicKey, config.PushPrivateKey) + webpushInitDone = true + } catch (err: any) { + console.error(err) + } } const checkAuth = (req: Request, res: Response): boolean => { @@ -104,14 +119,18 @@ export const main = async (): Promise => { res.status(400).send({ err: "'data' is missing" }) return } - const subscription: PushSubscription | undefined = req.body?.subscription - if (subscription === undefined) { - res.status(400).send({ err: "'subscription' is missing" }) + const subscriptions: PushSubscription[] | undefined = req.body?.subscriptions + if (subscriptions === undefined) { + res.status(400).send({ err: "'subscriptions' is missing" }) + return + } + if (!webpushInitDone) { + res.json({ result: [] }).end() return } - const result = await sendPushToSubscription(subscription, data) - res.json({ result }) + const result = await sendPushToSubscription(subscriptions, data) + res.json({ result }).end() } } ]