mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 03:14:40 +03:00
Improve push notifications (#5397)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
a948b20155
commit
83725fc541
@ -20,6 +20,7 @@ import {
|
||||
DOMAIN_MODEL,
|
||||
Hierarchy,
|
||||
IndexKind,
|
||||
type Space,
|
||||
type Account,
|
||||
type AttachedDoc,
|
||||
type Class,
|
||||
@ -60,8 +61,8 @@ import {
|
||||
type CommonInboxNotification,
|
||||
type CommonNotificationType,
|
||||
type DocNotifyContext,
|
||||
type DocUpdates,
|
||||
type DocUpdateTx,
|
||||
type DocUpdates,
|
||||
type InboxNotification,
|
||||
type MentionInboxNotification,
|
||||
type NotificationContextPresenter,
|
||||
@ -73,8 +74,8 @@ import {
|
||||
type NotificationSetting,
|
||||
type NotificationStatus,
|
||||
type NotificationTemplate,
|
||||
type PushSubscription,
|
||||
type NotificationType,
|
||||
type PushSubscription,
|
||||
type PushSubscriptionKeys
|
||||
} from '@hcengineering/notification'
|
||||
import { getEmbeddedLabel, type Asset, type IntlString, type Resource } from '@hcengineering/platform'
|
||||
@ -91,6 +92,8 @@ export const DOMAIN_NOTIFICATION = 'notification' as Domain
|
||||
|
||||
@Model(notification.class.BrowserNotification, core.class.Doc, DOMAIN_NOTIFICATION)
|
||||
export class TBrowserNotification extends TDoc implements BrowserNotification {
|
||||
senderId?: Ref<Account> | undefined
|
||||
tag!: Ref<Doc<Space>>
|
||||
title!: string
|
||||
body!: string
|
||||
onClickLocation?: Location | undefined
|
||||
@ -365,8 +368,7 @@ export function createModel (builder: Builder): void {
|
||||
core.space.Model,
|
||||
{
|
||||
label: notification.string.Push,
|
||||
depends: notification.providers.PlatformNotification,
|
||||
onChange: notification.function.CheckPushPermission
|
||||
depends: notification.providers.PlatformNotification
|
||||
},
|
||||
notification.providers.BrowserNotification
|
||||
)
|
||||
|
@ -85,14 +85,15 @@ export function addNotification (
|
||||
title: string,
|
||||
subTitle: string,
|
||||
component: AnyComponent | AnySvelteComponent,
|
||||
params?: Record<string, any>
|
||||
params?: Record<string, any>,
|
||||
severity: NotificationSeverity = NotificationSeverity.Success
|
||||
): void {
|
||||
const closeTimeout = parseInt(localStorage.getItem('#platform.notification.timeout') ?? '10000')
|
||||
const notification: Notification = {
|
||||
id: generateId(),
|
||||
title,
|
||||
subTitle,
|
||||
severity: NotificationSeverity.Success,
|
||||
severity,
|
||||
position: NotificationPosition.BottomRight,
|
||||
component,
|
||||
closeTimeout,
|
||||
|
@ -46,6 +46,8 @@
|
||||
"UnstarDocument": "Unstar document",
|
||||
"Unsubscribe": "Unsubscribe",
|
||||
"Push": "Push",
|
||||
"Unreads": "Unreads"
|
||||
"Unreads": "Unreads",
|
||||
"EnablePush": "Enable push notifications",
|
||||
"NotificationBlockedInBrowser": "Notifications are blocked in your browser. Please enable notifications in your browser settings"
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +45,8 @@
|
||||
"ArchiveAllConfirmationMessage": "¿Estás seguro de que quieres archivar todas las notificaciones? Esta operación no se puede deshacer.",
|
||||
"StarDocument": "Marcar documento",
|
||||
"UnstarDocument": "Desmarcar documento",
|
||||
"Push": "Push"
|
||||
"Push": "Push",
|
||||
"EnablePush": "Habilitar notificaciones push",
|
||||
"NotificationBlockedInBrowser": "Las notificaciones están bloqueadas en tu navegador. Por favor, habilita las notificaciones en la configuración de tu navegador."
|
||||
}
|
||||
}
|
@ -45,6 +45,8 @@
|
||||
"ArchiveAllConfirmationMessage": "Esta seguro que quer arquivar todas as notificações? Esta operação não se pode desfazer.",
|
||||
"StarDocument": "Marcar documento",
|
||||
"UnstarDocument": "Desmarcar documento",
|
||||
"Push": "Push"
|
||||
"Push": "Push",
|
||||
"EnablePush": "Ativar notificações push",
|
||||
"NotificationBlockedInBrowser": "Notificações bloqueadas no navegador. Por favor habilite las notificaciones en la configuración de su navegador."
|
||||
}
|
||||
}
|
@ -46,6 +46,8 @@
|
||||
"UnstarDocument": "Удалить из избранного",
|
||||
"Unsubscribe": "Отписаться",
|
||||
"Push": "Push",
|
||||
"Unreads": "Непрочитанные"
|
||||
"Unreads": "Непрочитанные",
|
||||
"EnablePush": "Включить Push-уведомления",
|
||||
"NotificationBlockedInBrowser": "Уведомления заблокированы в вашем браузере. Пожалуйста, включите уведомления в настройках браузера"
|
||||
}
|
||||
}
|
||||
|
@ -14,76 +14,50 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { getCurrentAccount } from '@hcengineering/core'
|
||||
import notification from '@hcengineering/notification'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { getCurrentLocation, navigate, parseLocation } from '@hcengineering/ui'
|
||||
import notification, { BrowserNotification, NotificationStatus } from '@hcengineering/notification'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { checkPermission, pushAllowed, subscribePush } from '../utils'
|
||||
import { NotificationSeverity, addNotification } from '@hcengineering/ui'
|
||||
import Notification from './Notification.svelte'
|
||||
|
||||
async function check (allowed: boolean) {
|
||||
if (allowed) {
|
||||
query.unsubscribe()
|
||||
return
|
||||
}
|
||||
const res = await checkPermission(true)
|
||||
if (res) {
|
||||
query.unsubscribe()
|
||||
return
|
||||
}
|
||||
const isSubscribed = await subscribePush()
|
||||
if (isSubscribed) {
|
||||
query.unsubscribe()
|
||||
return
|
||||
}
|
||||
query.query(
|
||||
notification.class.BrowserNotification,
|
||||
{
|
||||
user: getCurrentAccount()._id,
|
||||
status: NotificationStatus.New,
|
||||
createdOn: { $gt: Date.now() }
|
||||
},
|
||||
(res) => {
|
||||
if (res.length > 0) {
|
||||
notify(res[0])
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const client = getClient()
|
||||
|
||||
const publicKey = getMetadata(notification.metadata.PushPublicKey)
|
||||
|
||||
async function subscribe (): Promise<void> {
|
||||
if ('serviceWorker' in navigator && 'PushManager' in window && publicKey !== undefined) {
|
||||
try {
|
||||
const loc = getCurrentLocation()
|
||||
const registration = await navigator.serviceWorker.register('/serviceWorker.js', {
|
||||
scope: `${loc.path[0]}/${loc.path[1]}`
|
||||
})
|
||||
const current = await registration.pushManager.getSubscription()
|
||||
if (current == null) {
|
||||
const subscription = await registration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: publicKey
|
||||
})
|
||||
await client.createDoc(notification.class.PushSubscription, notification.space.Notifications, {
|
||||
user: getCurrentAccount()._id,
|
||||
endpoint: subscription.endpoint,
|
||||
keys: {
|
||||
p256dh: arrayBufferToBase64(subscription.getKey('p256dh')),
|
||||
auth: arrayBufferToBase64(subscription.getKey('auth'))
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const exists = await client.findOne(notification.class.PushSubscription, {
|
||||
user: getCurrentAccount()._id,
|
||||
endpoint: current.endpoint
|
||||
})
|
||||
if (exists === undefined) {
|
||||
await client.createDoc(notification.class.PushSubscription, notification.space.Notifications, {
|
||||
user: getCurrentAccount()._id,
|
||||
endpoint: current.endpoint,
|
||||
keys: {
|
||||
p256dh: arrayBufferToBase64(current.getKey('p256dh')),
|
||||
auth: arrayBufferToBase64(current.getKey('auth'))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
navigator.serviceWorker.addEventListener('message', (event) => {
|
||||
if (event.data && event.data.type === 'notification-click') {
|
||||
const { url } = event.data
|
||||
if (url !== undefined) {
|
||||
navigate(parseLocation(new URL(url)))
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('Service Worker registration failed:', err)
|
||||
}
|
||||
}
|
||||
async function notify (value: BrowserNotification): Promise<void> {
|
||||
addNotification(value.title, value.body, Notification, { value }, NotificationSeverity.Info)
|
||||
await client.update(value, { status: NotificationStatus.Notified })
|
||||
}
|
||||
|
||||
function arrayBufferToBase64 (buffer: ArrayBuffer | null): string {
|
||||
if (buffer) {
|
||||
const bytes = new Uint8Array(buffer)
|
||||
const array = Array.from(bytes)
|
||||
const binary = String.fromCharCode.apply(null, array)
|
||||
return btoa(binary)
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
const query = createQuery()
|
||||
|
||||
subscribe()
|
||||
$: check($pushAllowed)
|
||||
</script>
|
||||
|
@ -0,0 +1,52 @@
|
||||
<script lang="ts">
|
||||
import { PersonAccount } from '@hcengineering/contact'
|
||||
import { Avatar, personAccountByIdStore, personByIdStore } from '@hcengineering/contact-resources'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { BrowserNotification } from '@hcengineering/notification'
|
||||
import { Button, Notification as PlatformNotification, NotificationToast, navigate } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { pushAvailable, subscribePush } from '../utils'
|
||||
import plugin from '../plugin'
|
||||
|
||||
export let notification: PlatformNotification
|
||||
export let onRemove: () => void
|
||||
|
||||
$: value = notification.params?.value as BrowserNotification
|
||||
|
||||
$: senderAccount =
|
||||
value.senderId !== undefined ? $personAccountByIdStore.get(value.senderId as Ref<PersonAccount>) : undefined
|
||||
$: sender = senderAccount !== undefined ? $personByIdStore.get(senderAccount.person) : undefined
|
||||
</script>
|
||||
|
||||
<NotificationToast title={notification.title} severity={notification.severity} onClose={onRemove}>
|
||||
<svelte:fragment slot="content">
|
||||
<div class="flex-row-center flex-wrap gap-2">
|
||||
{#if sender}
|
||||
<Avatar avatar={sender.avatar} name={sender.name} size={'small'} />
|
||||
{/if}
|
||||
<span class="overflow-label">
|
||||
{value.body}
|
||||
</span>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="buttons">
|
||||
{#if value.onClickLocation}
|
||||
<Button
|
||||
label={view.string.Open}
|
||||
on:click={() => {
|
||||
if (value.onClickLocation) {
|
||||
onRemove()
|
||||
navigate(value.onClickLocation)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
<Button
|
||||
label={plugin.string.EnablePush}
|
||||
disabled={!pushAvailable()}
|
||||
showTooltip={!pushAvailable() ? { label: plugin.string.NotificationBlockedInBrowser } : undefined}
|
||||
on:click={subscribePush}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</NotificationToast>
|
@ -35,6 +35,8 @@ export default mergeIds(notificationId, notification, {
|
||||
People: '' as IntlString,
|
||||
Read: '' as IntlString,
|
||||
Unread: '' as IntlString,
|
||||
Unreads: '' as IntlString
|
||||
Unreads: '' as IntlString,
|
||||
EnablePush: '' as IntlString,
|
||||
NotificationBlockedInBrowser: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -30,6 +30,7 @@ import {
|
||||
type WithLookup
|
||||
} from '@hcengineering/core'
|
||||
import notification, {
|
||||
NotificationStatus,
|
||||
decodeObjectURI,
|
||||
encodeObjectURI,
|
||||
notificationId,
|
||||
@ -45,14 +46,16 @@ import {
|
||||
getCurrentLocation,
|
||||
getLocation,
|
||||
navigate,
|
||||
parseLocation,
|
||||
showPopup,
|
||||
type Location,
|
||||
type ResolvedLocation
|
||||
} from '@hcengineering/ui'
|
||||
import { get } from 'svelte/store'
|
||||
import { get, writable } from 'svelte/store'
|
||||
|
||||
import { InboxNotificationsClientImpl } from './inboxNotificationsClient'
|
||||
import { type InboxData, type InboxNotificationsFilter } from './types'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
|
||||
export async function hasDocNotifyContextPinAction (docNotifyContext: DocNotifyContext): Promise<boolean> {
|
||||
if (docNotifyContext.hidden) {
|
||||
@ -540,6 +543,8 @@ export function openInboxDoc (
|
||||
navigate(loc)
|
||||
}
|
||||
|
||||
export const pushAllowed = writable<boolean>(false)
|
||||
|
||||
export async function checkPermission (value: boolean): Promise<boolean> {
|
||||
if (!value) return true
|
||||
if ('serviceWorker' in navigator && 'PushManager' in window) {
|
||||
@ -548,32 +553,121 @@ export async function checkPermission (value: boolean): Promise<boolean> {
|
||||
const registration = await navigator.serviceWorker.getRegistration(`/${loc.path[0]}/${loc.path[1]}`)
|
||||
if (registration !== undefined) {
|
||||
const current = await registration.pushManager.getSubscription()
|
||||
return current !== null
|
||||
const res = current !== null
|
||||
pushAllowed.set(current !== null)
|
||||
void registration.update()
|
||||
addWorkerListener()
|
||||
return res
|
||||
}
|
||||
} catch {
|
||||
pushAllowed.set(false)
|
||||
return false
|
||||
}
|
||||
}
|
||||
pushAllowed.set(false)
|
||||
return false
|
||||
}
|
||||
|
||||
export async function askPermission (): Promise<void> {
|
||||
if ('Notification' in window && Notification?.permission === 'default') {
|
||||
await Notification?.requestPermission()
|
||||
function addWorkerListener (): void {
|
||||
navigator.serviceWorker.addEventListener('message', (event) => {
|
||||
if (event.data !== undefined && event.data.type === 'notification-click') {
|
||||
const { url, _id } = event.data
|
||||
if (url !== undefined) {
|
||||
navigate(parseLocation(new URL(url)))
|
||||
}
|
||||
if (_id !== undefined) {
|
||||
void cleanTag(_id)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function pushAvailable (): boolean {
|
||||
const publicKey = getMetadata(notification.metadata.PushPublicKey)
|
||||
return (
|
||||
'serviceWorker' in navigator &&
|
||||
'PushManager' in window &&
|
||||
publicKey !== undefined &&
|
||||
'Notification' in window &&
|
||||
Notification.permission !== 'denied'
|
||||
)
|
||||
}
|
||||
|
||||
export async function subscribePush (): Promise<boolean> {
|
||||
const client = getClient()
|
||||
const publicKey = getMetadata(notification.metadata.PushPublicKey)
|
||||
if ('serviceWorker' in navigator && 'PushManager' in window && publicKey !== undefined) {
|
||||
try {
|
||||
const loc = getCurrentLocation()
|
||||
let registration = await navigator.serviceWorker.getRegistration(`/${loc.path[0]}/${loc.path[1]}`)
|
||||
if (registration !== undefined) {
|
||||
await registration.update()
|
||||
} else {
|
||||
registration = await navigator.serviceWorker.register('/serviceWorker.js', {
|
||||
scope: `/${loc.path[0]}/${loc.path[1]}`
|
||||
})
|
||||
}
|
||||
const current = await registration.pushManager.getSubscription()
|
||||
if (current == null) {
|
||||
const subscription = await registration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: publicKey
|
||||
})
|
||||
await client.createDoc(notification.class.PushSubscription, notification.space.Notifications, {
|
||||
user: getCurrentAccount()._id,
|
||||
endpoint: subscription.endpoint,
|
||||
keys: {
|
||||
p256dh: arrayBufferToBase64(subscription.getKey('p256dh')),
|
||||
auth: arrayBufferToBase64(subscription.getKey('auth'))
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const exists = await client.findOne(notification.class.PushSubscription, {
|
||||
user: getCurrentAccount()._id,
|
||||
endpoint: current.endpoint
|
||||
})
|
||||
if (exists === undefined) {
|
||||
await client.createDoc(notification.class.PushSubscription, notification.space.Notifications, {
|
||||
user: getCurrentAccount()._id,
|
||||
endpoint: current.endpoint,
|
||||
keys: {
|
||||
p256dh: arrayBufferToBase64(current.getKey('p256dh')),
|
||||
auth: arrayBufferToBase64(current.getKey('auth'))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
addWorkerListener()
|
||||
pushAllowed.set(true)
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Service Worker registration failed:', err)
|
||||
pushAllowed.set(false)
|
||||
return false
|
||||
}
|
||||
}
|
||||
pushAllowed.set(false)
|
||||
return false
|
||||
}
|
||||
|
||||
async function cleanTag (_id: Ref<Doc>): Promise<void> {
|
||||
const client = getClient()
|
||||
const notifications = await client.findAll(notification.class.BrowserNotification, {
|
||||
tag: _id,
|
||||
status: NotificationStatus.New
|
||||
})
|
||||
for (const notification of notifications) {
|
||||
await client.update(notification, { status: NotificationStatus.Notified })
|
||||
}
|
||||
}
|
||||
|
||||
export function notify (title: string, body: string, _id?: string, onClick?: () => void): void {
|
||||
if ('Notification' in window && Notification?.permission === 'granted') {
|
||||
const req: NotificationOptions = {
|
||||
body
|
||||
}
|
||||
if (_id !== undefined) {
|
||||
req.tag = _id
|
||||
}
|
||||
const notification = new Notification(title, req)
|
||||
if (onClick !== undefined) {
|
||||
notification.onclick = onClick
|
||||
}
|
||||
function arrayBufferToBase64 (buffer: ArrayBuffer | null): string {
|
||||
if (buffer != null) {
|
||||
const bytes = new Uint8Array(buffer)
|
||||
const array = Array.from(bytes)
|
||||
const binary = String.fromCharCode.apply(null, array)
|
||||
return btoa(binary)
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
@ -49,6 +49,8 @@ export interface BrowserNotification extends Doc {
|
||||
title: string
|
||||
body: string
|
||||
onClickLocation?: Location
|
||||
senderId?: Ref<Account>
|
||||
tag: Ref<Doc>
|
||||
}
|
||||
|
||||
export interface PushData {
|
||||
|
@ -18,41 +18,56 @@ self.addEventListener('push', (event: PushEvent) => {
|
||||
tag: payload.tag,
|
||||
data: {
|
||||
domain: payload.domain,
|
||||
url: payload.url
|
||||
url: payload.url,
|
||||
notificationId: payload.tag
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Listen for notification click event
|
||||
self.addEventListener('notificationclick', (event: any) => {
|
||||
async function handleNotificationClick (event: any): Promise<void> {
|
||||
event.notification.close()
|
||||
const clickedNotification = event.notification
|
||||
const notificationData = clickedNotification.data
|
||||
const notificationId = notificationData.notificationId
|
||||
const notificationUrl = notificationData.url
|
||||
const domain = notificationData.domain
|
||||
|
||||
if (notificationUrl !== undefined && domain !== undefined) {
|
||||
// Check if any client with the same origin is already open
|
||||
event.waitUntil(
|
||||
// Check all active clients (browser windows or tabs)
|
||||
self.clients
|
||||
.matchAll({
|
||||
type: 'window',
|
||||
includeUncontrolled: true
|
||||
const windowClients = (await self.clients.matchAll({
|
||||
type: 'window',
|
||||
includeUncontrolled: true
|
||||
})) as ReadonlyArray<any>
|
||||
|
||||
const targetUrl = new URL(notificationUrl)
|
||||
for (const client of windowClients) {
|
||||
const clientUrl = new URL(client.url, self.location.href)
|
||||
if (decodeURI(clientUrl.pathname) === targetUrl.pathname) {
|
||||
client.postMessage({
|
||||
type: 'notification-click',
|
||||
url: notificationUrl,
|
||||
_id: notificationId
|
||||
})
|
||||
.then((clientList: any) => {
|
||||
// Loop through each client
|
||||
for (const client of clientList) {
|
||||
// If a client has the same URL origin, focus and navigate to it
|
||||
if ((client.url as string)?.startsWith(domain)) {
|
||||
client.postMessage({
|
||||
type: 'notification-click',
|
||||
url: notificationUrl
|
||||
})
|
||||
return client.focus()
|
||||
}
|
||||
}
|
||||
// If no client with the same URL origin is found, open a new window/tab
|
||||
return self.clients.openWindow(notificationUrl)
|
||||
await client.focus()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for (const client of windowClients) {
|
||||
if ((client.url as string)?.startsWith(domain)) {
|
||||
client.postMessage({
|
||||
type: 'notification-click',
|
||||
url: notificationUrl,
|
||||
_id: notificationId
|
||||
})
|
||||
)
|
||||
await client.focus()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
console.log('No matching client found')
|
||||
// If no client with the same URL origin is found, open a new window/tab
|
||||
await self.clients.openWindow(notificationUrl)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
self.addEventListener('notificationclick', (e: any) => e.waitUntil(handleNotificationClick(e)))
|
||||
|
@ -61,6 +61,8 @@ import notification, {
|
||||
InboxNotification,
|
||||
MentionInboxNotification,
|
||||
notificationId,
|
||||
NotificationStatus,
|
||||
PushSubscription,
|
||||
NotificationType,
|
||||
PushData
|
||||
} from '@hcengineering/notification'
|
||||
@ -424,7 +426,8 @@ export async function pushInboxNotifications (
|
||||
const notificationTx = control.txFactory.createTxCreateDoc(_class, space, notificationData)
|
||||
res.push(notificationTx)
|
||||
if (shouldPush) {
|
||||
await createPushFromInbox(
|
||||
const now = Date.now()
|
||||
const pushTx = await createPushFromInbox(
|
||||
control,
|
||||
targetUser,
|
||||
attachedTo,
|
||||
@ -434,6 +437,10 @@ export async function pushInboxNotifications (
|
||||
senderId,
|
||||
notificationTx.objectId
|
||||
)
|
||||
console.log('Push takes', Date.now() - now, 'ms')
|
||||
if (pushTx !== undefined) {
|
||||
res.push(pushTx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -522,8 +529,8 @@ export async function createPushFromInbox (
|
||||
data: Data<InboxNotification>,
|
||||
_class: Ref<Class<InboxNotification>>,
|
||||
senderId: Ref<PersonAccount>,
|
||||
_id: string
|
||||
): Promise<void> {
|
||||
_id: Ref<Doc>
|
||||
): Promise<Tx | undefined> {
|
||||
let title: string = ''
|
||||
let body: string = ''
|
||||
if (control.hierarchy.isDerived(_class, notification.class.ActivityInboxNotification)) {
|
||||
@ -543,10 +550,24 @@ export async function createPushFromInbox (
|
||||
if (sender !== undefined) {
|
||||
senderPerson = (await control.findAll(contact.class.Person, { _id: sender.person }))[0]
|
||||
}
|
||||
await createPushNotification(control, targetUser, title, body, _id, senderPerson?.avatar, [
|
||||
const path = [
|
||||
workbenchId,
|
||||
control.workspace.workspaceUrl,
|
||||
notificationId,
|
||||
encodeObjectURI(attachedTo, attachedToClass)
|
||||
])
|
||||
]
|
||||
await createPushNotification(control, targetUser, title, body, _id, senderPerson?.avatar, path)
|
||||
return control.txFactory.createTxCreateDoc(notification.class.BrowserNotification, notification.space.Notifications, {
|
||||
user: targetUser,
|
||||
status: NotificationStatus.New,
|
||||
title,
|
||||
body,
|
||||
senderId,
|
||||
tag: _id,
|
||||
onClickLocation: {
|
||||
path
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function createPushNotification (
|
||||
@ -556,7 +577,7 @@ export async function createPushNotification (
|
||||
body: string,
|
||||
_id: string,
|
||||
senderAvatar?: string | null,
|
||||
subPath?: string[]
|
||||
path?: string[]
|
||||
): Promise<void> {
|
||||
const publicKey = getMetadata(notification.metadata.PushPublicKey)
|
||||
const privateKey = getMetadata(serverNotification.metadata.PushPrivateKey)
|
||||
@ -577,9 +598,8 @@ export async function createPushNotification (
|
||||
const domainPath = `${workbenchId}/${control.workspace.workspaceUrl}`
|
||||
const domain = concatLink(front, domainPath)
|
||||
data.domain = domain
|
||||
if (subPath !== undefined) {
|
||||
const path = [domainPath, ...subPath].join('/')
|
||||
const url = concatLink(front, path)
|
||||
if (path !== undefined) {
|
||||
const url = concatLink(front, path.join('/'))
|
||||
data.url = url
|
||||
}
|
||||
if (senderAvatar != null) {
|
||||
@ -598,14 +618,23 @@ export async function createPushNotification (
|
||||
webpush.setVapidDetails(subject, publicKey, privateKey)
|
||||
|
||||
for (const subscription of subscriptions) {
|
||||
try {
|
||||
await webpush.sendNotification(subscription, JSON.stringify(data))
|
||||
} catch (err) {
|
||||
console.log('Cannot send push notification to', targetUser, err)
|
||||
if (err instanceof WebPushError && err.body.includes('expired')) {
|
||||
const tx = control.txFactory.createTxRemoveDoc(subscription._class, subscription.space, subscription._id)
|
||||
await control.apply([tx], true)
|
||||
}
|
||||
void sendPushToSubscription(control, targetUser, subscription, data)
|
||||
}
|
||||
}
|
||||
|
||||
async function sendPushToSubscription (
|
||||
control: TriggerControl,
|
||||
targetUser: Ref<Account>,
|
||||
subscription: PushSubscription,
|
||||
data: PushData
|
||||
): Promise<void> {
|
||||
try {
|
||||
await webpush.sendNotification(subscription, JSON.stringify(data))
|
||||
} catch (err) {
|
||||
console.log('Cannot send push notification to', targetUser, err)
|
||||
if (err instanceof WebPushError && err.body.includes('expired')) {
|
||||
const tx = control.txFactory.createTxRemoveDoc(subscription._class, subscription.space, subscription._id)
|
||||
await control.apply([tx], true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -97,8 +97,7 @@ export async function getIssueNotificationContent (
|
||||
): Promise<NotificationContent> {
|
||||
const issue = doc as Issue
|
||||
|
||||
const issueShortName = await issueTextPresenter(doc)
|
||||
const issueTitle = `${issueShortName}: ${issue.title}`
|
||||
const issueTitle = await issueTextPresenter(doc)
|
||||
|
||||
const title = tracker.string.IssueNotificationTitle
|
||||
let body = tracker.string.IssueNotificationBody
|
||||
|
Loading…
Reference in New Issue
Block a user