UBERF-9230: Fix ses webpush (#7760)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2025-01-22 19:25:49 +07:00 committed by GitHub
parent 82a9204076
commit 90e8ca4e97
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 50 additions and 32 deletions

View File

@ -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<Account>,
subscription: PushSubscription,
subscriptions: PushSubscription[],
data: PushData
): Promise<void> {
try {
const result: 'ok' | 'clear-push' = (
const result: Ref<PushSubscription>[] = (
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 })

View File

@ -15,4 +15,8 @@
import { main } from './main'
void main()
void main().catch((err) => {
if (err != null) {
console.error(err)
}
})

View File

@ -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<Ref<PushSubscription>[]> {
const result: Ref<PushSubscription>[] = []
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<void> => {
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<any>, res: Response<any>): boolean => {
@ -104,14 +119,18 @@ export const main = async (): Promise<void> => {
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()
}
}
]