| 'use strict'; | 
|   | 
| var Stream      = require('stream').Stream, | 
|     util        = require('util'), | 
|     driver      = require('websocket-driver'), | 
|     EventTarget = require('./api/event_target'), | 
|     Event       = require('./api/event'); | 
|   | 
| var API = function(options) { | 
|   options = options || {}; | 
|   driver.validateOptions(options, ['headers', 'extensions', 'maxLength', 'ping', 'proxy', 'tls', 'ca']); | 
|   | 
|   this.readable = this.writable = true; | 
|   | 
|   var headers = options.headers; | 
|   if (headers) { | 
|     for (var name in headers) this._driver.setHeader(name, headers[name]); | 
|   } | 
|   | 
|   var extensions = options.extensions; | 
|   if (extensions) { | 
|     [].concat(extensions).forEach(this._driver.addExtension, this._driver); | 
|   } | 
|   | 
|   this._ping          = options.ping; | 
|   this._pingId        = 0; | 
|   this.readyState     = API.CONNECTING; | 
|   this.bufferedAmount = 0; | 
|   this.protocol       = ''; | 
|   this.url            = this._driver.url; | 
|   this.version        = this._driver.version; | 
|   | 
|   var self = this; | 
|   | 
|   this._driver.on('open',    function(e) { self._open() }); | 
|   this._driver.on('message', function(e) { self._receiveMessage(e.data) }); | 
|   this._driver.on('close',   function(e) { self._beginClose(e.reason, e.code) }); | 
|   | 
|   this._driver.on('error', function(error) { | 
|     self._emitError(error.message); | 
|   }); | 
|   this.on('error', function() {}); | 
|   | 
|   this._driver.messages.on('drain', function() { | 
|     self.emit('drain'); | 
|   }); | 
|   | 
|   if (this._ping) | 
|     this._pingTimer = setInterval(function() { | 
|       self._pingId += 1; | 
|       self.ping(self._pingId.toString()); | 
|     }, this._ping * 1000); | 
|   | 
|   this._configureStream(); | 
|   | 
|   if (!this._proxy) { | 
|     this._stream.pipe(this._driver.io); | 
|     this._driver.io.pipe(this._stream); | 
|   } | 
| }; | 
| util.inherits(API, Stream); | 
|   | 
| API.CONNECTING = 0; | 
| API.OPEN       = 1; | 
| API.CLOSING    = 2; | 
| API.CLOSED     = 3; | 
|   | 
| API.CLOSE_TIMEOUT = 30000; | 
|   | 
| var instance = { | 
|   write: function(data) { | 
|     return this.send(data); | 
|   }, | 
|   | 
|   end: function(data) { | 
|     if (data !== undefined) this.send(data); | 
|     this.close(); | 
|   }, | 
|   | 
|   pause: function() { | 
|     return this._driver.messages.pause(); | 
|   }, | 
|   | 
|   resume: function() { | 
|     return this._driver.messages.resume(); | 
|   }, | 
|   | 
|   send: function(data) { | 
|     if (this.readyState > API.OPEN) return false; | 
|     if (!(data instanceof Buffer)) data = String(data); | 
|     return this._driver.messages.write(data); | 
|   }, | 
|   | 
|   ping: function(message, callback) { | 
|     if (this.readyState > API.OPEN) return false; | 
|     return this._driver.ping(message, callback); | 
|   }, | 
|   | 
|   close: function(code, reason) { | 
|     if (code === undefined) code = 1000; | 
|     if (reason === undefined) reason = ''; | 
|   | 
|     if (code !== 1000 && (code < 3000 || code > 4999)) | 
|       throw new Error("Failed to execute 'close' on WebSocket: " + | 
|                       "The code must be either 1000, or between 3000 and 4999. " + | 
|                       code + " is neither."); | 
|   | 
|     if (this.readyState < API.CLOSING) { | 
|       var self = this; | 
|       this._closeTimer = setTimeout(function() { | 
|         self._beginClose('', 1006); | 
|       }, API.CLOSE_TIMEOUT); | 
|     } | 
|   | 
|     if (this.readyState !== API.CLOSED) this.readyState = API.CLOSING; | 
|   | 
|     this._driver.close(reason, code); | 
|   }, | 
|   | 
|   _configureStream: function() { | 
|     var self = this; | 
|   | 
|     this._stream.setTimeout(0); | 
|     this._stream.setNoDelay(true); | 
|   | 
|     ['close', 'end'].forEach(function(event) { | 
|       this._stream.on(event, function() { self._finalizeClose() }); | 
|     }, this); | 
|   | 
|     this._stream.on('error', function(error) { | 
|       self._emitError('Network error: ' + self.url + ': ' + error.message); | 
|       self._finalizeClose(); | 
|     }); | 
|   }, | 
|   | 
|   _open: function() { | 
|     if (this.readyState !== API.CONNECTING) return; | 
|   | 
|     this.readyState = API.OPEN; | 
|     this.protocol = this._driver.protocol || ''; | 
|   | 
|     var event = new Event('open'); | 
|     event.initEvent('open', false, false); | 
|     this.dispatchEvent(event); | 
|   }, | 
|   | 
|   _receiveMessage: function(data) { | 
|     if (this.readyState > API.OPEN) return false; | 
|   | 
|     if (this.readable) this.emit('data', data); | 
|   | 
|     var event = new Event('message', { data: data }); | 
|     event.initEvent('message', false, false); | 
|     this.dispatchEvent(event); | 
|   }, | 
|   | 
|   _emitError: function(message) { | 
|     if (this.readyState >= API.CLOSING) return; | 
|   | 
|     var event = new Event('error', { message: message }); | 
|     event.initEvent('error', false, false); | 
|     this.dispatchEvent(event); | 
|   }, | 
|   | 
|   _beginClose: function(reason, code) { | 
|     if (this.readyState === API.CLOSED) return; | 
|     this.readyState = API.CLOSING; | 
|     this._closeParams = [reason, code]; | 
|   | 
|     if (this._stream) { | 
|       this._stream.destroy(); | 
|       if (!this._stream.readable) this._finalizeClose(); | 
|     } | 
|   }, | 
|   | 
|   _finalizeClose: function() { | 
|     if (this.readyState === API.CLOSED) return; | 
|     this.readyState = API.CLOSED; | 
|   | 
|     if (this._closeTimer) clearTimeout(this._closeTimer); | 
|     if (this._pingTimer) clearInterval(this._pingTimer); | 
|     if (this._stream) this._stream.end(); | 
|   | 
|     if (this.readable) this.emit('end'); | 
|     this.readable = this.writable = false; | 
|   | 
|     var reason = this._closeParams ? this._closeParams[0] : '', | 
|         code   = this._closeParams ? this._closeParams[1] : 1006; | 
|   | 
|     var event = new Event('close', { code: code, reason: reason }); | 
|     event.initEvent('close', false, false); | 
|     this.dispatchEvent(event); | 
|   } | 
| }; | 
|   | 
| for (var method in instance) API.prototype[method] = instance[method]; | 
| for (var key in EventTarget) API.prototype[key] = EventTarget[key]; | 
|   | 
| module.exports = API; |