Viewing File: /usr/local/cpanel/base/frontend/jupiter/passenger/services/sseAPIService.js

/*
 * services/sseApiService.js                            Copyright 2022 cPanel, L.L.C.
 *                                                           All rights reserved.
 * copyright@cpanel.net                                         http://cpanel.net
 * This code is subject to the cPanel license. Unauthorized copying is prohibited
 */

/* global define: false */

define([
    "angular",
    "cjt/core",
    "lodash",
], function(
        angular,
        CJT,
        _
    ) {
    "use strict";

    var app = angular.module("cpanel.applicationManager.sseAPIService", []);

    app.factory("sseAPIService", [
        "$rootScope",
        function(
            $rootScope
        ) {

            /**
             * Format the data based on the configuration requested.
             * Currently only has the option to convert from string to JSON if config.json is true.
             *
             * @param  {Object} config - SSE configuration
             * @param  {String} data
             * @return {Any}
             */
            var _formatData = function(config, data) {
                if (config && config.json && data) {
                    data = JSON.parse(data);
                }
                return data;
            };

            /**
             * Custom event sent when JSON parsing is requested but fails for some reason.
             *
             * @event sse:<eventName>:error
             * @property {Object} data
             *   @property {String} data.error - Error message from the JSON parser.
             *   @property {String} data.data  - Data passed with the message that could not be parsed.
             */

            /**
             * Custom event sent.
             *
             * @event sse:<eventName>
             * @property {Object} data
             */

            /**
             * Send a format error if the parsing fails
             *
             * @param {String} eventName
             * @param {String} exception
             * @param {String} data
             * @fires sse:<evenName>:error
             */
            var _sendFormatErrorEvent = function(eventName, exception, data) {
                $rootScope.$broadcast(
                    "sse:" + eventName + ":error",
                    {
                        data: data,
                        error: exception
                    }
                );
            };

            /**
             * Create a message event handler callback. The callback will annotate the event with
             * a eventName field and will generated an angularjs event with the following name:
             *
             *   sse:<eventName>
             *
             * If a data element is available it will send the data along with the event in the
             * data field.
             *
             *
             * @param  {String} eventName The name of the event.
             * @param  {Object} config configuration
             * @return {Function}
             */
            var makeHandler = function(eventName, config) {
                return function(e) {
                    e.eventName = eventName;
                    var data = e.data;
                    try {
                        data = _formatData(config, data);
                    } catch (exception) {
                        _sendFormatErrorEvent(eventName, exception, data);
                        return;
                    }

                    $rootScope.$broadcast("sse:" + eventName, data);
                };
            };

            /**
             * Fired when the sse process is done.
             *
             * @event sse:done
             */

            /**
             * Fired when sse generated an error.
             *
             * @event sse:error
             */

            /**
             * Fired when the sse is running and the page is being unloaded.
             *
             * @event sse:beforeunload
             */

            /**
             * Connect the specified SSE url fire an angular event for the requested events.
             *
             * @param {String} url - url to connect to the sse event source.
             * @param {Array[String]} [events]  - array of additional events to register.
             * @param {Object}        [config]  - optional configuration options
             *   @param {Boolean} [config.json] - if true, will parse the e.data as json. otherwise, just returns the data to the caller as is.
             * @fires sse:beforeunload, sse:error, sse:done, sse:*
             */
            var connect = function connect(url, events, config) {

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

                var sseConfig = config || {};

                var sse = new EventSource(url);

                // Setup known events
                if (events) {
                    events.forEach(function(event) {
                        sse.addEventListener(event, makeHandler(event, sseConfig));
                    });
                }

                // Setup the error event handler
                sse.onerror = function(e) {
                    $rootScope.$broadcast("sse:error", e);
                };

                // Setup the beforeunload event handler
                window.addEventListener("beforeunload", function(e) {
                    if (sse) {
                        sse.close();
                        sse = null;
                        $rootScope.$broadcast("sse:beforeunload", e);
                    }
                });

                return sse;
            };

            /**
             * Fired when the sse polyfill is loaded when needed. It will fire both
             * when the polyfill is needed and finished loading and when the polyfill
             * is not needed.
             *
             * @event sse:ready
             */

            /**
             * Initialize the SSE resources
             *
             * @param {Function} [ready] Optional callback to call when the sse is ready to run.
             * @fires sse:ready
             */
            var initialize = function initialize(ready) {

                // Microsoft browsers (including Edge)
                // don’t support SSE as of November 2017.
                // https://developer.microsoft.com/en-us/microsoft-edge/platform/status/serversenteventseventsource/
                if (!window.EventSource) {
                    var script = document.createElement("script");
                    script.src = CJT.buildFullPath("libraries/eventsource-polyfill/eventsource.js");
                    script.onload = function() {
                        if (ready) {
                            ready();
                        }
                        $rootScope.$broadcast("sse:ready");
                    };
                    document.body.appendChild(script);
                } else {
                    if (ready) {
                        ready();
                    }
                    $rootScope.$broadcast("sse:ready");
                }
            };

            /**
             * Close the sse connection and clean up the sseApi state.
             *
             * @param {Object} SSE object
             */
            var close = function(sse) {
                if (sse) {
                    sse.close();
                }
            };

            return {
                initialize: initialize,
                connect: connect,
                close: close
            };
        }
    ]);
});
Back to Directory File Manager