Skip to content

Instantly share code, notes, and snippets.

@myobie myobie/socket.ts

Created Jul 2, 2020
Embed
What would you like to do?
An example of how I seem to always end up wrapping the phoenix socket client
import { Socket, Channel } from 'phoenix'
import { encode, decode } from './serializer'
import createState, { UpdateCallback } from './create-state'
import { enableMapSet } from 'immer'
enableMapSet()
type MessageCallback = (payload?: unknown) => void
type ChannelState = {
state: 'joining' | 'joined' | 'errored' | 'closed'
error?: string
}
type SocketState = {
isConnected: boolean
isFullyJoined: boolean
channelStates: Map<string, ChannelState>
}
export type SocketConnection = {
state: SocketState
onUpdate: (cb: UpdateCallback<SocketState>) => void
onMessage: (topic: string, event: string, callback: MessageCallback) => void
join: (topic: string, onReply?: (reply: unknown) => void) => void
close: () => void
}
function calculateIsFullyJoined (state: SocketState): boolean {
for (const [, channelState] of state.channelStates) {
if (channelState.state !== 'joined') {
return false
}
}
return true
}
export function connect (): SocketConnection {
const channels: Map<string, Channel> = new Map()
const messageCallbacks: Map<string, Map<string, MessageCallback[]>> = new Map()
const socket = new Socket('wss://localhost:4001/socket', {
params: {},
encode,
decode
})
const initialState: SocketState = {
isConnected: false,
isFullyJoined: false,
channelStates: new Map()
}
const state = createState(initialState)
socket.onOpen(() => {
state.update(draft => {
draft.isConnected = true
})
})
socket.onClose(() => {
state.update(draft => {
draft.isConnected = false
})
})
socket.onError(() => {
state.update(draft => {
draft.isConnected = false
})
})
socket.connect()
return {
get state (): SocketState {
return state.current as SocketState
},
onUpdate: state.onUpdate.bind(state),
onMessage (topic: string, event: string, callback: MessageCallback): void {
console.debug(`onMessage ${topic} ${event}`)
let topicCallbacks = messageCallbacks.get(topic)
if (topicCallbacks === undefined) {
topicCallbacks = new Map()
messageCallbacks.set(topic, topicCallbacks)
}
let callbacks = topicCallbacks.get(topic)
if (callbacks === undefined) {
callbacks = []
}
callbacks.push(callback)
const channel = channels.get(topic)
if (channel) {
channel.on(event, callback)
}
},
join (topic: string, onReply?: (reply: unknown) => void): void {
console.debug(`join ${topic}`)
const channel = socket.channel(topic)
channels.set(topic, channel)
state.update(draft => {
draft.channelStates.set(topic, { state: 'joining' })
draft.isFullyJoined = calculateIsFullyJoined(draft)
})
const topicCallbacks = messageCallbacks.get(topic)
if (topicCallbacks) {
for (const [event, callbacks] of topicCallbacks) {
for (const callback of callbacks) {
channel.on(event, callback)
}
}
}
channel.onClose(() => {
state.update(draft => {
draft.channelStates.set(topic, { state: 'closed' })
draft.isFullyJoined = calculateIsFullyJoined(draft)
})
})
channel.onError((e: unknown) => {
state.update(draft => {
draft.channelStates.set(topic, { state: 'errored', error: String(e) })
draft.isFullyJoined = calculateIsFullyJoined(draft)
})
})
channel
.join()
.receive('ok', (reply: unknown) => {
if (onReply) {
onReply(reply)
}
state.update(draft => {
draft.channelStates.set(topic, { state: 'joined' })
draft.isFullyJoined = calculateIsFullyJoined(draft)
})
})
.receive('error', (e: unknown) => {
state.update(draft => {
draft.channelStates.set(topic, { state: 'errored', error: String(e) })
draft.isFullyJoined = calculateIsFullyJoined(draft)
})
})
.receive('timeout', () => {
state.update(draft => {
draft.channelStates.set(topic, { state: 'errored', error: 'timeout' })
draft.isFullyJoined = calculateIsFullyJoined(draft)
})
})
},
close (): void {
console.debug('close')
}
//, push, etc
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.