django-channels
django-channels copied to clipboard
Handler objects
Hi there 👋,
I don't know if this repo is still active. But I recently did a channels integration and wrote web components that could hook to a socket.
We implemented consumer object mixins, that mirrors the channels' consumer Python API, but in JavaScript. I figured I share it here:
/**
* Mixin to consume a WebSocket connection.
*
* The class using this mixin must define the following methods:
* - onOpen(event)
* - onClose(event)
* - onError(event)
* - onMessage(event)
* The class using this mixin must also define the following property:
* - wsUrl: URL
* @param {*} Base - The base class to extend.
* @mixin
* @returns {Base} The new class.
*/
export function WebSocketConsumerMixin(Base) {
return class extends Base {
wsUrl
keepAlive = false
retry = 0
maxRetries = 10
maxBackOff = 1000 * 60 // 1 minute
minBackOff = 1000 // 1 second
maxJitter = 1000 // 1 second
/**
* Connect to the WebSocket server.
*/
open() {
console.info("Opening WebSocket connection to:", this.wsUrl)
this.ws = new WebSocket(this.wsUrl)
this.ws.addEventListener("open", this.onOpen.bind(this))
this.ws.addEventListener("close", this.onClose.bind(this))
this.ws.addEventListener("message", this.onMessage.bind(this))
// there is no need to bind `error` event, as `onClose` will be called right after
// and `Event` contains no useful information
}
/**
* Close the WebSocket connection.
*/
close() {
console.debug("Closing WebSocket connection", this)
this.keepAlive = false
this.ws.close()
}
/**
* Get the backoff time for the next retry.
* @returns {number} The backoff time in milliseconds.
*/
getBackOff() {
const jitter = Math.floor(Math.random() * this.maxJitter * 2) - this.maxJitter
return Math.min(2 ** this.retry * this.minBackOff, this.maxBackOff) + jitter
}
/**
* Event handler for the WebSocket connection.
* @param {Event} event - The event object.
*/
onOpen(event) {
console.debug("Socket opened:", event)
this.retry = 0
}
/**
* Event handler for the WebSocket connection.
* @param {Event} event - The event object.
*
* If keepAlive is true, the connection will be re-established after a delay.
*/
onClose(event) {
console.warn("Socket closed:", event)
if (this.keepAlive) {
if (this.retry < this.maxRetries) {
const backoff = this.getBackOff()
console.info(`Try to reconnect after ${backoff}ms`)
setTimeout(() => {
this.retry++
console.debug(`Reconnecting (retry ${this.retry})`)
this.open()
}, backoff)
} else {
throw new Error("Max retries reached")
}
}
}
/**
* Event handler for the WebSocket connection.
* @param {Event} event - The event object.
*/
onMessage(event) {
console.debug(event)
}
/**
* Send a message to the WebSocket server.
* @param {string} message - The message to send.
*/
send(message) {
this.ws.send(message)
}
}
}
/**
* Mixin to consume a WebSocket connection and send/receive JSON messages.
*
* Counterpart of Django Channels' `channels.generic.websocket.AsyncJsonWebsocketConsumer`,
* see also: https://channels.readthedocs.io/en/latest/topics/consumers.html#asyncjsonwebsocketconsumer
*
* The class using this mixin must define the following methods:
* - receive(data: object)
* - send(data: object)
* The class using this mixin must also define the following property:
* - wsUrl: URL
* @param {*} Base - The base class to extend.
* @mixin
* @returns {Base} The new class.
*/
export function JSONWebSocketConsumerMixin(Base) {
return class extends WebSocketConsumerMixin(Base) {
onMessage(event) {
this.receive(JSON.parse(event.data))
}
/**
* Receive a JSON message from the WebSocket server.
* @param {object} data - The JSON message received.
*/
receive(data) {
console.debug("Received:", data)
}
/**
* Send a JSON message to the WebSocket server.
* @param {object} data - The JSON message to send.
*/
send(data) {
console.debug("Sending:", data)
this.ws.send(JSON.stringify(data))
}
}
}
You can use them by like so:
class Doodle extends JSONWebSocketConsumerMixin(HTMLCanvasElement) {
connectedCallback() {
this.wsUrl = this.getAttribute('ws-url')
this.connect()
}
}
I figured I share it, since someone might find this helpful.
Cheers! Joe