Source: winjscontrib.messenger.js

/* 
 * WinJS Contrib v2.1.0.6
 * licensed under MIT license (see http://opensource.org/licenses/MIT)
 * sources available at https://github.com/gleborgne/winjscontrib
 */

var WinJSContrib = WinJSContrib || {};

(function () {

    function Messenger(receiver, sender) {
        var messenger = this;
        messenger.isWorker = receiver.document === undefined;
        messenger._pendings = {};
        messenger._receiver = receiver;
        messenger._sender = sender || receiver;
        messenger._bindedProcessEvent = messenger._processEvent.bind(messenger);

        if (messenger._receiver)
            messenger._receiver.addEventListener('message', messenger._bindedProcessEvent);
    };

    /**
     * @classdesc
     * Wrapper for messaging between main code and iframe or web worker. All returns are wrapped as WinJS.Promise to enable asynchronous scenarios     
     * @class
     * @param {DOMElement} receiver element that will receive messages
     * @param {DOMElement} sender element that will send messages
     */
    WinJSContrib.Messenger = WinJS.Class.mix(Messenger, WinJS.Utilities.eventMixin);

    /**
     * default path for smart worker js file
     */
    WinJSContrib.Messenger.SmartWorkerPath = '/scripts/winjscontrib/winjscontrib.messenger.worker.js';

    /**
     * @classdesc
     * Wrapper for {@link WinJSContrib.Messenger} when using it with a webworker
     * @class
     * @param {string} [path] path to web worker file
     */
    WinJSContrib.Messenger.SmartWorker = function (path) {
        if (window.Worker) {
            var w = new window.Worker(path || WinJSContrib.Messenger.SmartWorkerPath);
            return new WinJSContrib.Messenger(w, w);
        }

        return new WinJSContrib.Messenger(null, null);
    }


    WinJSContrib.Messenger.prototype._send = function (obj) {
        if (this.isWorker) {
            this._sender.postMessage(JSON.stringify(obj));
        }
        else {
            this._sender.postMessage(JSON.stringify(obj), '*');
        }
    };

    /**
     * import script files
     * @param {Array} scriptPaths an array of string paths to js files
     */
    WinJSContrib.Messenger.prototype.importScripts = function (scriptPaths) {
        if (!this._receiver)
            return WinJS.Promise.wrap();

        return this.start('_doImportScripts', scriptPaths);
    }

    WinJSContrib.Messenger.prototype._doImportScripts = function (scriptPaths) {
        return new WinJS.Promise(function (c) {
            if (typeof scriptPaths == 'string') {
                importScripts(scriptPaths);
            } else if (scriptPaths.length) {
                for (var i = 0 ; i < scriptPaths.length ; i++) {
                    importScripts(scriptPaths[i]);
                }
            }
            c();
        });
    }

    /**
     * run the callback in the web worker. The callback is serialized to string so you must pass all variable used inside the function as arguments
     * @param {function} func function callback
     * @param {...Object} args
     * @returns {WinJS.Promise}
     */
    WinJSContrib.Messenger.prototype.execute = function (func) {
        var messenger = this;
        var args = [];
        if (arguments.length > 1) {
            for (var i = 1 ; i < arguments.length; i++) {
                args.push(arguments[i]);
            }
        }

        if (messenger._receiver) {
            var f = 'var messengerFunction=' + func;

            return this.start('_runFunction', { f: f, args: args });
        }
        else {
            return WinJS.Promise.timeout(0).then(function () {
                return func.apply(null, args);
            });
        }
    }

    WinJSContrib.Messenger.prototype._runFunction = function (functionArgs) {
        var messenger = this;
        return new WinJS.Promise(function (c, e) {
            try {
                eval(functionArgs.f);
                var res = messengerFunction.apply(null, functionArgs.args);
                c(res);
            } catch (exception) {
                e(exception);
            }
        });
    }

    WinJSContrib.Messenger.prototype.map = function (object, functions) {
        var messenger = this;
        functions.forEach(function (name) {
            messenger[name] = function () {
                var f = object[name];
                return f.apply(object, arguments);
            }
        });
    }

    /**
     * start an operation within iframe or worker and get a promise for completion
     * @param {string} eventName name of the event/function to call
     * @param {Object} data event/function passed as argument
     * @returns {WinJS.Promise}
     */
    WinJSContrib.Messenger.prototype.start = function (eventName, data, asArgs) {
        var messenger = this;

        if (!messenger._receiver)
            return WinJS.Promise.wrapError('worker not supported');

        var wrapper = {
            id: WinJSContrib.Utils.guid(),
            complete: null, error: null, progress: null, promise: null
        }

        var event = {
            name: eventName,
            id: wrapper.id,
            type: 'run',
            data: data,
            asArgs: asArgs,
            sender: 'WinJSContrib.WinJSContrib.Messenger'
        };

        wrapper.promise = new WinJS.Promise(function (c, e, p) {
            wrapper.complete = c;
            wrapper.error = e;
            wrapper.progress = p;
        }, function oncancel() {
            console.warn('message canceled');
            event.type = 'cancel';
            messenger._send(event);
        });

        messenger._pendings[wrapper.id] = wrapper;
        var cleanUp = function () {
            delete messenger._pendings[wrapper.id];
        }
        wrapper.promise.done(cleanUp, cleanUp);
        messenger._send(event);

        return wrapper.promise;
    };

    WinJSContrib.Messenger.prototype._processEvent = function (arg) {
        var messenger = this;
       
        var details = typeof (arg.data) == 'string' ? JSON.parse(arg.data) : arg.data;
        var name = details.name;
        var data = details.data;
        var asArgs = details.asArgs;

        if (details.id) {
            var wrapper = messenger._pendings[details.id];
            if (wrapper && wrapper[details.type]) {
                wrapper[details.type](data);
                return;
            }

            if (details.type === 'cancel') {
                var current = messenger._pendings[details.id];
                if (current) {
                    current.promise.cancel();
                    delete messenger._pendings[details.id];
                }
            }
            else if (details.type==='run' && name && messenger[name]) {
                try {
                    var p = null;
                    if (asArgs) {
                        p = WinJS.Promise.as(messenger[name].apply(messenger[name], data));
                    } else {
                        p = WinJS.Promise.as(messenger[name](data));
                    }

                   messenger._pendings[details.id] = { promise: p, details: details };

                    p.then(function (arg) {
                        messenger._send({ name: name, id: details.id, type: 'complete', sender: 'WinJSContrib.WinJSContrib.Messenger', data: arg });
                    }, function (arg) {
                        messenger._send({ name: name, id: details.id, type: 'error', sender: 'WinJSContrib.WinJSContrib.Messenger', data: arg });
                    }, function (arg) {
                        messenger._send({ name: name, id: details.id, type: 'progress', sender: 'WinJSContrib.WinJSContrib.Messenger', data: arg });
                    }).then(function () {
                        delete messenger._pendings[details.id];
                    });
                } catch (exception) {
                    delete messenger._pendings[details.id];
                    messenger._send({ name: name, id: details.id, type: 'error', sender: 'WinJSContrib.WinJSContrib.Messenger', data: { description: exception.description, message: exception.message, stack: exception.stack } });
                }

                return;
            }
            else {
                messenger._send({ name: name, id: details.id, type: 'error', sender: 'WinJSContrib.WinJSContrib.Messenger', data: { message: 'callback function not found' } });
            }
        } else {
            if (name && messenger[name]) {
                messenger[name](data);
                return;
            }
            messenger.dispatchEvent(name, data);
        }
    };

    /**
     * release messenger and associated resources (if using webworker, worker is terminated
     */
    WinJSContrib.Messenger.prototype.dispose = function () {
        var messenger = this;
        if (messenger._receiver) {
            if (messenger._receiver.terminate)
                messenger._receiver.terminate();

            messenger._receiver.removeEventListener('message', messenger._bindedProcessEvent);
        }
    };
})();