Viewing File: /usr/local/cpanel/share/libraries/cjt2/src/services/APICatcher.js

/*
# cjt/services/APICatcher.js                      Copyright(c) 2020 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",
    "lodash",
    "cjt/util/locale",
    "cjt/services/onBeforeUnload",
    "cjt/services/APIService",
    "cjt/services/APIFailures",
],
function(angular, _, LOCALE) {
    "use strict";

    /* A service to simplify API interactions by providing default
         * error handling logic. Use this for situations where you’re not
         * all that concerned with how the API reports failure, as long
         * as it reports it *somehow*.
         *
         * Example usage:
         *
         *      var promise = APICatcher.promise( apiCall );
         *
         * The returned promise has a “catch”er registered that will
         * report the failure to the APIFailures service; anything that
         * is registered with that service will then receive a notification.
         * The “growlAPIReporter” decorator includes such registration and
         * is the intended “complement” module for APICatcher; however,
         * if you want some other means of catching unreported failures,
         * it’s as simple as creating a new module that registers with
         * APIFailures.
         */

    var module = angular.module("cjt2.services.apicatcher", [
        "cjt2.services.api",
        "cjt2.services.apifailures",
        "cjt2.services.onBeforeUnload"
    ]);

    // A function to iterate through a batch result and collect
    // error strings.
    function _collectFailureStrings(result, strings) {
        if (!strings) {
            strings = [];
        }

        if (result.is_batch) {
            result.data.forEach( function(d) {
                _collectFailureStrings(d, strings);
            } );
        } else if (result.error) {
            strings.push(result.error);
        }

        return strings;
    }

    module.factory("APICatcher", ["APIService", "APIFailures", "onBeforeUnload", "$q", "$log", function(APIService, APIFailures, onBeforeUnload, $q, $log) {

        var errorPhraseToSuppress;

        // ----------------------------------------------------------------------
        // APIService doesn’t expose the actual reported HTTP error status.
        // So we subclass APIService and install a custom “fail” handler that
        // checks the HTTP status and, if that status indicates that we should
        // suppress the error message, designates that phrase as “the phrase
        // to suppress”.
        //
        // It’s ugly, but the alternative would be to return an object, which
        // would break all handlers of the promise’s rejection case.
        //
        function APIServiceForCatcher() {
            return APIService.apply(this, arguments);
        }
        APIServiceForCatcher.prototype = Object.create(APIService.prototype);

        var baseHttpFailHandler = APIService.prototype.presetDefaultHandlers.fail;
        var customHttpFailHandler = function(xhr, deferred) {
            var mockDeferred = $q.defer();
            mockDeferred.promise.then(
                function(val) {
                    throw "Improper APICatcher success: " + val;
                },
                function(val) {

                    // Suppress display of the error if the failure
                    // is reported as HTTP status “0”.
                    // cf. https://yui.github.io/yui2/docs/yui_2.9.0_full/connection/index.html#failure
                    var shouldSuppress = onBeforeUnload.windowIsUnloading();
                    shouldSuppress = shouldSuppress && (xhr.status === 0);

                    if (shouldSuppress) {
                        errorPhraseToSuppress = val;
                    }

                    deferred.reject(val);
                }
            );

            baseHttpFailHandler.call(this, xhr, mockDeferred);
        };

        var presetDefaultHandlers = _.assign(
            {},
            APIService.prototype.presetDefaultHandlers
        );
        presetDefaultHandlers.fail = customHttpFailHandler;

        _.assign(
            APIServiceForCatcher.prototype,
            {
                presetDefaultHandlers: presetDefaultHandlers,
            }
        );

        // The tests mock APIService.promise, so let’s call into
        // that function rather than just assigning the function as
        // APIServiceForCatcher.promise.
        APIServiceForCatcher.promise = function _promise() {
            return APIService.promise.apply(this, arguments);
        };

        // ----------------------------------------------------------------------

        var MAX_MESSAGES_DISPLAYED = 6;

        function _processResultForMessages(result) {
            var messages = [];
            if (typeof result !== "object") {

                // Assume it's a string
                if (result !== errorPhraseToSuppress) {
                    messages.push({
                        type: "danger",
                        content: result,
                    });
                }
            } else {

                // Response Object
                if (result.error) {
                    _collectFailureStrings(result).forEach( function(str) {
                        messages.push({
                            type: "danger",
                            content: str,
                        });
                    } );
                }

                if (result.warnings && result.warnings.length) {
                    messages.push.apply(
                        messages,
                        result.warnings.map( function(w) {
                            return {
                                type: "warning",
                                content: w,
                            };
                        } )
                    );
                }
            }

            // emit warnings and errors
            if (messages.length) {
                var displayed = messages.slice(0, MAX_MESSAGES_DISPLAYED);

                var notDisplayed = messages.slice(MAX_MESSAGES_DISPLAYED);
                if (notDisplayed.length) {
                    var translateToLog = {
                        warning: "warn",
                        danger: "error",
                    };

                    notDisplayed.forEach( function(msg) {
                        $log[ translateToLog[msg.type] ](msg.content);
                    } );

                    displayed.push({
                        type: "warning",
                        content: "<em>" + LOCALE.maketext("The system suppressed [quant,_1,additional message,additional messages]. Check your browser console for the suppressed [numerate,_1,message,messages].", notDisplayed.length) + "</em>"
                    });
                }

                // UAPI and API2 have already escaped their errors
                // by this point. WHM v1 hasn’t.
                if (!result.messagesAreHtml) {
                    displayed.forEach( function(msg) {
                        msg.content = _.escape(msg.content);
                    } );
                }

                APIFailures.emit(displayed);
            }

            return result;
        }

        function _promiseOrCatch(apiCall) {
            var promise = APIServiceForCatcher.promise(apiCall);
            promise.then(_processResultForMessages, _processResultForMessages);
            return promise;
        }

        return {
            promise: _promiseOrCatch,
        };
    }] );
}
);
Back to Directory File Manager