All Downloads are FREE. Search and download functionalities are using the official Maven repository.

joynr.messaging.channel.LongPollingChannelMessageReceiver.js Maven / Gradle / Ivy

/*jslint es5: true */

/*
 * #%L
 * %%
 * Copyright (C) 2011 - 2015 BMW Car IT GmbH
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */

define(
        "joynr/messaging/channel/LongPollingChannelMessageReceiver",
        [
            "global/Promise",
            "uuid",
            "joynr/messaging/JoynrMessage",
            "JsonParser",
            "joynr/messaging/MessagingQos",
            "joynr/system/DiagnosticTags",
            "joynr/system/LoggerFactory",
            "joynr/util/UtilInternal",
            "joynr/util/LongTimer"
        ],
        function(
                Promise,
                uuid,
                JoynrMessage,
                JsonParser,
                MessagingQos,
                DiagnosticTags,
                LoggerFactory,
                Util,
                LongTimer) {

            /**
             * LongPollingChannelMessageReceiver handles the long poll to the bounce proxy.
             *
             * @name LongPollingChannelMessageReceiver
             * @constructor
             *
             * @param {Object}
             *            settings an object containing required input for LongPollingChannelMessageReceiver
             * @param {String}
             *            settings.bounceProxyUrl an object describing the bounce proxy URL
             * @param {CommunicationModule}
             *            settings.communicationModule the API required by the LongPollingChannelMessageReceiver to perform XMLHTTPRequests
             *            socket communication
             * @param {Object}
             *            settings.channelQos parameters specifying the qos requirements for the channel (creation)
             * @param {Number}
             *            settings.channelQos.creationTimeout_ms max time to create a channel
             * @param {Number}
             *            settings.channelQos.creationRetryDelay_ms retry interval after a failing channel creation
             *
             * @returns the LongPollingChannelMessageReceiver
             */
            function LongPollingChannelMessageReceiver(settings) {
                var storagePrefix = "joynr.channels";
                var persistency = settings.persistency;
                var onReceive;
                var channelOpMessagingQos = new MessagingQos();
                var channelId, channelUrl;
                var communicationModule = settings.communicationModule;
                var log = LoggerFactory.getLogger("joynr.messaging.LongPollingChannelMessageReceiver");
                var bounceProxyChannelBaseUrl = settings.bounceProxyUrl + "channels/";
                var channelCreationTimeout_ms = settings.channelQos && settings.channelQos.creationTimeout_ms ? settings.channelQos.creationTimeout_ms : 1000*60*60*24; // default: 1 day

                var channelCreationRetryDelay_ms = settings.channelQos && settings.channelQos.creationRetryDelay_ms ? settings.channelQos.creationRetryDelay_ms : 1000*30; // default: 30s
                var createChannelTimestamp;
                /**
                 * Retrieve the receiverId for the given channelId
                 *
                 * @name LongPollingChannelMessageReceiver#getReceiverId
                 * @function
                 * @private
                 */
                function getReceiverId(channelId) {
                    var receiverId =
                            persistency.getItem(storagePrefix + "." + channelId + ".receiverId");
                    if (receiverId === undefined || receiverId === null) {
                        receiverId = "tid-" + uuid();
                        persistency.setItem(
                                storagePrefix + "." + channelId + ".receiverId",
                                receiverId);
                    }
                    return receiverId;
                }

                /**
                 * Stop long polling
                 *
                 * @name LongPollingChannelMessageReceiver#stop
                 * @function
                 */
                this.stop = function stop() {
                    communicationModule.atmosphere.unsubscribeUrl(channelUrl);
                };

                /**
                 * Removes the channel with a given channel id
                 *
                 * @name LongPollingChannelMessageReceiver#clear
                 * @function
                 *
                 * @returns {Object} a promise object for async event handling
                 */
                this.clear =
                        function clear() {
                            log.debug("clearing channel with id " + channelId + " and url " + channelUrl);
                            return communicationModule.createXMLHTTPRequest({
                                type : "DELETE",
                                url : channelUrl,
                                timeout : channelOpMessagingQos.ttl
                            }).then(function() {
                                return;
                            }).catch(function(xhr, errorType) {
                                var errorString =
                                        "error while deleting channel on bounce proxy: "
                                            + errorType
                                            + (xhr.statusText ? (", " + xhr.statusText) : "")
                                            + (xhr.status === 0 ? "" : (", HTTP" + xhr.status))
                                            + (xhr.responseText ? (", " + xhr.responseText) : "");
                                log.error(
                                        "error while deleting channel on bounce proxy",
                                        JSON.stringify(DiagnosticTags.forChannel({
                                            channelId : channelId,
                                            channelUrl : channelUrl,
                                            status : xhr.status,
                                            responseText : xhr.responseText
                                        })));
                                throw new Error(errorString);
                            });
                        };

                /**
                 * Start long polling. Side effect: first makes sure that the channel exists on the bounceproxy
                 *
                 * @name LongPollingChannelMessageReceiver#start
                 * @function
                 *
                 * @param {Function}
                 *            onMessageCallback callback used to pass the received messages up the processing chain
                 * @param {Function}
                 *            onError callback used to inform about errors occurred during message recieve
                 */
                this.start =
                        function start(onMessageCallback, onError) {
                            var pollRequest;
                            var receiverId = getReceiverId(channelId);

                            communicationModule.atmosphere.unsubscribeUrl(channelUrl);

                            pollRequest = {
                                url : channelUrl,
                                transport : "long-polling",
                                contentType : "application/json",
                                timeout : 30000,
                                enableXDR : false,
                                enableProtocol : false,
                                readResponseHeaders : false,
                                attachHeadersAsQueryString : false,
                                dropAtmosphereHeaders : true,
                                dropHeaders : false,
                                logLevel : "debug",
                                headers : {
                                    "X-Atmosphere-tracking-id" : receiverId
                                }

                            };

                            // Called whenever one or more new
                            // messages are received
                            // from the bounceproxy.
                            pollRequest.onMessage =
                                    function onMessage(response) {
                                        var detectedTransport = response.transport, data, jsonParser, joynrMessage;

                                        try {
                                            if (response.status === 200) {
                                                data = response.responseBody;

                                                if (data.length > 0) {
                                                    jsonParser = new JsonParser(data);
                                                    while (jsonParser.hasNext) {
                                                        // pass the message on
                                                        joynrMessage = new JoynrMessage(jsonParser.next);

                                                        log
                                                                .info(
                                                                        "received message",
                                                                        JSON
                                                                                .stringify(DiagnosticTags
                                                                                        .forJoynrMessage(joynrMessage)));
                                                        onMessageCallback(joynrMessage);
                                                    }
                                                }
                                            }
                                        } catch (e) {
                                            log.debug("Exception while processing message: "
                                                + e.message);
                                            if (onError) {
                                                onError(e);
                                            }
                                        }
                                    };

                            pollRequest.onError = function(response) {
                                log.debug("Encountered atmosphere error :" + response.error);
                                if (onError) {
                                    onError(response.error);
                                }
                            };

                            communicationModule.atmosphere.subscribe(pollRequest);

                            log.debug("starting to listen on channel with id "
                                + channelId
                                + " and url "
                                + channelUrl);
                        };

                var callCreate = function callCreate(){
                    var createChannelUrl =
                        bounceProxyChannelBaseUrl
                        + "?ccid="
                        + encodeURIComponent(channelId);
                    var receiverId = getReceiverId(channelId);
                    return communicationModule.createXMLHTTPRequest({
                        type : "POST",
                        url : createChannelUrl,
                        headers : {
                            "X-Atmosphere-tracking-id" : receiverId
                        },
                        timeout : channelOpMessagingQos.ttl
                    }).then(function(xhr) {
                        channelUrl = xhr.getResponseHeader("Location");

                        if (!channelUrl) {
                            channelUrl = xhr.responseText;
                        }

                        log.debug("created channel with id "
                                + channelId
                                + " and url "
                                + channelUrl);

                        return channelUrl;
                    });
                };

                function logChannelCreationError(xhr){
                    log.error(
                            "error while creating channel on bounce proxy",
                            {
                                channelId : channelId,
                                channelUrl : channelUrl,
                                status : xhr.status,
                                responseText : xhr.responseText
                            });
                }

                var createInternal = function createInternal(resolve, reject) {
                    callCreate().then(resolve).catch(function(xhr, errorType) {
                        logChannelCreationError(xhr);
                        if (createChannelTimestamp + channelCreationTimeout_ms <= Date.now()) {
                            reject(new Error("Error creating channel"));
                        } else {
                            LongTimer.setTimeout(createInternal, channelCreationRetryDelay_ms, resolve, reject);
                        }
                    });
                };

                /**
                 * Create the given Channel on the bounceproxy
                 *
                 * @name LongPollingChannelMessageReceiver#create
                 * @function
                 *
                 * @param {String}
                 *            channelId the channelId that will be created, will be URI-Component-encoded
                 * @returns {Object} a promise object for async event handling
                 */
                this.create = function create(theChannelId) {
                    if (channelId !== undefined) {
                        throw new Error("LongPollingChannelMessageReceiver.create has already been called for channelId: " + theChannelId);
                    }
                    channelId = theChannelId;
                    createChannelTimestamp = Date.now();
                    return callCreate().catch(function(xhr, errorType) {
                        logChannelCreationError(xhr);
                        if (createChannelTimestamp + channelCreationTimeout_ms <= Date.now()) {
                            throw new Error("Error creating channel");
                        } else {
                            return new Promise(function(resolve, reject) {
                                LongTimer.setTimeout(createInternal, channelCreationRetryDelay_ms, resolve, reject);
                            });
                        }
                    });
                };

                /**
                 * Parse incoming JSON
                 *
                 * @name LongPollingChannelMessageReceiver#parse
                 * @function
                 * @private
                 */
                function parse(data) {
                    if (window.JSON && window.JSON.parse) {
                        try {
                            return window.JSON.parse(data);
                        } catch (err) {
                            return data;
                        }
                    }
                }

            }
            return LongPollingChannelMessageReceiver;
        });




© 2015 - 2025 Weber Informatics LLC | Privacy Policy