import type { Accessor, JSX } from 'solid-js' import type { Author, Reaction, Shout, Topic } from '../graphql/schema/core.gen' import { EventSource } from 'extended-eventsource' import { createContext, createEffect, createSignal, on, useContext } from 'solid-js' import { sseUrl } from '../config/config' import { Chat, Message } from '../graphql/schema/chat.gen' import { useSession } from './session' const RECONNECT_TIMES = 2 export interface SSEMessage { id: string entity: string // follower | shout | reaction action: string // create | delete | update | join | follow | seen payload: Author | Shout | Topic | Reaction | Chat | Message created_at?: number // unixtime x1000 seen?: boolean } type MessageHandler = (m: SSEMessage) => void export interface ConnectContextType { addHandler: (handler: MessageHandler) => void connected: Accessor } const ConnectContext = createContext({} as ConnectContextType) export const ConnectProvider = (props: { children: JSX.Element }) => { const [messageHandlers, setHandlers] = createSignal([]) const [connected, setConnected] = createSignal(false) const { session } = useSession() const [retried, setRetried] = createSignal(0) const addHandler = (handler: MessageHandler) => { setHandlers((hhh) => [...hhh, handler]) } createEffect( on( () => session()?.access_token, async (tkn) => { if (!sseUrl) return if (!tkn) return if (!connected() && retried() <= RECONNECT_TIMES) { console.info('[context.connect] got token, init SSE connection') try { const eventSource = new EventSource(sseUrl, { method: 'GET', headers: { 'Content-Type': 'application/json', Authorization: tkn, }, retry: 3000, }) 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') } } catch (error) { console.error('[context.connect] SSE init failed:', error) } } }, ), ) const value: ConnectContextType = { addHandler, connected } return {props.children} } export const useConnect = () => useContext(ConnectContext)