postmerge
This commit is contained in:
parent
8d0a6269e1
commit
b6134cc04a
17442
package-lock.json
generated
Normal file
17442
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -6,7 +6,6 @@ import { MediaItem } from '../../../pages/types'
|
|||
import { Icon } from '../../_shared/Icon'
|
||||
import { Image } from '../../_shared/Image'
|
||||
import { CardTopic } from '../../Feed/CardTopic'
|
||||
import { Image } from '../../_shared/Image'
|
||||
|
||||
import styles from './AudioHeader.module.scss'
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ import { CommentDate } from '../CommentDate'
|
|||
import { CommentRatingControl } from '../CommentRatingControl'
|
||||
|
||||
import styles from './Comment.module.scss'
|
||||
import { AuthorLink } from '../../Author/AhtorLink'
|
||||
|
||||
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
|
||||
|
||||
|
|
|
@ -1,9 +1,19 @@
|
|||
import type { Accessor, JSX } from 'solid-js'
|
||||
import { createContext, createSignal, onMount, useContext } from 'solid-js'
|
||||
import { createContext, createEffect, createSignal, onMount, useContext } from 'solid-js'
|
||||
import type { Chat, Message, MutationCreateMessageArgs } from '../graphql/types.gen'
|
||||
import { inboxClient } from '../utils/apiClient'
|
||||
import { loadMessages } from '../stores/inbox'
|
||||
import { MessageHandler, SSEMessage, useNotifications } from './notifications'
|
||||
import { getToken } from '../graphql/privateGraphQLClient'
|
||||
import { fetchEventSource } from '@microsoft/fetch-event-source'
|
||||
|
||||
export interface SSEMessage {
|
||||
id: string
|
||||
entity: string
|
||||
action: string
|
||||
payload: any // Author | Shout | Reaction | Message
|
||||
timestamp?: number
|
||||
seen?: boolean
|
||||
}
|
||||
|
||||
type InboxContextType = {
|
||||
chats: Accessor<Chat[]>
|
||||
|
@ -25,9 +35,6 @@ export function useInbox() {
|
|||
export const InboxProvider = (props: { children: JSX.Element }) => {
|
||||
const [chats, setChats] = createSignal<Chat[]>([])
|
||||
const [messages, setMessages] = createSignal<Message[]>([])
|
||||
const {
|
||||
actions: { setMessageHandler }
|
||||
} = useNotifications()
|
||||
|
||||
const handleMessage = (sseMessage) => {
|
||||
console.log('[context.inbox]:', sseMessage)
|
||||
|
@ -41,11 +48,34 @@ export const InboxProvider = (props: { children: JSX.Element }) => {
|
|||
}
|
||||
}
|
||||
|
||||
onMount(() =>
|
||||
setMessageHandler((_h) => {
|
||||
return handleMessage
|
||||
})
|
||||
)
|
||||
createEffect(async () => {
|
||||
const token = getToken()
|
||||
if (token) {
|
||||
await fetchEventSource('https://chat.discours.io/connect', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: token,
|
||||
},
|
||||
onmessage(event) {
|
||||
const m: SSEMessage = JSON.parse(event.data)
|
||||
console.log('[context.inbox] Received message:', m)
|
||||
if (m.entity === 'chat' || m.entity == 'message') {
|
||||
handleMessage(m)
|
||||
} else {
|
||||
console.debug(m)
|
||||
}
|
||||
},
|
||||
onclose() {
|
||||
console.log('[context.inbox] sse connection closed by server')
|
||||
},
|
||||
onerror(err) {
|
||||
console.error('[context.inbox] sse connection closed by error', err)
|
||||
throw new Error(err) // NOTE: simple hack to close the connection
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const loadChats = async () => {
|
||||
try {
|
||||
|
@ -73,7 +103,7 @@ export const InboxProvider = (props: { children: JSX.Element }) => {
|
|||
const currentChat = chats().find((chat) => chat.id === args.chat_id)
|
||||
setChats((prev) => [
|
||||
...prev.filter((c) => c.id !== currentChat.id),
|
||||
{ ...currentChat, updatedAt: message.createdAt }
|
||||
{ ...currentChat, updatedAt: message.createdAt },
|
||||
])
|
||||
} catch (error) {
|
||||
console.error('Error sending message:', error)
|
||||
|
@ -94,7 +124,7 @@ export const InboxProvider = (props: { children: JSX.Element }) => {
|
|||
createChat,
|
||||
loadChats,
|
||||
getMessages,
|
||||
sendMessage
|
||||
sendMessage,
|
||||
}
|
||||
|
||||
const value: InboxContextType = { chats, messages, actions }
|
||||
|
|
|
@ -12,27 +12,9 @@ import { apiBaseUrl } from '../utils/config'
|
|||
import SSEService, { EventData } from '../utils/sseService'
|
||||
|
||||
import { useSession } from './session'
|
||||
import { Portal } from 'solid-js/web'
|
||||
import { ShowIfAuthenticated } from '../components/_shared/ShowIfAuthenticated'
|
||||
import { IDBPDatabase, 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 const PAGE_SIZE = 20
|
||||
export interface SSEMessage {
|
||||
id: string
|
||||
entity: string
|
||||
action: string
|
||||
payload: any // Author | Shout | Reaction | Message
|
||||
timestamp?: number
|
||||
seen?: boolean
|
||||
}
|
||||
|
||||
export type MessageHandler = (m: SSEMessage) => void
|
||||
|
||||
type NotificationsContextType = {
|
||||
notificationEntities: Record<number, SSEMessage>
|
||||
notificationEntities: Record<number, Notification>
|
||||
unreadNotificationsCount: Accessor<number>
|
||||
sortedNotifications: Accessor<Notification[]>
|
||||
loadedNotificationsCount: Accessor<number>
|
||||
|
@ -43,7 +25,6 @@ type NotificationsContextType = {
|
|||
markNotificationAsRead: (notification: Notification) => Promise<void>
|
||||
markAllNotificationsAsRead: () => Promise<void>
|
||||
loadNotifications: (options: { limit: number; offset: number }) => Promise<Notification[]>
|
||||
setMessageHandler: (h: MessageHandler) => void
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,17 +42,7 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
|
|||
const [unreadNotificationsCount, setUnreadNotificationsCount] = createSignal(0)
|
||||
const [totalNotificationsCount, setTotalNotificationsCount] = createSignal(0)
|
||||
const { isAuthenticated, user } = useSession()
|
||||
const [notificationEntities, setNotificationEntities] = createStore<Record<number, SSEMessage>>({})
|
||||
const [db, setDb] = createSignal<Promise<IDBPDatabase<unknown>>>()
|
||||
|
||||
onMount(() => {
|
||||
const dbx = openDB('notifications-db', 1, {
|
||||
upgrade(indexedDb) {
|
||||
indexedDb.createObjectStore('notifications')
|
||||
},
|
||||
})
|
||||
setDb(dbx)
|
||||
})
|
||||
const [notificationEntities, setNotificationEntities] = createStore<Record<number, Notification>>({})
|
||||
|
||||
const loadNotifications = async (options: { limit: number; offset?: number }) => {
|
||||
const { notifications, totalUnreadCount, totalCount } = await apiClient.getNotifications(options)
|
||||
|
@ -82,34 +53,17 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
|
|||
|
||||
setTotalNotificationsCount(totalCount)
|
||||
setUnreadNotificationsCount(totalUnreadCount)
|
||||
setNotificationEntities(
|
||||
notifications.reduce((acc, notification) => {
|
||||
acc[notification.id] = notification
|
||||
return acc
|
||||
}, {}),
|
||||
)
|
||||
|
||||
setNotificationEntities(newNotificationEntities)
|
||||
return notifications
|
||||
}
|
||||
|
||||
const sortedNotifications = createMemo(() => {
|
||||
return Object.values(notificationEntities).sort((a, b) => b.timestamp - a.timestamp)
|
||||
return Object.values(notificationEntities).sort(
|
||||
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
|
||||
)
|
||||
})
|
||||
|
||||
const storeNotification = async (notification: SSEMessage) => {
|
||||
console.log('[context.notifications] Storing notification:', notification)
|
||||
|
||||
const storage = await db()
|
||||
const tx = storage.transaction('notifications', 'readwrite')
|
||||
const store = tx.objectStore('notifications')
|
||||
|
||||
await store.put(notification, 'id')
|
||||
await tx.done
|
||||
loadNotifications()
|
||||
}
|
||||
|
||||
const loadedNotificationsCount = createMemo(() => Object.keys(notificationEntities).length)
|
||||
|
||||
createEffect(() => {
|
||||
if (isAuthenticated()) {
|
||||
sseService.connect(`${apiBaseUrl}/subscribe/${user().id}`)
|
||||
|
@ -124,42 +78,6 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
|
|||
sseService.disconnect()
|
||||
}
|
||||
})
|
||||
const [messageHandler, setMessageHandler] = createSignal<MessageHandler>(console.warn)
|
||||
|
||||
createEffect(async () => {
|
||||
if (isAuthenticated()) {
|
||||
loadNotifications()
|
||||
|
||||
await fetchEventSource('https://chat.discours.io/connect', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: getToken(),
|
||||
},
|
||||
onmessage(event) {
|
||||
const m: SSEMessage = JSON.parse(event.data)
|
||||
console.log('[context.notifications] Received message:', m)
|
||||
if (m.entity === 'chat' || m.entity == 'message') {
|
||||
messageHandler()(m)
|
||||
} else {
|
||||
storeNotification({
|
||||
...m,
|
||||
id: event.id,
|
||||
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(err) // NOTE: simple hack to close the connection
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const markNotificationAsRead = async (notification: Notification) => {
|
||||
await apiClient.markNotificationAsRead(notification.id)
|
||||
|
@ -180,7 +98,6 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
|
|||
}
|
||||
|
||||
const actions = {
|
||||
setMessageHandler,
|
||||
showNotificationsPanel,
|
||||
hideNotificationsPanel,
|
||||
markNotificationAsRead,
|
||||
|
|
|
@ -115,7 +115,7 @@ main {
|
|||
position: relative;
|
||||
|
||||
.scrollToNew {
|
||||
osition: absolute;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
bottom: 8px;
|
||||
overflow: hidden;
|
||||
|
|
|
@ -28,12 +28,11 @@ import type {
|
|||
import { publicGraphQLClient } from '../graphql/publicGraphQLClient'
|
||||
import { getToken, privateGraphQLClient, privateInboxGraphQLClient } from '../graphql/privateGraphQLClient'
|
||||
import topicsAll from '../graphql/query/topics-all'
|
||||
import mySession from '../graphql/mutation/my-session'
|
||||
import topicsRandomQuery from '../graphql/query/topics-random'
|
||||
import authLogoutQuery from '../graphql/mutation/auth-logout'
|
||||
import authRegisterMutation from '../graphql/mutation/auth-register'
|
||||
import authSendLinkMutation from '../graphql/mutation/auth-send-link'
|
||||
import createChat from '../graphql/mutation/create-chat'
|
||||
import createMessage from '../graphql/mutation/create-chat-message'
|
||||
import authConfirmEmailMutation from '../graphql/mutation/auth-confirm-email'
|
||||
import followMutation from '../graphql/mutation/follow'
|
||||
import markAllNotificationsAsRead from '../graphql/mutation/mark-all-notifications-as-read'
|
||||
import markNotificationAsRead from '../graphql/mutation/mark-notification-as-read'
|
||||
|
@ -43,8 +42,6 @@ import reactionDestroy from '../graphql/mutation/reaction-destroy'
|
|||
import reactionUpdate from '../graphql/mutation/reaction-update'
|
||||
import unfollowMutation from '../graphql/mutation/unfollow'
|
||||
import updateProfile from '../graphql/mutation/update-profile'
|
||||
import { getToken, privateGraphQLClient } from '../graphql/privateGraphQLClient'
|
||||
import { publicGraphQLClient } from '../graphql/publicGraphQLClient'
|
||||
import shoutLoad from '../graphql/query/article-load'
|
||||
import shoutsLoadBy from '../graphql/query/articles-load-by'
|
||||
import authCheckEmailQuery from '../graphql/query/auth-check-email'
|
||||
|
@ -58,17 +55,15 @@ import topicBySlug from '../graphql/query/topic-by-slug'
|
|||
import reactionsLoadBy from '../graphql/query/reactions-load-by'
|
||||
import authorsLoadBy from '../graphql/query/authors-load-by'
|
||||
import chatMessagesLoadBy from '../graphql/query/chat-messages-load-by'
|
||||
import loadRecipients from '../graphql/query/chat-recipients'
|
||||
import myChats from '../graphql/query/chats-load'
|
||||
import draftsLoad from '../graphql/query/drafts-load'
|
||||
import myFeed from '../graphql/query/my-feed'
|
||||
import loadRecipients from '../graphql/query/chat-recipients'
|
||||
import updateProfile from '../graphql/mutation/update-profile'
|
||||
import createArticle from '../graphql/mutation/article-create'
|
||||
import updateArticle from '../graphql/mutation/article-update'
|
||||
import deleteShout from '../graphql/mutation/article-delete'
|
||||
// import notifications from '../graphql/query/notifications'
|
||||
// import markNotificationAsRead from '../graphql/mutation/mark-notification-as-read'
|
||||
import markAllNotificationsAsRead from '../graphql/mutation/mark-all-notifications-as-read'
|
||||
import notifications from '../graphql/query/notifications'
|
||||
|
||||
import markAsRead from '../graphql/mutation/chat-mark-as-read'
|
||||
import createChat from '../graphql/mutation/chat-create'
|
||||
import updateChat from '../graphql/mutation/chat-update'
|
||||
|
@ -371,8 +366,7 @@ export const apiClient = {
|
|||
.toPromise()
|
||||
return resp.data.loadReactionsBy
|
||||
},
|
||||
// TODO: store notifications in browser storage
|
||||
/*
|
||||
|
||||
getNotifications: async (params: NotificationsQueryParams): Promise<NotificationsQueryResult> => {
|
||||
const resp = await privateGraphQLClient.query(notifications, { params }).toPromise()
|
||||
return resp.data.loadNotifications
|
||||
|
@ -384,10 +378,6 @@ export const apiClient = {
|
|||
})
|
||||
.toPromise()
|
||||
},
|
||||
*/
|
||||
markAllNotificationsAsRead: async (): Promise<void> => {
|
||||
await privateGraphQLClient.mutation(markAllNotificationsAsRead, {}).toPromise()
|
||||
},
|
||||
|
||||
markAllNotificationsAsRead: async (): Promise<void> => {
|
||||
await privateGraphQLClient.mutation(markAllNotificationsAsRead, {}).toPromise()
|
||||
|
|
|
@ -7,7 +7,4 @@ export const apiBaseUrl = 'https://v2.discours.io'
|
|||
const defaultThumborUrl = 'https://images.discours.io'
|
||||
export const thumborUrl = import.meta.env.PUBLIC_THUMBOR_URL || defaultThumborUrl
|
||||
|
||||
const defaultThumborUrl = 'https://images.discours.io'
|
||||
export const thumborUrl = import.meta.env.PUBLIC_THUMBOR_URL || defaultThumborUrl
|
||||
|
||||
export const SENTRY_DSN = import.meta.env.PUBLIC_SENTRY_DSN || ''
|
||||
|
|
33
src/utils/sseService.ts
Normal file
33
src/utils/sseService.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
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