sse-msg-format

This commit is contained in:
Untone 2023-10-19 17:44:26 +03:00
parent 121cbfdbdd
commit 294b7da5da
3 changed files with 49 additions and 49 deletions

View File

@ -3,7 +3,7 @@ import { createMemo, createSignal, onMount, Show } from 'solid-js'
import { Author } from '../../../graphql/types.gen' import { Author } from '../../../graphql/types.gen'
import { openPage } from '@nanostores/router' import { openPage } from '@nanostores/router'
import { router, useRouter } from '../../../stores/router' import { router, useRouter } from '../../../stores/router'
import { ServerNotification, useNotifications } from '../../../context/notifications' import { SSEMessage, 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 type { ArticlePageSearchParams } from '../../Article/FullArticle' import type { ArticlePageSearchParams } from '../../Article/FullArticle'
@ -11,7 +11,7 @@ import { TimeAgo } from '../../_shared/TimeAgo'
import styles from './NotificationView.module.scss' import styles from './NotificationView.module.scss'
type Props = { type Props = {
notification: ServerNotification notification: SSEMessage
onClick: () => void onClick: () => void
dateTimeFormat: 'ago' | 'time' | 'date' dateTimeFormat: 'ago' | 'time' | 'date'
class?: string class?: string
@ -29,8 +29,9 @@ export enum NotificationType {
const TEMPLATES = { const TEMPLATES = {
// FIXME: set proper templates // FIXME: set proper templates
new_follower: 'new follower', 'follower:join': 'new follower',
new_shout: 'new shout', 'shout:create': 'new shout'
/*
new_reaction0: 'new like', new_reaction0: 'new like',
new_reaction1: 'new dislike', new_reaction1: 'new dislike',
new_reaction2: 'new agreement', new_reaction2: 'new agreement',
@ -45,13 +46,14 @@ const TEMPLATES = {
//"new_reaction11": "new footnote", //"new_reaction11": "new footnote",
new_reaction12: 'new acception', new_reaction12: 'new acception',
new_reaction13: 'new rejection' new_reaction13: 'new rejection'
*/
} }
export const NotificationView = (props: Props) => { export const NotificationView = (props: Props) => {
const { const {
actions: { markNotificationAsRead } actions: { markNotificationAsRead }
} = useNotifications() } = useNotifications()
const [data, setData] = createSignal<ServerNotification>(null) const [data, setData] = createSignal<SSEMessage>(null)
const [kind, setKind] = createSignal<NotificationType>() const [kind, setKind] = createSignal<NotificationType>()
const { changeSearchParam } = useRouter<ArticlePageSearchParams>() const { changeSearchParam } = useRouter<ArticlePageSearchParams>()
const { t, formatDate, formatTime } = useLocalize() const { t, formatDate, formatTime } = useLocalize()
@ -60,7 +62,7 @@ export const NotificationView = (props: Props) => {
setTimeout(() => setData(props.notification)) setTimeout(() => setData(props.notification))
}) })
const lastUser = createMemo(() => { const lastUser = createMemo(() => {
return props.notification.kind === 'new_follower' ? data().payload : data().payload.author return props.notification.entity === 'follower' ? data().payload : data().payload.author
}) })
const content = createMemo(() => { const content = createMemo(() => {
if (!data()) { if (!data()) {
@ -70,46 +72,39 @@ export const NotificationView = (props: Props) => {
// TODO: count occurencies from in-browser notifications-db // TODO: count occurencies from in-browser notifications-db
switch (props.notification.kind) { switch (props.notification.entity) {
case 'new_follower': { case 'follower': {
caption = '' caption = ''
author = data().payload author = data().payload
ntype = NotificationType.NewFollower ntype = NotificationType.NewFollower
break break
} }
case 'new_shout': { case 'shout': {
caption = data().payload.title caption = data().payload.title
author = data().payload.authors[-1] author = data().payload.authors[-1]
ntype = NotificationType.NewShout ntype = NotificationType.NewShout
break break
} }
case 'new_reaction6': { case 'reaction': {
ntype = data().payload.replyTo ? NotificationType.NewReply : NotificationType.NewComment ntype = data().payload.replyTo ? NotificationType.NewReply : NotificationType.NewComment
console.log(data().payload.kind)
// TODO: handle all needed reaction kinds
} }
case 'new_reaction0': {
ntype = NotificationType.NewLike
}
case 'new_reaction0': {
ntype = NotificationType.NewDislike
}
// TODO: add more reaction types
default: { default: {
caption = data().payload.shout.title caption = data().payload.shout.title
author = data().payload.author author = data().payload.author
} }
} }
setKind(ntype) // FIXME: use it somewhere if needed or remove setKind(ntype) // FIXME: use it somewhere if needed or remove
return t(TEMPLATES[props.notification.kind], { caption, author }) return t(TEMPLATES[`${props.notification.entity}:${props.notification.action}`], { 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' const subpath = props.notification.entity === 'follower' ? 'author' : 'article'
const slug = props.notification.kind.startsWith('new_reaction') const slug = props.notification.entity === 'reaction' ? data().payload.shout.slug : data().payload.slug
? data().payload.shout.slug
: data().payload.slug
openPage(router, subpath, { slug }) openPage(router, subpath, { slug })
props.onClick() props.onClick()
} }

View File

@ -3,7 +3,8 @@ import { createContext, createSignal, onMount, useContext } from 'solid-js'
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 { loadMessages } from '../stores/inbox' import { loadMessages } from '../stores/inbox'
import { ServerNotification, useNotifications } from './notifications' import { SSEMessage, useNotifications } from './notifications'
import { M } from '../components/_shared/Button/Button.module.scss'
type InboxContextType = { type InboxContextType = {
chats: Accessor<Chat[]> chats: Accessor<Chat[]>
@ -29,10 +30,16 @@ export const InboxProvider = (props: { children: JSX.Element }) => {
actions: { setMessageHandler } actions: { setMessageHandler }
} = useNotifications() } = useNotifications()
const handleMessage = (n: ServerNotification) => { const handleMessage = (m: SSEMessage) => {
// TODO: handle notification types here: new_message edit_message del_message console.log('[context.inbox] ', m)
const msg = n.payload as Message // TODO: handle all action types: create update delete join left
console.log(msg) if (m.action in ['create', 'update', 'delete']) {
const msg = m.payload
setMessages((mmm) => [msg, ...mmm])
} else if (m.action in ['left', 'join']) {
// TODO: set chat members
console.debug(m)
}
} }
onMount(() => { onMount(() => {

View File

@ -10,22 +10,23 @@ import { fetchEventSource } from '@microsoft/fetch-event-source'
import { getToken } from '../graphql/privateGraphQLClient' import { getToken } from '../graphql/privateGraphQLClient'
import { Author, Message, Reaction, Shout } from '../graphql/types.gen' import { Author, Message, Reaction, Shout } from '../graphql/types.gen'
export interface ServerNotification { export interface SSEMessage {
kind: string entity: string
action: string
payload: any // Author | Shout | Reaction | Message payload: any // Author | Shout | Reaction | Message
timestamp: number timestamp?: number
seen: boolean seen?: boolean
} }
type MessageHandler = (m: Message) => void type MessageHandler = (m: Message) => void
type NotificationsContextType = { type NotificationsContextType = {
notificationEntities: Record<number, ServerNotification> notificationEntities: Record<number, SSEMessage>
unreadNotificationsCount: Accessor<number> unreadNotificationsCount: Accessor<number>
sortedNotifications: Accessor<ServerNotification[]> sortedNotifications: Accessor<SSEMessage[]>
actions: { actions: {
showNotificationsPanel: () => void showNotificationsPanel: () => void
hideNotificationsPanel: () => void hideNotificationsPanel: () => void
markNotificationAsRead: (notification: ServerNotification) => Promise<void> markNotificationAsRead: (notification: SSEMessage) => Promise<void>
setMessageHandler: (h: MessageHandler) => void setMessageHandler: (h: MessageHandler) => void
} }
} }
@ -40,9 +41,7 @@ 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, ServerNotification>>( const [notificationEntities, setNotificationEntities] = createStore<Record<number, SSEMessage>>({})
{}
)
const [db, setDb] = createSignal<Promise<IDBPDatabase<unknown>>>() const [db, setDb] = createSignal<Promise<IDBPDatabase<unknown>>>()
onMount(() => { onMount(() => {
const dbx = openDB('notifications-db', 1, { const dbx = openDB('notifications-db', 1, {
@ -76,7 +75,7 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
return Object.values(notificationEntities).sort((a, b) => b.timestamp - a.timestamp) return Object.values(notificationEntities).sort((a, b) => b.timestamp - a.timestamp)
}) })
const storeNotification = async (notification: ServerNotification) => { const storeNotification = async (notification: SSEMessage) => {
console.log('[context.notifications] Storing notification:', notification) console.log('[context.notifications] Storing notification:', notification)
const storage = await db() const storage = await db()
@ -88,28 +87,27 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
loadNotifications() loadNotifications()
} }
const [messageHandler, setMessageHandler] = createSignal<(m: Message) => void>() const [messageHandler, setMessageHandler] = createSignal<(m: SSEMessage) => void>()
createEffect(async () => { createEffect(async () => {
if (isAuthenticated()) { if (isAuthenticated()) {
loadNotifications() loadNotifications()
await fetchEventSource('https://chat.discours.io/connect', { await fetchEventSource('https://chat.discours.io/', {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Authorization: 'Bearer ' + getToken() Authorization: 'Bearer ' + getToken()
}, },
onmessage(event) { onmessage(event) {
const n: { kind: string; payload: any } = JSON.parse(event.data) const m: SSEMessage = JSON.parse(event.data)
if (n.kind === 'new_message') { if (m.entity === 'chat') {
console.log('[context.notifications] Received message:', n) console.log('[context.notifications] Received message:', m)
messageHandler()(n.payload) messageHandler()(m)
} else { } else {
console.log('[context.notifications] Received notification:', n) console.log('[context.notifications] Received notification:', m)
storeNotification({ storeNotification({
kind: n.kind, ...m,
payload: n.payload,
timestamp: Date.now(), timestamp: Date.now(),
seen: false seen: false
}) })
@ -126,7 +124,7 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
} }
}) })
const markNotificationAsRead = async (notification: ServerNotification) => { const markNotificationAsRead = async (notification: SSEMessage) => {
console.log('[context.notifications] Marking notification as read:', notification) console.log('[context.notifications] Marking notification as read:', notification)
const storage = await db() const storage = await db()