Extract trigger for pushes (#7767)
Some checks are pending
CI / build (push) Waiting to run
CI / svelte-check (push) Blocked by required conditions
CI / formatting (push) Blocked by required conditions
CI / test (push) Blocked by required conditions
CI / uitest (push) Waiting to run
CI / uitest-pg (push) Waiting to run
CI / uitest-qms (push) Waiting to run
CI / docker-build (push) Blocked by required conditions
CI / dist-build (push) Blocked by required conditions

Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
Kristina 2025-01-22 20:45:03 +04:00 committed by GitHub
parent 86da890c13
commit bba3b48bb5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 408 additions and 234 deletions

View File

@ -239,6 +239,9 @@ export class TInboxNotification extends TDoc implements InboxNotification {
@Prop(TypeBoolean(), core.string.Boolean)
archived!: boolean
objectId!: Ref<Doc>
objectClass!: Ref<Class<Doc>>
declare space: Ref<PersonSpace>
title?: IntlString

View File

@ -247,6 +247,43 @@ export async function migrateSettings (client: MigrationClient): Promise<void> {
)
}
export async function migrateNotificationsObject (client: MigrationClient): Promise<void> {
while (true) {
const notifications = await client.find<InboxNotification>(
DOMAIN_NOTIFICATION,
{ objectId: { $exists: false }, docNotifyContext: { $exists: true } },
{ limit: 500 }
)
if (notifications.length === 0) return
const contextIds = Array.from(new Set(notifications.map((n) => n.docNotifyContext)))
const contexts = await client.find<DocNotifyContext>(DOMAIN_DOC_NOTIFY, { _id: { $in: contextIds } })
for (const context of contexts) {
await client.update(
DOMAIN_NOTIFICATION,
{ docNotifyContext: context._id, objectId: { $exists: false } },
{ objectId: context.objectId, objectClass: context.objectClass }
)
}
const toDelete: Ref<InboxNotification>[] = []
for (const notification of notifications) {
const context = contexts.find((c) => c._id === notification.docNotifyContext)
if (context === undefined) {
toDelete.push(notification._id)
}
}
if (toDelete.length > 0) {
await client.deleteMany(DOMAIN_NOTIFICATION, { _id: { $in: toDelete } })
}
}
}
export const notificationOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {
await tryMigrate(client, notificationId, [
@ -429,6 +466,10 @@ export const notificationOperation: MigrateOperation = {
func: async (client) => {
await client.update(DOMAIN_DOC_NOTIFY, { space: core.space.Space }, { space: core.space.Workspace })
}
},
{
state: 'migrate-notifications-object',
func: migrateNotificationsObject
}
])
},

View File

@ -103,4 +103,13 @@ export function createModel (builder: Builder): void {
mixin: contact.mixin.Employee
}
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverNotification.trigger.PushNotificationsHandler,
isAsync: true,
txMatch: {
_class: core.class.TxCreateDoc,
objectClass: notification.class.InboxNotification
}
})
}

View File

@ -235,6 +235,8 @@ export interface InboxNotification extends Doc<PersonSpace> {
isViewed: boolean
docNotifyContext: Ref<DocNotifyContext>
objectId: Ref<Doc>
objectClass: Ref<Class<Doc>>
// For browser notifications
title?: IntlString

View File

@ -151,6 +151,8 @@ export async function getPersonNotificationTxes (
messageHtml: reference.message,
mentionedIn: reference.attachedDocId ?? reference.srcDocId,
mentionedInClass: reference.attachedDocClass ?? reference.srcDocClass,
objectId: reference.srcDocId,
objectClass: reference.srcDocClass,
user: receiver[0]._id,
isViewed: false,
archived: false
@ -238,9 +240,6 @@ export async function getPersonNotificationTxes (
modifiedOn: originTx.modifiedOn,
modifiedBy: sender._id
}
const subscriptions = await control.findAll(control.ctx, notification.class.PushSubscription, {
user: receiverInfo._id
})
const msg = control.hierarchy.isDerived(data.mentionedInClass, activity.class.ActivityMessage)
? (await control.findAll(control.ctx, data.mentionedInClass, { _id: data.mentionedIn }))[0]
@ -248,14 +247,11 @@ export async function getPersonNotificationTxes (
await applyNotificationProviders(
notificationData,
notifyResult,
reference.srcDocId,
reference.srcDocClass,
control,
res,
doc,
receiverInfo,
senderInfo,
subscriptions,
notification.class.MentionInboxNotification,
msg as ActivityMessage
)

View File

@ -15,16 +15,8 @@
//
import activity, { ActivityMessage, DocUpdateMessage } from '@hcengineering/activity'
import { Analytics } from '@hcengineering/analytics'
import chunter, { ChatMessage } from '@hcengineering/chunter'
import contact, {
Employee,
getAvatarProviderId,
getGravatarUrl,
Person,
PersonAccount,
type AvatarInfo
} from '@hcengineering/contact'
import contact, { Employee, Person, PersonAccount } from '@hcengineering/contact'
import core, {
Account,
AnyAttribute,
@ -33,7 +25,6 @@ import core, {
Class,
Collection,
combineAttributes,
concatLink,
Data,
Doc,
DocumentUpdate,
@ -64,26 +55,28 @@ import notification, {
DocNotifyContext,
InboxNotification,
MentionInboxNotification,
notificationId,
NotificationType,
PushData,
PushSubscription
NotificationType
} from '@hcengineering/notification'
import { getMetadata, getResource, translate } from '@hcengineering/platform'
import serverCore, { type TriggerControl } from '@hcengineering/server-core'
import { getResource, translate } from '@hcengineering/platform'
import { type TriggerControl } from '@hcengineering/server-core'
import serverNotification, {
getPersonAccountById,
NOTIFICATION_BODY_SIZE,
PUSH_NOTIFICATION_TITLE_SIZE,
ReceiverInfo,
SenderInfo
} from '@hcengineering/server-notification'
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 {
AvailableProvidersCache,
AvailableProvidersCacheKey,
Content,
ContextsCache,
ContextsCacheKey,
NotifyParams,
NotifyResult
} from './types'
import {
createPullCollaboratorsTx,
createPushCollaboratorsTx,
@ -105,6 +98,7 @@ import {
updateNotifyContextsSpace,
type NotificationProviderControl
} from './utils'
import { PushNotificationsHandler } from './push'
export function getPushCollaboratorTx (
control: TriggerControl,
@ -165,20 +159,8 @@ export async function getCommonNotificationTxes (
if (notificationTx !== undefined) {
const notificationData = TxProcessor.createDoc2Doc(notificationTx)
const subscriptions = await control.findAll(ctx, notification.class.PushSubscription, { user: receiver._id })
await applyNotificationProviders(
notificationData,
notifyResult,
attachedTo,
attachedToClass,
control,
res,
doc,
receiver,
sender,
subscriptions,
_class
)
await applyNotificationProviders(notificationData, notifyResult, control, res, doc, receiver, sender, _class)
}
return res
@ -383,6 +365,8 @@ export async function pushInboxNotifications (
isViewed: false,
docNotifyContext: docNotifyContextId,
archived: false,
objectId,
objectClass,
...data
}
const notificationTx = control.txFactory.createTxCreateDoc(_class, receiver.space, notificationData)
@ -489,156 +473,6 @@ export async function getTranslatedNotificationContent (
return { title: '', body: '' }
}
function isReactionMessage (message?: ActivityMessage): boolean {
return (
message !== undefined &&
message._class === activity.class.DocUpdateMessage &&
(message as DocUpdateMessage).objectClass === activity.class.Reaction
)
}
export async function createPushFromInbox (
control: TriggerControl,
receiver: ReceiverInfo,
attachedTo: Ref<Doc>,
attachedToClass: Ref<Class<Doc>>,
data: Data<InboxNotification>,
_class: Ref<Class<InboxNotification>>,
sender: SenderInfo,
_id: Ref<Doc>,
subscriptions: PushSubscription[],
message?: ActivityMessage
): Promise<Tx | undefined> {
let { title, body } = await getTranslatedNotificationContent(data, _class, control)
if (title === '' || body === '') {
return
}
title = title.slice(0, PUSH_NOTIFICATION_TITLE_SIZE)
const senderPerson = sender.person
const linkProviders = control.modelDb.findAllSync(serverView.mixin.ServerLinkIdProvider, {})
const provider = linkProviders.find(({ _id }) => _id === attachedToClass)
let id: string = attachedTo
if (provider !== undefined) {
const encodeFn = await getResource(provider.encode)
const doc = (await control.findAll(control.ctx, attachedToClass, { _id: attachedTo }))[0]
if (doc === undefined) {
return
}
id = await encodeFn(doc, control)
}
const path = [workbenchId, control.workspace.workspaceUrl, notificationId, encodeObjectURI(id, attachedToClass)]
await createPushNotification(
control,
receiver._id as Ref<PersonAccount>,
title,
body,
_id,
subscriptions,
senderPerson,
path
)
return control.txFactory.createTxCreateDoc(notification.class.BrowserNotification, receiver.space, {
user: receiver._id,
title,
body,
senderId: sender._id,
tag: _id,
objectId: attachedTo,
objectClass: attachedToClass,
messageId: isReactionMessage(message) ? (message?.attachedTo as Ref<ActivityMessage>) : message?._id,
messageClass: isReactionMessage(message)
? (message?.attachedToClass as Ref<Class<ActivityMessage>>)
: message?._class,
onClickLocation: {
path
}
})
}
export async function createPushNotification (
control: TriggerControl,
target: Ref<PersonAccount>,
title: string,
body: string,
_id: string,
subscriptions: PushSubscription[],
senderAvatar?: Data<AvatarInfo>,
path?: string[]
): Promise<void> {
const sesURL: string | undefined = getMetadata(serverNotification.metadata.SesUrl)
const sesAuth: string | undefined = getMetadata(serverNotification.metadata.SesAuthToken)
if (sesURL === undefined || sesURL === '') return
const userSubscriptions = subscriptions.filter((it) => it.user === target)
const data: PushData = {
title,
body
}
if (_id !== undefined) {
data.tag = _id
}
const front = control.branding?.front ?? getMetadata(serverCore.metadata.FrontUrl) ?? ''
const domainPath = `${workbenchId}/${control.workspace.workspaceUrl}`
data.domain = concatLink(front, domainPath)
if (path !== undefined) {
data.url = concatLink(front, path.join('/'))
}
if (senderAvatar != null) {
const provider = getAvatarProviderId(senderAvatar.avatarType)
if (provider === contact.avatarProvider.Image) {
if (senderAvatar.avatar != null) {
const url = await control.storageAdapter.getUrl(control.ctx, control.workspace, senderAvatar.avatar)
data.icon = url.includes('://') ? url : concatLink(front, url)
}
} else if (provider === contact.avatarProvider.Gravatar && senderAvatar.avatarProps?.url !== undefined) {
data.icon = getGravatarUrl(senderAvatar.avatarProps?.url, 512)
}
}
void sendPushToSubscription(sesURL, sesAuth, control, target, userSubscriptions, data)
}
async function sendPushToSubscription (
sesURL: string,
sesAuth: string | undefined,
control: TriggerControl,
targetUser: Ref<Account>,
subscriptions: PushSubscription[],
data: PushData
): Promise<void> {
try {
const result: Ref<PushSubscription>[] = (
await (
await fetch(concatLink(sesURL, '/web-push'), {
method: 'post',
headers: {
'Content-Type': 'application/json',
...(sesAuth != null ? { Authorization: `Bearer ${sesAuth}` } : {})
},
body: JSON.stringify({
subscriptions,
data
})
})
).json()
).result
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 })
}
}
/**
* @public
*/
@ -682,40 +516,16 @@ export async function pushActivityInboxNotifications (
export async function applyNotificationProviders (
data: InboxNotification,
notifyResult: NotifyResult,
attachedTo: Ref<Doc>,
attachedToClass: Ref<Class<Doc>>,
control: TriggerControl,
res: Tx[],
object: Doc,
receiver: ReceiverInfo,
sender: SenderInfo,
subscriptions: PushSubscription[],
_class = notification.class.ActivityInboxNotification,
message?: ActivityMessage
): Promise<void> {
const resources = control.modelDb.findAllSync(serverNotification.class.NotificationProviderResources, {})
for (const [provider, types] of notifyResult.entries()) {
if (provider === notification.providers.PushNotificationProvider) {
// const now = Date.now()
const pushTx = await createPushFromInbox(
control,
receiver,
attachedTo,
attachedToClass,
data,
_class,
sender,
data._id,
subscriptions,
message
)
if (pushTx !== undefined) {
res.push(pushTx)
}
continue
}
const resource = resources.find((it) => it.provider === provider)
if (resource === undefined) continue
@ -789,8 +599,7 @@ export async function getNotificationTxes (
params: NotifyParams,
docNotifyContexts: DocNotifyContext[],
activityMessages: ActivityMessage[],
settings: NotificationProviderControl,
subscriptions: PushSubscription[]
settings: NotificationProviderControl
): Promise<Tx[]> {
if (receiver.account === undefined) {
return []
@ -828,17 +637,23 @@ export async function getNotificationTxes (
if (notificationTx !== undefined) {
const notificationData = TxProcessor.createDoc2Doc(notificationTx)
const current: AvailableProvidersCache = control.contextCache.get(AvailableProvidersCacheKey) ?? new Map()
const providers = Array.from(notifyResult.keys()).filter(
(p) => p !== notification.providers.InboxNotificationProvider
)
if (providers.length > 0) {
current.set(notificationData._id, providers)
control.contextCache.set('AvailableNotificationProviders', current)
}
await applyNotificationProviders(
notificationData,
notifyResult,
message.attachedTo,
message.attachedToClass,
control,
res,
object,
receiver,
sender,
subscriptions,
notificationData._class,
message
)
@ -1030,9 +845,6 @@ export async function createCollabDocInfo (
}
const settings = await getNotificationProviderControl(ctx, control)
const subscriptions = (await control.queryFind(ctx, notification.class.PushSubscription, {})).filter((it) =>
targets.has(it.user as Ref<PersonAccount>)
)
for (const target of targets) {
const info: ReceiverInfo | undefined = toReceiverInfo(control.hierarchy, usersInfo.get(target))
@ -1049,8 +861,7 @@ export async function createCollabDocInfo (
params,
notifyContexts,
docMessages,
settings,
subscriptions
settings
)
const ids = new Set(targetRes.map((it) => it._id))
if (info.account?.email !== undefined) {
@ -2031,6 +1842,7 @@ async function OnDocRemove (txes: TxCUD<Doc>[], control: TriggerControl): Promis
}
export * from './types'
export * from './push'
export * from './utils'
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
@ -2039,7 +1851,8 @@ export default async () => ({
OnAttributeCreate,
OnAttributeUpdate,
OnDocRemove,
OnEmployeeDeactivate
OnEmployeeDeactivate,
PushNotificationsHandler
},
function: {
IsUserInFieldValueTypeMatch: isUserInFieldValueTypeMatch,

View File

@ -0,0 +1,295 @@
//
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import serverCore, { TriggerControl } from '@hcengineering/server-core'
import serverNotification, { PUSH_NOTIFICATION_TITLE_SIZE } from '@hcengineering/server-notification'
import {
Account,
Class,
concatLink,
Data,
Doc,
Hierarchy,
Ref,
Tx,
TxCreateDoc,
TxProcessor
} from '@hcengineering/core'
import notification, {
ActivityInboxNotification,
InboxNotification,
MentionInboxNotification,
notificationId,
PushData,
PushSubscription
} from '@hcengineering/notification'
import activity, { ActivityMessage } from '@hcengineering/activity'
import serverView from '@hcengineering/server-view'
import { getMetadata, getResource } from '@hcengineering/platform'
import { workbenchId } from '@hcengineering/workbench'
import { encodeObjectURI } from '@hcengineering/view'
import contact, {
type AvatarInfo,
getAvatarProviderId,
getGravatarUrl,
Person,
PersonAccount,
PersonSpace
} from '@hcengineering/contact'
import { AvailableProvidersCache, AvailableProvidersCacheKey, getTranslatedNotificationContent } from './index'
async function createPushFromInbox (
control: TriggerControl,
n: InboxNotification,
receiver: Ref<Account>,
receiverSpace: Ref<PersonSpace>,
subscriptions: PushSubscription[],
senderPerson?: Person
): Promise<Tx | undefined> {
let { title, body } = await getTranslatedNotificationContent(n, n._class, control)
if (title === '' || body === '') {
return
}
title = title.slice(0, PUSH_NOTIFICATION_TITLE_SIZE)
const linkProviders = control.modelDb.findAllSync(serverView.mixin.ServerLinkIdProvider, {})
const provider = linkProviders.find(({ _id }) => _id === n.objectClass)
let id: string = n.objectId
if (provider !== undefined) {
const encodeFn = await getResource(provider.encode)
const cache: Map<Ref<Doc>, Doc> = control.contextCache.get('PushNotificationsHandler') ?? new Map()
const doc = cache.get(n.objectId) ?? (await control.findAll(control.ctx, n.objectClass, { _id: n.objectId }))[0]
if (doc === undefined) {
return
}
cache.set(n.objectId, doc)
control.contextCache.set('PushNotificationsHandler', cache)
id = await encodeFn(doc, control)
}
const path = [workbenchId, control.workspace.workspaceUrl, notificationId, encodeObjectURI(id, n.objectClass)]
await createPushNotification(
control,
receiver as Ref<PersonAccount>,
title,
body,
n._id,
subscriptions,
senderPerson,
path
)
const messageInfo = getMessageInfo(n, control.hierarchy)
return control.txFactory.createTxCreateDoc(notification.class.BrowserNotification, receiverSpace, {
user: receiver,
title,
body,
senderId: n.createdBy ?? n.modifiedBy,
tag: n._id,
objectId: n.objectId,
objectClass: n.objectClass,
messageId: messageInfo._id,
messageClass: messageInfo._class,
onClickLocation: {
path
}
})
}
function getMessageInfo (
n: InboxNotification,
hierarchy: Hierarchy
): {
_id?: Ref<ActivityMessage>
_class?: Ref<Class<ActivityMessage>>
} {
if (hierarchy.isDerived(n._class, notification.class.ActivityInboxNotification)) {
const activityNotification = n as ActivityInboxNotification
if (
activityNotification.attachedToClass === activity.class.DocUpdateMessage &&
hierarchy.isDerived(activityNotification.objectClass, activity.class.ActivityMessage)
) {
return {
_id: activityNotification.objectId as Ref<ActivityMessage>,
_class: activityNotification.objectClass
}
}
return {
_id: activityNotification.attachedTo,
_class: activityNotification.attachedToClass
}
}
if (hierarchy.isDerived(n._class, notification.class.MentionInboxNotification)) {
const mentionNotification = n as MentionInboxNotification
if (hierarchy.isDerived(mentionNotification.mentionedInClass, activity.class.ActivityMessage)) {
return {
_id: mentionNotification.mentionedIn as Ref<ActivityMessage>,
_class: mentionNotification.mentionedInClass
}
}
}
return {}
}
export async function createPushNotification (
control: TriggerControl,
target: Ref<PersonAccount>,
title: string,
body: string,
_id: string,
subscriptions: PushSubscription[],
senderAvatar?: Data<AvatarInfo>,
path?: string[]
): Promise<void> {
const sesURL: string | undefined = getMetadata(serverNotification.metadata.SesUrl)
const sesAuth: string | undefined = getMetadata(serverNotification.metadata.SesAuthToken)
if (sesURL === undefined || sesURL === '') return
const userSubscriptions = subscriptions.filter((it) => it.user === target)
const data: PushData = {
title,
body
}
if (_id !== undefined) {
data.tag = _id
}
const front = control.branding?.front ?? getMetadata(serverCore.metadata.FrontUrl) ?? ''
const domainPath = `${workbenchId}/${control.workspace.workspaceUrl}`
data.domain = concatLink(front, domainPath)
if (path !== undefined) {
data.url = concatLink(front, path.join('/'))
}
if (senderAvatar != null) {
const provider = getAvatarProviderId(senderAvatar.avatarType)
if (provider === contact.avatarProvider.Image) {
if (senderAvatar.avatar != null) {
const url = await control.storageAdapter.getUrl(control.ctx, control.workspace, senderAvatar.avatar)
data.icon = url.includes('://') ? url : concatLink(front, url)
}
} else if (provider === contact.avatarProvider.Gravatar && senderAvatar.avatarProps?.url !== undefined) {
data.icon = getGravatarUrl(senderAvatar.avatarProps?.url, 512)
}
}
void sendPushToSubscription(sesURL, sesAuth, control, target, userSubscriptions, data)
}
async function sendPushToSubscription (
sesURL: string,
sesAuth: string | undefined,
control: TriggerControl,
targetUser: Ref<Account>,
subscriptions: PushSubscription[],
data: PushData
): Promise<void> {
try {
const result: Ref<PushSubscription>[] = (
await (
await fetch(concatLink(sesURL, '/web-push'), {
method: 'post',
headers: {
'Content-Type': 'application/json',
...(sesAuth != null ? { Authorization: `Bearer ${sesAuth}` } : {})
},
body: JSON.stringify({
subscriptions,
data
})
})
).json()
).result
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 })
}
}
export async function PushNotificationsHandler (
txes: TxCreateDoc<InboxNotification>[],
control: TriggerControl
): Promise<Tx[]> {
const availableProviders: AvailableProvidersCache = control.contextCache.get(AvailableProvidersCacheKey) ?? new Map()
const all: InboxNotification[] = txes
.map((tx) => TxProcessor.createDoc2Doc(tx))
.filter(
(it) =>
availableProviders.get(it._id)?.find((p) => p === notification.providers.PushNotificationProvider) !== undefined
)
if (all.length === 0) {
return []
}
const receivers = new Set(all.map((it) => it.user))
const subscriptions = (await control.queryFind(control.ctx, notification.class.PushSubscription, {})).filter((it) =>
receivers.has(it.user)
)
if (subscriptions.length === 0) {
return []
}
const senders = Array.from(new Set(all.map((it) => it.createdBy)))
const senderAccounts = await control.modelDb.findAll(contact.class.PersonAccount, {
_id: { $in: senders as Ref<PersonAccount>[] }
})
const senderPersons = await control.findAll(control.ctx, contact.class.Person, {
_id: { $in: Array.from(new Set(senderAccounts.map((it) => it.person))) }
})
const res: Tx[] = []
for (const inboxNotification of all) {
const { user } = inboxNotification
const userSubscriptions = subscriptions.filter((it) => it.user === user)
if (userSubscriptions.length === 0) continue
const senderAccount = senderAccounts.find(
(it) => it._id === (inboxNotification.createdBy ?? inboxNotification.modifiedBy)
)
const senderPerson =
senderAccount !== undefined ? senderPersons.find((it) => it._id === senderAccount.person) : undefined
const tx = await createPushFromInbox(
control,
inboxNotification,
user,
inboxNotification.space,
userSubscriptions,
senderPerson
)
if (tx !== undefined) {
res.push(tx)
}
}
return res
}

View File

@ -12,7 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { BaseNotificationType, DocNotifyContext, NotificationProvider } from '@hcengineering/notification'
import {
BaseNotificationType,
DocNotifyContext,
InboxNotification,
NotificationProvider
} from '@hcengineering/notification'
import { Ref } from '@hcengineering/core'
/**
@ -39,3 +44,6 @@ export const ContextsCacheKey = 'DocNotifyContexts'
export interface ContextsCache {
contexts: Map<string, Ref<DocNotifyContext>>
}
export const AvailableProvidersCacheKey = 'AvailableNotificationProviders'
export type AvailableProvidersCache = Map<Ref<InboxNotification>, Ref<NotificationProvider>[]>

View File

@ -661,3 +661,11 @@ export async function getObjectSpace (control: TriggerControl, doc: Doc, cache:
: (cache.get(doc.space) as Space) ??
(await control.findAll<Space>(control.ctx, core.class.Space, { _id: doc.space }, { limit: 1 }))[0]
}
export function isReactionMessage (message?: ActivityMessage): boolean {
return (
message !== undefined &&
message._class === activity.class.DocUpdateMessage &&
(message as DocUpdateMessage).objectClass === activity.class.Reaction
)
}

View File

@ -168,7 +168,8 @@ export default plugin(serverNotificationId, {
OnAttributeUpdate: '' as Resource<TriggerFunc>,
OnReactionChanged: '' as Resource<TriggerFunc>,
OnDocRemove: '' as Resource<TriggerFunc>,
OnEmployeeDeactivate: '' as Resource<TriggerFunc>
OnEmployeeDeactivate: '' as Resource<TriggerFunc>,
PushNotificationsHandler: '' as Resource<TriggerFunc>
},
function: {
IsUserInFieldValueTypeMatch: '' as TypeMatchFunc,

View File

@ -161,10 +161,7 @@ async function getRequestNotificationTx (
}
const notificationControl = await getNotificationProviderControl(ctx, control)
const collaboratorsSet = new Set(collaborators)
const subscriptions = (await control.queryFind(control.ctx, notification.class.PushSubscription, {})).filter((it) =>
collaboratorsSet.has(it.user)
)
for (const target of collaborators) {
const targetInfo = toReceiverInfo(control.hierarchy, usersInfo.get(target))
if (targetInfo === undefined) continue
@ -179,8 +176,7 @@ async function getRequestNotificationTx (
{ isOwn: true, isSpace: false, shouldUpdateTimestamp: true },
notifyContexts,
messages,
notificationControl,
subscriptions
notificationControl
)
res.push(...txes)
}

View File

@ -36,6 +36,8 @@ export async function createNotification (
} else {
await client.createDoc(notification.class.CommonInboxNotification, data.space, {
user: data.user,
objectId: forDoc._id,
objectClass: forDoc._class,
icon: github.icon.Github,
message: data.message,
props: data.props,