webapp/src/context/connect.tsx

87 lines
2.9 KiB
TypeScript
Raw Normal View History

2023-11-28 13:18:25 +00:00
import type { Accessor, JSX } from 'solid-js'
2023-12-15 13:45:34 +00:00
import { EventStreamContentType, fetchEventSource } from '@microsoft/fetch-event-source'
2023-11-28 13:18:25 +00:00
import { createContext, useContext, createSignal, createEffect } from 'solid-js'
2023-11-28 15:36:00 +00:00
2023-12-14 00:04:07 +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-01-23 16:32:57 +00:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any
payload: any // Author Shout Message Reaction Chat
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>
}
const ConnectContext = createContext<ConnectContextType>()
export const ConnectProvider = (props: { children: JSX.Element }) => {
const [messageHandlers, setHandlers] = createSignal<Array<MessageHandler>>([])
// const [messages, setMessages] = createSignal<Array<SSEMessage>>([]);
const [connected, setConnected] = createSignal(false)
2023-12-24 08:16:41 +00:00
const { session } = useSession()
2023-11-28 13:18:25 +00:00
const addHandler = (handler: MessageHandler) => {
setHandlers((hhh) => [...hhh, handler])
}
2023-12-15 13:45:34 +00:00
const [retried, setRetried] = createSignal<number>(0)
createEffect(async () => {
2023-12-24 08:16:41 +00:00
const token = session()?.access_token
2023-12-17 12:36:47 +00:00
if (token && !connected()) {
2023-12-24 08:16:41 +00:00
console.info('[context.connect] init SSE connection')
2023-12-17 12:36:47 +00:00
await fetchEventSource('https://connect.discours.io', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: token,
},
onmessage(event) {
const m: SSEMessage = JSON.parse(event.data)
console.log('[context.connect] Received message:', m)
// Iterate over all registered handlers and call them
messageHandlers().forEach((handler) => handler(m))
},
async onopen(response) {
console.log('[context.connect] SSE connection opened', response)
if (response.ok && response.headers.get('content-type') === EventStreamContentType) {
setConnected(true)
} else if (response.status === 401) {
throw new Error('unauthorized')
} else {
setRetried((r) => r + 1)
throw new Error('Internal Error')
}
},
onclose() {
console.log('[context.connect] SSE connection closed by server')
setConnected(false)
},
onerror(err) {
if (err.message === 'unauthorized' || retried() > RECONNECT_TIMES) {
throw err // rethrow to stop the operation
}
},
})
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)