Skip to content

Instantly share code, notes, and snippets.

@bluebrown
Last active December 2, 2019 18:33
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 bluebrown/53e5144ea5c7e8fb1c77c20187844273 to your computer and use it in GitHub Desktop.
Save bluebrown/53e5144ea5c7e8fb1c77c20187844273 to your computer and use it in GitHub Desktop.
golongpoll javascript client implementation with socket.io like interface
import {LongpollManager} from 'http://0.0.0.0:6611/client.js'
let lpm = new LongpollManager('http://0.0.0.0:6611')
lpm.subscribe('example', (data) => document.write(`${JSON.stringify(data)} <br>`))
lpm.publish('example', {hello: 'world'}) // publish objects to given category
/* eslint-disable no-unused-vars */
const dflt = {
timeout: 60,
sinceTime: new Date().getTime(),
successDelay: 10,
errorDelay: 1000 * 3,
subSuffix: '/events',
pubSuffix: '/events',
}
/**
* Longpoll resource that implements the golongpoll protocol. All its methods are chainable
* as it always returns itself
*/
export function GoLongpollSource(baseUrl, category, config = dflt, eventCallback = (data) => {}) {
this.baseUrl = baseUrl
this.category = category
this.config = { ...dflt, ...config }
this.eventCallback = eventCallback
this.signal = null
this.next = null
/**
* update configration. The change will apply on the next poll
*/
this.updateConfig = function (config = {}) {
this.config = { ...this.config, ...config }
return this
}
/**
* generate a url based on the current config that matches the golongpoll protocol
*/
this.pollString = function () {
return '?category=' + this.category +
(this.config.sinceTime ? '&since_time=' + this.config.sinceTime : '') +
'&timeout=' + this.config.timeout
}
/**
* use to repeat subscription after event has been received
*/
this.repeat = function (delay = this.config.errorDelay) {
this.next = setTimeout(() => this.subscribe(), delay)
return this
}
/**
* unsubscribe from the category. It is possible to resubscribe
* at a later point in time and keep using the resource
*/
this.unsubscribe = function () {
clearTimeout(this.next)
this.controller.abort()
return this
}
/**
* subscribe to the given category and apply current config. On the http response,
* pass the err and event into the provided eventCallback and then
* repeat the process with some delay, depending on the outcome of the last poll
*/
this.subscribe = function () {
this.controller = new AbortController()
this.signal = this.controller.signal
fetch(this.baseUrl + this.config.subSuffix + this.pollString(), { signal: this.signal })
.then((byteStream) => byteStream.json())
.then(({ error, timeout, events }) => {
if (error) throw error
if (timeout || events) {
if (events && events.length > 0) {
events.forEach((event) => {
this.eventCallback(event.data)
this.config.sinceTime = event.timestamp
})
}
return this.repeat(this.config.successDelay)
}
})
.catch((err) => {
if (err.name == 'AbortError') {
console.log('subscription has been cancelled')
return this
}
return this.repeat()
})
return this
}
/**
* chain is some sugar to write a litte more pretty
*/
this.chain = function (callback) {
callback(this)
return this
}
}
/**
* manage longpoll resources. defaults can be partially or complelty overwritten
*/
export function LongpollManager(baseUrl, defaults = dflt) {
this.baseUrl = baseUrl
this.defaults = { ...dflt, ...defaults }
this.sources = {}
/**
* create a new longpoll resource for given category that can be used to subscribe
* to events from the pubsub server instance. The eventcallback will be called on every
* http response and yield the event data
*/
this.createResource = function (category, eventCallback=(data) =>{}) {
this.sources[category] = new GoLongpollSource(
this.baseUrl,
category,
this.defaults,
eventCallback,
)
return this.sources[category]
}
/**
* start polling the given resource. If the resource does not exist,
* createResource will be called before subscribing. This allows to subscribe
* with this short form if the default configuration of the manager instance are
* sufficent for the desired resource.
*/
this.subscribe = function (category, eventCallback) {
if (!this.sources[category]) {
this.createResource(category, eventCallback)
}
return this.sources[category].subscribe(
eventCallback || this.sources[category].eventCallback
)
}
/**
* usubscribe resource for given category. This will cancel the current request.
* and not start another one until the subscribe method is call
*/
this.unsubscribe = function (category) {
if (!this.sources[category]) {
throw 'resource does not exist'
}
return this.sources[category].unsubscribe()
}
/**
* pubish and event to the given category.
* The message must be an object that can be parsed to valid json
*/
this.publish = function (category, message) {
return fetch(this.baseUrl + this.defaults.pubSuffix + '?category=' + category, {
method: 'POST',
body: JSON.stringify(message)
})
.then((b) => b.text())
.catch((err) => {
throw err.message
})
}
/**
* update the managers default configuation. This will not apply
* to existing resources but only for resources that are created after the change
* to update existing resources use the updateResourceConfig method instead
*/
this.updateDefaultConfig = function (config) {
this.defaults = { ...this.defaults, ...config }
}
/**
* update the configration of an existing resource. Not all paramaters have to be passed in
* so it is possible to only overwrite what is needed
*/
this.updateResourceConfig = function (category, config) {
if (!this.sources[category]) {
throw 'resource does not exist'
}
this.sources[category].updateConfig(config)
return this.sources[category]
}
}
export default LongpollManager
// import the module. make sure you define your script as module in your html file
import {LongpollManager} from 'http://0.0.0.0:6611/client.js'
// instantiate a new manager
let lpm = new LongpollManager('http://0.0.0.0:6611', { // overwrite some of the defaults
timeout: 120,
sinceTime: new Date().getTime() - 1000*10
})
// use default configs from manager and start polling via shortform
lpm.subscribe('example', (data) => {
document.body.prepend(document.createElement('br'))
document.body.prepend(document.createTextNode(JSON.stringify(data)))
})
// or use the long form and chain custom config for given resource before subscribing
lpm.createResource('example2',(data) => console.log(data))
.updateConfig({timeout:5, successDelay: 1000*1})
.subscribe()
// the following is just some sugar to build custom chains
.chain((longpollResource) => console.log(longpollResource))
.chain((lp) => setTimeout(() => lp.unsubscribe(), 1000*15))
// publish js objects, lpm will stringify them to json before posting
setInterval(({obj) => {obj.data++;lpm.publish('example', obj)}, 1000*5, {data: 0})
// update resourceConfig. Will start to take affect on the next poll
setTimeout(() => lpm.updateResourceConfig('example', {timeout: 30, successDelay: 1000*3}), 1000*20)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment