Skip to content

Instantly share code, notes, and snippets.

@pirate
Created February 26, 2016 22:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pirate/93d8b84dcba325486744 to your computer and use it in GitHub Desktop.
Save pirate/93d8b84dcba325486744 to your computer and use it in GitHub Desktop.
Websocket redux action queuer
/* Socket wrapper that gracefully handles disconnects and passes messages to redux as actions. */
export default class SocketRouter {
constructor(store, notifier, loadStart, loadFinish, socket_url) {
// takes a redux store, optional functions to display notifications & loading bars, and an optional socket_url
this.ready = false
this.queue = []
this.store = store || null
this.reconnects = -1
this.socket_url = socket_url || this._detectPath(window.location)
this.notifier = notifier || ((message, pending) => console.log(message, pending && 'Pending...'))
this.loadStart = loadStart || (() => console.log('Loading...'))
this.loadFinish = loadFinish || (() => console.log('Finished loading.'))
this._setupSocket()
}
dispatch(message) {
if (this.ready) {
console.log('SENT', message)
this.socket.send(JSON.stringify(message))
return true
}
console.log('QUEUED', message)
this.queue.push(message)
return false
}
send_action(type, data) {
this.loadStart()
this.notifier(type + '...', true)
this.dispatch({...data, type, TIMESTAMP: ((new Date).getTime() - action.time)})
}
_setupSocket() {
this.socket = new WebSocket(this.socket_url)
this.socket.onmessage = this._onmessage.bind(this)
this.socket.onopen = this._onopen.bind(this)
this.socket.onclose = this._onclose.bind(this)
}
_flush() {
if (this.ready)
this.queue = this.queue.filter(action => this.dispatch(action))
return this.queue
}
_onmessage(message) {
const action = JSON.parse(message.data)
console.log('RECEIVED', action);
// if redux store is present
if (this.store) {
// if response has any errors, display them
// can be a plain str or a dict
// errors = ['text1', {text: 'text2'}, {style: 'success', text: 'text3'}]
(action.errors || []).map(error =>
this.store.dispatch({type: 'ALERT', alert: {
style: error.style || 'danger',
text: error.text || error,
}}))
if (action.type == 'PING') {
const delay = ((new Date).getTime() - action.time) / 1000
const comment = this._humanizeSpeed(delay)
this.notifier(
`Connection Speed: ${delay}s (${comment}).` + (this.reconnects ?
` ${this.reconnects} reconnects.` : ''), false)
setTimeout(this.notifier.bind(this), 3000)
} else if (action.type) {
this.store.dispatch(action)
this.loadFinish()
this.notifier()
}
}
}
_onopen() {
if (this.disconnected_timeout) clearTimeout(this.disconnected_timeout)
this.ready = true
this.reconnects++
this.notifier('Checking server sync...', true)
this.send_action('PING', {time: (new Date).getTime()})
this._flush()
}
_onclose() {
this.ready = false
this._setupSocket()
if (this.disconnected_timeout)
clearTimeout(this.disconnected_timeout)
this.disconnected_timeout = setTimeout(this.notifier.bind(this,
'Websocket disconnected, attempting to reconnect...', true), 4000)
}
_detectPath(location) {
var path = location.pathname
path = path.endsWith('/') ? path.substring(0, path.length-1) : path
return 'ws://' + location.hostname + path + '/websocket'
}
_humanizeSpeed(seconds) {
if (seconds < 0.1) return 'instantaneous'
if (seconds < 0.2) return 'wicked fast'
if (seconds < 0.5) return 'fast'
if (seconds < 0.8) return 'ok'
if (seconds < 1.2) return 'laggy'
if (seconds > 1.2) return 'very slow'
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment