{"size": 26666, "ext": "js", "max_stars_repo_path": "notifications/notifications.js", "max_stars_repo_name": "gitana/cloudcms-server", "max_stars_repo_stars_event_min_datetime": "2015-08-11T01:14:43.000Z", "max_stars_repo_stars_event_max_datetime": "2021-03-25T09:08:45.000Z", "max_issues_repo_path": "notifications/notifications.js", "max_issues_repo_name": "gitana/cloudcms-server", "max_issues_repo_issues_event_min_datetime": "2015-11-20T11:07:38.000Z", "max_issues_repo_issues_event_max_datetime": "2021-08-24T13:16:57.000Z", "max_forks_repo_path": "notifications/notifications.js", "max_forks_repo_name": "gitana/cloudcms-server", "max_forks_repo_forks_event_min_datetime": "2015-11-07T14:10:34.000Z", "max_forks_repo_forks_event_max_datetime": "2022-02-06T21:58:06.000Z"}
{"max_stars_count": 18, "max_issues_count": 4, "max_forks_count": 17, "avg_line_length": 40.1596385542, "max_line_length": 160, "alphanum_fraction": 0.3565964149}
var async = require("async");
var cluster = require("cluster");
var logFactory = require("../util/logger");

var logEnabled = true;

var logger = this.logger = logFactory("notifications", { wid: true });

if (typeof(process.env.CLOUDCMS_NOTIFICATIONS_LOGGER_LEVEL) !== "undefined") {
    logger.setLevel(("" + process.env.CLOUDCMS_NOTIFICATIONS_LOGGER_LEVEL).toLowerCase(), true);
}
else {
    logger.setLevel("info");
}

var logInfo = function(text)
{
    if (logEnabled)
    {
        logger.info(text);
    }
};

var determineHost = function(item, obj)
{
    var host = null;

    // if virtual hosts aren't enabled, then host is the standalone host ("local")
    if (!process.configuration.virtualHost || !process.configuration.virtualHost.enabled)
    {
        host = process.env.CLOUDCMS_STANDALONE_HOST;
    }
    else
    {
        // assume host if specified on the item
        host = item.host;

        // if not specified on the item, assume value imputed based on virtual host being active
        if (process.configuration.virtualHost && process.configuration.virtualHost.enabled)
        {
            if (!host && item.tenantDnsSlug)
            {
                host = item.tenantDnsSlug + ".cloudcms.net";
            }
        }

        // if we have an object, maybe we can use thaT?
        if (!host && obj)
        {
            host = obj.host;

            if (process.configuration.virtualHost && process.configuration.virtualHost.enabled)
            {
                if (!host && obj.tenantDnsSlug)
                {
                    host = obj.tenantDnsSlug + ".cloudcms.net";
                }
            }
        }
    }

    return host;
};

var handleNotificationMessages = function(items, callback) {

    if (!items) {
        return callback();
    }

    // wrap the processing of each item into a series function
    var fns = [];
    for (var i = 0; i < items.length; i++)
    {
        var fn = function(item, i) {
            return function(done) {

                //logFn("WORKING ON ITEM: " + i + ", item: " + JSON.stringify(item, null, "  "));

                var operation = item.operation;

                /**
                 * @deprecated support for "invalidate_object"
                 * This is left in to support legacy installations of the API that are still doing things object-by-object.
                 * Newer API versions do bulk operations and use the "invalidate_objects" notification event.
                 *
                 * This will be removed at some point in the future.  Please upgrade to the latest Cloud CMS API.
                 */
                if (operation === "invalidate_object")
                {
                    var type = item.type;

                    if (type === "node")
                    {
                        var ref = item.ref;
                        var nodeId = item.id;
                        var branchId = item.branchId;
                        var repositoryId = item.repositoryId;

                        // TEMP: some legacy support to aid in transition
                        if (!repositoryId || !branchId || !nodeId)
                        {
                            var identifier = ref.substring(ref.indexOf("://") + 3);
                            var parts = identifier.split("/").reverse();

                            nodeId = parts[0];
                            branchId = parts[1];
                            repositoryId = parts[2];
                        }

                        var host = determineHost(item);

                        var paths = item.paths || {};

                        // broadcast invalidation
                        process.broadcast.publish("node_invalidation", {
                            "ref": ref,
                            "nodeId": nodeId,
                            "branchId": branchId,
                            "repositoryId": repositoryId,
                            "isMasterBranch": item.isMasterBranch,
                            "host": host,
                            "paths": paths
                        }, function(err) {
                            return done(err);
                        });
                    }
                    else
                    {
                        return done();
                    }
                }
                else if (operation === "invalidate_objects")
                {
                    var invalidations = item.invalidations;
                    if (invalidations && invalidations.length > 0)
                    {
                        var z_fns = [];
                        for (var z = 0; z < invalidations.length; z++)
                        {
                            var z_fn = function(item, obj, z) {
                                return function(z_done) {

                                    var host = determineHost(item, obj);

                                    var type = obj.type;

                                    if (type === "node")
                                    {
                                        var ref = obj.ref;
                                        var nodeId = obj.id;
                                        var branchId = obj.branchId;
                                        var repositoryId = obj.repositoryId;

                                        var paths = obj.paths || {};

                                        // TEMP: some legacy support to aid in transition
                                        if (!repositoryId || !branchId || !nodeId)
                                        {
                                            var identifier = ref.substring(ref.indexOf("://") + 3);
                                            var parts = identifier.split("/").reverse();

                                            nodeId = parts[0];
                                            branchId = parts[1];
                                            repositoryId = parts[2];
                                        }

                                        logInfo("Sending node invalidation for host: " + host);

                                        // broadcast invalidation
                                        process.broadcast.publish("node_invalidation", {
                                            "ref": ref,
                                            "nodeId": nodeId,
                                            "branchId": branchId,
                                            "repositoryId": repositoryId,
                                            "isMasterBranch": obj.isMasterBranch,
                                            "host": host,
                                            "paths": paths
                                        }, z_done);
                                    }
                                    else if (type === "tenant")
                                    {
                                        //var ref = obj.ref;

                                        // broadcast the cleanup_app event for this host
                                        process.broadcast.publish("cleanup_app", {
                                            "host": host
                                        }, z_done);
                                    }
                                    else if (type === "settings")
                                    {
                                        var ref = obj.ref;
                                        var settingsKey = obj.settingsKey;
                                        var settingsScope = obj.settingsScope;

                                        // broadcast invalidation
                                        process.broadcast.publish("settings_invalidation", {
                                            "ref": ref,
                                            "settingsKey": settingsKey,
                                            "settingsScope": settingsScope,
                                            "host": host
                                        }, z_done);
                                    }
                                    else if (type === "application")
                                    {
                                        var ref = obj.ref;
                                        var applicationId = obj.applicationId;
                                        var deploymentKey = obj.deploymentKey;
                                        var stackId = obj.stackId;
                                        var stackMembers = obj.stackMembers;

                                        process.broadcast.publish("application_invalidation", {
                                            "ref": ref,
                                            "applicationId": applicationId,
                                            "deploymentKey": deploymentKey,
                                            "host": host,
                                            "stackId": stackId,
                                            "stackMembers": stackMembers
                                        });

                                        z_done();
                                    }
                                    else if (type === "uiconfig")
                                    {
                                        var ref = obj.ref;
                                        var id = obj.id;

                                        // broadcast invalidation
                                        process.broadcast.publish("uiconfig_invalidation", {
                                            "ref": ref,
                                            "id": id,
                                            "host": host
                                        }, z_done);
                                    }
                                    else if (type === "deploy_app")
                                    {
                                        var body = obj.body;

                                        process.broadcast.publish("deploy_app", {
                                            "body": body
                                        });

                                        z_done();
                                    }
                                    else if (type === "undeploy_app")
                                    {
                                        var body = obj.body;

                                        process.broadcast.publish("undeploy_app", {
                                            "body": body
                                        });

                                        z_done();
                                    }
                                    else if (type === "redeploy_app")
                                    {
                                        var body = obj.body;

                                        process.broadcast.publish("redeploy_app", {
                                            "body": body
                                        });

                                        z_done();
                                    }
                                    else if (type === "start_app")
                                    {
                                        var body = obj.body;

                                        process.broadcast.publish("start_app", {
                                            "body": body
                                        });

                                        z_done();
                                    }
                                    else if (type === "stop_app")
                                    {
                                        var body = obj.body;

                                        process.broadcast.publish("stop_app", {
                                            "body": body
                                        });

                                        z_done();
                                    }
                                    else if (type === "restart_app")
                                    {
                                        var body = obj.body;

                                        process.broadcast.publish("restart_app", {
                                            "body": body
                                        });

                                        z_done();
                                    }
                                    else if (type === "cleanup_app")
                                    {
                                        var host = obj.host;

                                        process.broadcast.publish("cleanup_app", {
                                            "host": host
                                        });

                                        z_done();
                                    }
                                    else if (type === "module_deploy")
                                    {
                                        process.broadcast.publish("module_deploy", {
                                            "host": obj.host,
                                            "moduleId": obj.moduleId,
                                            "moduleConfig": obj.moduleConfig
                                        });

                                        z_done();
                                    }
                                    else if (type === "module_undeploy")
                                    {
                                        process.broadcast.publish("module_undeploy", {
                                            "host": obj.host,
                                            "moduleId": obj.moduleId,
                                            "moduleConfig": obj.moduleConfig
                                        });

                                        z_done();
                                    }
                                    else if (type === "module_refresh")
                                    {
                                        process.broadcast.publish("module_refresh", {
                                            "host": obj.host,
                                            "moduleId": obj.moduleId,
                                            "moduleConfig": obj.moduleConfig
                                        });

                                        z_done();
                                    }
                                    else
                                    {
                                        z_done();
                                    }
                                }
                            }(item, invalidations[z], z);
                            z_fns.push(z_fn);
                        }

                        async.series(z_fns, function(err) {
                            return done(err);
                        });
                    }
                    else
                    {
                        return done();
                    }
                }
                else if (operation === "invalidate_application")
                {
                    // TODO: invalidate any cache dependent on application
                    return done();
                }
                else if (operation === "invalidate_application_page_rendition")
                {
                    logInfo("invalidate_application_page_rendition event\n" + JSON.stringify(item,null,2));

                    var deploymentKey = item.deploymentKey;
                    var applicationId = item.applicationId;

                    var repositoryId = item.repositoryId;
                    var branchId = item.branchId;
                    var isMasterBranch = item.isMasterBranch;

                    var host = determineHost(item);

                    // SAFETY CHECK: if no repository and/or branch, just bail
                    if (!repositoryId || !branchId) {
                        logInfo("Missing repositoryId or branchId, skipping WCM page invalidation (1)");
                        return done();
                    }

                    var scope = item.scope;
                    var key = item.key;
                    var pageCacheKey = item.pageCacheKey;

                    var message = {
                        "key": key,
                        "scope": scope,
                        "pageCacheKey": pageCacheKey,
                        "applicationId": applicationId,
                        "deploymentKey": deploymentKey,
                        "host": host,
                        "repositoryId": repositoryId,
                        "branchId": branchId,
                        "isMasterBranch": isMasterBranch
                    };

                    var fragmentCacheKey = item.fragmentCacheKey;
                    if (fragmentCacheKey) {
                        message.fragmentCacheKey = fragmentCacheKey;
                    }

                    // broadcast invalidation
                    process.broadcast.publish("invalidate_page_rendition", message, function(err) {
                        if (err) {
                            logInfo("published invalidate_page_rendition message. err:" + err + "\nmessage: " + JSON.stringify(item,null,2));
                        }
                        return done(err);
                    });
                }
                else if (operation === "invalidate_application_page_renditions")
                {
                    logInfo("invalidate_application_page_renditions event");

                    var invalidations = item.invalidations;
                    if (invalidations && invalidations.length > 0)
                    {
                        var z_fns = [];
                        for (var z = 0; z < invalidations.length; z++)
                        {
                            var z_fn = function(item, obj) {
                                return function(z_done) {

                                    var deploymentKey = obj.deploymentKey;
                                    var applicationId = obj.applicationId;

                                    var repositoryId = obj.repositoryId;
                                    var branchId = obj.branchId;
                                    var isMasterBranch = obj.isMasterBranch;

                                    var host = determineHost(item, obj);

                                    // SAFETY CHECK: if no repository and/or branch, just bail
                                    if (!repositoryId || !branchId) {
                                        logInfo("Missing repositoryId or branchId, skipping WCM page invalidation (2)");
                                        return z_done();
                                    }

                                    var scope = obj.scope;
                                    var key = obj.key;
                                    var pageCacheKey = obj.pageCacheKey;

                                    var message = {
                                        "key": key,
                                        "scope": scope,
                                        "pageCacheKey": pageCacheKey,
                                        "applicationId": applicationId,
                                        "deploymentKey": deploymentKey,
                                        "host": host,
                                        "repositoryId": repositoryId,
                                        "branchId": branchId,
                                        "isMasterBranch": isMasterBranch
                                    };

                                    var fragmentCacheKey = obj.fragmentCacheKey;
                                    if (fragmentCacheKey) {
                                        message.fragmentCacheKey = fragmentCacheKey;
                                    }

                                    // broadcast invalidation
                                    process.broadcast.publish("invalidate_page_rendition", message, function(err) {
                                        if (err) {
                                            logInfo("published invalidate_page_rendition message. err:" + err + "\nmessage: " + JSON.stringify(message,null,2));
                                        }
                                        z_done(err);
                                    });

                                }
                            }(item, invalidations[z]);
                            z_fns.push(z_fn);
                        }

                        async.series(z_fns, function(err) {
                            return done(err);
                        });
                    }
                }
                else if (operation === "invalidate_application_all_page_renditions")
                {
                    var deploymentKey = item.deploymentKey;
                    var applicationId = item.applicationId;
                    var scope = item.scope;

                    var host = determineHost(item);

                    var message = {
                        "applicationId": applicationId,
                        "deploymentKey": deploymentKey,
                        "scope": scope,
                        "host": host
                    };

                    // broadcast invalidation
                    process.broadcast.publish("invalidate_all_page_renditions", message, function(err) {
                        if (err) {
                            logInfo("published invalidate_all_page_renditions message. err:" + err + "\nmessage: " + JSON.stringify(message,null,2));
                        }
                        return done(err);
                    });
                }
                else
                {
                    logInfo("Unknown notification item: " + JSON.stringify(item));

                    // just assume it's something we can't deal with
                    return done({
                        "message": "Unknown notification item: " + item.rawMessage
                    });
                }
            }
        }(items[i], i);
        fns.push(fn);
    }

    // run all of the functions in series
    async.series(fns, function(err) {
        callback(err);
    });
};

var completeRunnerFn = function(provider, printStartMessage)
{
    return runnerFn(provider, printStartMessage);
};

var runnerCount = 0;
var runnerFn = function(provider, printStartMessage)
{
    var wid = "main";
    if (cluster && cluster.worker)
    {
        wid = cluster.worker.id;
    }

    var runner = function(provider, runnerCount, wid, printStartMessage)
    {
        return function() {

            if (printStartMessage)
            {
                logInfo("[" + runnerCount + "] Starting notifications loop");
            }

            provider.process(function(err, items, postHandleCallback) {

                if (err)
                {
                    logInfo("[" + runnerCount + "] Notification Provider error: " + err, err.stack);

                    // start it up again
                    return completeRunnerFn(provider);
                }

                if (!items) {
                    items = [];
                }

                if (items.length === 0)
                {
                    // start it up again
                    return completeRunnerFn(provider, false);
                }

                logInfo("[" + runnerCount + "] Notification Provider found: " + items.length + " notification items");

                handleNotificationMessages(items, function (err) {

                    logInfo("[" + runnerCount + "] Notification Provider handled: " + items.length + " items");

                    postHandleCallback(err, items, function (err, items, deletedItems) {

                        if (err) {
                            logInfo("[" + runnerCount + "] Notification Provider completed - handled: " + items.length + ", deleted: " + deletedItems.length);
                        }

                        // start it up again
                        return completeRunnerFn(provider, true);

                    });
                });
            });
        }  ;
    }(provider, runnerCount++, wid, printStartMessage);

    setTimeout(runner, 500);
};


module.exports = function()
{
    var r = {};

    r.start = function(callback) {

        var config = process.configuration;
        if (!config["notifications"])
        {
            config["notifications"] = {
                "enabled": false,
                "log": true,
                "type": "",
                "configuration": {}
            };
        }

        var notifications = config["notifications"];
        if ("log" in notifications) {
            logEnabled = notifications.log;
        }
        if (typeof(process.env.CLOUDCMS_NOTIFICATIONS_ENABLED) !== "undefined")
        {
            if (!process.env.CLOUDCMS_NOTIFICATIONS_ENABLED || process.env.CLOUDCMS_NOTIFICATIONS_ENABLED === "false")
            {
                notifications.enabled = false;
            }
            else if (process.env.CLOUDCMS_NOTIFICATIONS_ENABLED || process.env.CLOUDCMS_NOTIFICATIONS_ENABLED === "true")
            {
                notifications.enabled = true;
            }
        }

        if (notifications.enabled)
        {
            if (process.env.CLOUDCMS_NOTIFICATIONS_TYPE)
            {
                notifications.type = process.env.CLOUDCMS_NOTIFICATIONS_TYPE;
            }

            if (!notifications.type)
            {
                console.error("Notification.type is not configured");
                return callback();
            }

            var type = notifications.type;
            var configuration = notifications.configuration;

            var provider = require("./providers/" + type);
            provider.start(configuration, function (err) {

                if (err)
                {
                    return callback(err);
                }

                // this starts the "thread" for the provider listener
                runnerFn(provider, true);

                callback();
            });
        }
        else
        {
            callback();
        }
    };

    r.notify = function(items, callback)
    {
        if (!callback) {
            callback = function() { };
        }

        handleNotificationMessages(items, callback);
    };

    return r;
}();
