// Copyright 2015 The Closure Library Authors. All Rights Reserved. // // 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. /** * @fileoverview adaptor of XhrStreamReader to the NodeReadableStream interface. */ goog.provide('goog.net.streams.XhrNodeReadableStream'); goog.require('goog.array'); goog.require('goog.log'); goog.require('goog.net.streams.NodeReadableStream'); goog.require('goog.net.streams.XhrStreamReader'); /** * The XhrNodeReadableStream class. * * @param {!goog.net.streams.XhrStreamReader} xhrReader The XhrStreamReader * object that handles the events of the underlying Xhr. * @constructor * @implements {goog.net.streams.NodeReadableStream} * @struct * @final * @package */ goog.net.streams.XhrNodeReadableStream = function(xhrReader) { /** * @const * @private {?goog.log.Logger} the logger. */ this.logger_ = goog.log.getLogger('goog.net.streams.XhrNodeReadableStream'); /** * The xhr reader. * * @private {!goog.net.streams.XhrStreamReader} the xhr reader. */ this.xhrReader_ = xhrReader; this.xhrReader_.setDataHandler(goog.bind(this.onData_, this)); this.xhrReader_.setStatusHandler(goog.bind(this.onStatusChange_, this)); /** * The callback map, keyed by eventTypes. * * @private {!Object>} */ this.callbackMap_ = {}; /** * The callback-once map, keyed by eventTypes. * * @private {!Object>} */ this.callbackOnceMap_ = {}; }; /** * @override */ goog.net.streams.XhrNodeReadableStream.prototype.on = function( eventType, callback) { var callbacks = this.callbackMap_[eventType]; if (!callbacks) { callbacks = []; this.callbackMap_[eventType] = callbacks; } callbacks.push(callback); return this; }; /** * @override */ goog.net.streams.XhrNodeReadableStream.prototype.addListener = function( eventType, callback) { this.on(eventType, callback); return this; }; /** * @override */ goog.net.streams.XhrNodeReadableStream.prototype.removeListener = function( eventType, callback) { var callbacks = this.callbackMap_[eventType]; if (callbacks) { goog.array.remove(callbacks, callback); // keep the empty array } var onceCallbacks = this.callbackOnceMap_[eventType]; if (onceCallbacks) { goog.array.remove(onceCallbacks, callback); } return this; }; /** * @override */ goog.net.streams.XhrNodeReadableStream.prototype.once = function( eventType, callback) { var callbacks = this.callbackOnceMap_[eventType]; if (!callbacks) { callbacks = []; this.callbackOnceMap_[eventType] = callbacks; } callbacks.push(callback); return this; }; /** * Handles any new data from XHR. * * @param {!Array} messages New messages, to be delivered in order * and atomically. * @private */ goog.net.streams.XhrNodeReadableStream.prototype.onData_ = function(messages) { var callbacks = this.callbackMap_[goog.net.streams.NodeReadableStream.EventType.DATA]; if (callbacks) { this.doMessages_(messages, callbacks); } var onceCallbacks = this.callbackOnceMap_[goog.net.streams.NodeReadableStream.EventType.DATA]; if (onceCallbacks) { this.doMessages_(messages, onceCallbacks); } this.callbackOnceMap_[goog.net.streams.NodeReadableStream.EventType.DATA] = []; }; /** * Deliver messages to registered callbacks. * * Exceptions are caught and logged (debug), and ignored otherwise. * * @param {!Array} messages The messages to be delivered * @param {!Array} callbacks The callbacks. * @private */ goog.net.streams.XhrNodeReadableStream.prototype.doMessages_ = function( messages, callbacks) { var self = this; for (var i = 0; i < messages.length; i++) { var message = messages[i]; goog.array.forEach(callbacks, function(callback) { try { callback(message); } catch (ex) { self.handleError_('message-callback exception (ignored) ' + ex); } }); } }; /** * Handles any state changes from XHR. * * @private */ goog.net.streams.XhrNodeReadableStream.prototype.onStatusChange_ = function() { var currentStatus = this.xhrReader_.getStatus(); var Status = goog.net.streams.XhrStreamReader.Status; var EventType = goog.net.streams.NodeReadableStream.EventType; switch (currentStatus) { case Status.ACTIVE: this.doStatus_(EventType.READABLE); break; case Status.BAD_DATA: case Status.HANDLER_EXCEPTION: case Status.NO_DATA: case Status.TIMEOUT: case Status.XHR_ERROR: this.doStatus_(EventType.ERROR); break; case Status.CANCELLED: this.doStatus_(EventType.CLOSE); break; case Status.SUCCESS: this.doStatus_(EventType.END); break; } }; /** * Run status change callbacks. * * @param {string} eventType The event type * @private */ goog.net.streams.XhrNodeReadableStream.prototype.doStatus_ = function( eventType) { var callbacks = this.callbackMap_[eventType]; var self = this; if (callbacks) { goog.array.forEach(callbacks, function(callback) { try { callback(); } catch (ex) { self.handleError_('status-callback exception (ignored) ' + ex); } }); } var onceCallbacks = this.callbackOnceMap_[eventType]; if (onceCallbacks) { goog.array.forEach(onceCallbacks, function(callback) { callback(); }); } this.callbackOnceMap_[eventType] = []; }; /** * Log an error * * @param {string} message The error message * @private */ goog.net.streams.XhrNodeReadableStream.prototype.handleError_ = function( message) { goog.log.error(this.logger_, message); };