/**
*
* ## A simple PubSub
*
* 1. subscribe a function to a channel:
* ```
* let subscriptionId = kiss.pubsub.subscribe("MY_CHANNEL_NAME", (messageData) => { console.log(messageData) })
* ```
*
* 2. publish a message on a channel:
* ```
* kiss.pubsub.publish("MY_CHANNEL_NAME", {foo: "bar"})
* ```
*
* 3. unsubscribe a function:
* ```
* kiss.pubsub.unsubscribe(subscriptionId)
* ```
*
* 4. list all active subscriptions
* ```
* kiss.pubsub.list()
* ```
*
* @namespace
*
*/
kiss.pubsub = {
subscriptions: {},
channelsNotLogged: ["EVT_ROUTE_UPDATED", "EVT_CONTAINERS_RESIZED"],
/**
* Publish a message on a specific channel + on the global channel, if any
*
* The global channel is the "*" channel, which is a special channel that will be called for all messages.
* Sometimes, it can be convenient to observe all messages, without subscribing to a specific channel.
*
* @param {string} channel - The channel name
* @param {object} [messageData] - The data published into the channel
*/
publish(channel, messageData) {
let targetChannel = kiss.pubsub.subscriptions[channel]
// If the channel doesn't exist, we publish only the global channel
if (!targetChannel) {
kiss.pubsub.publishOnGlobalChannel(messageData)
return
}
if (!kiss.pubsub.channelsNotLogged.includes(channel.toUpperCase())) {
log.info("kiss.pubsub - publish on channel: " + channel, messageData)
}
// Browse all the subscriptions of kiss.pubsub channel
Object.keys(targetChannel).forEach(subscriptionId => {
// Get the function to execute
let fn = targetChannel[subscriptionId]
try {
// Pass the message data to the function
fn(messageData)
}
catch(err) {
log.err("kiss.pubsub - publish - Error with subscription id: " + subscriptionId, err)
}
})
// Publish the message on the global channel
kiss.pubsub.publishOnGlobalChannel(messageData)
},
/**
* Publish a message on the global channel (*)
*
* @param {*} messageData
*/
publishOnGlobalChannel(messageData) {
if (kiss.pubsub.subscriptions["*"]) {
let globalChannel = kiss.pubsub.subscriptions["*"]
Object.keys(globalChannel).forEach(subscriptionId => {
// Get the function to execute
let fn = globalChannel[subscriptionId]
try {
// Pass the message data to the function
fn(messageData)
}
catch(err) {
log.err("kiss.pubsub - publish - Error with subscription id: " + subscriptionId, err)
}
})
}
},
/**
* Subscribe a function to a channel
*
* The global channel is the "*" channel, which is a special channel that will be called for all messages.
* Sometimes, it can be convenient to observe all messages, without subscribing to a specific channel.
*
* @param {string} channel - The channel name, or "*" to subscribe to all channels
* @param {function} fn - The function to subscribe to the channel
* @param {string} [description] - Optional description of the subscription
* @returns {string} The subscription id
*/
subscribe(channel, fn, description) {
let subscriptionId = kiss.tools.shortUid()
// If the channel doesn't exist yet, we create it
if (!kiss.pubsub.subscriptions[channel]) kiss.pubsub.subscriptions[channel] = {}
// Subscribe the function to the channel
kiss.pubsub.subscriptions[channel][subscriptionId] = fn
kiss.pubsub.subscriptions[channel][subscriptionId].description = description
// Return the subscription id so we can use it to unsubscribe the function later
return subscriptionId
},
/**
* Subscribe a function to a channel, but unsubscribe it after the first call
*
* @param {string} channel - The channel name
* @param {function} fn - The function to subscribe to the channel
* @param {string} [description] - Optional description of the subscription
* @returns {string} The subscription id
*/
subscribeOnce(channel, fn, description) {
let subscriptionId = kiss.pubsub.subscribe(channel, fn, description)
// Unsubscribe the function after the first call
let originalFn = kiss.pubsub.subscriptions[channel][subscriptionId]
kiss.pubsub.subscriptions[channel][subscriptionId] = function(messageData) {
originalFn(messageData)
kiss.pubsub.unsubscribe(subscriptionId)
}
return subscriptionId
},
/**
* Unsubscribe a function from the pubsub
*
* @param {string} subscriptionId - The id of the subscription to remove
*/
unsubscribe(subscriptionId) {
// Scan all the channels
Object.keys(kiss.pubsub.subscriptions).forEach(channel => {
let channelSubscriptions = kiss.pubsub.subscriptions[channel]
// For each channel, scan all the subscriptions
Object.keys(channelSubscriptions).forEach(channelSubscriptionId => {
// If the subscription is found, we delete it
if (channelSubscriptionId == subscriptionId) {
delete kiss.pubsub.subscriptions[channel][subscriptionId]
return
}
})
})
},
/**
* Return the number of subscriptions.
*
* Mainly used for debugging (useful to track memory leaks).
*
* @returns {number}
*/
getCount() {
let count = 0
Object.keys(kiss.pubsub.subscriptions).forEach(channel => {
let channelSubscriptions = kiss.pubsub.subscriptions[channel]
Object.keys(channelSubscriptions).forEach(() => count++)
})
return count
},
/**
* Get a subscription by id.
*
* Mainly used for debug purpose, when you need to check which function is registered in the pubsub.
*
* @param {string} subscriptionId - The id of the subscription to retrieve
* @return {function} The subscribed function
*/
get(subscriptionId) {
let subscription
// Scan all the channels
Object.keys(kiss.pubsub.subscriptions).forEach(channel => {
let channelSubscriptions = kiss.pubsub.subscriptions[channel]
// For each channel, scan all the subscriptions
Object.keys(channelSubscriptions).forEach(channelSubscriptionId => {
// If the subscription is found, we return it
if (channelSubscriptionId == subscriptionId) {
subscription = kiss.pubsub.subscriptions[channel][subscriptionId]
}
})
})
return subscription
},
/**
* List all the subscriptions in the console.
*
* Mainly used for debug purpose, when you need an overview of subscriptions.
*
* @param {boolean} [showFunction] - If true, show the subscribed function
*/
list(showFunction) {
let counter = 0
// Scan all the channels
Object.keys(kiss.pubsub.subscriptions).forEach(channel => {
let channelSubscriptions = kiss.pubsub.subscriptions[channel]
// For each channel, scan all the subscriptions
Object.keys(channelSubscriptions).forEach(channelSubscriptionId => {
let subscription = kiss.pubsub.subscriptions[channel][channelSubscriptionId]
let description = kiss.pubsub.subscriptions[channel][channelSubscriptionId].description
log("-----------------------------------------------------------------")
log.info("Subscription " + counter.pad(5) + " - subscription id: " + channelSubscriptionId)
if (description) log.ack("Description: ", description)
if (showFunction) log.ack("Function: ", subscription)
counter++
})
})
log("Total number of subscriptions: " + counter)
}
};
// Shortcuts
const publish = kiss.pubsub.publish
const subscribe = kiss.pubsub.subscribe
const unsubscribe = kiss.pubsub.unsubscribe;
Source