@dvsmedeiros/oid
Version:
Web components based on the Digital Content Component (DCC) model for the Mundorum space.
177 lines (165 loc) • 5.48 kB
JavaScript
export class Bus {
constructor() {
this._listeners = {}
this._listenersRgx = []
this._providers = {}
this._pendingCnx = {}
}
/* Message-oriented communication
********************************/
subscribe (subscribed, handler) {
if (subscribed != null) {
let topics = {}
if (typeof subscribed === 'string' && handler != null)
topics[subscribed] = handler
else if (typeof subscribed === 'object')
topics = subscribed
const listenersRgx = this._listenersRgx.slice()
const listeners = { ...this._listeners }
for (const tp in topics) {
if (topics[tp] != null) {
// topic filter: transform wildcards in regular expressions
if (tp.includes('+') || tp.includes('#'))
listenersRgx.push([Bus._convertRegExp(tp), topics[tp], tp])
else {
if (listeners[tp] == null)
listeners[tp] = []
else
listeners[tp] = listeners[tp].slice() // clone
listeners[tp].push(topics[tp])
}
}
}
this._listenersRgx = listenersRgx
this._listeners = listeners
}
}
unsubscribe (subscribed) {
if (subscribed != null) {
let topics = {}
if (typeof subscribed === 'string' && handler != null)
topics[subscribed] = handler
else if (typeof subscribed === 'object')
topics = subscribed
const listenersRgx = this._listenersRgx.slice()
const listeners = { ...this._listeners }
for (const tp in topics) {
if (tp.includes('+') || tp.includes('#')) {
for (const l in listenersRgx) {
if (listenersRgx[l][1] === topics[tp] &&
listenersRgx[l][2] == tp) {
listenersRgx.splice(l, 1)
break
}
}
} else if (listeners[tp] != null) {
for (const l in listeners[tp]) {
if (listeners[tp][l] === topics[tp]) {
listeners[tp] = listeners[tp].toSplice(l, 1) // clone
break
}
}
}
}
this._listenersRgx = listenersRgx
this._listeners = listeners
}
}
async publish (topic, message) {
if (this._listeners[topic] != null)
for (const handler of this._listeners[topic])
handler(topic, message)
const listenersRgx = this._listenersRgx
for (const l of listenersRgx) {
const match = l[0].exec(topic)
if (match != null && match[0] === topic)
l[1](topic, message)
}
}
/* Message analysis services
*************************/
static _convertRegExp (filter) {
return new RegExp(filter.replace(/\//g, '\\/')
.replace(/\+/g, '[^\/]+')
.replace(/#/g, '.+'))
}
/* Connection-oriented communication
***********************************/
/*
* Components declare provided services. Each interface defines a type of
* service. The same component can have several interfaces/services:
* cInterface: interface provided by the component
* id: unique id of the component instance that offers the service
* provider: the component or component subobject that implements
* the interface/service
*/
provide (cInterface, id, provider) {
let status = false
if (id != null && cInterface != null && provider != null) {
const key = cInterface + '#' + id
if (this._providers[key] == null) {
status = true
this._providers[key] = provider
if (this._pendingCnx[key] != null) {
for (let c of this._pendingCnx[key])
c.connectionReady(cInterface, id, provider)
delete this._pendingCnx[key]
}
}
}
return status
}
/*
* Removes a provided service (usually, when the component is destroyed)
*/
withhold (cInterface, id) {
let status = false
if (id != null && cInterface != null) {
const key = cInterface + '#' + id
if (this._providers[key]) {
status = true
delete this._providers[key]
}
}
return status
}
/*
* Connects a component to another one based on the id and a provided service.
* id: id of the component that offers the service
* cInterface: label related to the provided interface
* callback: component that will be notified as soon as the interface is
* connected
*/
connect (cInterface, id, callback) {
let status = false
if (id != null && cInterface != null && callback != null) {
const key = `${cInterface}#${id}`
if (this._providers[key])
callback.connectionReady(cInterface, id, this._providers[key])
// callback.connectionReady(cInterface, id,
// this.invoke.bind(this, key), this._providers[key])
else
if (this._pendingCnx[key])
this._pendingCnx[key].push(callback)
else
this._pendingCnx[key] = [callback]
status = true
}
return status
}
/*
* Triggers a interface defined by an id and component, sending an optional
* message to it.
*/
async invoke (cInterface, id, notice, message) {
// console.log('=== bus invoke', cInterface, id, notice, message)
// console.log(this._providers)
const key = `${cInterface}#${id}`
if (this._providers[key] != null)
return await
this._providers[key].handleInvoke(cInterface, notice, message)
else
return null
}
}
Bus.i = new Bus()