Last active
December 2, 2019 18:33
-
-
Save bluebrown/53e5144ea5c7e8fb1c77c20187844273 to your computer and use it in GitHub Desktop.
golongpoll javascript client implementation with socket.io like interface
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 {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 |
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
/* 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 |
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 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