<commit_before>//var q = require("q");

var light = (function () {
    var GLOBAL = {};

    GLOBAL.DEFAULT_HANDLE_NAME = "$$default";
    GLOBAL._TEST_OBJECTS_;
    GLOBAL.actors =  { };
    GLOBAL.actorsDef = [];
    GLOBAL.eventSubscribers = {};
    GLOBAL.system = {};
    GLOBAL.utility = {};
    GLOBAL.utility.execSurpressError = function (o, e, context, notificationInfo) {
        _light["event"].notify(e, context, notificationInfo);
        if (typeof o === "function") {
            try { o(e, context, notificationInfo); } catch (ex) {
                console.error("SUPRESSED ERROR : " + ex);
            }
        }
    };
    GLOBAL.utility.tryCatch = function (context, f, success, error) {
        try {
            var result = f();
            GLOBAL.utility.execSurpressError(function () {
                success(result, context);
            }, null, context, "trying-service");
        } catch (e) {
            GLOBAL.utility.execSurpressError(function () {
                error(e, context);
            }, e, context, "service-throws");
        }
    };

    GLOBAL.handles = [];

    var setUpEventSubscriberBase = function (name, id, o) {
        setUpEventSubscriberBase.ref = setUpEventSubscriberBase.ref || 0;
        setUpEventSubscriberBase.ref++;
        GLOBAL.eventSubscribers[id] = GLOBAL.eventSubscribers[id] || {};
        GLOBAL.eventSubscribers[id].sub = GLOBAL.eventSubscribers[id].sub || [];
        GLOBAL.eventSubscribers[id].sub.push({
            service: o,
            ref: setUpEventSubscriberBase.ref
        });
        return setUpEventSubscriberBase.ref;
    };
    var createEventEmitter = function (name, id, f) {
        GLOBAL.eventSubscribers[id] = GLOBAL.eventSubscribers[id] || {};
        GLOBAL.eventSubscribers[id].sub = GLOBAL.eventSubscribers[id].sub || [];
        GLOBAL.eventSubscribers[id].notify = GLOBAL.eventSubscribers[id].notify || function (o, context, notificationType) {
            var _id = id;
            var l = GLOBAL.eventSubscribers[_id].sub.length;
            for (var i = 0; i < l; i++) {
                var item = GLOBAL.eventSubscribers[_id].sub[i];
                var notificationInfo = {
                    index: i,
                    notificationType: notificationType
                };
                f(item, o, context, notificationInfo);
            }
        };
    };
    var setUpNotification = function (name, id) {
        return createEventEmitter(name, id, function (item, o, context, notificationInfo) {
            GLOBAL.utility.tryCatch(context, function () { return item.service(); }, function () { }, function () { GLOBAL.utility.execSurpressError(item.service.error, o, context, notificationInfo); });
        });
    };
    var setUpSystemEventSubscriptionFx = function (name, that, id) {
        that[name] = function (e) {
            setUpEventSubscriberBase(name, id, e);
        };
        createEventEmitter(name, id, function (item, o, context, notificationInfo) {
            if (typeof item === "function") {
                try { item(o, context, notificationInfo) } catch (e) { }
            }
        });
    };
    var setUpSystemEvent = function (that, event, name) {
        setUpSystemEventSubscriptionFx(event, that, name + "." + event);
        that[event].notify = GLOBAL.eventSubscribers[name + "." + event].notify;
    };
    var setUpServiceEvent = function (that, event, name) {
        var id = name + "." + event;
        that[event] = function (o) {
            return setUpEventSubscriberBase(name, id, o);
        };
        that[event].forEachSubscriber = that[event].forEachSubscriber || function (f) {
            var l = GLOBAL.eventSubscribers[id].sub.length;
            for (var i = 0; i < l; i++) {
                var item = GLOBAL.eventSubscribers[id].sub[i];
                f && f(item);
            }
        };
        setUpNotification(name, id);
        that[event].notify = GLOBAL.eventSubscribers[id].notify;
    };

    var getServiceByName = function (serviceName) {
        var item = GLOBAL.actors[serviceName];
        return item;
    };
    function parseJSON(data) {
        return JSON && JSON.parse ? JSON.parse(data) : (new Function("return " + data))();
    }
    /*
     !!!!!!!!!!!!!!!!
    */
    function isArray(o) {
        return Object.prototype.toString.call(o) === '[object Array]';
    }

    var getApplicablehandle_Test = function (context, serviceItem,  definition, serviceName, arg) {
        var testhandleName = GLOBAL._TEST_OBJECTS_ && GLOBAL._TEST_OBJECTS_[serviceName] && GLOBAL._TEST_OBJECTS_[serviceName].handleName;
       // testhandleName = isArray(testhandleName) ? testhandleName : (testhandleName ? [testhandleName] : []);

        var testhandle = GLOBAL._TEST_OBJECTS_ && GLOBAL._TEST_OBJECTS_[serviceName] && GLOBAL._TEST_OBJECTS_[serviceName].handle;

        GLOBAL.system.$$currentContext = {
            handles: GLOBAL.handles,
            definition: definition,
            serviceName: serviceName,
            handleName: undefined,
            arg: arg
        };
        tmpDefinition = testhandle.call(GLOBAL.system, definition);
        return tmpDefinition;
    };

    var getApplicablehandle_RealTest = function (context, serviceItem, handleName, definition, serviceName, arg, testhandle, testhandleName) {
        if (testhandleName) {
            handleName = testhandleName;
        }
       var lastResult;

        var isAMatch = false;
        var length = GLOBAL.handles.length;
        for (var j = 0; j < length; j++) {
            var pipe = GLOBAL.handles[j];
            isAMatch = handleName && (pipe.name === handleName);
            if (isAMatch) {
                GLOBAL.system.$$currentContext = {
                    handles: GLOBAL.handles,
                    definition: definition,
                    serviceName: serviceName,
                    handleName: pipe.name,
                    arg: arg
                };
                tmpDefinition = (testhandle || pipe.definition).call(GLOBAL.system, definition);
                break;
            }
        }

        return tmpDefinition;
    };

    var getApplicablehandle = function (context, serviceItem, handleName, definition, serviceName, arg) {
       //  handleName = isArray(handleName) ? handleName : (handleName ? [handleName] : []);

        var tmpDefinition;
        var testhandleName = GLOBAL._TEST_OBJECTS_ && GLOBAL._TEST_OBJECTS_[serviceName] && GLOBAL._TEST_OBJECTS_[serviceName].handleName;
       // testhandleName = isArray(testhandleName) ? testhandleName : (testhandleName ? [testhandleName] : []);

        var testhandle = GLOBAL._TEST_OBJECTS_ && GLOBAL._TEST_OBJECTS_[serviceName] && GLOBAL._TEST_OBJECTS_[serviceName].handle;
        if (testhandle && !testhandleName) {
            tmpDefinition = getApplicablehandle_Test(context, serviceItem, definition, serviceName, arg);
        }
        else {
            tmpDefinition = getApplicablehandle_RealTest(context, serviceItem, handleName, definition, serviceName, arg, testhandle, testhandleName);
        }
        return tmpDefinition;
    };
    var runSuppliedServiceFunction = function (context, serviceItem, handleName, definition, serviceName, arg) {

        //start testing
        if (GLOBAL._TEST_OBJECTS_ && GLOBAL._TEST_OBJECTS_[serviceName] && GLOBAL._TEST_OBJECTS_[serviceName].service) {
            handleName = GLOBAL._TEST_OBJECTS_[serviceName].handleName || handleName;
            definition = GLOBAL._TEST_OBJECTS_[serviceName].service || definition;
        }

        //end testing
         var returnDefinitionFromHandle = getApplicablehandle(context, serviceItem, handleName, definition, serviceName, arg);

        //expecting function from pipe plugin
         if (typeof returnDefinitionFromHandle !== "function") {
            var message = "Cannot process service or handle '" + serviceName + "' "
            message = message + (returnDefinitionFromHandle ? "'" + handleName + "' service pipe must return a function" : "no matching service pipe  exists ");
            console.error(message);
            throw message;
        }

         return returnDefinitionFromHandle.call(GLOBAL.system, arg, chainService());
    };

    var createServiceDefinitionFromSuppliedFn = function (context, serviceItem, handleName, definition, serviceName) {
        setUpServiceEvent(serviceItem, "before", serviceName);
        setUpServiceEvent(serviceItem, "after", serviceName);
        setUpServiceEvent(serviceItem, "error", serviceName);
        setUpServiceEvent(serviceItem, "success", serviceName);

        return function (arg, callerContext) {
            var result;
            context.callerContext = callerContext;
            GLOBAL.utility.tryCatch(context, function () { return serviceItem["before"].notify(); }, function () { }, function () { });

            GLOBAL.utility.tryCatch(context, function () {
                result = runSuppliedServiceFunction(context, serviceItem, handleName, definition, serviceName, arg);
                return result;
            }, function (o) {
                return serviceItem["success"].notify(o, context, "service-success");
            }, function (o) {
                return serviceItem["error"].notify(o, context, "service-error");
            });

            GLOBAL.utility.tryCatch(context, function () { return serviceItem["after"].notify(); }, function () { }, function () { });
            return result;
        }
    };

    var defineService = function (serviceName, hNamesOrDefinition, definition) {
        if (!definition) {
            definition = hNamesOrDefinition;
            hNamesOrDefinition = GLOBAL.DEFAULT_HANDLE_NAME;
        }

        var context = {
            name: serviceName, step: function (o) {
                _light["event"].notify(serviceName, context, "service-call");
                this.steps.push(o);
            },
            steps: []
        };
        var serviceItem = function (previousOrMostCurrentResultToBePassedToTheNextActor) {

            return serviceItem.redefinition(previousOrMostCurrentResultToBePassedToTheNextActor);
        };
        serviceItem.position = GLOBAL.actorsDef.length + 1;

        serviceItem.redefinition = createServiceDefinitionFromSuppliedFn(context, serviceItem, hNamesOrDefinition, definition, serviceName);

        serviceItem.me = serviceName;
        GLOBAL.actors[serviceName] = serviceItem;
        GLOBAL.actorsDef.push(GLOBAL.actors[serviceName]);
        GLOBAL.system[serviceName] = function (arg) {
            context.step(serviceName);
            return serviceItem.redefinition(arg, context);
        }
    };

    var eachAsync = function (actors, func, cb) {
        for (var actor in actors) {
            func(actor);
        }
        cb();
        //var result = {};
        //var finalTotal = 0;
        //result. total = 0;
        //result.finalTotal = 0;
        //result.hasRun = false;
        //for (var actor in arr) {
        //    result.total++;
        //    (function (a, result) {
        //            setTimeout(function () {
        //                func(a);
        //                if ((result.finalTotal >= result.total) && !result.hasRun) {
        //                    result.hasRun = true;
        //                     cb();
        //                } else {
        //                    result.finalTotal++;
        //                }
        //            }, 0);
        //        })(actor, result);
        //}

    }

    var chainService = function (cb) {
        chainService.totalChain = chainService.totalChain || 0;
        GLOBAL.actors

        var chain = {};
        chain.result = undefined;

        var buildFn = function (actor) {
            chain[actor] = (function (serviceName) {
                return function (arg) {
                    var currentResult;
                    var previousOrMostCurrentResultToBePassedToTheNextActor = arguments.length ? arg : chain.result;
                    chain.result = GLOBAL.actors[serviceName](previousOrMostCurrentResultToBePassedToTheNextActor);
                    return chain;
                };
            })(actor);
        };

        if (cb) {
            //todo use async to speed up things
            eachAsync(GLOBAL.actors, buildFn, function () {
                cb(chain);
            });
        } else {
            for (var actor in GLOBAL.actors) {
                buildFn(actor);
            }
        }

        chain.merge = function (arg) {
            var _arg;

            if (chain.result) {
                for (var attr in chain.result) {
                    _arg = _arg || {};
                    _arg[attr] = chain.result[attr];
                }
            }
            if (arg) {
                for (var attr in arg) {
                    _arg = _arg || {};
                    _arg[attr] = arg[attr];
                }
            }
            chain.result = _arg;

            return chain;
        };

        return chain;
    };

    var _light = function (f) {
        //  typeof f === "function" && f.call(GLOBAL.actors, chainService());

        chainService(function(cs){
         typeof f === "function" && f.call(GLOBAL.actors, cs);
        });

    };

    _light.startService = function ( f) {
        //typeof f === "function" && f.call(GLOBAL.actors, chainService());
        chainService(function (cs) {
            typeof f === "function" && f.call(GLOBAL.actors, cs);
        });
    };

    _light.version = 1;
    setUpSystemEvent(_light, "event", "$system");

    _light.handle = function (serviceName,/* condition,*/ definition) {
        GLOBAL.handles.push({
            name: serviceName,// todo check for unique name
            condition: function () { return true; } ,// condition,
            definition: definition
        });
    }

    _light.service = defineService;

    _light.advance = {
        testService: function (setup, f) {
            GLOBAL._TEST_OBJECTS_ = setup;
            f.call(GLOBAL.actors, chainService());
            GLOBAL._TEST_OBJECTS_ = undefined
        }
    };

    _light.handle(GLOBAL.DEFAULT_HANDLE_NAME, /*function (definition) { return typeof definition === "function"; },*/ function (definition) { return definition; });

    return _light;
})();

// default function handle plugin

//light.handle("dom", function (definition) { return true; }, function (definition) {
//    return function () {
//     document.body.innerHTML=   definition();
//    }
//});

//light.service("setThings", "dom", function () { return "<bold>wooooooooooooo</bold>";  });

if (typeof module !== "undefined" && ('exports' in module)) {
    module.exports = light;
}

if (typeof define === 'function' && define.amd) {
    define('light', [], function () { return light; });
}<commit_msg>input handle name is array<commit_after>//var q = require("q");

var light = (function () {
    var GLOBAL = {};

    GLOBAL.DEFAULT_HANDLE_NAME = "$$default";
    GLOBAL._TEST_OBJECTS_;
    GLOBAL.actors =  { };
    GLOBAL.actorsDef = [];
    GLOBAL.eventSubscribers = {};
    GLOBAL.system = {};
    GLOBAL.utility = {};
    GLOBAL.utility.execSurpressError = function (o, e, context, notificationInfo) {
        _light["event"].notify(e, context, notificationInfo);
        if (typeof o === "function") {
            try { o(e, context, notificationInfo); } catch (ex) {
                console.error("SUPRESSED ERROR : " + ex);
            }
        }
    };
    GLOBAL.utility.tryCatch = function (context, f, success, error) {
        try {
            var result = f();
            GLOBAL.utility.execSurpressError(function () {
                success(result, context);
            }, null, context, "trying-service");
        } catch (e) {
            GLOBAL.utility.execSurpressError(function () {
                error(e, context);
            }, e, context, "service-throws");
        }
    };

    GLOBAL.handles = [];

    var setUpEventSubscriberBase = function (name, id, o) {
        setUpEventSubscriberBase.ref = setUpEventSubscriberBase.ref || 0;
        setUpEventSubscriberBase.ref++;
        GLOBAL.eventSubscribers[id] = GLOBAL.eventSubscribers[id] || {};
        GLOBAL.eventSubscribers[id].sub = GLOBAL.eventSubscribers[id].sub || [];
        GLOBAL.eventSubscribers[id].sub.push({
            service: o,
            ref: setUpEventSubscriberBase.ref
        });
        return setUpEventSubscriberBase.ref;
    };
    var createEventEmitter = function (name, id, f) {
        GLOBAL.eventSubscribers[id] = GLOBAL.eventSubscribers[id] || {};
        GLOBAL.eventSubscribers[id].sub = GLOBAL.eventSubscribers[id].sub || [];
        GLOBAL.eventSubscribers[id].notify = GLOBAL.eventSubscribers[id].notify || function (o, context, notificationType) {
            var _id = id;
            var l = GLOBAL.eventSubscribers[_id].sub.length;
            for (var i = 0; i < l; i++) {
                var item = GLOBAL.eventSubscribers[_id].sub[i];
                var notificationInfo = {
                    index: i,
                    notificationType: notificationType
                };
                f(item, o, context, notificationInfo);
            }
        };
    };
    var setUpNotification = function (name, id) {
        return createEventEmitter(name, id, function (item, o, context, notificationInfo) {
            GLOBAL.utility.tryCatch(context, function () { return item.service(); }, function () { }, function () { GLOBAL.utility.execSurpressError(item.service.error, o, context, notificationInfo); });
        });
    };
    var setUpSystemEventSubscriptionFx = function (name, that, id) {
        that[name] = function (e) {
            setUpEventSubscriberBase(name, id, e);
        };
        createEventEmitter(name, id, function (item, o, context, notificationInfo) {
            if (typeof item === "function") {
                try {
                    item(o, context, notificationInfo)
                } catch (e) { }
            }
        });
    };
    var setUpSystemEvent = function (that, event, name) {
        setUpSystemEventSubscriptionFx(event, that, name + "." + event);
        that[event].notify = GLOBAL.eventSubscribers[name + "." + event].notify;
    };
    var setUpServiceEvent = function (that, event, name) {
        var id = name + "." + event;
        that[event] = function (o) {
            return setUpEventSubscriberBase(name, id, o);
        };
        that[event].forEachSubscriber = that[event].forEachSubscriber || function (f) {
            var l = GLOBAL.eventSubscribers[id].sub.length;
            for (var i = 0; i < l; i++) {
                var item = GLOBAL.eventSubscribers[id].sub[i];
                f && f(item);
            }
        };
        setUpNotification(name, id);
        that[event].notify = GLOBAL.eventSubscribers[id].notify;
    };

    var getServiceByName = function (serviceName) {
        var item = GLOBAL.actors[serviceName];
        return item;
    };
    function parseJSON(data) {
        return JSON && JSON.parse ? JSON.parse(data) : (new Function("return " + data))();
    }
    /*
     !!!!!!!!!!!!!!!!
    */
    function isArray(o) {
        return Object.prototype.toString.call(o) === '[object Array]';
    }

    var getApplicablehandle_Test = function (context, serviceItem,  definition, serviceName, arg) {
        var testhandleName = GLOBAL._TEST_OBJECTS_ && GLOBAL._TEST_OBJECTS_[serviceName] && GLOBAL._TEST_OBJECTS_[serviceName].handleName;
       // testhandleName = isArray(testhandleName) ? testhandleName : (testhandleName ? [testhandleName] : []);

        var testhandle = GLOBAL._TEST_OBJECTS_ && GLOBAL._TEST_OBJECTS_[serviceName] && GLOBAL._TEST_OBJECTS_[serviceName].handle;

        GLOBAL.system.$$currentContext = {
            handles: GLOBAL.handles,
            definition: definition,
            serviceName: serviceName,
            handleName: undefined,
            arg: arg
        };
        tmpDefinition = testhandle.call(GLOBAL.system, definition);
        return tmpDefinition;
    };

    var getApplicablehandle_RealTest = function (context, serviceItem, handleName, definition, serviceName, arg, testhandle, testhandleName) {
        if (testhandleName) {
            handleName = testhandleName;
        }
       var lastResult;

        var isAMatch = false;
        var length = GLOBAL.handles.length;
        for (var j = 0; j < length; j++) {
            var pipe = GLOBAL.handles[j];
            isAMatch = handleName && (pipe.name === handleName);
            if (isAMatch) {
                GLOBAL.system.$$currentContext = {
                    handles: GLOBAL.handles,
                    definition: definition,
                    serviceName: serviceName,
                    handleName: pipe.name,
                    arg: arg
                };
                tmpDefinition = (testhandle || pipe.definition).call(GLOBAL.system, definition);
                break;
            }
        }

        return tmpDefinition;
    };

    var getApplicablehandle = function (context, serviceItem, handleName, definition, serviceName, arg) {
       //  handleName = isArray(handleName) ? handleName : (handleName ? [handleName] : []);

        var tmpDefinition;
        var testhandleName = GLOBAL._TEST_OBJECTS_ && GLOBAL._TEST_OBJECTS_[serviceName] && GLOBAL._TEST_OBJECTS_[serviceName].handleName;
       // testhandleName = isArray(testhandleName) ? testhandleName : (testhandleName ? [testhandleName] : []);

        var testhandle = GLOBAL._TEST_OBJECTS_ && GLOBAL._TEST_OBJECTS_[serviceName] && GLOBAL._TEST_OBJECTS_[serviceName].handle;
        if (testhandle && !testhandleName) {
            tmpDefinition = getApplicablehandle_Test(context, serviceItem, definition, serviceName, arg);
        }
        else {
            tmpDefinition = getApplicablehandle_RealTest(context, serviceItem, handleName, definition, serviceName, arg, testhandle, testhandleName);
        }
        return tmpDefinition;
    };
    var runSuppliedServiceFunction = function (context, serviceItem, handleNames, definition, serviceName, arg) {

        //start testing
        if (GLOBAL._TEST_OBJECTS_ && GLOBAL._TEST_OBJECTS_[serviceName] && GLOBAL._TEST_OBJECTS_[serviceName].service) {
            handleNames = GLOBAL._TEST_OBJECTS_[serviceName].handleNames || handleNames;
            definition = GLOBAL._TEST_OBJECTS_[serviceName].service || definition;
        }
        handleNames=isArray(handleNames)?handleNames:(handleNames?[handleNames]:[]);

        var handleName = handleNames[0];
        //end testing
         var returnDefinitionFromHandle = getApplicablehandle(context, serviceItem, handleName, definition, serviceName, arg);

        //expecting function from pipe plugin
         if (typeof returnDefinitionFromHandle !== "function") {
            var message = "Cannot process service or handle '" + serviceName + "' "
            message = message + (returnDefinitionFromHandle ? "'" + handleName + "' service pipe must return a function" : "no matching service pipe  exists ");
            console.error(message);
            throw message;
        }

         return returnDefinitionFromHandle.call(GLOBAL.system, arg, chainService());
    };

    var createServiceDefinitionFromSuppliedFn = function (context, serviceItem, handleName, definition, serviceName) {
        setUpServiceEvent(serviceItem, "before", serviceName);
        setUpServiceEvent(serviceItem, "after", serviceName);
        setUpServiceEvent(serviceItem, "error", serviceName);
        setUpServiceEvent(serviceItem, "success", serviceName);

        return function (arg, callerContext) {
            var result;
            context.callerContext = callerContext;
            GLOBAL.utility.tryCatch(context, function () { return serviceItem["before"].notify(); }, function () { }, function () { });

            GLOBAL.utility.tryCatch(context, function () {
                result = runSuppliedServiceFunction(context, serviceItem, handleName, definition, serviceName, arg);
                return result;
            }, function (o) {
                return serviceItem["success"].notify(o, context, "service-success");
            }, function (o) {
                return serviceItem["error"].notify(o, context, "service-error");
            });

            GLOBAL.utility.tryCatch(context, function () { return serviceItem["after"].notify(); }, function () { }, function () { });
            return result;
        }
    };

    var defineService = function (serviceName, hNamesOrDefinition, definition) {
        if (!definition) {
            definition = hNamesOrDefinition;
            hNamesOrDefinition = GLOBAL.DEFAULT_HANDLE_NAME;
        }

        var context = {
            name: serviceName, step: function (o) {
                _light["event"].notify(serviceName, context, "service-call");
                this.steps.push(o);
            },
            steps: []
        };
        var serviceItem = function (previousOrMostCurrentResultToBePassedToTheNextActor) {

            return serviceItem.redefinition(previousOrMostCurrentResultToBePassedToTheNextActor);
        };
        serviceItem.position = GLOBAL.actorsDef.length + 1;

        serviceItem.redefinition = createServiceDefinitionFromSuppliedFn(context, serviceItem, hNamesOrDefinition, definition, serviceName);

        serviceItem.me = serviceName;
        GLOBAL.actors[serviceName] = serviceItem;
        GLOBAL.actorsDef.push(GLOBAL.actors[serviceName]);
        GLOBAL.system[serviceName] = function (arg) {
            context.step(serviceName);
            return serviceItem.redefinition(arg, context);
        }
    };

    var eachAsync = function (actors, func, cb) {
        for (var actor in actors) {
            func(actor);
        }
        cb();
        //var result = {};
        //var finalTotal = 0;
        //result. total = 0;
        //result.finalTotal = 0;
        //result.hasRun = false;
        //for (var actor in arr) {
        //    result.total++;
        //    (function (a, result) {
        //            setTimeout(function () {
        //                func(a);
        //                if ((result.finalTotal >= result.total) && !result.hasRun) {
        //                    result.hasRun = true;
        //                     cb();
        //                } else {
        //                    result.finalTotal++;
        //                }
        //            }, 0);
        //        })(actor, result);
        //}

    }

    var chainService = function (cb) {
        chainService.totalChain = chainService.totalChain || 0;
        GLOBAL.actors

        var chain = {};
        chain.result = undefined;

        var buildFn = function (actor) {
            chain[actor] = (function (serviceName) {
                return function (arg) {
                    var currentResult;
                    var previousOrMostCurrentResultToBePassedToTheNextActor = arguments.length ? arg : chain.result;
                    chain.result = GLOBAL.actors[serviceName](previousOrMostCurrentResultToBePassedToTheNextActor);
                    return chain;
                };
            })(actor);
        };

        if (cb) {
            //todo use async to speed up things
            eachAsync(GLOBAL.actors, buildFn, function () {
                cb(chain);
            });
        } else {
            for (var actor in GLOBAL.actors) {
                buildFn(actor);
            }
        }

        chain.merge = function (arg) {
            var _arg;

            if (chain.result) {
                for (var attr in chain.result) {
                    _arg = _arg || {};
                    _arg[attr] = chain.result[attr];
                }
            }
            if (arg) {
                for (var attr in arg) {
                    _arg = _arg || {};
                    _arg[attr] = arg[attr];
                }
            }
            chain.result = _arg;

            return chain;
        };

        return chain;
    };

    var _light = function (f) {
        //  typeof f === "function" && f.call(GLOBAL.actors, chainService());

        chainService(function(cs){
         typeof f === "function" && f.call(GLOBAL.actors, cs);
        });

    };

    _light.startService = function ( f) {
        //typeof f === "function" && f.call(GLOBAL.actors, chainService());
        chainService(function (cs) {
            typeof f === "function" && f.call(GLOBAL.actors, cs);
        });
    };

    _light.version = 1;
    setUpSystemEvent(_light, "event", "$system");

    _light.handle = function (serviceName,/* condition,*/ definition) {
        GLOBAL.handles.push({
            name: serviceName,// todo check for unique name
            condition: function () { return true; } ,// condition,
            definition: definition
        });
    }

    _light.service = defineService;

    _light.advance = {
        testService: function (setup, f) {
            GLOBAL._TEST_OBJECTS_ = setup;
            f.call(GLOBAL.actors, chainService());
            GLOBAL._TEST_OBJECTS_ = undefined
        }
    };

    _light.handle(GLOBAL.DEFAULT_HANDLE_NAME, /*function (definition) { return typeof definition === "function"; },*/ function (definition) { return definition; });

    return _light;
})();

// default function handle plugin

//light.handle("dom", function (definition) { return true; }, function (definition) {
//    return function () {
//     document.body.innerHTML=   definition();
//    }
//});

//light.service("setThings", "dom", function () { return "<bold>wooooooooooooo</bold>";  });

if (typeof module !== "undefined" && ('exports' in module)) {
    module.exports = light;
}

if (typeof define === 'function' && define.amd) {
    define('light', [], function () { return light; });
}<|endoftext|>


<commit_before>'use strict';

const refData = require('../refData');

module.exports = (SuperClass) => {
  /**
   * @class
   * @classdesc adds functions to the location model which can only be accessed if the reference data is used
   * @mixin LocationMix
   */
  return class LocationMix extends SuperClass {
    /**
     * @constructor
     * @param {Object} payload
     */
    constructor(payload) { super(payload); }

    /**
     * @method LocationMix~updateLocation
     * @desc Updates the location wiht a new raw data
     * @param {Object} location the raw location object to be parsed
     * @override
     */
    updateLocation(location) {
      if (location) {
        const refLocation = (refData && Object.keys(location) === ['crs']) ? refData.v3.getLocation(location.crs) : null;
        const loc = (refLocation) ? refLocation : location;

        this._tpl = loc.tpl;
        this._crs = loc.crs;
        this._toc = loc.toc;
        this._locname = loc.locname;
      }
    }

  };
}
<commit_msg>trying external doc<commit_after>'use strict';

const refData = require('../refData');

/**
 * The built in string object.
 * @external module:openraildata/common.Location
 * @see {@link https://github.com/CarbonCollins/openraildata-common-nodejs/blob/master/docs/api.md|Location}
 */

module.exports = (SuperClass) => {
  /**
   * @class
   * @classdesc adds functions to the location model which can only be accessed if the reference data is used
   * @memberof module:openraildata/referencedata
   * @augments module:openraildata/referencedata.Location
   * @extends module:openraildata/common.Location
   * @mixin LocationMix
   */
  return class LocationMix extends SuperClass {
    /**
     * @constructor
     * @param {Object} payload
     */
    constructor(payload) { super(payload); }

    /**
     * @method LocationMix~updateLocation
     * @desc Updates the location wiht a new raw data
     * @param {Object} location the raw location object to be parsed
     * @override
     */
    updateLocation(location) {
      if (location) {
        const refLocation = (refData && Object.keys(location) === ['crs']) ? refData.v3.getLocation(location.crs) : null;
        const loc = (refLocation) ? refLocation : location;

        this._tpl = loc.tpl;
        this._crs = loc.crs;
        this._toc = loc.toc;
        this._locname = loc.locname;
      }
    }

  };
}
<|endoftext|>


<commit_before>'use strict';

const fs = require('fs-extra');
const pathUtils = require('path');
const crypto = require('crypto');
const babel = require('babel-core');
const CoffeeScript = require('coffee-script');
const yaml = require('js-yaml');
const { promisify } = require('util')

const HASH = 'sha';
const TMP_DIR = pathUtils.join(__dirname, '../../build/tmp/');
const BABEL_OPTIONS = {
    presets: ['es2015', 'es2016', 'es2017'],
    filename: '',
    babelrc: false,
    ast: false
};
const COFFEE_SCRIPT_EXTS = ['.coffee', '.litcoffee'];
const YAML_EXTS = ['.yaml', '.yml'];
const REALPATH = fs.realpathSync('.');
const HASH_LENGTH = 40;
const MTIME_DELAY = 1000;

const filesCache = Object.create(null);

const fsReadFile = promisify(fs.readFile);

fs.ensureDirSync(TMP_DIR);

process.on('exit', function () {
    const files = fs.readdirSync(TMP_DIR);
    const maxDate = Date.now() - 1000 * 60 * 60;
    for (var i = 0; i < files.length; i++) {
        const filename = pathUtils.join(TMP_DIR, files[i]);
        const stat = fs.statSync(filename);
        if (stat.atime < maxDate) {
            fs.unlinkSync(filename);
        }
    }
});

function absolutePath(path) {
    if (pathUtils.isAbsolute(path)) {
        return path;
    }
    return pathUtils.join(REALPATH, path);
}

function createHash(string) {
    return crypto.createHash(HASH).update(string).digest('hex');
}

async function readCacheFile(path) {
    const file = await fsReadFile(path, 'utf-8');
    return {
        hash: file.slice(0, HASH_LENGTH),
        code: file.slice(HASH_LENGTH)
    }
}

function readCacheFileSync(path) {
    const file = fs.readFileSync(path, 'utf-8');
    return {
        hash: file.slice(0, HASH_LENGTH),
        code: file.slice(HASH_LENGTH)
    }
}

async function writeCacheFile(path, hash, code) {
    const file = hash + code;
    await promisify(fs.writeFile)(path, file, 'utf-8');
}

function writeCacheFileSync(path, hash, code) {
    const file = hash + code;
    fs.writeFileSync(path, file, 'utf-8');
}

exports.BABEL_OPTIONS = BABEL_OPTIONS;

exports.compileFile = function (file, filename) {
    const extname = pathUtils.extname(filename);
    switch (extname) {
    case '.coffee':
        return CoffeeScript.compile(file);
    case '.litcoffee':
        return CoffeeScript.compile(file, {literate: true});
    case '.js':
        BABEL_OPTIONS.filename = filename;
        return babel.transform(file, BABEL_OPTIONS).code;
    case '.yaml':
    case '.yml': {
        const object = yaml.safeLoad(file, { filename: filename, json: true });
        return JSON.stringify(object);
    }
    case '.nml': {
        const bundle = require('src/nml-parser').bundle({ nml: file, path: filename }).bundle;
        return CoffeeScript.compile(bundle);
    }
    default:
        return file;
    }
};

exports.fileHash = async function (filename) {
    const code = await exports.getFile(filename);
    return createHash(code)
};

exports.getFile = async function (filename) {
    var path = absolutePath(filename);

    // get file from memory cache
    var fileCache = filesCache[path];
    const fileStat = await promisify(fs.stat)(path);
    if (!fileStat.isFile()) {
        throw new TypeError(`Path ${path} is not a file`);
    }
    if (fileCache) {
        if (fileStat.mtime < fileCache.mtime) {
            return fileCache.code;
        }
    }

    // get file from cache
    let code;
    let hash;
    const pathHash = createHash(path);
    const cacheFilename = pathUtils.join(TMP_DIR, pathHash);
    let cacheStat;
    try {
        cacheStat = await promisify(fs.stat)(cacheFilename);
        if (cacheStat.mtime - MTIME_DELAY >= fileStat.mtime) {
            const cache = await readCacheFile(cacheFilename);
            code = cache.code;
            hash = cache.hash;
        }
    }
    catch (error) {}

    // get file
    if (code === undefined) {
        const file = await promisify(fs.readFile)(path, 'utf-8');

        // use cache when both files are the same
        if (cacheStat) {
            const cache = await readCacheFile(cacheFilename);
            if (cache.hash === createHash(file)) {
                code = cache.code;
            }
        }

        if (code === undefined) {
            code = exports.compileFile(file, filename);
            await writeCacheFile(cacheFilename, createHash(file), code);
        }
    }

    // update memory cache
    if (!fileCache) {
        fileCache = filesCache[path] = {mtime: 0, code: ''};
    }

    // some of systems has 1 second precision for file modification time;
    // to prevent using cache for new files, we use current time back by 1 second;
    // if you're getting file multiple times just after change; it won't be optimized
    fileCache.mtime = Date.now() - MTIME_DELAY;

    fileCache.code = code;

    return code;
};

exports.getFileSync = function (filename) {
    var path = absolutePath(filename);

    // get file from memory cache
    var fileCache = filesCache[path];
    const fileStat = fs.statSync(path);
    if (!fileStat.isFile()) {
        throw new TypeError(`Path ${path} is not a file`);
    }
    if (fileCache) {
        if (fileStat.mtime < fileCache.mtime) {
            return fileCache.code;
        }
    }

    // get file from cache
    let code;
    let hash;
    const pathHash = createHash(path);
    const cacheFilename = pathUtils.join(TMP_DIR, pathHash);
    let cacheStat;
    try {
        cacheStat = fs.statSync(cacheFilename);
        if (cacheStat.mtime - MTIME_DELAY >= fileStat.mtime) {
            const cache = readCacheFileSync(cacheFilename);
            code = cache.code;
            hash = cache.hash;
        }
    }
    catch (error) {}

    // get file
    if (code === undefined) {
        const file = fs.readFileSync(path, 'utf-8');

        // use cache when both files are the same
        if (cacheStat) {
            const cache = readCacheFileSync(cacheFilename);
            if (cache.hash === createHash(file)) {
                code = cache.code;
            }
        }

        if (code === undefined) {
            code = exports.compileFile(file, filename);
            writeCacheFileSync(cacheFilename, createHash(file), code);
        }
    }

    // update memory cache
    if (!fileCache) {
        fileCache = filesCache[path] = {mtime: 0, code: ''};
    }

    // some of systems has 1 second precision for file modification time;
    // to prevent using cache for new files, we use current time back by 1 second;
    // if you're getting file multiple times just after change; it won't be optimized
    fileCache.mtime = Date.now() - MTIME_DELAY;

    fileCache.code = code;

    return code;
};

exports.registerFilenameResolver = function () {
    const Module = module.constructor;
    const filenamesCache = Object.create(null);
    const resolveFilename = Module._resolveFilename
    Module._resolveFilename = function (request, parent, isMain) {
        const cache = filenamesCache[request];
        if (cache) {
            return cache;
        } else {
            const filename = resolveFilename(request, parent, isMain);
            if (request[0] !== '.') {
                filenamesCache[request] = filename;
            }
            return filename;
        }
    };

    exports.registerFilenameResolver = () => {};
};

exports.registerCoffeeScript = function () {
    function compile (module, filename) {
        const file = exports.getFileSync(filename);
        return module._compile(file, filename);
    }

    for (var i = 0; i < COFFEE_SCRIPT_EXTS.length; i++) {
        require.extensions[COFFEE_SCRIPT_EXTS[i]] = compile;
    }

    exports.registerCoffeeScript = () => {};
};

exports.registerBabel = function () {
    const _super = require.extensions['.js'];
    require.extensions['.js'] = function (module, filename) {
        if (filename.indexOf('node_modules') >= 0
            && (
                filename.indexOf('node_modules/neft') < 0
                || filename.indexOf('node_modules/neft/node_modules') >= 0
            )) {
            return _super(module, filename);
        }
        const file = exports.getFileSync(filename);
        return module._compile(file, filename);
    };

    exports.registerBabel = () => {};
};

exports.registerYaml = function () {
    function compile (module, filename) {
        const file = exports.getFileSync(filename);
        module.exports = JSON.parse(file);
    }

    for (var i = 0; i < YAML_EXTS.length; i++) {
        require.extensions[YAML_EXTS[i]] = compile;
    }

    exports.registerYaml = () => {};
};

exports.registerTxt = (function () {
    const registeredExtensions = Object.create(null);

    return function (extnames) {
        if (!Array.isArray(extnames)) {
            extnames = ['.txt'];
        }

        function compile (module, filename) {
            module.exports = exports.getFileSync(filename);
        }

        for (var i = 0; i < extnames.length; i++) {
            const extname = extnames[i];
            if (registeredExtensions[extname]) {
                continue;
            }
            registeredExtensions[extname] = true;
            require.extensions[extname] = compile;
        }
    };
}());

exports.registerNml = function () {
    require.extensions['.nml'] = function (module, filename) {
        const file = exports.getFileSync(filename);
        return module._compile(file, filename);
    };

    exports.registerNml = () => {};
};

exports.register = function () {
    exports.registerFilenameResolver();
    exports.registerCoffeeScript();
    exports.registerBabel();
    exports.registerYaml();
    exports.registerTxt();
    exports.registerNml();
};
<commit_msg>sha -> sha256 for random hashes<commit_after>'use strict';

const fs = require('fs-extra');
const pathUtils = require('path');
const crypto = require('crypto');
const babel = require('babel-core');
const CoffeeScript = require('coffee-script');
const yaml = require('js-yaml');
const { promisify } = require('util')

const HASH = 'sha256';
const HASH_LENGTH = 64;
const TMP_DIR = pathUtils.join(__dirname, '../../build/tmp/');
const BABEL_OPTIONS = {
    presets: ['es2015', 'es2016', 'es2017'],
    filename: '',
    babelrc: false,
    ast: false
};
const COFFEE_SCRIPT_EXTS = ['.coffee', '.litcoffee'];
const YAML_EXTS = ['.yaml', '.yml'];
const REALPATH = fs.realpathSync('.');
const MTIME_DELAY = 1000;

const filesCache = Object.create(null);

const fsReadFile = promisify(fs.readFile);

fs.ensureDirSync(TMP_DIR);

process.on('exit', function () {
    const files = fs.readdirSync(TMP_DIR);
    const maxDate = Date.now() - 1000 * 60 * 60;
    for (var i = 0; i < files.length; i++) {
        const filename = pathUtils.join(TMP_DIR, files[i]);
        const stat = fs.statSync(filename);
        if (stat.atime < maxDate) {
            fs.unlinkSync(filename);
        }
    }
});

function absolutePath(path) {
    if (pathUtils.isAbsolute(path)) {
        return path;
    }
    return pathUtils.join(REALPATH, path);
}

function createHash(string) {
    return crypto.createHash(HASH).update(string).digest('hex');
}

async function readCacheFile(path) {
    const file = await fsReadFile(path, 'utf-8');
    return {
        hash: file.slice(0, HASH_LENGTH),
        code: file.slice(HASH_LENGTH)
    }
}

function readCacheFileSync(path) {
    const file = fs.readFileSync(path, 'utf-8');
    return {
        hash: file.slice(0, HASH_LENGTH),
        code: file.slice(HASH_LENGTH)
    }
}

async function writeCacheFile(path, hash, code) {
    const file = hash + code;
    await promisify(fs.writeFile)(path, file, 'utf-8');
}

function writeCacheFileSync(path, hash, code) {
    const file = hash + code;
    fs.writeFileSync(path, file, 'utf-8');
}

exports.BABEL_OPTIONS = BABEL_OPTIONS;

exports.compileFile = function (file, filename) {
    const extname = pathUtils.extname(filename);
    switch (extname) {
    case '.coffee':
        return CoffeeScript.compile(file);
    case '.litcoffee':
        return CoffeeScript.compile(file, {literate: true});
    case '.js':
        BABEL_OPTIONS.filename = filename;
        return babel.transform(file, BABEL_OPTIONS).code;
    case '.yaml':
    case '.yml': {
        const object = yaml.safeLoad(file, { filename: filename, json: true });
        return JSON.stringify(object);
    }
    case '.nml': {
        const bundle = require('src/nml-parser').bundle({ nml: file, path: filename }).bundle;
        return CoffeeScript.compile(bundle);
    }
    default:
        return file;
    }
};

exports.fileHash = async function (filename) {
    const code = await exports.getFile(filename);
    return createHash(code)
};

exports.getFile = async function (filename) {
    var path = absolutePath(filename);

    // get file from memory cache
    var fileCache = filesCache[path];
    const fileStat = await promisify(fs.stat)(path);
    if (!fileStat.isFile()) {
        throw new TypeError(`Path ${path} is not a file`);
    }
    if (fileCache) {
        if (fileStat.mtime < fileCache.mtime) {
            return fileCache.code;
        }
    }

    // get file from cache
    let code;
    let hash;
    const pathHash = createHash(path);
    const cacheFilename = pathUtils.join(TMP_DIR, pathHash);
    let cacheStat;
    try {
        cacheStat = await promisify(fs.stat)(cacheFilename);
        if (cacheStat.mtime - MTIME_DELAY >= fileStat.mtime) {
            const cache = await readCacheFile(cacheFilename);
            code = cache.code;
            hash = cache.hash;
        }
    }
    catch (error) {}

    // get file
    if (code === undefined) {
        const file = await promisify(fs.readFile)(path, 'utf-8');

        // use cache when both files are the same
        if (cacheStat) {
            const cache = await readCacheFile(cacheFilename);
            if (cache.hash === createHash(file)) {
                code = cache.code;
            }
        }

        if (code === undefined) {
            code = exports.compileFile(file, filename);
            await writeCacheFile(cacheFilename, createHash(file), code);
        }
    }

    // update memory cache
    if (!fileCache) {
        fileCache = filesCache[path] = {mtime: 0, code: ''};
    }

    // some of systems has 1 second precision for file modification time;
    // to prevent using cache for new files, we use current time back by 1 second;
    // if you're getting file multiple times just after change; it won't be optimized
    fileCache.mtime = Date.now() - MTIME_DELAY;

    fileCache.code = code;

    return code;
};

exports.getFileSync = function (filename) {
    var path = absolutePath(filename);

    // get file from memory cache
    var fileCache = filesCache[path];
    const fileStat = fs.statSync(path);
    if (!fileStat.isFile()) {
        throw new TypeError(`Path ${path} is not a file`);
    }
    if (fileCache) {
        if (fileStat.mtime < fileCache.mtime) {
            return fileCache.code;
        }
    }

    // get file from cache
    let code;
    let hash;
    const pathHash = createHash(path);
    const cacheFilename = pathUtils.join(TMP_DIR, pathHash);
    let cacheStat;
    try {
        cacheStat = fs.statSync(cacheFilename);
        if (cacheStat.mtime - MTIME_DELAY >= fileStat.mtime) {
            const cache = readCacheFileSync(cacheFilename);
            code = cache.code;
            hash = cache.hash;
        }
    }
    catch (error) {}

    // get file
    if (code === undefined) {
        const file = fs.readFileSync(path, 'utf-8');

        // use cache when both files are the same
        if (cacheStat) {
            const cache = readCacheFileSync(cacheFilename);
            if (cache.hash === createHash(file)) {
                code = cache.code;
            }
        }

        if (code === undefined) {
            code = exports.compileFile(file, filename);
            writeCacheFileSync(cacheFilename, createHash(file), code);
        }
    }

    // update memory cache
    if (!fileCache) {
        fileCache = filesCache[path] = {mtime: 0, code: ''};
    }

    // some of systems has 1 second precision for file modification time;
    // to prevent using cache for new files, we use current time back by 1 second;
    // if you're getting file multiple times just after change; it won't be optimized
    fileCache.mtime = Date.now() - MTIME_DELAY;

    fileCache.code = code;

    return code;
};

exports.registerFilenameResolver = function () {
    const Module = module.constructor;
    const filenamesCache = Object.create(null);
    const resolveFilename = Module._resolveFilename
    Module._resolveFilename = function (request, parent, isMain) {
        const cache = filenamesCache[request];
        if (cache) {
            return cache;
        } else {
            const filename = resolveFilename(request, parent, isMain);
            if (request[0] !== '.') {
                filenamesCache[request] = filename;
            }
            return filename;
        }
    };

    exports.registerFilenameResolver = () => {};
};

exports.registerCoffeeScript = function () {
    function compile (module, filename) {
        const file = exports.getFileSync(filename);
        return module._compile(file, filename);
    }

    for (var i = 0; i < COFFEE_SCRIPT_EXTS.length; i++) {
        require.extensions[COFFEE_SCRIPT_EXTS[i]] = compile;
    }

    exports.registerCoffeeScript = () => {};
};

exports.registerBabel = function () {
    const _super = require.extensions['.js'];
    require.extensions['.js'] = function (module, filename) {
        if (filename.indexOf('node_modules') >= 0
            && (
                filename.indexOf('node_modules/neft') < 0
                || filename.indexOf('node_modules/neft/node_modules') >= 0
            )) {
            return _super(module, filename);
        }
        const file = exports.getFileSync(filename);
        return module._compile(file, filename);
    };

    exports.registerBabel = () => {};
};

exports.registerYaml = function () {
    function compile (module, filename) {
        const file = exports.getFileSync(filename);
        module.exports = JSON.parse(file);
    }

    for (var i = 0; i < YAML_EXTS.length; i++) {
        require.extensions[YAML_EXTS[i]] = compile;
    }

    exports.registerYaml = () => {};
};

exports.registerTxt = (function () {
    const registeredExtensions = Object.create(null);

    return function (extnames) {
        if (!Array.isArray(extnames)) {
            extnames = ['.txt'];
        }

        function compile (module, filename) {
            module.exports = exports.getFileSync(filename);
        }

        for (var i = 0; i < extnames.length; i++) {
            const extname = extnames[i];
            if (registeredExtensions[extname]) {
                continue;
            }
            registeredExtensions[extname] = true;
            require.extensions[extname] = compile;
        }
    };
}());

exports.registerNml = function () {
    require.extensions['.nml'] = function (module, filename) {
        const file = exports.getFileSync(filename);
        return module._compile(file, filename);
    };

    exports.registerNml = () => {};
};

exports.register = function () {
    exports.registerFilenameResolver();
    exports.registerCoffeeScript();
    exports.registerBabel();
    exports.registerYaml();
    exports.registerTxt();
    exports.registerNml();
};
<|endoftext|>


<commit_before>"use strict";
/**
 * Create a notification
 * @constructor
 */
function Notification (payload) {
	this.encoding = "utf8";

	if (payload) {
		this.payload = payload;
		this.compiled = JSON.stringify(payload);
	} else {
		this.payload = {};
		this.compiled = false;
	}

	this.aps = {};
	this.expiry = 0;
	this.priority = 10;

	this.truncateAtWordEnd = false;
}

Notification.prototype = {
	get alert() {
		return this.aps.alert;
	},
	set alert(value) {
		var type = typeof value;
		if (type === "string" || type === "object" || value === undefined) {
			this.aps.alert = value;
		}
	},

	get alertText() {
		if (this.aps.alert) {
			return this.aps.alert.body || this.aps.alert;
		}
		return undefined;
	},

	set alertText(value) {
		if(typeof this.alert !== "object") {
			this.alert = value;
		}
		else {
			this.prepareAlert();
			this.alert.body = value;
		}
	},

	set locKey(value) {
		if(typeof this.aps.alert !== "object") {
			this.aps.alert = {"body": this.aps.alert};
		}
		this.aps.alert["loc-key"] = value;
	},

	set locArgs(value) {
		if(typeof this.aps.alert !== "object") {
			this.aps.alert = {"body": this.aps.alert};
		}
		this.aps.alert["loc-args"] = value;
	},

	set title(value) {
		if(typeof this.aps.alert !== "object") {
			this.aps.alert = {"body": this.aps.alert};
		}
		this.aps.alert["title"] = value;
	},

	set titleLocKey(value) {
		if(typeof this.aps.alert !== "object") {
			this.aps.alert = {"body": this.aps.alert};
		}
		this.aps.alert["title-loc-key"] = value;
	},

	set titleLocArgs(value) {
		if(typeof this.aps.alert !== "object") {
			this.aps.alert = {"body": this.aps.alert};
		}
		this.aps.alert["title-loc-args"] = value;
	},

	set action(value) {
		if(typeof this.aps.alert !== "object") {
			this.aps.alert = {"body": this.aps.alert};
		}
		this.aps.alert["action"] = value;
	},

	set actionLocKey(value) {
		if(typeof this.aps.alert !== "object") {
			this.aps.alert = {"body": this.aps.alert};
		}
		this.aps.alert["action-loc-key"] = value;
	},

	set launchImage(value) {
		if(typeof this.aps.alert !== "object") {
			this.aps.alert = {"body": this.aps.alert};
		}
		this.aps.alert["launch-image"] = value;
	},

	get badge() {
		return this.aps.badge;
	},
	set badge(value) {
		if (typeof value === "number" || value === undefined) {
			this.aps.badge = value;
		}
	},

	get sound() {
		return this.aps.sound;
	},
	set sound(value) {
		if (typeof value === "string" || value === undefined) {
			this.aps.sound = value;
		}
	},

	get contentAvailable() {
		if (this.aps["content-available"] === 1) {
			return true;
		}
		return this._contentAvailable;
	},
	set contentAvailable(value) {
		if (value === true) {
			this.aps["content-available"] = 1;
		} else {
			this.aps["content-available"] = undefined;
		}
	},

	get mdm() {
		return this._mdm;
	},
	set mdm(value) {
		this._mdm = value;
	},

	get urlArgs() {
		return this.aps["url-args"];
	},
	set urlArgs(value) {
		if(Array.isArray(value) || value === undefined) {
			this.aps["url-args"] = value;
		}
	},

	get category() {
		return this.aps.category;
	},
	set category(value) {
		if(typeof value === "string" || value === undefined) {
			this.aps.category = value;
		}
	}
};

/**
 * Set the expiry value on the payload
 * @param {Number} [expiry] Timestamp when the notification should expire.
 * @since v1.3.5
 */
Notification.prototype.setExpiry = function (expiry) {
	this.expiry = expiry;
	return this;
};

/**
 * Set the priority
 * @param {Number} [priority=10] Priority value for the notification.
 * @since v1.3.9
 */
Notification.prototype.setPriority = function (priority) {
	this.priority = priority;
	return this;
};

/**
 * Set the "badge" value on the alert object
 * @param {Number} [badge] Badge Value
 * @since v1.3.5
 */
Notification.prototype.setBadge = function (badge) {
	this.badge = badge;
	return this;
};

/**
 * Set the "sound" value on the alert object
 * @param {String} [sound] Sound file name
 * @since v1.3.5
 */
Notification.prototype.setSound = function (sound) {
	this.sound = sound;
	return this;
};

/**
 * Set the alert text for the notification
 * @param {String} alertText The text of the alert message.
 * @see The Payload Documentation
 * @since v1.2.0
 */
Notification.prototype.setAlertText = function (text) {
	this.alertText = text;
	return this;
};

/**
 * Set the alert title for the notification - used with Safari Push Notifications and iOS Push Notifications displayed on Apple Watch
 * @param {String} title The title for the alert.
 * @see The Pushing Notifications in the Notification Programming Guide for Websites
 * @since v1.5.0
 */
Notification.prototype.setTitle = function(title) {
	this.title = title;
	return this;
};

/**
 * Set the alert title-loc-key for the notification - used with iOS Push Notifications displayed on Apple Watch. Please note: The corresponding localization key must be in your host app's (i.e. iPhone app) Localizable.strings file and not inside your WatchKit extension or WatchKit app.
 * @param {String} titleLocKey The localization key for the alert title.
 * @see The Payload Documentation
 * @since XXX
 */
Notification.prototype.setTitleLocKey = function(titleLocKey) {
	this.titleLocKey = titleLocKey;
	return this;
};

/**
 * Set the alert title-loc-args for the notification - used with iOS Push Notifications displayed on Apple Watch
 * @param {String[]} [titleLocArgs] Variable string values to appear in place of the format specifiers in title-loc-key.
 * @see The Payload Documentation
 * @since XXX
 */
Notification.prototype.setTitleLocArgs = function(titleLocArgs) {
	this.titleLocArgs = titleLocArgs;
	return this;
};

/**
 * Set the alert action label for the notification - used with Safari Push Notifications
 * @param {String} alertLabel The label for the alert action button.
 * @see The Pushing Notifications in the Notification Programming Guide for Websites
 * @since v1.5.0
 */
Notification.prototype.setAction = function(action) {
	this.action = action;
	return this;
};

/**
 * Set the action-loc-key property on the alert object
 * @param {String} [key] If a string is specified, displays an alert with two buttons, whose behavior is described in Table 3-1. However, iOS uses the string as a key to get a localized string in the current localization to use for the right button’s title instead of “View”. If the value is null, the system displays an alert with a single OK button that simply dismisses the alert when tapped.
 * @see The Payload Documentation
 * @since v1.2.0
 */
Notification.prototype.setActionLocKey = function (key) {
	this.actionLocKey = key;
	return this;
};

/**
 * Set the loc-key parameter on the alert object
 * @param {String} [key] A key to an alert-message string in a Localizable.strings file for the current localization (which is set by the user’s language preference).
 * @see The Payload Documentation
 * @since v1.2.0
 */
Notification.prototype.setLocKey = function (key) {
	this.locKey = key;
	return this;
};

/**
 * Set the loc-args parameter on the alert object
 * @param {String[]} [args] Variable string values to appear in place of the format specifiers in loc-key.
 * @see The Payload Documentation
 * @since v1.2.0
 */
Notification.prototype.setLocArgs = function (args) {
	this.locArgs = args;
	return this;
};

/**
 * Set the launch-image parameter on the alert object
 * @param {String} [image] The filename of an image file in the application bundle; it may include the extension or omit it.
 * @see The Payload Documentation
 * @since v1.2.0
 */
Notification.prototype.setLaunchImage = function (image) {
	this.launchImage = image;
	return this;
};

/**
 * Set the 'content-available' flag on the payload
 * @param {Boolean} [contentAvailable] Whether the content-available flag should be set or not.
 * @since v1.3.5
 */
Notification.prototype.setContentAvailable = function (contentAvailable) {
	this.contentAvailable = contentAvailable;
	return this;
};

/**
 * Set the 'mdm' flag on the payload
 * @param {Object} [mdm] The mdm property for the payload.
 * @since v1.3.5
 */
Notification.prototype.setMDM = function (mdm) {
	this.mdm = mdm;
	return this;
};

/**
 * Set the urlArgs for the notification
 * @param {Array} [urlArgs] The url args for the endpoint
 * @see The Web Payload Documentation
 * @since v1.4.1
 */
Notification.prototype.setUrlArgs = function (urlArgs) {
	this.urlArgs = urlArgs;
	return this;
};

/**
 * Set the category for the notification
 * @param {String} [category] The category for the push notification action
 */
Notification.prototype.setCategory = function (category) {
	this.category = category;
	return this;
};

/**
 * If an alert object doesn't already exist create it and transfer any existing message into the .body property
 * @private
 * @since v1.2.0
 */
Notification.prototype.prepareAlert = function () {
	var existingValue = this.alert;
	if(typeof existingValue !== "object") {
		this.alert = {};
		if(typeof existingValue === "string") {
			this.alert.body = existingValue;
		}
	}
};

Notification.prototype.headers = function headers() {
	let headers = {};

	if (this.priority !== 10) {
		headers["apns-priority"] = this.priority;
	}

	if (this.id) {
		headers["apns-id"] = this.id;
	}

	if (this.expiry > 0) {
		headers["apns-expiration"] = this.expiry;
	}

	if (this.topic) {
		headers["apns-topic"] = this.topic;
	}

	return headers;
};

/**
 * Compile a notification down to its JSON format. Compilation is final, changes made to the notification after this method is called will not be reflected in further calls.
 * @returns {String} JSON payload for the notification.
 * @since v1.3.0
 */
Notification.prototype.compile = function () {
	if(!this.compiled) {
		this.compiled = JSON.stringify(this);
	}
	return this.compiled;
};

/**
 * @returns {Number} Byte length of the notification payload
 * @since v1.2.0
 */
Notification.prototype.length = function () {
	return Buffer.byteLength(this.compile(), this.encoding || "utf8");
};

/**
 * If the notification payload is too long to send this method will attempt to trim the alert body text.
 * @returns {Number} The number of characters removed from the body text. If a negative value is returned, the text is too short to be trimmed enough.
 * @since v1.2.0
 */
Notification.prototype.trim = require("./trim");

/**
 * @private
 */
Notification.prototype.apsPayload = function() {
	var aps = this.aps;

	return Object.keys(aps).find( key => aps[key] !== undefined ) ? aps : undefined;
};

Notification.prototype.toJSON = function () {
	if (typeof this.mdm === "string") {
		return { "mdm": this.mdm };
	}

	this.payload.aps = this.apsPayload();

	return this.payload;
};

module.exports = Notification;
<commit_msg>Refactor the alert preparation<commit_after>"use strict";
/**
 * Create a notification
 * @constructor
 */
function Notification (payload) {
	this.encoding = "utf8";

	if (payload) {
		this.payload = payload;
		this.compiled = JSON.stringify(payload);
	} else {
		this.payload = {};
		this.compiled = false;
	}

	this.aps = {};
	this.expiry = 0;
	this.priority = 10;

	this.truncateAtWordEnd = false;
}

Notification.prototype = {
	get alert() {
		return this.aps.alert;
	},
	set alert(value) {
		var type = typeof value;
		if (type === "string" || type === "object" || value === undefined) {
			this.aps.alert = value;
		}
	},

	get alertText() {
		if (this.aps.alert) {
			return this.aps.alert.body || this.aps.alert;
		}
		return undefined;
	},

	set alertText(value) {
		if(typeof this.alert !== "object") {
			this.alert = value;
		}
		else {
			this.prepareAlert();
			this.alert.body = value;
		}
	},

	set locKey(value) {
		this.prepareAlert();
		this.aps.alert["loc-key"] = value;
	},

	set locArgs(value) {
		this.prepareAlert();
		this.aps.alert["loc-args"] = value;
	},

	set title(value) {
		this.prepareAlert();
		this.aps.alert["title"] = value;
	},

	set titleLocKey(value) {
		this.prepareAlert();
		this.aps.alert["title-loc-key"] = value;
	},

	set titleLocArgs(value) {
		this.prepareAlert();
		this.aps.alert["title-loc-args"] = value;
	},

	set action(value) {
		this.prepareAlert();
		this.aps.alert["action"] = value;
	},

	set actionLocKey(value) {
		this.prepareAlert();
		this.aps.alert["action-loc-key"] = value;
	},

	set launchImage(value) {
		this.prepareAlert();
		this.aps.alert["launch-image"] = value;
	},

	get badge() {
		return this.aps.badge;
	},
	set badge(value) {
		if (typeof value === "number" || value === undefined) {
			this.aps.badge = value;
		}
	},

	get sound() {
		return this.aps.sound;
	},
	set sound(value) {
		if (typeof value === "string" || value === undefined) {
			this.aps.sound = value;
		}
	},

	get contentAvailable() {
		if (this.aps["content-available"] === 1) {
			return true;
		}
		return this._contentAvailable;
	},
	set contentAvailable(value) {
		if (value === true) {
			this.aps["content-available"] = 1;
		} else {
			this.aps["content-available"] = undefined;
		}
	},

	get mdm() {
		return this._mdm;
	},
	set mdm(value) {
		this._mdm = value;
	},

	get urlArgs() {
		return this.aps["url-args"];
	},
	set urlArgs(value) {
		if(Array.isArray(value) || value === undefined) {
			this.aps["url-args"] = value;
		}
	},

	get category() {
		return this.aps.category;
	},
	set category(value) {
		if(typeof value === "string" || value === undefined) {
			this.aps.category = value;
		}
	}
};

/**
 * Set the expiry value on the payload
 * @param {Number} [expiry] Timestamp when the notification should expire.
 * @since v1.3.5
 */
Notification.prototype.setExpiry = function (expiry) {
	this.expiry = expiry;
	return this;
};

/**
 * Set the priority
 * @param {Number} [priority=10] Priority value for the notification.
 * @since v1.3.9
 */
Notification.prototype.setPriority = function (priority) {
	this.priority = priority;
	return this;
};

/**
 * Set the "badge" value on the alert object
 * @param {Number} [badge] Badge Value
 * @since v1.3.5
 */
Notification.prototype.setBadge = function (badge) {
	this.badge = badge;
	return this;
};

/**
 * Set the "sound" value on the alert object
 * @param {String} [sound] Sound file name
 * @since v1.3.5
 */
Notification.prototype.setSound = function (sound) {
	this.sound = sound;
	return this;
};

/**
 * Set the alert text for the notification
 * @param {String} alertText The text of the alert message.
 * @see The Payload Documentation
 * @since v1.2.0
 */
Notification.prototype.setAlertText = function (text) {
	this.alertText = text;
	return this;
};

/**
 * Set the alert title for the notification - used with Safari Push Notifications and iOS Push Notifications displayed on Apple Watch
 * @param {String} title The title for the alert.
 * @see The Pushing Notifications in the Notification Programming Guide for Websites
 * @since v1.5.0
 */
Notification.prototype.setTitle = function(title) {
	this.title = title;
	return this;
};

/**
 * Set the alert title-loc-key for the notification - used with iOS Push Notifications displayed on Apple Watch. Please note: The corresponding localization key must be in your host app's (i.e. iPhone app) Localizable.strings file and not inside your WatchKit extension or WatchKit app.
 * @param {String} titleLocKey The localization key for the alert title.
 * @see The Payload Documentation
 * @since XXX
 */
Notification.prototype.setTitleLocKey = function(titleLocKey) {
	this.titleLocKey = titleLocKey;
	return this;
};

/**
 * Set the alert title-loc-args for the notification - used with iOS Push Notifications displayed on Apple Watch
 * @param {String[]} [titleLocArgs] Variable string values to appear in place of the format specifiers in title-loc-key.
 * @see The Payload Documentation
 * @since XXX
 */
Notification.prototype.setTitleLocArgs = function(titleLocArgs) {
	this.titleLocArgs = titleLocArgs;
	return this;
};

/**
 * Set the alert action label for the notification - used with Safari Push Notifications
 * @param {String} alertLabel The label for the alert action button.
 * @see The Pushing Notifications in the Notification Programming Guide for Websites
 * @since v1.5.0
 */
Notification.prototype.setAction = function(action) {
	this.action = action;
	return this;
};

/**
 * Set the action-loc-key property on the alert object
 * @param {String} [key] If a string is specified, displays an alert with two buttons, whose behavior is described in Table 3-1. However, iOS uses the string as a key to get a localized string in the current localization to use for the right button’s title instead of “View”. If the value is null, the system displays an alert with a single OK button that simply dismisses the alert when tapped.
 * @see The Payload Documentation
 * @since v1.2.0
 */
Notification.prototype.setActionLocKey = function (key) {
	this.actionLocKey = key;
	return this;
};

/**
 * Set the loc-key parameter on the alert object
 * @param {String} [key] A key to an alert-message string in a Localizable.strings file for the current localization (which is set by the user’s language preference).
 * @see The Payload Documentation
 * @since v1.2.0
 */
Notification.prototype.setLocKey = function (key) {
	this.locKey = key;
	return this;
};

/**
 * Set the loc-args parameter on the alert object
 * @param {String[]} [args] Variable string values to appear in place of the format specifiers in loc-key.
 * @see The Payload Documentation
 * @since v1.2.0
 */
Notification.prototype.setLocArgs = function (args) {
	this.locArgs = args;
	return this;
};

/**
 * Set the launch-image parameter on the alert object
 * @param {String} [image] The filename of an image file in the application bundle; it may include the extension or omit it.
 * @see The Payload Documentation
 * @since v1.2.0
 */
Notification.prototype.setLaunchImage = function (image) {
	this.launchImage = image;
	return this;
};

/**
 * Set the 'content-available' flag on the payload
 * @param {Boolean} [contentAvailable] Whether the content-available flag should be set or not.
 * @since v1.3.5
 */
Notification.prototype.setContentAvailable = function (contentAvailable) {
	this.contentAvailable = contentAvailable;
	return this;
};

/**
 * Set the 'mdm' flag on the payload
 * @param {Object} [mdm] The mdm property for the payload.
 * @since v1.3.5
 */
Notification.prototype.setMDM = function (mdm) {
	this.mdm = mdm;
	return this;
};

/**
 * Set the urlArgs for the notification
 * @param {Array} [urlArgs] The url args for the endpoint
 * @see The Web Payload Documentation
 * @since v1.4.1
 */
Notification.prototype.setUrlArgs = function (urlArgs) {
	this.urlArgs = urlArgs;
	return this;
};

/**
 * Set the category for the notification
 * @param {String} [category] The category for the push notification action
 */
Notification.prototype.setCategory = function (category) {
	this.category = category;
	return this;
};

/**
 * If an alert object doesn't already exist create it and transfer any existing message into the .body property
 * @private
 * @since v1.2.0
 */
Notification.prototype.prepareAlert = function () {
	if(typeof this.aps.alert !== "object") {
		this.aps.alert = {"body": this.aps.alert};
	}
};

Notification.prototype.headers = function headers() {
	let headers = {};

	if (this.priority !== 10) {
		headers["apns-priority"] = this.priority;
	}

	if (this.id) {
		headers["apns-id"] = this.id;
	}

	if (this.expiry > 0) {
		headers["apns-expiration"] = this.expiry;
	}

	if (this.topic) {
		headers["apns-topic"] = this.topic;
	}

	return headers;
};

/**
 * Compile a notification down to its JSON format. Compilation is final, changes made to the notification after this method is called will not be reflected in further calls.
 * @returns {String} JSON payload for the notification.
 * @since v1.3.0
 */
Notification.prototype.compile = function () {
	if(!this.compiled) {
		this.compiled = JSON.stringify(this);
	}
	return this.compiled;
};

/**
 * @returns {Number} Byte length of the notification payload
 * @since v1.2.0
 */
Notification.prototype.length = function () {
	return Buffer.byteLength(this.compile(), this.encoding || "utf8");
};

/**
 * If the notification payload is too long to send this method will attempt to trim the alert body text.
 * @returns {Number} The number of characters removed from the body text. If a negative value is returned, the text is too short to be trimmed enough.
 * @since v1.2.0
 */
Notification.prototype.trim = require("./trim");

/**
 * @private
 */
Notification.prototype.apsPayload = function() {
	var aps = this.aps;

	return Object.keys(aps).find( key => aps[key] !== undefined ) ? aps : undefined;
};

Notification.prototype.toJSON = function () {
	if (typeof this.mdm === "string") {
		return { "mdm": this.mdm };
	}

	this.payload.aps = this.apsPayload();

	return this.payload;
};

module.exports = Notification;
<|endoftext|>