Created
July 2, 2020 16:13
-
-
Save myobie/d6e91c2aa1e371b7d587c83d1495a47c to your computer and use it in GitHub Desktop.
An example of how I seem to always end up wrapping the phoenix socket client
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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