notifications-postmerge-fixes
This commit is contained in:
parent
26ba530f9e
commit
359bfc3f7a
6
package-lock.json
generated
6
package-lock.json
generated
|
@ -12,6 +12,7 @@
|
||||||
"form-data": "4.0.0",
|
"form-data": "4.0.0",
|
||||||
"i18next": "22.4.15",
|
"i18next": "22.4.15",
|
||||||
"i18next-icu": "2.3.0",
|
"i18next-icu": "2.3.0",
|
||||||
|
"idb": "7.1.1",
|
||||||
"intl-messageformat": "10.5.3",
|
"intl-messageformat": "10.5.3",
|
||||||
"mailgun.js": "8.2.1",
|
"mailgun.js": "8.2.1",
|
||||||
"node-fetch": "3.3.1"
|
"node-fetch": "3.3.1"
|
||||||
|
@ -10444,6 +10445,11 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/idb": {
|
||||||
|
"version": "7.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
|
||||||
|
"integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ=="
|
||||||
|
},
|
||||||
"node_modules/ieee754": {
|
"node_modules/ieee754": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
"form-data": "4.0.0",
|
"form-data": "4.0.0",
|
||||||
"i18next": "22.4.15",
|
"i18next": "22.4.15",
|
||||||
"i18next-icu": "2.3.0",
|
"i18next-icu": "2.3.0",
|
||||||
|
"idb": "7.1.1",
|
||||||
"intl-messageformat": "10.5.3",
|
"intl-messageformat": "10.5.3",
|
||||||
"mailgun.js": "8.2.1",
|
"mailgun.js": "8.2.1",
|
||||||
"node-fetch": "3.3.1"
|
"node-fetch": "3.3.1"
|
||||||
|
|
|
@ -1,112 +1,113 @@
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import styles from './NotificationView.module.scss'
|
import styles from './NotificationView.module.scss'
|
||||||
import type { Notification } from '../../../graphql/types.gen'
|
|
||||||
import { formatDate } from '../../../utils'
|
import { formatDate } from '../../../utils'
|
||||||
import { createMemo, createSignal, onMount, Show } from 'solid-js'
|
import { createMemo, createSignal, onMount, Show } from 'solid-js'
|
||||||
import { NotificationType } from '../../../graphql/types.gen'
|
import { Author } from '../../../graphql/types.gen'
|
||||||
import { openPage } from '@nanostores/router'
|
import { openPage } from '@nanostores/router'
|
||||||
import { router } from '../../../stores/router'
|
import { router } from '../../../stores/router'
|
||||||
import { useNotifications } from '../../../context/notifications'
|
import { ServerNotification, useNotifications } from '../../../context/notifications'
|
||||||
import { Userpic } from '../../Author/Userpic'
|
import { Userpic } from '../../Author/Userpic'
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import notifications from '../../../graphql/query/notifications'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
notification: Notification
|
notification: ServerNotification
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
class?: string
|
class?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type NotificationData = {
|
// NOTE: not a graphql generated type
|
||||||
shout: {
|
export enum NotificationType {
|
||||||
slug: string
|
NewComment = 'NEW_COMMENT',
|
||||||
title: string
|
NewReply = 'NEW_REPLY',
|
||||||
}
|
NewFollower = 'NEW_FOLLOWER',
|
||||||
users: {
|
NewShout = 'NEW_SHOUT',
|
||||||
id: number
|
NewLike = 'NEW_LIKE',
|
||||||
name: string
|
NewDislike = 'NEW_DISLIKE'
|
||||||
slug: string
|
}
|
||||||
userpic: string
|
|
||||||
}[]
|
const TEMPLATES = {
|
||||||
|
// FIXME: set proper templates
|
||||||
|
new_follower: 'new follower',
|
||||||
|
new_shout: 'new shout',
|
||||||
|
new_reaction0: 'new like',
|
||||||
|
new_reaction1: 'new dislike',
|
||||||
|
new_reaction2: 'new agreement',
|
||||||
|
new_reaction3: 'new disagreement',
|
||||||
|
new_reaction4: 'new proof',
|
||||||
|
new_reaction5: 'new disproof',
|
||||||
|
new_reaction6: 'new comment',
|
||||||
|
new_reaction7: 'new quote',
|
||||||
|
new_reaction8: 'new proposal',
|
||||||
|
new_reaction9: 'new question',
|
||||||
|
new_reaction10: 'new remark',
|
||||||
|
//"new_reaction11": "new footnote",
|
||||||
|
new_reaction12: 'new acception',
|
||||||
|
new_reaction13: 'new rejection'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NotificationView = (props: Props) => {
|
export const NotificationView = (props: Props) => {
|
||||||
const {
|
const {
|
||||||
actions: { markNotificationAsRead }
|
actions: { markNotificationAsRead }
|
||||||
} = useNotifications()
|
} = useNotifications()
|
||||||
|
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
|
const [data, setData] = createSignal<ServerNotification>(null)
|
||||||
const [data, setData] = createSignal<NotificationData>(null)
|
const [kind, setKind] = createSignal<NotificationType>()
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
setTimeout(() => setData(JSON.parse(props.notification.data)))
|
setTimeout(() => setData(props.notification))
|
||||||
})
|
})
|
||||||
|
|
||||||
const lastUser = createMemo(() => {
|
const lastUser = createMemo(() => {
|
||||||
if (!data()) {
|
return props.notification.kind === 'new_follower' ? data().payload : data().payload.author
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return data().users[data().users.length - 1]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const content = createMemo(() => {
|
const content = createMemo(() => {
|
||||||
if (!data()) {
|
if (!data()) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
let caption: string, author: Author, ntype: NotificationType
|
||||||
|
|
||||||
let shoutTitle = ''
|
// TODO: count occurencies from in-browser notifications-db
|
||||||
let i = 0
|
|
||||||
const shoutTitleWords = data().shout.title.split(' ')
|
|
||||||
|
|
||||||
while (shoutTitle.length <= 30 && i < shoutTitleWords.length) {
|
switch (props.notification.kind) {
|
||||||
shoutTitle += shoutTitleWords[i] + ' '
|
case 'new_follower': {
|
||||||
i++
|
caption = ''
|
||||||
|
author = data().payload
|
||||||
|
ntype = NotificationType.NewFollower
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
case 'new_shout': {
|
||||||
if (shoutTitle.length < data().shout.title.length) {
|
caption = data().payload.title
|
||||||
shoutTitle += '...'
|
author = data().payload.authors[-1]
|
||||||
|
ntype = NotificationType.NewShout
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
case 'new_reaction6': {
|
||||||
switch (props.notification.type) {
|
ntype = data().payload.replyTo ? NotificationType.NewReply : NotificationType.NewComment
|
||||||
case NotificationType.NewComment: {
|
|
||||||
return t('NewCommentNotificationText', {
|
|
||||||
commentsCount: props.notification.occurrences,
|
|
||||||
shoutTitle,
|
|
||||||
lastCommenterName: lastUser().name,
|
|
||||||
restUsersCount: data().users.length - 1
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
case NotificationType.NewReply: {
|
case 'new_reaction0': {
|
||||||
return t('NewReplyNotificationText', {
|
ntype = NotificationType.NewLike
|
||||||
commentsCount: props.notification.occurrences,
|
}
|
||||||
shoutTitle,
|
case 'new_reaction0': {
|
||||||
lastCommenterName: lastUser().name,
|
ntype = NotificationType.NewDislike
|
||||||
restUsersCount: data().users.length - 1
|
}
|
||||||
})
|
// TODO: add more reaction types
|
||||||
|
default: {
|
||||||
|
caption = data().payload.shout.title
|
||||||
|
author = data().payload.author
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setKind(ntype) // FIXME: use it somewhere if needed or remove
|
||||||
|
return t(TEMPLATES[props.notification.kind], { caption, author })
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
if (!props.notification.seen) {
|
if (!props.notification.seen) {
|
||||||
markNotificationAsRead(props.notification)
|
markNotificationAsRead(props.notification)
|
||||||
}
|
}
|
||||||
|
const subpath = props.notification.kind === 'new_follower' ? 'author' : 'article'
|
||||||
openPage(router, 'article', { slug: data().shout.slug })
|
const slug = props.notification.kind.startsWith('new_reaction')
|
||||||
|
? data().payload.shout.slug
|
||||||
|
: data().payload.slug
|
||||||
|
openPage(router, subpath, { slug })
|
||||||
props.onClick()
|
props.onClick()
|
||||||
|
|
||||||
// switch (props.notification.type) {
|
|
||||||
// case NotificationType.NewComment: {
|
|
||||||
// openPage(router, 'article', { slug: data().shout.slug })
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
// case NotificationType.NewReply: {
|
|
||||||
// openPage(router, 'article', { slug: data().shout.slug })
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -120,7 +121,7 @@ export const NotificationView = (props: Props) => {
|
||||||
<Userpic name={lastUser().name} userpic={lastUser().userpic} class={styles.userpic} />
|
<Userpic name={lastUser().name} userpic={lastUser().userpic} class={styles.userpic} />
|
||||||
<div>{content()}</div>
|
<div>{content()}</div>
|
||||||
<div class={styles.timeContainer}>
|
<div class={styles.timeContainer}>
|
||||||
{/*{formatDate(new Date(props.notification.createdAt), { month: 'numeric' })}*/}
|
{/*{formatDate(new Date(props.notification.timestamp), { month: 'numeric' })}*/}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useEscKeyDownHandler } from '../../utils/useEscKeyDownHandler'
|
||||||
import { useOutsideClickHandler } from '../../utils/useOutsideClickHandler'
|
import { useOutsideClickHandler } from '../../utils/useOutsideClickHandler'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import { createEffect, For, onCleanup, onMount } from 'solid-js'
|
import { createEffect, For } from 'solid-js'
|
||||||
import { useNotifications } from '../../context/notifications'
|
import { useNotifications } from '../../context/notifications'
|
||||||
import { NotificationView } from './NotificationView'
|
import { NotificationView } from './NotificationView'
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import type { Accessor, JSX } from 'solid-js'
|
import type { Accessor, JSX } from 'solid-js'
|
||||||
import { createContext, createSignal, useContext } from 'solid-js'
|
import { createContext, createSignal, useContext } from 'solid-js'
|
||||||
import { fetchEventSource } from '@microsoft/fetch-event-source'
|
|
||||||
import type { Chat, Message, MutationCreateMessageArgs } from '../graphql/types.gen'
|
import type { Chat, Message, MutationCreateMessageArgs } from '../graphql/types.gen'
|
||||||
import { inboxClient } from '../utils/apiClient'
|
import { inboxClient } from '../utils/apiClient'
|
||||||
import { getToken } from '../graphql/privateGraphQLClient'
|
|
||||||
import { loadMessages } from '../stores/inbox'
|
import { loadMessages } from '../stores/inbox'
|
||||||
|
import { ServerNotification, useNotifications } from './notifications'
|
||||||
|
|
||||||
type InboxContextType = {
|
type InboxContextType = {
|
||||||
chats: Accessor<Chat[]>
|
chats: Accessor<Chat[]>
|
||||||
|
@ -26,26 +25,13 @@ export function useInbox() {
|
||||||
export const InboxProvider = (props: { children: JSX.Element }) => {
|
export const InboxProvider = (props: { children: JSX.Element }) => {
|
||||||
const [chats, setChats] = createSignal<Chat[]>([])
|
const [chats, setChats] = createSignal<Chat[]>([])
|
||||||
const [messages, setMessages] = createSignal<Message[]>([])
|
const [messages, setMessages] = createSignal<Message[]>([])
|
||||||
|
const {
|
||||||
|
actions: { setMessageHandler }
|
||||||
|
} = useNotifications()
|
||||||
|
|
||||||
fetchEventSource('https://chat.discours.io/connect', {
|
setMessageHandler((n: ServerNotification) => {
|
||||||
method: 'GET',
|
console.debug(n)
|
||||||
headers: {
|
// TODO: handle new message
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: 'Bearer ' + getToken()
|
|
||||||
},
|
|
||||||
onmessage(event) {
|
|
||||||
const message = JSON.parse(event.data)
|
|
||||||
console.log('Received message:', message)
|
|
||||||
// TODO: Add the message to the appropriate chat
|
|
||||||
},
|
|
||||||
onclose() {
|
|
||||||
console.log('sse connection closed by server')
|
|
||||||
},
|
|
||||||
onerror(err) {
|
|
||||||
console.error('sse connection closed by error', err)
|
|
||||||
|
|
||||||
throw new Error() // NOTE: simple hack to close the connection
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const loadChats = async () => {
|
const loadChats = async () => {
|
||||||
|
|
|
@ -1,22 +1,31 @@
|
||||||
import type { Accessor, JSX } from 'solid-js'
|
import type { Accessor, JSX } from 'solid-js'
|
||||||
import { createContext, createEffect, createMemo, createSignal, useContext } from 'solid-js'
|
import { createContext, createEffect, createMemo, createSignal, useContext } from 'solid-js'
|
||||||
import { useSession } from './session'
|
import { useSession } from './session'
|
||||||
import SSEService, { EventData } from '../utils/sseService'
|
|
||||||
import { apiBaseUrl } from '../utils/config'
|
|
||||||
import { Portal } from 'solid-js/web'
|
import { Portal } from 'solid-js/web'
|
||||||
import { ShowIfAuthenticated } from '../components/_shared/ShowIfAuthenticated'
|
import { ShowIfAuthenticated } from '../components/_shared/ShowIfAuthenticated'
|
||||||
import { NotificationsPanel } from '../components/NotificationsPanel'
|
import { NotificationsPanel } from '../components/NotificationsPanel'
|
||||||
import { apiClient } from '../utils/apiClient'
|
|
||||||
import { createStore } from 'solid-js/store'
|
import { createStore } from 'solid-js/store'
|
||||||
import { Notification } from '../graphql/types.gen'
|
import { openDB } from 'idb'
|
||||||
|
import { fetchEventSource } from '@microsoft/fetch-event-source'
|
||||||
|
import { getToken } from '../graphql/privateGraphQLClient'
|
||||||
|
import { Author, Message, Reaction, Shout } from '../graphql/types.gen'
|
||||||
|
|
||||||
|
export interface ServerNotification {
|
||||||
|
kind: string
|
||||||
|
payload: any // Author | Shout | Reaction | Message
|
||||||
|
timestamp: number
|
||||||
|
seen: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessageHandler = (m: Message) => void
|
||||||
type NotificationsContextType = {
|
type NotificationsContextType = {
|
||||||
notificationEntities: Record<number, Notification>
|
notificationEntities: Record<number, ServerNotification>
|
||||||
unreadNotificationsCount: Accessor<number>
|
unreadNotificationsCount: Accessor<number>
|
||||||
sortedNotifications: Accessor<Notification[]>
|
sortedNotifications: Accessor<ServerNotification[]>
|
||||||
actions: {
|
actions: {
|
||||||
showNotificationsPanel: () => void
|
showNotificationsPanel: () => void
|
||||||
markNotificationAsRead: (notification: Notification) => Promise<void>
|
markNotificationAsRead: (notification: ServerNotification) => Promise<void>
|
||||||
|
setMessageHandler: (MessageHandler) => void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,53 +35,95 @@ export function useNotifications() {
|
||||||
return useContext(NotificationsContext)
|
return useContext(NotificationsContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
const sseService = new SSEService()
|
|
||||||
|
|
||||||
export const NotificationsProvider = (props: { children: JSX.Element }) => {
|
export const NotificationsProvider = (props: { children: JSX.Element }) => {
|
||||||
const [isNotificationsPanelOpen, setIsNotificationsPanelOpen] = createSignal(false)
|
const [isNotificationsPanelOpen, setIsNotificationsPanelOpen] = createSignal(false)
|
||||||
const [unreadNotificationsCount, setUnreadNotificationsCount] = createSignal(0)
|
const [unreadNotificationsCount, setUnreadNotificationsCount] = createSignal(0)
|
||||||
const { isAuthenticated, user } = useSession()
|
const { isAuthenticated, user } = useSession()
|
||||||
const [notificationEntities, setNotificationEntities] = createStore<Record<number, Notification>>({})
|
const [notificationEntities, setNotificationEntities] = createStore<Record<number, ServerNotification>>(
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
|
const dbPromise = openDB('notifications-db', 1, {
|
||||||
|
upgrade(db) {
|
||||||
|
db.createObjectStore('notifications')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const loadNotifications = async () => {
|
const loadNotifications = async () => {
|
||||||
const { notifications, totalUnreadCount } = await apiClient.getNotifications({
|
const db = await dbPromise
|
||||||
limit: 100
|
const notifications = await db.getAll('notifications')
|
||||||
})
|
const totalUnreadCount = notifications.filter((notification) => !notification.read).length
|
||||||
const newNotificationEntities = notifications.reduce((acc, notification) => {
|
|
||||||
|
setUnreadNotificationsCount(totalUnreadCount)
|
||||||
|
setNotificationEntities(
|
||||||
|
notifications.reduce((acc, notification) => {
|
||||||
acc[notification.id] = notification
|
acc[notification.id] = notification
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
|
)
|
||||||
|
|
||||||
setUnreadNotificationsCount(totalUnreadCount)
|
|
||||||
setNotificationEntities(newNotificationEntities)
|
|
||||||
return notifications
|
return notifications
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortedNotifications = createMemo(() => {
|
const sortedNotifications = createMemo(() => {
|
||||||
return Object.values(notificationEntities).sort(
|
return Object.values(notificationEntities).sort((a, b) => b.timestamp - a.timestamp)
|
||||||
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const storeNotification = async (notification: ServerNotification) => {
|
||||||
|
const db = await dbPromise
|
||||||
|
const tx = db.transaction('notifications', 'readwrite')
|
||||||
|
const store = tx.objectStore('notifications')
|
||||||
|
const id = Date.now()
|
||||||
|
const data: ServerNotification = {
|
||||||
|
...notification,
|
||||||
|
timestamp: id,
|
||||||
|
seen: false
|
||||||
|
}
|
||||||
|
await store.put(data, id)
|
||||||
|
await tx.done
|
||||||
|
loadNotifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
const [messageHandler, setMessageHandler] = createSignal<(m: Message) => void>()
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (isAuthenticated()) {
|
if (isAuthenticated()) {
|
||||||
loadNotifications()
|
loadNotifications()
|
||||||
|
|
||||||
sseService.connect(`${apiBaseUrl}/subscribe/${user().id}`)
|
fetchEventSource('https://chat.discours.io/connect', {
|
||||||
sseService.subscribeToEvent('message', (data: EventData) => {
|
method: 'GET',
|
||||||
if (data.type === 'newNotifications') {
|
headers: {
|
||||||
loadNotifications()
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: 'Bearer ' + getToken()
|
||||||
|
},
|
||||||
|
onmessage(event) {
|
||||||
|
const n: { kind: string; payload: any } = JSON.parse(event.data)
|
||||||
|
if (n.kind === 'new_message') {
|
||||||
|
messageHandler()(n.payload)
|
||||||
} else {
|
} else {
|
||||||
console.error(`[NotificationsProvider] unknown message type: ${JSON.stringify(data)}`)
|
console.log('[context.notifications] Received notification:', n)
|
||||||
|
storeNotification({
|
||||||
|
kind: n.kind,
|
||||||
|
payload: n.payload,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
seen: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onclose() {
|
||||||
|
console.log('[context.notifications] sse connection closed by server')
|
||||||
|
},
|
||||||
|
onerror(err) {
|
||||||
|
console.error('[context.notifications] sse connection closed by error', err)
|
||||||
|
throw new Error() // NOTE: simple hack to close the connection
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
sseService.disconnect()
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const markNotificationAsRead = async (notification: Notification) => {
|
const markNotificationAsRead = async (notification: ServerNotification) => {
|
||||||
await apiClient.markNotificationAsRead(notification.id)
|
const db = await dbPromise
|
||||||
|
await db.put('notifications', { ...notification, seen: true })
|
||||||
loadNotifications()
|
loadNotifications()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +131,7 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
|
||||||
setIsNotificationsPanelOpen(true)
|
setIsNotificationsPanelOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const actions = { showNotificationsPanel, markNotificationAsRead }
|
const actions = { showNotificationsPanel, markNotificationAsRead, setMessageHandler }
|
||||||
|
|
||||||
const value: NotificationsContextType = {
|
const value: NotificationsContextType = {
|
||||||
notificationEntities,
|
notificationEntities,
|
||||||
|
|
|
@ -314,7 +314,11 @@ export type Notification = {
|
||||||
|
|
||||||
export enum NotificationType {
|
export enum NotificationType {
|
||||||
NewComment = 'NEW_COMMENT',
|
NewComment = 'NEW_COMMENT',
|
||||||
NewReply = 'NEW_REPLY'
|
NewReply = 'NEW_REPLY',
|
||||||
|
NewFollower = 'NEW_FOLLOWER',
|
||||||
|
NewShout = 'NEW_SHOUT',
|
||||||
|
NewLike = 'NEW_LIKE',
|
||||||
|
NewDislike = 'NEW_DISLIKE'
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NotificationsQueryParams = {
|
export type NotificationsQueryParams = {
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
export type EventData = {
|
|
||||||
type: string
|
|
||||||
}
|
|
||||||
|
|
||||||
class SSEService {
|
|
||||||
private eventSource: EventSource | null
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.eventSource = null
|
|
||||||
}
|
|
||||||
|
|
||||||
public connect(url: string): void {
|
|
||||||
this.eventSource = new EventSource(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
public disconnect(): void {
|
|
||||||
if (this.eventSource) {
|
|
||||||
this.eventSource.close()
|
|
||||||
this.eventSource = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public subscribeToEvent(eventName: string, callback: (eventData: EventData) => void): void {
|
|
||||||
if (this.eventSource) {
|
|
||||||
this.eventSource.addEventListener(eventName, (event: MessageEvent) => {
|
|
||||||
const data = JSON.parse(event.data)
|
|
||||||
callback(data)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SSEService
|
|
Loading…
Reference in New Issue
Block a user