UNPKG

cordova-chrome-net

Version:
1,180 lines (1,023 loc) 33.7 kB
/*global chrome */ /** * net * === * * The net module provides you with an asynchronous network wrapper. It * contains methods for creating both servers and clients (called streams). * You can include this module with require('chrome-net') */ var EventEmitter = require('events').EventEmitter var inherits = require('inherits') var is = require('core-util-is') var stream = require('stream') var deprecate = require('util').deprecate var timers = require('timers') // Track open servers and sockets to route incoming sockets (via onAccept and onReceive) // to the right handlers. var servers = {} var sockets = {} var listenersAdded = false // if (typeof chrome !== 'undefined') { // chrome.sockets.tcpServer.onAccept.addListener(onAccept) // chrome.sockets.tcpServer.onAcceptError.addListener(onAcceptError) // chrome.sockets.tcp.onReceive.addListener(onReceive) // chrome.sockets.tcp.onReceiveError.addListener(onReceiveError) // } function onAccept (info) { if (info.socketId in servers) { servers[info.socketId]._onAccept(info.clientSocketId) } else { console.error('Unknown server socket id: ' + info.socketId) } } function onAcceptError (info) { if (info.socketId in servers) { servers[info.socketId]._onAcceptError(info.resultCode) } else { console.error('Unknown server socket id: ' + info.socketId) } } function onReceive (info) { if (info.socketId in sockets) { sockets[info.socketId]._onReceive(info.data) } else { console.error('Unknown socket id: ' + info.socketId) } } function onReceiveError (info) { if (info.socketId in sockets) { sockets[info.socketId]._onReceiveError(info.resultCode) } else { if (info.resultCode === -100) return // net::ERR_CONNECTION_CLOSED console.error('Unknown socket id: ' + info.socketId) } } /** * Creates a new TCP server. The connectionListener argument is automatically * set as a listener for the 'connection' event. * * @param {Object} options * @param {function} listener * @return {Server} */ exports.createServer = function (options, listener) { return new Server(options, listener) } /** * net.connect(options, [connectionListener]) * net.createConnection(options, [connectionListener]) * * Constructs a new socket object and opens the socket to the given location. * When the socket is established, the 'connect' event will be emitted. * * For TCP sockets, options argument should be an object which specifies: * * port: Port the client should connect to (Required). * host: Host the client should connect to. Defaults to 'localhost'. * localAddress: Local interface to bind to for network connections. * * =============================================================== * * net.connect(port, [host], [connectListener]) * net.createConnection(port, [host], [connectListener]) * * Creates a TCP connection to port on host. If host is omitted, * 'localhost' will be assumed. The connectListener parameter will be * added as an listener for the 'connect' event. * * @param {Object} options * @param {function} listener * @return {Socket} */ exports.connect = exports.createConnection = function () { var args = normalizeConnectArgs(arguments) var s = new Socket(args[0]) return Socket.prototype.connect.apply(s, args) } inherits(Server, EventEmitter) /** * Class: net.Server * ================= * * This class is used to create a TCP server. * * Event: 'listening' * Emitted when the server has been bound after calling server.listen. * * Event: 'connection' * - Socket object The connection object * Emitted when a new connection is made. socket is an instance of net.Socket. * * Event: 'close' * Emitted when the server closes. Note that if connections exist, this event * is not emitted until all connections are ended. * * Event: 'error' * - Error Object * Emitted when an error occurs. The 'close' event will be called directly * following this event. See example in discussion of server.listen. */ function Server (/* [options], listener */) { var self = this if (!(self instanceof Server)) return new Server(arguments[0], arguments[1]) EventEmitter.call(self) if (!listenersAdded) { if (typeof chrome !== 'undefined') { chrome.sockets.tcpServer.onAccept.addListener(onAccept) chrome.sockets.tcpServer.onAcceptError.addListener(onAcceptError) chrome.sockets.tcp.onReceive.addListener(onReceive) chrome.sockets.tcp.onReceiveError.addListener(onReceiveError) listenersAdded = true } } var options if (is.isFunction(arguments[0])) { options = {} self.on('connection', arguments[0]) } else { options = arguments[0] || {} if (is.isFunction(arguments[1])) { self.on('connection', arguments[1]) } } self._connections = 0 Object.defineProperty(self, 'connections', { get: deprecate(function () { return self._connections }, 'connections property is deprecated. Use getConnections() method'), set: deprecate(function (val) { return (self._connections = val) }, 'connections property is deprecated. Use getConnections() method'), configurable: true, enumerable: false }) self.id = null // a number > 0 self._connecting = false self.allowHalfOpen = options.allowHalfOpen || false self.pauseOnConnect = !!options.pauseOnConnect self._address = null self._host = null self._port = null self._backlog = null } exports.Server = Server Server.prototype._usingSlaves = false // not used /** * server.listen(port, [host], [backlog], [callback]) * * Begin accepting connections on the specified port and host. If the host is * omitted, the server will accept connections directed to any IPv4 address * (INADDR_ANY). A port value of zero will assign a random port. * * Backlog is the maximum length of the queue of pending connections. The * actual length will be determined by your OS through sysctl settings such as * tcp_max_syn_backlog and somaxconn on linux. The default value of this * parameter is 511 (not 512). * * This function is asynchronous. When the server has been bound, 'listening' * event will be emitted. The last parameter callback will be added as an * listener for the 'listening' event. * * @return {Socket} */ Server.prototype.listen = function (/* variable arguments... */) { var self = this var lastArg = arguments[arguments.length - 1] if (is.isFunction(lastArg)) { self.once('listening', lastArg) } var port = toNumber(arguments[0]) var address // The third optional argument is the backlog size. // When the ip is omitted it can be the second argument. var backlog = toNumber(arguments[1]) || toNumber(arguments[2]) || undefined if (is.isObject(arguments[0])) { var h = arguments[0] if (h._handle || h.handle) { throw new Error('handle is not supported in Chrome Apps.') } if (is.isNumber(h.fd) && h.fd >= 0) { throw new Error('fd is not supported in Chrome Apps.') } // The first argument is a configuration object if (h.backlog) { backlog = h.backlog } if (is.isNumber(h.port)) { address = h.host || null port = h.port } else if (h.path && isPipeName(h.path)) { throw new Error('Pipes are not supported in Chrome Apps.') } else { throw new Error('Invalid listen argument: ' + h) } } else if (isPipeName(arguments[0])) { // UNIX socket or Windows pipe. throw new Error('Pipes are not supported in Chrome Apps.') } else if (is.isUndefined(arguments[1]) || is.isFunction(arguments[1]) || is.isNumber(arguments[1])) { // The first argument is the port, no IP given. address = null } else { // The first argument is the port, the second an IP. address = arguments[1] } // now do something with port, address, backlog if (self.id) { self.close() } // If port is invalid or undefined, bind to a random port. self._port = port | 0 if (self._port < 0 || self._port > 65535) { // allow 0 for random port throw new RangeError('port should be >= 0 and < 65536: ' + self._port) } self._host = address var isAny6 = !self._host if (isAny6) { self._host = '::' } self._backlog = is.isNumber(backlog) ? backlog : undefined self._connecting = true chrome.sockets.tcpServer.create(function (createInfo) { if (!self._connecting || self.id) { // ignoreLastError() chrome.sockets.tcpServer.close(createInfo.socketId) return } // if (chrome.runtime.lastError) { // self.emit('error', new Error(chrome.runtime.lastError.message)) // return // } var socketId = self.id = createInfo.socketId servers[self.id] = self function listen () { chrome.sockets.tcpServer.listen(self.id, self._host, self._port, self._backlog, function (result) { // callback may be after close if (self.id !== socketId) { // ignoreLastError() return } if (result !== 0 && isAny6) { // ignoreLastError() self._host = '0.0.0.0' // try IPv4 isAny6 = false return listen() } self._onListen(result) }) } listen() }) return self } Server.prototype._onListen = function (result) { var self = this self._connecting = false if (result === 0) { var idBefore = self.id chrome.sockets.tcpServer.getInfo(self.id, function (info) { if (self.id !== idBefore) { // ignoreLastError() return } // if (chrome.runtime.lastError) { // self._onListen(-2) // net::ERR_FAILED // return // } self._address = { port: info.localPort, family: info.localAddress && info.localAddress.indexOf(':') !== -1 ? 'IPv6' : 'IPv4', address: info.localAddress } self.emit('listening') }) } else { self.emit('error', errnoException(result, 'listen')) chrome.sockets.tcpServer.close(self.id) delete servers[self.id] self.id = null } } Server.prototype._onAccept = function (clientSocketId) { var self = this // Set the `maxConnections` property to reject connections when the server's // connection count gets high. if (self.maxConnections && self._connections >= self.maxConnections) { chrome.sockets.tcp.close(clientSocketId) console.warn('Rejected connection - hit `maxConnections` limit') return } self._connections += 1 var acceptedSocket = new Socket({ server: self, id: clientSocketId, allowHalfOpen: self.allowHalfOpen, pauseOnCreate: self.pauseOnConnect }) acceptedSocket.on('connect', function () { self.emit('connection', acceptedSocket) }) } Server.prototype._onAcceptError = function (resultCode) { var self = this self.emit('error', errnoException(resultCode, 'accept')) self.close() } /** * Stops the server from accepting new connections and keeps existing * connections. This function is asynchronous, the server is finally closed * when all connections are ended and the server emits a 'close' event. * Optionally, you can pass a callback to listen for the 'close' event. * @param {function} callback */ Server.prototype.close = function (callback) { var self = this if (callback) { if (!self.id) { self.once('close', function () { callback(new Error('Not running')) }) } else { self.once('close', callback) } } if (self.id) { chrome.sockets.tcpServer.close(self.id) delete servers[self.id] self.id = null } self._address = null self._connecting = false self._emitCloseIfDrained() return self } Server.prototype._emitCloseIfDrained = function () { var self = this if (self.id || self._connecting || self._connections) { return } process.nextTick(function () { if (self.id || self._connecting || self._connections) { return } self.emit('close') }) } /** * Returns the bound address, the address family name and port of the socket * as reported by the operating system. Returns an object with three * properties, e.g. { port: 12346, family: 'IPv4', address: '127.0.0.1' } * * @return {Object} information */ Server.prototype.address = function () { return this._address } Server.prototype.unref = function () { // No chrome.socket equivalent } Server.prototype.ref = function () { // No chrome.socket equivalent } /** * Asynchronously get the number of concurrent connections on the server. * Works when sockets were sent to forks. * * Callback should take two arguments err and count. * * @param {function} callback */ Server.prototype.getConnections = function (callback) { var self = this process.nextTick(function () { callback(null, self._connections) }) } inherits(Socket, stream.Duplex) /** * Class: net.Socket * ================= * * This object is an abstraction of a TCP or UNIX socket. net.Socket instances * implement a duplex Stream interface. They can be created by the user and * used as a client (with connect()) or they can be created by Node and passed * to the user through the 'connection' event of a server. * * Construct a new socket object. * * options is an object with the following defaults: * * { fd: null // NO CHROME EQUIVALENT * type: null * allowHalfOpen: false // NO CHROME EQUIVALENT * } * * `type` can only be 'tcp4' (for now). * * Event: 'connect' * Emitted when a socket connection is successfully established. See * connect(). * * Event: 'data' * - Buffer object * Emitted when data is received. The argument data will be a Buffer or * String. Encoding of data is set by socket.setEncoding(). (See the Readable * Stream section for more information.) * * Note that the data will be lost if there is no listener when a Socket * emits a 'data' event. * * Event: 'end' * Emitted when the other end of the socket sends a FIN packet. * * By default (allowHalfOpen == false) the socket will destroy its file * descriptor once it has written out its pending write queue. However, * by setting allowHalfOpen == true the socket will not automatically * end() its side allowing the user to write arbitrary amounts of data, * with the caveat that the user is required to end() their side now. * * Event: 'timeout' * Emitted if the socket times out from inactivity. This is only to notify * that the socket has been idle. The user must manually close the connection. * * See also: socket.setTimeout() * * Event: 'drain' * Emitted when the write buffer becomes empty. Can be used to throttle * uploads. * * See also: the return values of socket.write() * * Event: 'error' * - Error object * Emitted when an error occurs. The 'close' event will be called directly * following this event. * * Event: 'close' * - had_error Boolean true if the socket had a transmission error * Emitted once the socket is fully closed. The argument had_error is a * boolean which says if the socket was closed due to a transmission error. */ function Socket (options) { var self = this if (!(self instanceof Socket)) return new Socket(options) if (!listenersAdded) { if (typeof chrome !== 'undefined') { chrome.sockets.tcpServer.onAccept.addListener(onAccept) chrome.sockets.tcpServer.onAcceptError.addListener(onAcceptError) chrome.sockets.tcp.onReceive.addListener(onReceive) chrome.sockets.tcp.onReceiveError.addListener(onReceiveError) listenersAdded = true } } if (is.isNumber(options)) { options = { fd: options } // Legacy interface. } else if (is.isUndefined(options)) { options = {} } if (options.handle) { throw new Error('handle is not supported in Chrome Apps.') } else if (!is.isUndefined(options.fd)) { throw new Error('fd is not supported in Chrome Apps.') } options.decodeStrings = true options.objectMode = false stream.Duplex.call(self, options) self.destroyed = false self._hadError = false // Used by _http_client.js self.id = null // a number > 0 self._parent = null self._host = null self._port = null self._pendingData = null self.ondata = null self.onend = null self._init() self._reset() // default to *not* allowing half open sockets // Note: this is not possible in Chrome Apps, see https://crbug.com/124952 self.allowHalfOpen = options.allowHalfOpen || false // shut down the socket when we're finished with it. self.on('finish', self.destroy) if (options.server) { self.server = options.server self.id = options.id sockets[self.id] = self if (options.pauseOnCreate) { // stop the handle from reading and pause the stream // (Already paused in Chrome version) self._readableState.flowing = false } // For incoming sockets (from server), it's already connected. self._connecting = true self.writable = true self._onConnect() } } exports.Socket = Socket // called when creating new Socket, or when re-using a closed Socket Socket.prototype._init = function () { var self = this // The amount of received bytes. self.bytesRead = 0 self._bytesDispatched = 0 } // called when creating new Socket, or when closing a Socket Socket.prototype._reset = function () { var self = this self.remoteAddress = self.remotePort = self.localAddress = self.localPort = null self.remoteFamily = 'IPv4' self.readable = self.writable = false self._connecting = false } /** * socket.connect(port, [host], [connectListener]) * socket.connect(options, [connectListener]) * * Opens the connection for a given socket. If port and host are given, then * the socket will be opened as a TCP socket, if host is omitted, localhost * will be assumed. If a path is given, the socket will be opened as a unix * socket to that path. * * Normally this method is not needed, as net.createConnection opens the * socket. Use this only if you are implementing a custom Socket. * * This function is asynchronous. When the 'connect' event is emitted the * socket is established. If there is a problem connecting, the 'connect' * event will not be emitted, the 'error' event will be emitted with the * exception. * * The connectListener parameter will be added as an listener for the * 'connect' event. * * @param {Object} options * @param {function} cb * @return {Socket} this socket (for chaining) */ Socket.prototype.connect = function () { var self = this var args = normalizeConnectArgs(arguments) var options = args[0] var cb = args[1] if (options.path) { throw new Error('Pipes are not supported in Chrome Apps.') } if (self.id) { // already connected, destroy and connect again self.destroy() } if (self.destroyed) { self._readableState.reading = false self._readableState.ended = false self._readableState.endEmitted = false self._writableState.ended = false self._writableState.ending = false self._writableState.finished = false self._writableState.errorEmitted = false self._writableState.length = 0 self.destroyed = false } self._connecting = true self.writable = true self._host = options.host || 'localhost' self._port = Number(options.port) if (self._port < 0 || self._port > 65535 || isNaN(self._port)) { throw new RangeError('port should be >= 0 and < 65536: ' + options.port) } self._init() self._unrefTimer() if (is.isFunction(cb)) { self.once('connect', cb) } chrome.sockets.tcp.create(function (createInfo) { if (!self._connecting || self.id) { // ignoreLastError() chrome.sockets.tcp.close(createInfo.socketId) return } // if (chrome.runtime.lastError) { // self.destroy(new Error(chrome.runtime.lastError.message)) // return // } self.id = createInfo.socketId sockets[self.id] = self chrome.sockets.tcp.setPaused(self.id, true) chrome.sockets.tcp.connect(self.id, self._host, self._port, function (result) { // callback may come after call to destroy if (self.id !== createInfo.socketId) { // ignoreLastError() return } if (result !== 0) { self.destroy(errnoException(result, 'connect')) return } self._unrefTimer() self._onConnect() }) }) return self } Socket.prototype._onConnect = function () { var self = this var idBefore = self.id chrome.sockets.tcp.getInfo(self.id, function (result) { if (self.id !== idBefore) { // ignoreLastError() return } // if (chrome.runtime.lastError) { // self.destroy(new Error(chrome.runtime.lastError.message)) // return // } self.remoteAddress = result.peerAddress self.remoteFamily = result.peerAddress && result.peerAddress.indexOf(':') !== -1 ? 'IPv6' : 'IPv4' self.remotePort = result.peerPort self.localAddress = result.localAddress self.localPort = result.localPort self._connecting = false self.readable = true self.emit('connect') // start the first read, or get an immediate EOF. // this doesn't actually consume any bytes, because len=0 // TODO: replace _readableState.flowing with isPaused() after https://github.com/substack/node-browserify/issues/1341 if (self._readableState.flowing) self.read(0) }) } /** * The number of characters currently buffered to be written. * @type {number} */ Object.defineProperty(Socket.prototype, 'bufferSize', { get: function () { var self = this if (self.id) { var bytes = this._writableState.length if (self._pendingData) bytes += self._pendingData.length return bytes } } }) Socket.prototype.end = function (data, encoding) { var self = this stream.Duplex.prototype.end.call(self, data, encoding) self.writable = false } Socket.prototype._write = function (chunk, encoding, callback) { var self = this if (!callback) callback = function () {} if (self._connecting) { self._pendingData = chunk self.once('connect', function () { self._write(chunk, encoding, callback) }) return } self._pendingData = null if (!this.id) { callback(new Error('This socket is closed.')) return } // assuming buffer is browser implementation (`buffer` package on npm) var buffer = chunk.buffer if (chunk.byteOffset || chunk.byteLength !== buffer.byteLength) { buffer = buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength) } var idBefore = self.id chrome.sockets.tcp.send(self.id, buffer, function (sendInfo) { if (self.id !== idBefore) { // ignoreLastError() return } if (sendInfo.resultCode < 0) { self.destroy(errnoException(sendInfo.resultCode, 'write'), callback) } else { self._unrefTimer() callback(null) } }) self._bytesDispatched += chunk.length } Socket.prototype._read = function (bufferSize) { var self = this if (self._connecting || !self.id) { self.once('connect', self._read.bind(self, bufferSize)) return } chrome.sockets.tcp.setPaused(self.id, false) var idBefore = self.id chrome.sockets.tcp.getInfo(self.id, function (result) { if (self.id !== idBefore) { // ignoreLastError() return } // if (chrome.runtime.lastError || !result.connected) { // self._onReceiveError(-15) // workaround for https://crbug.com/518161 // } if (!result.connected) { self._onReceiveError(-15) // workaround for https://crbug.com/518161 } }) } Socket.prototype._onReceive = function (data) { var self = this // assuming buffer is browser implementation (`buffer` package on npm) var buffer = Buffer._augment(new Uint8Array(data)) var offset = self.bytesRead self.bytesRead += buffer.length self._unrefTimer() if (self.ondata) { console.error('socket.ondata = func is non-standard, use socket.on(\'data\', func)') self.ondata(buffer, offset, self.bytesRead) } if (!self.push(buffer)) { // if returns false, then apply backpressure chrome.sockets.tcp.setPaused(self.id, true) } } Socket.prototype._onReceiveError = function (resultCode) { var self = this if (resultCode === -100) { // net::ERR_CONNECTION_CLOSED if (self.onend) { console.error('socket.onend = func is non-standard, use socket.on(\'end\', func)') self.once('end', self.onend) } self.push(null) self.destroy() } else if (resultCode < 0) { self.destroy(errnoException(resultCode, 'read')) } } /** * The amount of bytes sent. * @return {number} */ Object.defineProperty(Socket.prototype, 'bytesWritten', { get: function () { var self = this if (self.id) return self._bytesDispatched + self.bufferSize } }) Socket.prototype.destroy = function (exception) { var self = this self._destroy(exception) } Socket.prototype._destroy = function (exception, cb) { var self = this function fireErrorCallbacks () { if (cb) cb(exception) if (exception && !self._writableState.errorEmitted) { process.nextTick(function () { self.emit('error', exception) }) self._writableState.errorEmitted = true } } if (self.destroyed) { // already destroyed, fire error callbacks fireErrorCallbacks() return } if (self.server) { self.server._connections -= 1 if (self.server._emitCloseIfDrained) self.server._emitCloseIfDrained() self.server = null } self._reset() for (var s = self; s !== null; s = s._parent) timers.unenroll(s) self.destroyed = true // If _destroy() has been called before chrome.sockets.tcp.create() // callback, we don't have an id. Therefore we don't need to close // or disconnect if (self.id) { delete sockets[self.id] chrome.sockets.tcp.close(self.id, function () { if (self.destroyed) { self.emit('close', !!exception) } }) self.id = null } fireErrorCallbacks() } Socket.prototype.destroySoon = function () { var self = this if (self.writable) self.end() if (self._writableState.finished) self.destroy() } /** * Sets the socket to timeout after timeout milliseconds of inactivity on the socket. * By default net.Socket do not have a timeout. When an idle timeout is triggered the * socket will receive a 'timeout' event but the connection will not be severed. The * user must manually end() or destroy() the socket. * * If timeout is 0, then the existing idle timeout is disabled. * * The optional callback parameter will be added as a one time listener for the 'timeout' event. * * @param {number} timeout * @param {function} callback */ Socket.prototype.setTimeout = function (timeout, callback) { var self = this if (timeout === 0) { timers.unenroll(self) if (callback) { self.removeListener('timeout', callback) } } else { timers.enroll(self, timeout) timers._unrefActive(self) if (callback) { self.once('timeout', callback) } } } Socket.prototype._onTimeout = function () { this.emit('timeout') } Socket.prototype._unrefTimer = function unrefTimer () { for (var s = this; s !== null; s = s._parent) { timers._unrefActive(s) } } /** * Disables the Nagle algorithm. By default TCP connections use the Nagle * algorithm, they buffer data before sending it off. Setting true for noDelay * will immediately fire off data each time socket.write() is called. noDelay * defaults to true. * * NOTE: The Chrome version of this function is async, whereas the node * version is sync. Keep this in mind. * * @param {boolean} [noDelay] Optional * @param {function} callback CHROME-SPECIFIC: Called when the configuration * operation is done. */ Socket.prototype.setNoDelay = function (noDelay, callback) { var self = this if (self.id) { // backwards compatibility: assume true when `enable` is omitted noDelay = is.isUndefined(noDelay) ? true : !!noDelay chrome.sockets.tcp.setNoDelay(self.id, noDelay, chromeCallbackWrap(callback)) } } /** * Enable/disable keep-alive functionality, and optionally set the initial * delay before the first keepalive probe is sent on an idle socket. enable * defaults to false. * * Set initialDelay (in milliseconds) to set the delay between the last data * packet received and the first keepalive probe. Setting 0 for initialDelay * will leave the value unchanged from the default (or previous) setting. * Defaults to 0. * * NOTE: The Chrome version of this function is async, whereas the node * version is sync. Keep this in mind. * * @param {boolean} [enable] Optional * @param {number} [initialDelay] * @param {function} callback CHROME-SPECIFIC: Called when the configuration * operation is done. */ Socket.prototype.setKeepAlive = function (enable, initialDelay, callback) { var self = this if (self.id) { chrome.sockets.tcp.setKeepAlive(self.id, !!enable, ~~(initialDelay / 1000), chromeCallbackWrap(callback)) } } /** * Returns the bound address, the address family name and port of the socket * as reported by the operating system. Returns an object with three * properties, e.g. { port: 12346, family: 'IPv4', address: '127.0.0.1' } * * @return {Object} information */ Socket.prototype.address = function () { var self = this return { address: self.localAddress, port: self.localPort, family: self.localAddress && self.localAddress.indexOf(':') !== -1 ? 'IPv6' : 'IPv4' } } Object.defineProperty(Socket.prototype, 'readyState', { get: function () { var self = this if (self._connecting) { return 'opening' } else if (self.readable && self.writable) { return 'open' } else { return 'closed' } } }) Socket.prototype.unref = function () { // No chrome.socket equivalent } Socket.prototype.ref = function () { // No chrome.socket equivalent } // // EXPORTED HELPERS // // Source: https://developers.google.com/web/fundamentals/input/form/provide-real-time-validation#use-these-attributes-to-validate-input var IPv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/ var IPv6Regex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/ exports.isIPv4 = IPv4Regex.test.bind(IPv4Regex) exports.isIPv6 = IPv6Regex.test.bind(IPv6Regex) exports.isIP = function (ip) { return exports.isIPv4(ip) ? 4 : exports.isIPv6(ip) ? 6 : 0 } // // HELPERS // /** * Returns an array [options] or [options, cb] * It is the same as the argument of Socket.prototype.connect(). */ function normalizeConnectArgs (args) { var options = {} if (is.isObject(args[0])) { // connect(options, [cb]) options = args[0] } else if (isPipeName(args[0])) { // connect(path, [cb]) throw new Error('Pipes are not supported in Chrome Apps.') } else { // connect(port, [host], [cb]) options.port = args[0] if (is.isString(args[1])) { options.host = args[1] } } var cb = args[args.length - 1] return is.isFunction(cb) ? [options, cb] : [options] } function toNumber (x) { return (x = Number(x)) >= 0 ? x : false } function isPipeName (s) { return is.isString(s) && toNumber(s) === false } // This prevents "Unchecked runtime.lastError" errors // function ignoreLastError () { // chrome.runtime.lastError // call the getter function // } function chromeCallbackWrap (callback) { return function () { var error // if (chrome.runtime.lastError) { // console.error(chrome.runtime.lastError.message) // error = new Error(chrome.runtime.lastError.message) // } if (callback) callback(error) } } // Full list of possible error codes: https://code.google.com/p/chrome-browser/source/browse/trunk/src/net/base/net_error_list.h // TODO: Try to reproduce errors in both node & Chrome Apps and extend this list // (what conditions lead to EPIPE?) var errorChromeToUv = { '-10': 'EACCES', '-22': 'EACCES', '-138': 'EACCES', '-147': 'EADDRINUSE', '-108': 'EADDRNOTAVAIL', '-103': 'ECONNABORTED', '-102': 'ECONNREFUSED', '-101': 'ECONNRESET', '-16': 'EEXIST', '-8': 'EFBIG', '-109': 'EHOSTUNREACH', '-4': 'EINVAL', '-23': 'EISCONN', '-6': 'ENOENT', '-13': 'ENOMEM', '-106': 'ENONET', '-18': 'ENOSPC', '-11': 'ENOSYS', '-15': 'ENOTCONN', '-105': 'ENOTFOUND', '-118': 'ETIMEDOUT', '-100': 'EOF' } function errnoException (err, syscall) { var uvCode = errorChromeToUv[err] || 'UNKNOWN' var message = syscall + ' ' + err // if (chrome.runtime.lastError) { // message += ' ' + chrome.runtime.lastError.message // } message += ' (mapped uv code: ' + uvCode + ')' var e = new Error(message) e.code = e.errno = uvCode // TODO: expose chrome error code; what property name? e.syscall = syscall return e }