2023-11-28 13:18:25 +00:00
|
|
|
import type { Accessor, JSX } from 'solid-js'
|
2024-02-04 11:25:21 +00:00
|
|
|
import type { Author, Reaction, Shout, Topic } from '../graphql/schema/core.gen'
|
2024-02-04 09:03:15 +00:00
|
|
|
|
2024-06-24 17:50:27 +00:00
|
|
|
import { EventSource } from 'extended-eventsource'
|
2024-05-21 13:51:13 +00:00
|
|
|
import { createContext, createEffect, createSignal, on, useContext } from 'solid-js'
|
2023-11-28 15:36:00 +00:00
|
|
|
|
2024-06-24 17:50:27 +00:00
|
|
|
import { sseUrl } from '../config/config'
|
2024-02-04 09:07:08 +00:00
|
|
|
import { Chat, Message } from '../graphql/schema/chat.gen'
|
2024-02-04 11:25:21 +00:00
|
|
|
import { useSession } from './session'
|
2023-11-28 13:18:25 +00:00
|
|
|
|
2023-12-15 13:45:34 +00:00
|
|
|
const RECONNECT_TIMES = 2
|
|
|
|
|
2023-11-28 13:18:25 +00:00
|
|
|
export interface SSEMessage {
|
|
|
|
id: string
|
2023-12-24 08:16:41 +00:00
|
|
|
entity: string // follower | shout | reaction
|
|
|
|
action: string // create | delete | update | join | follow | seen
|
2024-02-04 09:07:08 +00:00
|
|
|
payload: Author | Shout | Topic | Reaction | Chat | Message
|
2023-11-28 13:18:25 +00:00
|
|
|
created_at?: number // unixtime x1000
|
|
|
|
seen?: boolean
|
|
|
|
}
|
|
|
|
|
|
|
|
type MessageHandler = (m: SSEMessage) => void
|
|
|
|
|
|
|
|
export interface ConnectContextType {
|
|
|
|
addHandler: (handler: MessageHandler) => void
|
|
|
|
connected: Accessor<boolean>
|
|
|
|
}
|
|
|
|
|
2024-06-24 17:50:27 +00:00
|
|
|
const ConnectContext = createContext<ConnectContextType>({} as ConnectContextType)
|
2023-11-28 13:18:25 +00:00
|
|
|
|
|
|
|
export const ConnectProvider = (props: { children: JSX.Element }) => {
|
2024-02-05 15:04:23 +00:00
|
|
|
const [messageHandlers, setHandlers] = createSignal<MessageHandler[]>([])
|
2023-11-28 13:18:25 +00:00
|
|
|
const [connected, setConnected] = createSignal(false)
|
2023-12-24 08:16:41 +00:00
|
|
|
const { session } = useSession()
|
2024-05-18 17:43:20 +00:00
|
|
|
const [retried, setRetried] = createSignal<number>(0)
|
2023-11-28 13:18:25 +00:00
|
|
|
|
|
|
|
const addHandler = (handler: MessageHandler) => {
|
|
|
|
setHandlers((hhh) => [...hhh, handler])
|
|
|
|
}
|
|
|
|
|
2024-05-21 13:51:13 +00:00
|
|
|
createEffect(
|
|
|
|
on(
|
|
|
|
() => session()?.access_token,
|
2024-05-24 14:59:15 +00:00
|
|
|
async (tkn) => {
|
2024-05-21 13:51:13 +00:00
|
|
|
if (!sseUrl) return
|
|
|
|
if (!tkn) return
|
|
|
|
if (!connected() && retried() <= RECONNECT_TIMES) {
|
|
|
|
console.info('[context.connect] got token, init SSE connection')
|
|
|
|
try {
|
2024-06-24 17:50:27 +00:00
|
|
|
const eventSource = new EventSource(sseUrl, {
|
2024-05-21 13:51:13 +00:00
|
|
|
method: 'GET',
|
|
|
|
headers: {
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
Authorization: tkn,
|
|
|
|
},
|
2024-06-24 17:50:27 +00:00
|
|
|
retry: 3000,
|
2024-05-21 13:51:13 +00:00
|
|
|
})
|
2024-06-24 17:50:27 +00:00
|
|
|
|
|
|
|
eventSource.onopen = (ev) => {
|
|
|
|
console.log('[context.connect] SSE connection opened', ev)
|
|
|
|
setConnected(true)
|
|
|
|
setRetried(0)
|
|
|
|
}
|
|
|
|
|
|
|
|
eventSource.onmessage = (event: MessageEvent) => {
|
|
|
|
const m: SSEMessage = JSON.parse(event.data || '{}')
|
|
|
|
console.log('[context.connect] Received message:', m)
|
|
|
|
messageHandlers().forEach((handler) => handler(m))
|
|
|
|
}
|
|
|
|
|
|
|
|
eventSource.onerror = (error) => {
|
|
|
|
console.error('[context.connect] SSE connection error:', error)
|
|
|
|
setConnected(false)
|
|
|
|
if (retried() < RECONNECT_TIMES) {
|
|
|
|
setRetried((r) => r + 1)
|
|
|
|
} else throw Error('failed')
|
|
|
|
}
|
2024-05-21 13:51:13 +00:00
|
|
|
} catch (error) {
|
2024-06-24 17:50:27 +00:00
|
|
|
console.error('[context.connect] SSE init failed:', error)
|
2024-05-21 13:51:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
),
|
|
|
|
)
|
2023-11-28 13:18:25 +00:00
|
|
|
|
|
|
|
const value: ConnectContextType = { addHandler, connected }
|
|
|
|
|
|
|
|
return <ConnectContext.Provider value={value}>{props.children}</ConnectContext.Provider>
|
|
|
|
}
|
|
|
|
|
|
|
|
export const useConnect = () => useContext(ConnectContext)
|