1 var Util = require('util'); 2 var Client = require('./Client'); 3 var Constants = require('./Constants'); 4 5 /** 6 * Creates an instance of StreamClient. 7 * 8 * @constructor 9 * @this {StreamClient} 10 * @param {String} consumerKey OAuth consumer key. 11 * @param {String} consumerSecret OAuth consumer secret. 12 * @param {String} token OAuth token. 13 * @param {String} tokenSecret OAuth token secret. 14 */ 15 var StreamClient = function(consumerKey, consumerSecret, token, tokenSecret) 16 { 17 Client.call(this, consumerKey, consumerSecret, token, tokenSecret); 18 19 this._apiBaseUrlString = Constants.StreamApiBaseURLString; 20 this._apiVersion = Constants.StreamApiVersion; 21 }; 22 23 Util.inherits(StreamClient, Client); 24 25 /** 26 * Returns whether or not the stream client is currently running. 27 * 28 * @this {StreamClient} 29 * @return true if the StreamClient is running; false otherwise. 30 */ 31 StreamClient.prototype.isRunning = function() 32 { 33 for (var connection in this._connections) 34 { 35 return true; 36 } 37 38 return false; 39 } 40 41 /** 42 * Creates an asynchronous connection to the Twitter Stream API and begins 43 * listening for public statuses that match one or more filter predicates. 44 * 45 * Listeners should be attached to the <code>StreamClient</code> instance in 46 * order to respond to events: 47 * 48 * <pre> 49 * // Request to remove geolocation information from a status 50 * twitterStreamClient.on('deleteLocation', function(data) { 51 * console.log(data); 52 * }); 53 * 54 * // Status deletion request 55 * twitterStreamClient.on('deleteTweet', function(data) { 56 * console.log(data); 57 * }); 58 * 59 * // Connection to the stream has been closed 60 * twitterStreamClient.on('end', function() { 61 * console.log('Connection closed.'); 62 * }); 63 * 64 * // An error has occurred 65 * twitterStreamClient.on('error', function(error) { 66 * console.log('Error: ' + error.code ? error.code + ' ' + error.message : error.message); 67 * }); 68 * 69 * // A retweet has been received 70 * twitterStreamClient.on('retweet', function(retweet) { 71 * console.log(retweet); 72 * }); 73 * 74 * // A tweet has been received 75 * twitterStreamClient.on('tweet', function(tweet) { 76 * console.log(tweet); 77 * }); 78 * </pre> 79 * 80 * See <a href="https://dev.twitter.com/docs/streaming-api/concepts">Twitter Streaming API Concepts</a> 81 * for information on the structure of the JSON responses returned from the Twitter Streaming API. 82 * 83 * @this {StreamClient} 84 * @param {Array} keywords A set of keywords to track. 85 * @param {Array} locations A set of one or more latitude/longitude pairs defining geofences to track. 86 * @param {Array} users A set of users to track. 87 * @param {integer} count Number of previous statuses to deliver before transitioning to live stream delivery. 88 */ 89 StreamClient.prototype.start = function(keywords, locations, users, count) 90 { 91 if (this.isRunning() === true) 92 { 93 throw new Error('StreamClient is currently running.'); 94 } 95 96 var parameters = {}; 97 98 if (keywords !== undefined && keywords !== null) 99 { 100 if (keywords instanceof Array) 101 { 102 parameters['track'] = keywords.join(','); 103 } 104 else 105 { 106 throw new Error('Expected Array object.'); 107 } 108 } 109 110 if (locations !== undefined && locations !== null) 111 { 112 if (locations instanceof Array) 113 { 114 parameters['locations'] = locations.join(','); 115 } 116 else 117 { 118 throw new Error('Expected Array object.'); 119 } 120 } 121 122 if (users !== undefined && users !== null) 123 { 124 if (users instanceof Array) 125 { 126 parameters['follow'] = users.join(','); 127 } 128 else 129 { 130 throw new Error('Expected Array object.'); 131 } 132 } 133 134 if (count !== undefined && count !== null) 135 { 136 if (isNaN(count) === false) 137 { 138 parameters['count'] = count; 139 } 140 else 141 { 142 throw new Error('Expected integer.'); 143 } 144 } 145 146 this._createPostRequest('statuses/filter', 'json', parameters); 147 }; 148 149 /** 150 * Disconnects from the Twitter Streaming API. 151 * 152 * @this {StreamClient} 153 */ 154 StreamClient.prototype.stop = function() 155 { 156 for (var connection in this._connections) 157 { 158 connection.end(); 159 } 160 }; 161 162 /** 163 * Handles the ending of a request. 164 * 165 * @private 166 * @this {StreamClient} 167 * @param {http.ClientRequest} aRequest The request object. 168 */ 169 StreamClient.prototype._requestDidClose = function(aRequest) 170 { 171 Client.prototype._requestDidClose.call(this, aRequest); 172 173 this.emit('close'); 174 }; 175 176 /** 177 * Handles the closing of a request. 178 * 179 * @private 180 * @this {StreamClient} 181 * @param {http.ClientRequest} aRequest The request object. 182 */ 183 StreamClient.prototype._requestDidEnd = function(aRequest) 184 { 185 Client.prototype._requestDidEnd.call(this, aRequest); 186 187 this.emit('end'); 188 }; 189 190 /** 191 * Handles the failure of a request. 192 * 193 * @private 194 * @this {StreamClient} 195 * @param {http.ClientRequest} aRequest The request object. 196 * @param {Error} aError An Error object. 197 */ 198 StreamClient.prototype._requestDidFailWithError = function(aRequest, aError) 199 { 200 Client.prototype._requestDidFailWithError.call(this, aRequest, aError); 201 202 this.emit('error', aError); 203 }; 204 205 /** 206 * Handles data received from the Twitter stream. 207 * 208 * @private 209 * @this {StreamClient} 210 * @param {http.ClientRequest} aRequest The request object. 211 * @param {Buffer} aData 212 */ 213 StreamClient.prototype._requestDidReceiveData = function(aRequest, aData) 214 { 215 var connection = this._connectionForKey(aRequest.hash); 216 if (connection === undefined) return; 217 218 connection['data'] = connection['data'] + aData.toString('utf8'); 219 220 var index = -1; 221 while ((index = connection['data'].indexOf(Constants.StreamApiObjectTerminator)) !== -1) 222 { 223 var jsonString = connection['data'].slice(0, index); 224 connection['data'] = connection['data'].slice(index + Constants.StreamApiObjectTerminator.length); 225 var object = JSON.parse(jsonString); 226 227 if (object.delete !== undefined) 228 { 229 this.emit('deleteTweet', object.delete); 230 } 231 else if (object.scrub_geo !== undefined) 232 { 233 this.emit('deleteLocation', object.scrub_geo); 234 } 235 else if (object.limit !== undefined) 236 { 237 this.emit('limit', object.limit); 238 } 239 else if (object.retweeted_status !== undefined) 240 { 241 this.emit('retweet', object.retweeted_status); 242 } 243 else 244 { 245 this.emit('tweet', object); 246 } 247 } 248 }; 249 250 module.exports = StreamClient; 251