import { Presence } from '@supabase/phoenix' import type { PresenceState, PresenceStates } from './types' import type { RealtimePresenceOptions, RealtimePresenceState, Presence as RealtimePresenceType, } from '../RealtimePresence' import ChannelAdapter from './channelAdapter' export default class PresenceAdapter { private presence: Presence constructor(channel: ChannelAdapter, opts?: RealtimePresenceOptions) { const phoenixOptions = phoenixPresenceOptions(opts) this.presence = new Presence(channel.getChannel(), phoenixOptions) this.presence.onJoin((key, currentPresence, newPresence) => { const onJoinPayload = PresenceAdapter.onJoinPayload(key, currentPresence, newPresence) channel.getChannel().trigger('presence', onJoinPayload) }) this.presence.onLeave((key, currentPresence, leftPresence) => { const onLeavePayload = PresenceAdapter.onLeavePayload(key, currentPresence, leftPresence) channel.getChannel().trigger('presence', onLeavePayload) }) this.presence.onSync(() => { channel.getChannel().trigger('presence', { event: 'sync' }) }) } get state() { return PresenceAdapter.transformState(this.presence.state) } /** * @private * Remove 'metas' key * Change 'phx_ref' to 'presence_ref' * Remove 'phx_ref' and 'phx_ref_prev' * * @example Transform state * // returns { * abc123: [ * { presence_ref: '2', user_id: 1 }, * { presence_ref: '3', user_id: 2 } * ] * } * RealtimePresence.transformState({ * abc123: { * metas: [ * { phx_ref: '2', phx_ref_prev: '1' user_id: 1 }, * { phx_ref: '3', user_id: 2 } * ] * } * }) * */ static transformState(state: PresenceStates): RealtimePresenceState { state = cloneState(state) return Object.getOwnPropertyNames(state).reduce((newState, key) => { const presences = state[key] newState[key] = transformState(presences) return newState }, {} as RealtimePresenceState) } static onJoinPayload(key: string, currentPresence: PresenceState, newPresence: PresenceState) { const currentPresences = parseCurrentPresences(currentPresence) const newPresences = transformState(newPresence) return { event: 'join', key, currentPresences, newPresences, } } static onLeavePayload(key: string, currentPresence: PresenceState, leftPresence: PresenceState) { const currentPresences = parseCurrentPresences(currentPresence) const leftPresences = transformState(leftPresence) return { event: 'leave', key, currentPresences, leftPresences, } } } function transformState(presences: PresenceState) { return presences.metas.map((presence) => { presence['presence_ref'] = presence['phx_ref'] delete presence['phx_ref'] delete presence['phx_ref_prev'] return presence }) as RealtimePresenceType[] } function cloneState(state: PresenceStates): PresenceStates { return JSON.parse(JSON.stringify(state)) } function phoenixPresenceOptions(opts?: RealtimePresenceOptions) { return opts?.events && { events: opts.events } } function parseCurrentPresences(currentPresences?: PresenceState) { return currentPresences?.metas ? transformState(currentPresences) : [] }