├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── package.json └── test ├── chrome-app ├── background.js ├── manifest.json └── window.html ├── client ├── tape-disconnect.js ├── tape-edge-cases.js ├── tape-helper.js ├── tape-tcp.js ├── tcp-connect-direct.js ├── tcp-connect.js ├── tcp-listen.js └── tcp-send-buffer.js ├── function-bind.js ├── helper.js ├── ip.js ├── tcp-connect.js ├── tcp-listen.js ├── tcp-send-buffer.js └── tcp-tape.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | bundle.js 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .travis.yml 2 | test/ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - lts/* 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Feross Aboukhadijeh, John Hiesey & Jan Schär 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chrome-net [![travis][travis-image]][travis-url] [![npm][npm-image]][npm-url] [![downloads][downloads-image]][downloads-url] [![javascript style guide][standard-image]][standard-url] 2 | 3 | [travis-image]: https://img.shields.io/travis/feross/chrome-net/master.svg 4 | [travis-url]: https://travis-ci.org/feross/chrome-net 5 | [npm-image]: https://img.shields.io/npm/v/chrome-net.svg 6 | [npm-url]: https://npmjs.org/package/chrome-net 7 | [downloads-image]: https://img.shields.io/npm/dm/chrome-net.svg 8 | [downloads-url]: https://npmjs.org/package/chrome-net 9 | [standard-image]: https://img.shields.io/badge/code_style-standard-brightgreen.svg 10 | [standard-url]: https://standardjs.com 11 | 12 | ### Use the Node `net` API in Chrome Apps 13 | 14 | This module lets you use the Node.js [net](https://nodejs.org/api/net.html) (TCP) API in [Chrome Packaged Apps](https://developer.chrome.com/apps/about_apps). 15 | 16 | Instead of learning the quirks of Chrome's `chrome.sockets` API for networking in Chrome Apps just **use the higher-level node API you're familiar with**. Then, compile your code with [browserify](https://github.com/substack/node-browserify) and you're all set! 17 | 18 | ## install 19 | 20 | ``` 21 | npm install chrome-net 22 | ``` 23 | 24 | ## methods 25 | 26 | Use node's `net` API, including all parameter list shorthands and variations. 27 | 28 | Example TCP client: 29 | 30 | ```js 31 | var net = require('chrome-net') 32 | 33 | var client = net.createConnection({ 34 | port: 1337, 35 | host: '127.0.0.1' 36 | }) 37 | 38 | client.write('beep') 39 | 40 | client.on('data', function (data) { 41 | console.log(data) 42 | }) 43 | 44 | // .pipe() streaming API works too! 45 | 46 | ``` 47 | 48 | Example TCP server: 49 | 50 | ```js 51 | var net = require('chrome-net') 52 | 53 | var server = net.createServer() 54 | 55 | server.on('listening', function () { 56 | console.log('listening') 57 | }) 58 | 59 | server.on('connection', function (sock) { 60 | console.log('Connection from ' + sock.remoteAddress + ':' + sock.remotePort) 61 | sock.on('data', function (data) { 62 | console.log(data) 63 | }) 64 | }) 65 | 66 | server.listen(1337) 67 | 68 | ``` 69 | 70 | See nodejs.org for full API documentation: [net](https://nodejs.org/api/net.html) 71 | 72 | ## contribute 73 | 74 | To run tests, use `npm test`. The tests will run TCP and UDP servers and launch a few different Chrome Packaged Apps with browserified client code. The tests currently require Chrome on Windows or Chrome Canary on Mac. If you're on Linux, feel free to send a pull request to fix this limitation. 75 | 76 | ## license 77 | 78 | MIT. Copyright (c) [Feross Aboukhadijeh](http://feross.org), John Hiesey & Jan Schär. 79 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! chrome-net. MIT License. Feross Aboukhadijeh */ 2 | /* global chrome */ 3 | 'use strict' 4 | 5 | /** 6 | * net 7 | * === 8 | * 9 | * The net module provides you with an asynchronous network wrapper. It 10 | * contains methods for creating both servers and clients (called streams). 11 | * You can include this module with require('chrome-net') 12 | */ 13 | 14 | const EventEmitter = require('events') 15 | const inherits = require('inherits') 16 | const stream = require('stream') 17 | const deprecate = require('util').deprecate 18 | const timers = require('timers') 19 | const Buffer = require('buffer').Buffer 20 | 21 | // Track open servers and sockets to route incoming sockets (via onAccept and onReceive) 22 | // to the right handlers. 23 | const servers = {} 24 | const sockets = {} 25 | 26 | // Thorough check for Chrome App since both Edge and Chrome implement dummy chrome object 27 | if ( 28 | typeof chrome === 'object' && 29 | typeof chrome.runtime === 'object' && 30 | typeof chrome.runtime.id === 'string' && 31 | typeof chrome.sockets === 'object' && 32 | typeof chrome.sockets.tcpServer === 'object' && 33 | typeof chrome.sockets.tcp === 'object' 34 | ) { 35 | chrome.sockets.tcpServer.onAccept.addListener(onAccept) 36 | chrome.sockets.tcpServer.onAcceptError.addListener(onAcceptError) 37 | chrome.sockets.tcp.onReceive.addListener(onReceive) 38 | chrome.sockets.tcp.onReceiveError.addListener(onReceiveError) 39 | } 40 | 41 | function onAccept (info) { 42 | if (info.socketId in servers) { 43 | servers[info.socketId]._onAccept(info.clientSocketId) 44 | } else { 45 | console.error('Unknown server socket id: ' + info.socketId) 46 | } 47 | } 48 | 49 | function onAcceptError (info) { 50 | if (info.socketId in servers) { 51 | servers[info.socketId]._onAcceptError(info.resultCode) 52 | } else { 53 | console.error('Unknown server socket id: ' + info.socketId) 54 | } 55 | } 56 | 57 | function onReceive (info) { 58 | if (info.socketId in sockets) { 59 | sockets[info.socketId]._onReceive(info.data) 60 | } else { 61 | console.error('Unknown socket id: ' + info.socketId) 62 | } 63 | } 64 | 65 | function onReceiveError (info) { 66 | if (info.socketId in sockets) { 67 | sockets[info.socketId]._onReceiveError(info.resultCode) 68 | } else { 69 | if (info.resultCode === -100) return // net::ERR_CONNECTION_CLOSED 70 | console.error('Unknown socket id: ' + info.socketId) 71 | } 72 | } 73 | 74 | /** 75 | * Creates a new TCP server. The connectionListener argument is automatically 76 | * set as a listener for the 'connection' event. 77 | * 78 | * @param {Object} options 79 | * @param {function} connectionListener 80 | * @return {Server} 81 | */ 82 | exports.createServer = function (options, connectionListener) { 83 | return new Server(options, connectionListener) 84 | } 85 | 86 | /** 87 | * net.connect(options, [connectionListener]) 88 | * net.createConnection(options, [connectionListener]) 89 | * 90 | * Constructs a new socket object and opens the socket to the given location. 91 | * When the socket is established, the 'connect' event will be emitted. 92 | * 93 | * For TCP sockets, options argument should be an object which specifies: 94 | * 95 | * port: Port the client should connect to (Required). 96 | * host: Host the client should connect to. Defaults to 'localhost'. 97 | * localAddress: Local interface to bind to for network connections. 98 | * 99 | * =============================================================== 100 | * 101 | * net.connect(port, [host], [connectListener]) 102 | * net.createConnection(port, [host], [connectListener]) 103 | * 104 | * Creates a TCP connection to port on host. If host is omitted, 105 | * 'localhost' will be assumed. The connectListener parameter will be 106 | * added as an listener for the 'connect' event. 107 | * 108 | * @param {Object} options 109 | * @param {function} listener 110 | * @return {Socket} 111 | */ 112 | exports.connect = exports.createConnection = function () { 113 | const argsLen = arguments.length 114 | let args = new Array(argsLen) 115 | for (let i = 0; i < argsLen; i++) args[i] = arguments[i] 116 | args = normalizeConnectArgs(args) 117 | const s = new Socket(args[0]) 118 | return Socket.prototype.connect.apply(s, args) 119 | } 120 | 121 | inherits(Server, EventEmitter) 122 | 123 | /** 124 | * Class: net.Server 125 | * ================= 126 | * 127 | * This class is used to create a TCP server. 128 | * 129 | * Event: 'listening' 130 | * Emitted when the server has been bound after calling server.listen. 131 | * 132 | * Event: 'connection' 133 | * - Socket object The connection object 134 | * Emitted when a new connection is made. socket is an instance of net.Socket. 135 | * 136 | * Event: 'close' 137 | * Emitted when the server closes. Note that if connections exist, this event 138 | * is not emitted until all connections are ended. 139 | * 140 | * Event: 'error' 141 | * - Error Object 142 | * Emitted when an error occurs. The 'close' event will be called directly 143 | * following this event. See example in discussion of server.listen. 144 | */ 145 | function Server (options, connectionListener) { 146 | if (!(this instanceof Server)) return new Server(options, connectionListener) 147 | EventEmitter.call(this) 148 | 149 | if (typeof options === 'function') { 150 | connectionListener = options 151 | options = {} 152 | this.on('connection', connectionListener) 153 | } else if (options == null || typeof options === 'object') { 154 | options = options || {} 155 | 156 | if (typeof connectionListener === 'function') { 157 | this.on('connection', connectionListener) 158 | } 159 | } else { 160 | throw new TypeError('options must be an object') 161 | } 162 | 163 | this._connections = 0 164 | 165 | Object.defineProperty(this, 'connections', { 166 | get: deprecate(() => this._connections, 167 | 'Server.connections property is deprecated. ' + 168 | 'Use Server.getConnections method instead.'), 169 | set: deprecate((val) => (this._connections = val), 170 | 'Server.connections property is deprecated.'), 171 | configurable: true, 172 | enumerable: false 173 | }) 174 | 175 | this.id = null // a number > 0 176 | this.connecting = false 177 | 178 | this.allowHalfOpen = options.allowHalfOpen || false 179 | this.pauseOnConnect = !!options.pauseOnConnect 180 | this._address = null 181 | 182 | this._host = null 183 | this._port = null 184 | this._backlog = null 185 | } 186 | exports.Server = Server 187 | 188 | Server.prototype._usingSlaves = false // not used 189 | 190 | /** 191 | * server.listen(port, [host], [backlog], [callback]) 192 | * 193 | * Begin accepting connections on the specified port and host. If the host is 194 | * omitted, the server will accept connections directed to any IPv4 address 195 | * (INADDR_ANY). A port value of zero will assign a random port. 196 | * 197 | * Backlog is the maximum length of the queue of pending connections. The 198 | * actual length will be determined by your OS through sysctl settings such as 199 | * tcp_max_syn_backlog and somaxconn on linux. The default value of this 200 | * parameter is 511 (not 512). 201 | * 202 | * This function is asynchronous. When the server has been bound, 'listening' 203 | * event will be emitted. The last parameter callback will be added as an 204 | * listener for the 'listening' event. 205 | * 206 | * @return {Socket} 207 | */ 208 | Server.prototype.listen = function (/* variable arguments... */) { 209 | const lastArg = arguments[arguments.length - 1] 210 | if (typeof lastArg === 'function') { 211 | this.once('listening', lastArg) 212 | } 213 | 214 | let port = toNumber(arguments[0]) 215 | 216 | let address 217 | 218 | // The third optional argument is the backlog size. 219 | // When the ip is omitted it can be the second argument. 220 | let backlog = toNumber(arguments[1]) || toNumber(arguments[2]) || undefined 221 | 222 | if (arguments[0] !== null && typeof arguments[0] === 'object') { 223 | const h = arguments[0] 224 | 225 | if (h._handle || h.handle) { 226 | throw new Error('handle is not supported in Chrome Apps.') 227 | } 228 | if (typeof h.fd === 'number' && h.fd >= 0) { 229 | throw new Error('fd is not supported in Chrome Apps.') 230 | } 231 | 232 | // The first argument is a configuration object 233 | if (h.backlog) { 234 | backlog = h.backlog 235 | } 236 | 237 | if (typeof h.port === 'number' || typeof h.port === 'string' || 238 | (typeof h.port === 'undefined' && 'port' in h)) { 239 | // Undefined is interpreted as zero (random port) for consistency 240 | // with net.connect(). 241 | address = h.host || null 242 | port = h.port 243 | } else if (h.path && isPipeName(h.path)) { 244 | throw new Error('Pipes are not supported in Chrome Apps.') 245 | } else { 246 | throw new Error('Invalid listen argument: ' + h) 247 | } 248 | } else if (isPipeName(arguments[0])) { 249 | // UNIX socket or Windows pipe. 250 | throw new Error('Pipes are not supported in Chrome Apps.') 251 | } else if (arguments[1] === undefined || 252 | typeof arguments[1] === 'function' || 253 | typeof arguments[1] === 'number') { 254 | // The first argument is the port, no IP given. 255 | address = null 256 | } else { 257 | // The first argument is the port, the second an IP. 258 | address = arguments[1] 259 | } 260 | 261 | // now do something with port, address, backlog 262 | 263 | if (this.id) { 264 | this.close() 265 | } 266 | 267 | // If port is invalid or undefined, bind to a random port. 268 | assertPort(port) 269 | this._port = port | 0 270 | 271 | this._host = address 272 | 273 | let isAny6 = !this._host 274 | if (isAny6) { 275 | this._host = '::' 276 | } 277 | 278 | this._backlog = typeof backlog === 'number' ? backlog : undefined 279 | 280 | this.connecting = true 281 | 282 | chrome.sockets.tcpServer.create((createInfo) => { 283 | if (!this.connecting || this.id) { 284 | ignoreLastError() 285 | chrome.sockets.tcpServer.close(createInfo.socketId) 286 | return 287 | } 288 | if (chrome.runtime.lastError) { 289 | this.emit('error', new Error(chrome.runtime.lastError.message)) 290 | return 291 | } 292 | 293 | const socketId = this.id = createInfo.socketId 294 | servers[this.id] = this 295 | 296 | const listen = () => chrome.sockets.tcpServer.listen(this.id, this._host, 297 | this._port, this._backlog, 298 | (result) => { 299 | // callback may be after close 300 | if (this.id !== socketId) { 301 | ignoreLastError() 302 | return 303 | } 304 | if (result !== 0 && isAny6) { 305 | ignoreLastError() 306 | this._host = '0.0.0.0' // try IPv4 307 | isAny6 = false 308 | return listen() 309 | } 310 | 311 | this._onListen(result) 312 | }) 313 | listen() 314 | }) 315 | 316 | return this 317 | } 318 | 319 | Server.prototype._onListen = function (result) { 320 | this.connecting = false 321 | 322 | if (result === 0) { 323 | const idBefore = this.id 324 | chrome.sockets.tcpServer.getInfo(this.id, (info) => { 325 | if (this.id !== idBefore) { 326 | ignoreLastError() 327 | return 328 | } 329 | if (chrome.runtime.lastError) { 330 | this._onListen(-2) // net::ERR_FAILED 331 | return 332 | } 333 | 334 | this._address = { 335 | port: info.localPort, 336 | family: info.localAddress && info.localAddress.indexOf(':') !== -1 337 | ? 'IPv6' 338 | : 'IPv4', 339 | address: info.localAddress 340 | } 341 | this.emit('listening') 342 | }) 343 | } else { 344 | this.emit('error', exceptionWithHostPort(result, 'listen', this._host, this._port)) 345 | if (this.id) { 346 | chrome.sockets.tcpServer.close(this.id) 347 | delete servers[this.id] 348 | this.id = null 349 | } 350 | } 351 | } 352 | 353 | Server.prototype._onAccept = function (clientSocketId) { 354 | // Set the `maxConnections` property to reject connections when the server's 355 | // connection count gets high. 356 | if (this.maxConnections && this._connections >= this.maxConnections) { 357 | chrome.sockets.tcp.close(clientSocketId) 358 | console.warn('Rejected connection - hit `maxConnections` limit') 359 | return 360 | } 361 | 362 | this._connections += 1 363 | 364 | const acceptedSocket = new Socket({ 365 | server: this, 366 | id: clientSocketId, 367 | allowHalfOpen: this.allowHalfOpen, 368 | pauseOnCreate: this.pauseOnConnect 369 | }) 370 | acceptedSocket.on('connect', () => this.emit('connection', acceptedSocket)) 371 | } 372 | 373 | Server.prototype._onAcceptError = function (resultCode) { 374 | this.emit('error', errnoException(resultCode, 'accept')) 375 | this.close() 376 | } 377 | 378 | /** 379 | * Stops the server from accepting new connections and keeps existing 380 | * connections. This function is asynchronous, the server is finally closed 381 | * when all connections are ended and the server emits a 'close' event. 382 | * Optionally, you can pass a callback to listen for the 'close' event. 383 | * @param {function} callback 384 | */ 385 | Server.prototype.close = function (callback) { 386 | if (typeof callback === 'function') { 387 | if (!this.id) { 388 | this.once('close', () => callback(new Error('Not running'))) 389 | } else { 390 | this.once('close', callback) 391 | } 392 | } 393 | 394 | if (this.id) { 395 | chrome.sockets.tcpServer.close(this.id) 396 | delete servers[this.id] 397 | this.id = null 398 | } 399 | this._address = null 400 | this.connecting = false 401 | 402 | this._emitCloseIfDrained() 403 | 404 | return this 405 | } 406 | 407 | Server.prototype._emitCloseIfDrained = function () { 408 | if (this.id || this.connecting || this._connections) { 409 | return 410 | } 411 | 412 | process.nextTick(emitCloseNT, this) 413 | } 414 | 415 | function emitCloseNT (self) { 416 | if (self.id || self.connecting || self._connections) { 417 | return 418 | } 419 | self.emit('close') 420 | } 421 | 422 | Object.defineProperty(Server.prototype, 'listening', { 423 | get: function () { 424 | return !!this._address 425 | }, 426 | configurable: true, 427 | enumerable: true 428 | }) 429 | 430 | /** 431 | * Returns the bound address, the address family name and port of the socket 432 | * as reported by the operating system. Returns an object with three 433 | * properties, e.g. { port: 12346, family: 'IPv4', address: '127.0.0.1' } 434 | * 435 | * @return {Object} information 436 | */ 437 | Server.prototype.address = function () { 438 | return this._address 439 | } 440 | 441 | Server.prototype.unref = 442 | Server.prototype.ref = function () { 443 | // No chrome.socket equivalent 444 | return this 445 | } 446 | 447 | /** 448 | * Asynchronously get the number of concurrent connections on the server. 449 | * Works when sockets were sent to forks. 450 | * 451 | * Callback should take two arguments err and count. 452 | * 453 | * @param {function} callback 454 | */ 455 | Server.prototype.getConnections = function (callback) { 456 | process.nextTick(callback, null, this._connections) 457 | } 458 | 459 | inherits(Socket, stream.Duplex) 460 | 461 | /** 462 | * Class: net.Socket 463 | * ================= 464 | * 465 | * This object is an abstraction of a TCP or UNIX socket. net.Socket instances 466 | * implement a duplex Stream interface. They can be created by the user and 467 | * used as a client (with connect()) or they can be created by Node and passed 468 | * to the user through the 'connection' event of a server. 469 | * 470 | * Construct a new socket object. 471 | * 472 | * options is an object with the following defaults: 473 | * 474 | * { fd: null // NO CHROME EQUIVALENT 475 | * type: null 476 | * allowHalfOpen: false // NO CHROME EQUIVALENT 477 | * } 478 | * 479 | * `type` can only be 'tcp4' (for now). 480 | * 481 | * Event: 'connect' 482 | * Emitted when a socket connection is successfully established. See 483 | * connect(). 484 | * 485 | * Event: 'data' 486 | * - Buffer object 487 | * Emitted when data is received. The argument data will be a Buffer or 488 | * String. Encoding of data is set by socket.setEncoding(). (See the Readable 489 | * Stream section for more information.) 490 | * 491 | * Note that the data will be lost if there is no listener when a Socket 492 | * emits a 'data' event. 493 | * 494 | * Event: 'end' 495 | * Emitted when the other end of the socket sends a FIN packet. 496 | * 497 | * By default (allowHalfOpen == false) the socket will destroy its file 498 | * descriptor once it has written out its pending write queue. However, 499 | * by setting allowHalfOpen == true the socket will not automatically 500 | * end() its side allowing the user to write arbitrary amounts of data, 501 | * with the caveat that the user is required to end() their side now. 502 | * 503 | * Event: 'timeout' 504 | * Emitted if the socket times out from inactivity. This is only to notify 505 | * that the socket has been idle. The user must manually close the connection. 506 | * 507 | * See also: socket.setTimeout() 508 | * 509 | * Event: 'drain' 510 | * Emitted when the write buffer becomes empty. Can be used to throttle 511 | * uploads. 512 | * 513 | * See also: the return values of socket.write() 514 | * 515 | * Event: 'error' 516 | * - Error object 517 | * Emitted when an error occurs. The 'close' event will be called directly 518 | * following this event. 519 | * 520 | * Event: 'close' 521 | * - had_error Boolean true if the socket had a transmission error 522 | * Emitted once the socket is fully closed. The argument had_error is a 523 | * boolean which says if the socket was closed due to a transmission error. 524 | */ 525 | function Socket (options) { 526 | if (!(this instanceof Socket)) return new Socket(options) 527 | 528 | if (typeof options === 'number') { 529 | options = { fd: options } // Legacy interface. 530 | } else if (options === undefined) { 531 | options = {} 532 | } 533 | 534 | if (options.handle) { 535 | throw new Error('handle is not supported in Chrome Apps.') 536 | } else if (options.fd !== undefined) { 537 | throw new Error('fd is not supported in Chrome Apps.') 538 | } 539 | 540 | options.decodeStrings = true 541 | options.objectMode = false 542 | stream.Duplex.call(this, options) 543 | 544 | this.destroyed = false 545 | this._hadError = false // Used by _http_client.js 546 | this.id = null // a number > 0 547 | this._parent = null 548 | this._host = null 549 | this._port = null 550 | this._pendingData = null 551 | 552 | this.ondata = null 553 | this.onend = null 554 | 555 | this._init() 556 | this._reset() 557 | 558 | // default to *not* allowing half open sockets 559 | // Note: this is not possible in Chrome Apps, see https://crbug.com/124952 560 | this.allowHalfOpen = options.allowHalfOpen || false 561 | 562 | // shut down the socket when we're finished with it. 563 | this.on('finish', this.destroy) 564 | 565 | if (options.server) { 566 | this.server = this._server = options.server 567 | this.id = options.id 568 | sockets[this.id] = this 569 | 570 | if (options.pauseOnCreate) { 571 | // stop the handle from reading and pause the stream 572 | // (Already paused in Chrome version) 573 | this._readableState.flowing = false 574 | } 575 | 576 | // For incoming sockets (from server), it's already connected. 577 | this.connecting = true 578 | this.writable = true 579 | this._onConnect() 580 | } 581 | } 582 | exports.Socket = Socket 583 | 584 | // called when creating new Socket, or when re-using a closed Socket 585 | Socket.prototype._init = function () { 586 | // The amount of received bytes. 587 | this.bytesRead = 0 588 | 589 | this._bytesDispatched = 0 590 | 591 | // Reserve properties 592 | this.server = null 593 | this._server = null 594 | } 595 | 596 | // called when creating new Socket, or when closing a Socket 597 | Socket.prototype._reset = function () { 598 | this.remoteAddress = this.remotePort = 599 | this.localAddress = this.localPort = null 600 | this.remoteFamily = 'IPv4' 601 | this.readable = this.writable = false 602 | this.connecting = false 603 | } 604 | 605 | /** 606 | * socket.connect(port, [host], [connectListener]) 607 | * socket.connect(options, [connectListener]) 608 | * 609 | * Opens the connection for a given socket. If port and host are given, then 610 | * the socket will be opened as a TCP socket, if host is omitted, localhost 611 | * will be assumed. If a path is given, the socket will be opened as a unix 612 | * socket to that path. 613 | * 614 | * Normally this method is not needed, as net.createConnection opens the 615 | * socket. Use this only if you are implementing a custom Socket. 616 | * 617 | * This function is asynchronous. When the 'connect' event is emitted the 618 | * socket is established. If there is a problem connecting, the 'connect' 619 | * event will not be emitted, the 'error' event will be emitted with the 620 | * exception. 621 | * 622 | * The connectListener parameter will be added as an listener for the 623 | * 'connect' event. 624 | * 625 | * @param {Object} options 626 | * @param {function} cb 627 | * @return {Socket} this socket (for chaining) 628 | */ 629 | Socket.prototype.connect = function () { 630 | const argsLen = arguments.length 631 | let args = new Array(argsLen) 632 | for (let i = 0; i < argsLen; i++) args[i] = arguments[i] 633 | args = normalizeConnectArgs(args) 634 | const options = args[0] 635 | const cb = args[1] 636 | 637 | if (options.path) { 638 | throw new Error('Pipes are not supported in Chrome Apps.') 639 | } 640 | 641 | if (this.id) { 642 | // already connected, destroy and connect again 643 | this.destroy() 644 | } 645 | 646 | if (this.destroyed) { 647 | this._readableState.reading = false 648 | this._readableState.ended = false 649 | this._readableState.endEmitted = false 650 | this._writableState.ended = false 651 | this._writableState.ending = false 652 | this._writableState.finished = false 653 | this._writableState.errorEmitted = false 654 | this._writableState.length = 0 655 | this.destroyed = false 656 | } 657 | 658 | this.connecting = true 659 | this.writable = true 660 | 661 | this._host = options.host || 'localhost' 662 | this._port = options.port 663 | 664 | if (typeof this._port !== 'undefined') { 665 | if (typeof this._port !== 'number' && typeof this._port !== 'string') { 666 | throw new TypeError('"port" option should be a number or string: ' + this._port) 667 | } 668 | if (!isLegalPort(this._port)) { 669 | throw new RangeError('"port" option should be >= 0 and < 65536: ' + this._port) 670 | } 671 | } 672 | this._port |= 0 673 | 674 | this._init() 675 | 676 | this._unrefTimer() 677 | 678 | if (typeof cb === 'function') { 679 | this.once('connect', cb) 680 | } 681 | 682 | chrome.sockets.tcp.create((createInfo) => { 683 | if (!this.connecting || this.id) { 684 | ignoreLastError() 685 | chrome.sockets.tcp.close(createInfo.socketId) 686 | return 687 | } 688 | if (chrome.runtime.lastError) { 689 | this.destroy(new Error(chrome.runtime.lastError.message)) 690 | return 691 | } 692 | 693 | this.id = createInfo.socketId 694 | sockets[this.id] = this 695 | 696 | chrome.sockets.tcp.setPaused(this.id, true) 697 | 698 | chrome.sockets.tcp.connect(this.id, this._host, this._port, (result) => { 699 | // callback may come after call to destroy 700 | if (this.id !== createInfo.socketId) { 701 | ignoreLastError() 702 | return 703 | } 704 | if (result !== 0) { 705 | this.destroy(exceptionWithHostPort(result, 'connect', this._host, this._port)) 706 | return 707 | } 708 | 709 | this._unrefTimer() 710 | this._onConnect() 711 | }) 712 | }) 713 | 714 | return this 715 | } 716 | 717 | Socket.prototype._onConnect = function () { 718 | const idBefore = this.id 719 | chrome.sockets.tcp.getInfo(this.id, (result) => { 720 | if (this.id !== idBefore) { 721 | ignoreLastError() 722 | return 723 | } 724 | if (chrome.runtime.lastError) { 725 | this.destroy(new Error(chrome.runtime.lastError.message)) 726 | return 727 | } 728 | 729 | this.remoteAddress = result.peerAddress 730 | this.remoteFamily = result.peerAddress && result.peerAddress.indexOf(':') !== -1 731 | ? 'IPv6' 732 | : 'IPv4' 733 | this.remotePort = result.peerPort 734 | this.localAddress = result.localAddress 735 | this.localPort = result.localPort 736 | 737 | this.connecting = false 738 | this.readable = true 739 | 740 | this.emit('connect') 741 | // start the first read, or get an immediate EOF. 742 | // this doesn't actually consume any bytes, because len=0 743 | if (!this.isPaused()) this.read(0) 744 | }) 745 | } 746 | 747 | /** 748 | * The number of characters currently buffered to be written. 749 | * @type {number} 750 | */ 751 | Object.defineProperty(Socket.prototype, 'bufferSize', { 752 | get: function () { 753 | if (this.id) { 754 | let bytes = this._writableState.length 755 | if (this._pendingData) bytes += this._pendingData.length 756 | return bytes 757 | } 758 | } 759 | }) 760 | 761 | Socket.prototype.end = function (data, encoding) { 762 | stream.Duplex.prototype.end.call(this, data, encoding) 763 | this.writable = false 764 | } 765 | 766 | Socket.prototype._write = function (chunk, encoding, callback) { 767 | if (!callback) callback = () => {} 768 | 769 | if (this.connecting) { 770 | this._pendingData = chunk 771 | this.once('connect', () => this._write(chunk, encoding, callback)) 772 | return 773 | } 774 | this._pendingData = null 775 | 776 | if (!this.id) { 777 | callback(new Error('This socket is closed')) 778 | return 779 | } 780 | 781 | // assuming buffer is browser implementation (`buffer` package on npm) 782 | let buffer = chunk.buffer 783 | if (chunk.byteLength !== buffer.byteLength) { 784 | buffer = buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength) 785 | } 786 | 787 | const idBefore = this.id 788 | chrome.sockets.tcp.send(this.id, buffer, (sendInfo) => { 789 | if (this.id !== idBefore) { 790 | ignoreLastError() 791 | return 792 | } 793 | 794 | if (sendInfo.resultCode < 0) { 795 | this._destroy(exceptionWithHostPort(sendInfo.resultCode, 'write', this.remoteAddress, this.remotePort), callback) 796 | } else { 797 | this._unrefTimer() 798 | callback(null) 799 | } 800 | }) 801 | 802 | this._bytesDispatched += chunk.length 803 | } 804 | 805 | Socket.prototype._read = function (bufferSize) { 806 | if (this.connecting || !this.id) { 807 | this.once('connect', () => this._read(bufferSize)) 808 | return 809 | } 810 | 811 | chrome.sockets.tcp.setPaused(this.id, false) 812 | 813 | const idBefore = this.id 814 | chrome.sockets.tcp.getInfo(this.id, (result) => { 815 | if (this.id !== idBefore) { 816 | ignoreLastError() 817 | return 818 | } 819 | if (chrome.runtime.lastError || !result.connected) { 820 | this._onReceiveError(-15) // workaround for https://crbug.com/518161 821 | } 822 | }) 823 | } 824 | 825 | Socket.prototype._onReceive = function (data) { 826 | const buffer = Buffer.from(data) 827 | const offset = this.bytesRead 828 | 829 | this.bytesRead += buffer.length 830 | this._unrefTimer() 831 | 832 | if (this.ondata) { 833 | console.error('socket.ondata = func is non-standard, use socket.on(\'data\', func)') 834 | this.ondata(buffer, offset, this.bytesRead) 835 | } 836 | if (!this.push(buffer)) { // if returns false, then apply backpressure 837 | chrome.sockets.tcp.setPaused(this.id, true) 838 | } 839 | } 840 | 841 | Socket.prototype._onReceiveError = function (resultCode) { 842 | if (resultCode === -100) { // net::ERR_CONNECTION_CLOSED 843 | if (this.onend) { 844 | console.error('socket.onend = func is non-standard, use socket.on(\'end\', func)') 845 | this.once('end', this.onend) 846 | } 847 | this.push(null) 848 | this.destroy() 849 | } else if (resultCode < 0) { 850 | this.destroy(errnoException(resultCode, 'read')) 851 | } 852 | } 853 | 854 | function protoGetter (name, callback) { 855 | Object.defineProperty(Socket.prototype, name, { 856 | configurable: false, 857 | enumerable: true, 858 | get: callback 859 | }) 860 | } 861 | 862 | /** 863 | * The amount of bytes sent. 864 | * @return {number} 865 | */ 866 | protoGetter('bytesWritten', function bytesWritten () { 867 | if (this.id) return this._bytesDispatched + this.bufferSize 868 | }) 869 | 870 | Socket.prototype.destroy = function (exception) { 871 | this._destroy(exception) 872 | } 873 | 874 | Socket.prototype._destroy = function (exception, cb) { 875 | const fireErrorCallbacks = () => { 876 | if (cb) cb(exception) 877 | if (exception && !this._writableState.errorEmitted) { 878 | process.nextTick(emitErrorNT, this, exception) 879 | this._writableState.errorEmitted = true 880 | } 881 | } 882 | 883 | if (this.destroyed) { 884 | // already destroyed, fire error callbacks 885 | fireErrorCallbacks() 886 | return 887 | } 888 | 889 | if (this._server) { 890 | this._server._connections -= 1 891 | if (this._server._emitCloseIfDrained) this._server._emitCloseIfDrained() 892 | } 893 | 894 | this._reset() 895 | 896 | for (let s = this; s !== null; s = s._parent) timers.unenroll(s) // eslint-disable-line node/no-deprecated-api 897 | 898 | this.destroyed = true 899 | 900 | // If _destroy() has been called before chrome.sockets.tcp.create() 901 | // callback, we don't have an id. Therefore we don't need to close 902 | // or disconnect 903 | if (this.id) { 904 | delete sockets[this.id] 905 | chrome.sockets.tcp.close(this.id, () => { 906 | if (this.destroyed) { 907 | this.emit('close', !!exception) 908 | } 909 | }) 910 | this.id = null 911 | } 912 | 913 | fireErrorCallbacks() 914 | } 915 | 916 | Socket.prototype.destroySoon = function () { 917 | if (this.writable) this.end() 918 | 919 | if (this._writableState.finished) this.destroy() 920 | } 921 | 922 | /** 923 | * Sets the socket to timeout after timeout milliseconds of inactivity on the socket. 924 | * By default net.Socket do not have a timeout. When an idle timeout is triggered the 925 | * socket will receive a 'timeout' event but the connection will not be severed. The 926 | * user must manually end() or destroy() the socket. 927 | * 928 | * If timeout is 0, then the existing idle timeout is disabled. 929 | * 930 | * The optional callback parameter will be added as a one time listener for the 'timeout' event. 931 | * 932 | * @param {number} timeout 933 | * @param {function} callback 934 | */ 935 | Socket.prototype.setTimeout = function (timeout, callback) { 936 | if (timeout === 0) { 937 | timers.unenroll(this) // eslint-disable-line node/no-deprecated-api 938 | if (callback) { 939 | this.removeListener('timeout', callback) 940 | } 941 | } else { 942 | timers.enroll(this, timeout) // eslint-disable-line node/no-deprecated-api 943 | timers._unrefActive(this) 944 | if (callback) { 945 | this.once('timeout', callback) 946 | } 947 | } 948 | 949 | return this 950 | } 951 | 952 | Socket.prototype._onTimeout = function () { 953 | this.emit('timeout') 954 | } 955 | 956 | Socket.prototype._unrefTimer = function unrefTimer () { 957 | for (let s = this; s !== null; s = s._parent) { 958 | timers._unrefActive(s) 959 | } 960 | } 961 | 962 | /** 963 | * Disables the Nagle algorithm. By default TCP connections use the Nagle 964 | * algorithm, they buffer data before sending it off. Setting true for noDelay 965 | * will immediately fire off data each time socket.write() is called. noDelay 966 | * defaults to true. 967 | * 968 | * NOTE: The Chrome version of this function is async, whereas the node 969 | * version is sync. Keep this in mind. 970 | * 971 | * @param {boolean} [noDelay] Optional 972 | * @param {function} callback CHROME-SPECIFIC: Called when the configuration 973 | * operation is done. 974 | */ 975 | Socket.prototype.setNoDelay = function (noDelay, callback) { 976 | if (!this.id) { 977 | this.once('connect', () => this.setNoDelay(noDelay, callback)) 978 | return this 979 | } 980 | 981 | // backwards compatibility: assume true when `noDelay` is omitted 982 | noDelay = noDelay === undefined ? true : !!noDelay 983 | chrome.sockets.tcp.setNoDelay(this.id, noDelay, chromeCallbackWrap(callback)) 984 | 985 | return this 986 | } 987 | 988 | /** 989 | * Enable/disable keep-alive functionality, and optionally set the initial 990 | * delay before the first keepalive probe is sent on an idle socket. enable 991 | * defaults to false. 992 | * 993 | * Set initialDelay (in milliseconds) to set the delay between the last data 994 | * packet received and the first keepalive probe. Setting 0 for initialDelay 995 | * will leave the value unchanged from the default (or previous) setting. 996 | * Defaults to 0. 997 | * 998 | * NOTE: The Chrome version of this function is async, whereas the node 999 | * version is sync. Keep this in mind. 1000 | * 1001 | * @param {boolean} [enable] Optional 1002 | * @param {number} [initialDelay] 1003 | * @param {function} callback CHROME-SPECIFIC: Called when the configuration 1004 | * operation is done. 1005 | */ 1006 | Socket.prototype.setKeepAlive = function (enable, initialDelay, callback) { 1007 | if (!this.id) { 1008 | this.once('connect', () => this.setKeepAlive(enable, initialDelay, callback)) 1009 | return this 1010 | } 1011 | 1012 | chrome.sockets.tcp.setKeepAlive(this.id, !!enable, ~~(initialDelay / 1000), 1013 | chromeCallbackWrap(callback)) 1014 | 1015 | return this 1016 | } 1017 | 1018 | /** 1019 | * Returns the bound address, the address family name and port of the socket 1020 | * as reported by the operating system. Returns an object with three 1021 | * properties, e.g. { port: 12346, family: 'IPv4', address: '127.0.0.1' } 1022 | * 1023 | * @return {Object} information 1024 | */ 1025 | Socket.prototype.address = function () { 1026 | return { 1027 | address: this.localAddress, 1028 | port: this.localPort, 1029 | family: this.localAddress && this.localAddress.indexOf(':') !== -1 1030 | ? 'IPv6' 1031 | : 'IPv4' 1032 | } 1033 | } 1034 | 1035 | Object.defineProperty(Socket.prototype, '_connecting', { 1036 | get: function () { 1037 | return this.connecting 1038 | } 1039 | }) 1040 | 1041 | Object.defineProperty(Socket.prototype, 'readyState', { 1042 | get: function () { 1043 | if (this.connecting) { 1044 | return 'opening' 1045 | } else if (this.readable && this.writable) { 1046 | return 'open' 1047 | } else { 1048 | return 'closed' 1049 | } 1050 | } 1051 | }) 1052 | 1053 | Socket.prototype.unref = 1054 | Socket.prototype.ref = function () { 1055 | // No chrome.socket equivalent 1056 | return this 1057 | } 1058 | 1059 | // 1060 | // EXPORTED HELPERS 1061 | // 1062 | 1063 | // Source: https://developers.google.com/web/fundamentals/input/form/provide-real-time-validation#use-these-attributes-to-validate-input 1064 | const 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]?)$/ 1065 | const 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]))$/ 1066 | 1067 | exports.isIPv4 = IPv4Regex.test.bind(IPv4Regex) 1068 | exports.isIPv6 = IPv6Regex.test.bind(IPv6Regex) 1069 | 1070 | exports.isIP = function (ip) { 1071 | return exports.isIPv4(ip) ? 4 : exports.isIPv6(ip) ? 6 : 0 1072 | } 1073 | 1074 | // 1075 | // HELPERS 1076 | // 1077 | 1078 | /** 1079 | * Returns an array [options] or [options, cb] 1080 | * It is the same as the argument of Socket.prototype.connect(). 1081 | */ 1082 | function normalizeConnectArgs (args) { 1083 | let options = {} 1084 | 1085 | if (args[0] !== null && typeof args[0] === 'object') { 1086 | // connect(options, [cb]) 1087 | options = args[0] 1088 | } else if (isPipeName(args[0])) { 1089 | // connect(path, [cb]) 1090 | throw new Error('Pipes are not supported in Chrome Apps.') 1091 | } else { 1092 | // connect(port, [host], [cb]) 1093 | options.port = args[0] 1094 | if (typeof args[1] === 'string') { 1095 | options.host = args[1] 1096 | } 1097 | } 1098 | 1099 | const cb = args[args.length - 1] 1100 | return typeof cb === 'function' ? [options, cb] : [options] 1101 | } 1102 | 1103 | function toNumber (x) { 1104 | return (x = Number(x)) >= 0 ? x : false 1105 | } 1106 | 1107 | function isPipeName (s) { 1108 | return typeof s === 'string' && toNumber(s) === false 1109 | } 1110 | 1111 | // Check that the port number is not NaN when coerced to a number, 1112 | // is an integer and that it falls within the legal range of port numbers. 1113 | function isLegalPort (port) { 1114 | if ((typeof port !== 'number' && typeof port !== 'string') || 1115 | (typeof port === 'string' && port.trim().length === 0)) { 1116 | return false 1117 | } 1118 | return +port === (+port >>> 0) && port <= 0xFFFF 1119 | } 1120 | 1121 | function assertPort (port) { 1122 | if (typeof port !== 'undefined' && !isLegalPort(port)) { 1123 | throw new RangeError('"port" argument must be >= 0 and < 65536') 1124 | } 1125 | } 1126 | 1127 | // Call the getter function to prevent "Unchecked runtime.lastError" errors 1128 | function ignoreLastError () { 1129 | void chrome.runtime.lastError // eslint-disable-line no-void 1130 | } 1131 | 1132 | function chromeCallbackWrap (callback) { 1133 | return () => { 1134 | let error 1135 | if (chrome.runtime.lastError) { 1136 | console.error(chrome.runtime.lastError.message) 1137 | error = new Error(chrome.runtime.lastError.message) 1138 | } 1139 | if (callback) callback(error) 1140 | } 1141 | } 1142 | 1143 | function emitErrorNT (self, err) { 1144 | self.emit('error', err) 1145 | } 1146 | 1147 | // Full list of possible error codes: https://code.google.com/p/chrome-browser/source/browse/trunk/src/net/base/net_error_list.h 1148 | // TODO: Try to reproduce errors in both node & Chrome Apps and extend this list 1149 | // (what conditions lead to EPIPE?) 1150 | const errorChromeToUv = { 1151 | '-10': 'EACCES', 1152 | '-22': 'EACCES', 1153 | '-138': 'EACCES', 1154 | '-147': 'EADDRINUSE', 1155 | '-108': 'EADDRNOTAVAIL', 1156 | '-103': 'ECONNABORTED', 1157 | '-102': 'ECONNREFUSED', 1158 | '-101': 'ECONNRESET', 1159 | '-16': 'EEXIST', 1160 | '-8': 'EFBIG', 1161 | '-109': 'EHOSTUNREACH', 1162 | '-4': 'EINVAL', 1163 | '-23': 'EISCONN', 1164 | '-6': 'ENOENT', 1165 | '-13': 'ENOMEM', 1166 | '-106': 'ENONET', 1167 | '-18': 'ENOSPC', 1168 | '-11': 'ENOSYS', 1169 | '-15': 'ENOTCONN', 1170 | '-105': 'ENOTFOUND', 1171 | '-118': 'ETIMEDOUT', 1172 | '-100': 'EOF' 1173 | } 1174 | function errnoException (err, syscall, details) { 1175 | const uvCode = errorChromeToUv[err] || 'UNKNOWN' 1176 | let message = syscall + ' ' + err + ' ' + details 1177 | if (chrome.runtime.lastError) { 1178 | message += ' ' + chrome.runtime.lastError.message 1179 | } 1180 | message += ' (mapped uv code: ' + uvCode + ')' 1181 | const e = new Error(message) 1182 | e.code = e.errno = uvCode 1183 | // TODO: expose chrome error code; what property name? 1184 | e.syscall = syscall 1185 | return e 1186 | } 1187 | 1188 | function exceptionWithHostPort (err, syscall, address, port, additional) { 1189 | let details 1190 | if (port && port > 0) { 1191 | details = address + ':' + port 1192 | } else { 1193 | details = address 1194 | } 1195 | 1196 | if (additional) { 1197 | details += ' - Local (' + additional + ')' 1198 | } 1199 | const ex = errnoException(err, syscall, details) 1200 | ex.address = address 1201 | if (port) { 1202 | ex.port = port 1203 | } 1204 | return ex 1205 | } 1206 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chrome-net", 3 | "description": "Use the Node `net` API in Chrome Apps", 4 | "version": "3.3.4", 5 | "author": { 6 | "name": "Feross Aboukhadijeh", 7 | "email": "feross@feross.org", 8 | "url": "https://feross.org" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/feross/chrome-net/issues" 12 | }, 13 | "contributors": [ 14 | "John Hiesey ", 15 | "Jan Schär " 16 | ], 17 | "dependencies": { 18 | "inherits": "^2.0.1" 19 | }, 20 | "devDependencies": { 21 | "browserify": "^16.2.3", 22 | "chrome-dgram": "^3.0.1", 23 | "envify": "^4.1.0", 24 | "once": "1.x", 25 | "portfinder": "^1.0.2", 26 | "run-auto": "^2.0.0", 27 | "standard": "*", 28 | "tape": "^5.0.0", 29 | "through": "2.x" 30 | }, 31 | "files": [ 32 | "index.js" 33 | ], 34 | "homepage": "https://github.com/feross/chrome-net", 35 | "keywords": [ 36 | "chrome app", 37 | "chrome.socket", 38 | "socket api", 39 | "net", 40 | "tcp", 41 | "wrapper", 42 | "client", 43 | "server" 44 | ], 45 | "license": "MIT", 46 | "main": "index.js", 47 | "repository": { 48 | "type": "git", 49 | "url": "git://github.com/feross/chrome-net.git" 50 | }, 51 | "scripts": { 52 | "test": "standard", 53 | "test-browser": "tape test/*.js" 54 | }, 55 | "funding": [ 56 | { 57 | "type": "github", 58 | "url": "https://github.com/sponsors/feross" 59 | }, 60 | { 61 | "type": "patreon", 62 | "url": "https://www.patreon.com/feross" 63 | }, 64 | { 65 | "type": "consulting", 66 | "url": "https://feross.org/support" 67 | } 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /test/chrome-app/background.js: -------------------------------------------------------------------------------- 1 | /* global chrome */ 2 | 3 | chrome.app.runtime.onLaunched.addListener(function () { 4 | chrome.app.window.create('window.html', { 5 | bounds: { 6 | width: 100, 7 | height: 100 8 | } 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /test/chrome-app/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | 4 | "name": "Hello World!", 5 | "description": "My first Chrome App.", 6 | "version": "0.1.0", 7 | 8 | "app": { 9 | "background": { 10 | "scripts": ["background.js"] 11 | } 12 | }, 13 | 14 | "permissions": [ 15 | "*://*/*" 16 | ], 17 | 18 | "sockets": { 19 | "udp": { 20 | "bind": "*", 21 | "send": "*" 22 | }, 23 | "tcp": { 24 | "connect": "*" 25 | }, 26 | "tcpServer": { 27 | "listen": "*" 28 | } 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /test/chrome-app/window.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/client/tape-disconnect.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | const net = require('net') 3 | 4 | const PORT0 = Number(process.env.PORT0) 5 | 6 | test('disconnect client socket with wait', function (t) { 7 | const server = net.createServer().listen(PORT0, '127.0.0.1') 8 | server.once('listening', function () { 9 | const socket = net.connect(PORT0, '127.0.0.1') 10 | socket.once('connect', function () { 11 | setTimeout(function () { 12 | socket.destroy() 13 | }, 500) 14 | }) 15 | }) 16 | server.on('connection', function (con) { 17 | con.resume() // allow FIN to be received 18 | con.on('close', function () { 19 | server.close() 20 | t.end() 21 | }) 22 | }) 23 | }) 24 | 25 | test('disconnect server socket with wait', function (t) { 26 | const server = net.createServer().listen(PORT0, '127.0.0.1') 27 | server.once('listening', function () { 28 | const socket = net.connect(PORT0, '127.0.0.1') 29 | socket.once('connect', function () { 30 | socket.resume() // allow FIN to be received 31 | }) 32 | socket.on('close', function () { 33 | server.close() 34 | t.end() 35 | }) 36 | }) 37 | server.on('connection', function (con) { 38 | setTimeout(function () { 39 | con.destroy() 40 | }, 500) 41 | }) 42 | }) 43 | 44 | test('disconnect client socket', function (t) { 45 | const server = net.createServer().listen(PORT0, '127.0.0.1') 46 | server.once('listening', function () { 47 | const socket = net.connect(PORT0, '127.0.0.1') 48 | socket.once('connect', function () { 49 | socket.destroy() 50 | }) 51 | }) 52 | server.on('connection', function (con) { 53 | con.resume() // allow FIN to be received 54 | con.on('close', function () { 55 | server.close() 56 | t.end() 57 | }) 58 | con.on('error', function () {}) 59 | }) 60 | }) 61 | 62 | test('disconnect server socket', function (t) { 63 | const server = net.createServer().listen(PORT0, '127.0.0.1') 64 | server.once('listening', function () { 65 | const socket = net.connect(PORT0, '127.0.0.1') 66 | socket.once('connect', function () { 67 | socket.resume() // allow FIN to be received 68 | }) 69 | socket.on('close', function () { 70 | server.close() 71 | t.end() 72 | }) 73 | socket.on('error', function () {}) 74 | }) 75 | server.on('connection', function (con) { 76 | con.destroy() 77 | }) 78 | }) 79 | -------------------------------------------------------------------------------- /test/client/tape-edge-cases.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | const net = require('net') 3 | 4 | const PORT0 = Number(process.env.PORT0) 5 | const PORT1 = Number(process.env.PORT1) 6 | 7 | // listen 8 | 9 | test('listen on already listening server', function (t) { 10 | const server = net.createServer() 11 | server.listen(0, '127.0.0.1') 12 | server.once('listening', function () { 13 | t.ok(server.address().port, 'a port was assigned') 14 | server.listen(server.address().port, '127.0.0.1') 15 | server.once('listening', function () { 16 | server.close() 17 | t.end() 18 | }) 19 | }) 20 | server.on('error', function (error) { 21 | t.error(error, 'should not trigger EADDRINUSE') 22 | }) 23 | }) 24 | 25 | test('second listen call overrides first', function (t) { 26 | const server = net.createServer() 27 | server.listen(PORT0, '127.0.0.1') 28 | server.listen(PORT1, '127.0.0.1') 29 | server.once('listening', function () { 30 | t.equal(server.address().port, PORT1) 31 | server.close() 32 | t.end() 33 | }) 34 | }) 35 | 36 | test('can cancel listen call', function (t) { 37 | const server = net.createServer() 38 | server.listen(PORT0, '127.0.0.1') 39 | server.close() 40 | server.once('listening', function () { 41 | t.fail('shouldn\'t be listening') 42 | }) 43 | setTimeout(function () { 44 | t.end() 45 | }, 400) 46 | }) 47 | 48 | // connect 49 | 50 | test('connect on already connected socket', function (t) { 51 | const server = net.createServer().listen(PORT0, '127.0.0.1') 52 | server.on('connection', function (con) { 53 | con.resume() // allow FIN to be received 54 | }) 55 | server.once('listening', function () { 56 | const socket = net.connect(PORT0, '127.0.0.1') 57 | socket.once('connect', function () { 58 | socket.connect(PORT0, '127.0.0.1') 59 | socket.once('connect', function () { 60 | socket.destroy() 61 | server.close() 62 | server.on('close', function () { // ensure all connections are closed 63 | t.end() 64 | }) 65 | }) 66 | }) 67 | socket.on('error', function (error) { 68 | t.error(error, 'should not trigger EADDRINUSE') 69 | }) 70 | }) 71 | }) 72 | 73 | test('second connect call overrides first', function (t) { 74 | const server = net.createServer().listen(PORT1, '127.0.0.1') 75 | server.once('listening', function () { 76 | const socket = net.connect(PORT0, '127.0.0.1') 77 | socket.connect(PORT1, '127.0.0.1') 78 | socket.once('connect', function () { 79 | t.equal(socket.remotePort, PORT1) 80 | socket.destroy() 81 | server.close() 82 | t.end() 83 | }) 84 | socket.on('error', function (error) { 85 | t.error(error) 86 | }) 87 | }) 88 | }) 89 | 90 | test('can cancel connect call', function (t) { 91 | const socket = net.connect(PORT0, '127.0.0.1') 92 | socket.destroy() 93 | socket.once('connect', function () { 94 | t.fail('shouldn\'t be connected') 95 | }) 96 | socket.on('error', function (error) { 97 | t.error(error) 98 | }) 99 | setTimeout(function () { 100 | t.end() 101 | }, 400) 102 | }) 103 | -------------------------------------------------------------------------------- /test/client/tape-helper.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | const net = require('net') 3 | 4 | const TAPE_PORT = Number(process.env.TAPE_PORT) 5 | 6 | const con = net.connect(TAPE_PORT, '127.0.0.1') 7 | 8 | let success = false 9 | test.createStream().on('data', function (log) { 10 | con.write(JSON.stringify({ op: 'log', log: log.toString() }) + '\n') 11 | success = log === '\n# ok\n' 12 | }).on('end', function () { 13 | con.write(JSON.stringify({ op: 'end', success }) + '\n') 14 | con.end() 15 | }) 16 | 17 | require('./tape-tcp.js') 18 | require('./tape-disconnect.js') 19 | require('./tape-edge-cases.js') 20 | -------------------------------------------------------------------------------- /test/client/tape-tcp.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | const net = require('net') 3 | 4 | const PORT0 = Number(process.env.PORT0) 5 | 6 | test('TCP listen', function (t) { 7 | t.throws(function () { 8 | net.createServer().listen({ fd: 0 }) 9 | }, /fd is not supported/, 'throws when trying to use named pipes') 10 | t.throws(function () { 11 | net.createServer().listen({ path: 'pipename' }) 12 | }, /Pipes are not supported/, 'throws when trying to use named pipes') 13 | t.throws(function () { 14 | net.createServer().listen(65536) 15 | }, /"port" argument must be >= 0 and < 65536/, 'throws when using invalid port 65536') 16 | t.end() 17 | }) 18 | 19 | test('TCP connect', function (t) { 20 | t.throws(function () { 21 | net.connect('pipename') 22 | }, /Pipes are not supported/, 'throws when trying to use named pipes') 23 | t.throws(function () { 24 | net.connect(65536) 25 | }, /"port" option should be >= 0 and < 65536/, 'throws when using invalid port 65536') 26 | t.end() 27 | }) 28 | 29 | function isPaused (socket) { 30 | return socket.isPaused() 31 | } 32 | 33 | test('Pause on connect', function (t) { 34 | const server = net.createServer({ pauseOnConnect: true }) 35 | let socket 36 | server.listen(0, '127.0.0.1') 37 | server.on('connection', function (con) { 38 | con.on('data', function () {}) 39 | t.ok(isPaused(con), 'isPaused() returns true for incoming socket') 40 | socket.destroy() 41 | server.close() 42 | t.end() 43 | }) 44 | server.on('listening', function () { 45 | t.ok(server.address().port, 'a port was assigned') 46 | socket = net.connect(server.address().port, '127.0.0.1') 47 | }) 48 | }) 49 | 50 | test('Pause on connect = false', function (t) { 51 | const server = net.createServer() 52 | let socket 53 | server.listen(0, '127.0.0.1') 54 | server.on('connection', function (con) { 55 | con.on('data', function () {}) 56 | t.ok(!isPaused(con), 'isPaused() returns false for incoming socket') 57 | t.ok(!isPaused(socket), 'isPaused() returns false for outgoing socket') 58 | socket.destroy() 59 | server.close() 60 | t.end() 61 | }) 62 | server.on('listening', function () { 63 | t.ok(server.address().port, 'a port was assigned') 64 | socket = net.connect(server.address().port, '127.0.0.1') 65 | socket.on('data', function () {}) 66 | }) 67 | }) 68 | 69 | test('server only emits close when 0 connections', function (t) { 70 | let socketClosed = false 71 | const server = net.createServer().listen(PORT0, '127.0.0.1') 72 | server.on('listening', function () { 73 | const socket = net.connect(PORT0, '127.0.0.1') 74 | socket.once('connect', function () { 75 | server.close() 76 | setTimeout(function () { 77 | socket.destroy() 78 | socketClosed = true 79 | }, 300) 80 | }) 81 | }) 82 | server.on('close', function () { 83 | t.ok(socketClosed, 'socket is closed on server close event') 84 | t.end() 85 | }) 86 | server.on('connection', function (con) { 87 | con.resume() // allow FIN to be received 88 | }) 89 | }) 90 | 91 | test('IPv4/v6 for listen', function (t) { 92 | const server = net.createServer().listen(0, '127.0.0.1') 93 | server.once('listening', function () { 94 | t.equal(server.address().family, 'IPv4') 95 | server.listen(0, '::1') 96 | server.once('listening', function () { 97 | t.equal(server.address().family, 'IPv6') 98 | server.close() 99 | t.end() 100 | }) 101 | }) 102 | server.on('error', function (error) { 103 | t.error(error) 104 | server.close() 105 | t.end() 106 | }) 107 | }) 108 | 109 | test('IPv4/v6 for connect', function (t) { 110 | const server = net.createServer().listen(PORT0, '::0') 111 | server.once('listening', function () { 112 | const socket = net.connect(PORT0, '127.0.0.1') 113 | socket.once('connect', function () { 114 | t.equal(socket.remoteFamily, 'IPv4') 115 | socket.connect(PORT0, '::1') 116 | socket.once('connect', function () { 117 | t.equal(socket.remoteFamily, 'IPv6') 118 | socket.destroy() 119 | server.close() 120 | t.end() 121 | }) 122 | }) 123 | socket.on('error', function (error) { 124 | t.error(error) 125 | socket.destroy() 126 | server.close() 127 | t.end() 128 | }) 129 | }) 130 | }) 131 | 132 | test('socket setTimeout', function (t) { 133 | const server = net.createServer().listen(PORT0, '127.0.0.1') 134 | server.once('listening', function () { 135 | const socket = net.connect(PORT0, '127.0.0.1') 136 | let timeoutExpected = false 137 | socket.setTimeout(100, function () { 138 | t.ok(timeoutExpected, 'Timeout is expected') 139 | socket.destroy() 140 | server.close() 141 | t.end() 142 | }) 143 | 144 | setTimeout(function () { 145 | socket.write('Ping') 146 | }, 60) 147 | socket.on('data', function () { 148 | setTimeout(function () { 149 | timeoutExpected = true 150 | }, 60) 151 | }) 152 | 153 | server.on('connection', function (con) { 154 | con.on('data', function () { 155 | setTimeout(function () { 156 | con.write('Pong') 157 | }, 60) 158 | }) 159 | }) 160 | }) 161 | }) 162 | 163 | test('accepted socket', function (t) { 164 | const server = net.createServer().listen(PORT0, '127.0.0.1') 165 | server.once('listening', function () { 166 | const socket = net.connect(PORT0, '127.0.0.1') 167 | 168 | server.on('connection', function (con) { 169 | t.ok(con.writable, 'socket.writable is true') 170 | socket.destroy() 171 | server.close() 172 | t.end() 173 | }) 174 | }) 175 | }) 176 | -------------------------------------------------------------------------------- /test/client/tcp-connect-direct.js: -------------------------------------------------------------------------------- 1 | const net = require('net') 2 | 3 | const PORT = Number(process.env.PORT) 4 | 5 | const client = new net.Socket() 6 | 7 | // If any errors are emitted, log them 8 | client.on('error', function (err) { 9 | console.error(err.stack) 10 | }) 11 | 12 | client.on('data', function (data) { 13 | if (data.toString() === 'boop') { 14 | client.write('pass') 15 | } else { 16 | client.write('fail') 17 | } 18 | }) 19 | 20 | client.connect(PORT, '127.0.0.1') 21 | client.write('beep') 22 | 23 | // TODO: 24 | // - test bytesWritten 25 | // - test bytesRead 26 | 27 | // streaming 28 | // var through = require('through') 29 | // client.pipe(through(function (data) { 30 | // console.log(bops.to(data)) 31 | // })) 32 | -------------------------------------------------------------------------------- /test/client/tcp-connect.js: -------------------------------------------------------------------------------- 1 | const net = require('net') 2 | 3 | const PORT = Number(process.env.PORT) 4 | 5 | const client = net.createConnection({ 6 | port: PORT, 7 | host: '127.0.0.1' 8 | }) 9 | 10 | // If any errors are emitted, log them 11 | client.on('error', function (err) { 12 | console.error(err.stack) 13 | }) 14 | 15 | client.on('data', function (data) { 16 | if (data.toString() === 'boop') { 17 | client.write('pass') 18 | } else { 19 | client.write('fail') 20 | } 21 | }) 22 | 23 | client.write('beep') 24 | 25 | // TODO: 26 | // - test bytesWritten 27 | // - test bytesRead 28 | 29 | // streaming 30 | // var through = require('through') 31 | // client.pipe(through(function (data) { 32 | // console.log(bops.to(data)) 33 | // })) 34 | -------------------------------------------------------------------------------- /test/client/tcp-listen.js: -------------------------------------------------------------------------------- 1 | const dgram = require('dgram') 2 | const net = require('net') 3 | 4 | const LISTEN_PORT = Number(process.env.LISTEN_PORT) 5 | const READY_PORT = Number(process.env.READY_PORT) 6 | 7 | const server = net.createServer() 8 | 9 | // If any errors are emitted, log them 10 | server.on('error', function (err) { 11 | console.error(err.stack) 12 | }) 13 | 14 | let readySock 15 | server.on('listening', function () { 16 | // Report to node that the TCP server is listening 17 | readySock = dgram.createSocket('udp4') 18 | readySock.on('error', function (err) { 19 | console.error(err.stack) 20 | }) 21 | readySock.send('listening', 0, 'listening'.length, READY_PORT, '127.0.0.1') 22 | }) 23 | 24 | server.on('connection', function (sock) { 25 | console.log('Connection opened from ' + sock.remoteAddress + ':' + sock.remotePort) 26 | 27 | sock.on('error', function (err) { 28 | console.error(err.stack) 29 | sock.write(err.message) 30 | }) 31 | 32 | sock.on('data', function (data) { 33 | if (data.toString() === 'beep') { 34 | sock.write('boop') 35 | } else { 36 | sock.write('fail') 37 | } 38 | }) 39 | 40 | // test that client stream ends correctly 41 | sock.on('end', function () { 42 | readySock.send('end', 0, 'end'.length, READY_PORT, '127.0.0.1') 43 | }) 44 | }) 45 | 46 | server.listen(LISTEN_PORT) 47 | -------------------------------------------------------------------------------- /test/client/tcp-send-buffer.js: -------------------------------------------------------------------------------- 1 | const net = require('net') 2 | const Buffer = require('buffer').Buffer 3 | 4 | const PORT = Number(process.env.PORT) 5 | 6 | const client = net.createConnection({ 7 | port: PORT, 8 | host: '127.0.0.1' 9 | }) 10 | 11 | // If any errors are emitted, log them 12 | client.on('error', function (err) { 13 | console.error(err.stack) 14 | }) 15 | 16 | client.on('data', function (data) { 17 | if (data.toString() === 'boop') { 18 | client.write('pass') 19 | } else { 20 | client.write('fail') 21 | } 22 | }) 23 | 24 | client.write(Buffer.from('|beep|').slice(1, 5)) 25 | 26 | // TODO: 27 | // - test bytesWritten 28 | // - test bytesRead 29 | 30 | // streaming 31 | // var through = require('through') 32 | // client.pipe(through(function (data) { 33 | // console.log(bops.to(data)) 34 | // })) 35 | -------------------------------------------------------------------------------- /test/function-bind.js: -------------------------------------------------------------------------------- 1 | module.exports = Function.prototype.bind 2 | -------------------------------------------------------------------------------- /test/helper.js: -------------------------------------------------------------------------------- 1 | const browserify = require('browserify') 2 | const builtins = require('browserify/lib/builtins.js') 3 | const cp = require('child_process') 4 | const envify = require('envify/custom') 5 | const fs = require('fs') 6 | const once = require('once') 7 | const path = require('path') 8 | 9 | let CHROME 10 | if (process.env.CHROME) { 11 | CHROME = process.env.CHROME 12 | } else if (process.platform === 'win32') { 13 | CHROME = '"%ProgramFiles(x86)%\\Google\\Chrome\\Application\\chrome.exe"' 14 | } else { 15 | CHROME = '/Applications/Google\\ Chrome\\ Canary.app/Contents/MacOS/Google\\ Chrome\\ Canary' 16 | } 17 | 18 | const BUNDLE_PATH = path.join(__dirname, 'chrome-app/bundle.js') 19 | 20 | builtins.net = require.resolve('../') 21 | builtins.dgram = require.resolve('chrome-dgram') 22 | builtins['function-bind'] = require.resolve('./function-bind.js') 23 | 24 | exports.browserify = function (filename, env, cb) { 25 | if (!env) env = {} 26 | if (!cb) cb = function () {} 27 | cb = once(cb) 28 | 29 | const b = browserify() 30 | b.add(path.join(__dirname, 'client', filename)) 31 | b.transform(envify(env)) 32 | 33 | b.bundle() 34 | .pipe(fs.createWriteStream(BUNDLE_PATH)) 35 | .on('close', cb) 36 | .on('error', cb) 37 | } 38 | 39 | exports.launchBrowser = function () { 40 | // chrome 40.0.2188.2 won't open extensions without absolute path. 41 | const app = path.join(__dirname, '..', 'test/chrome-app') 42 | const command = CHROME + ' --load-and-launch-app=' + app 43 | const env = { cwd: path.join(__dirname, '..') } 44 | 45 | return cp.exec(command, env, function () {}) 46 | } 47 | -------------------------------------------------------------------------------- /test/ip.js: -------------------------------------------------------------------------------- 1 | const chromeNet = require('../') 2 | const test = require('tape') 3 | 4 | test('net.isIP', function (t) { 5 | t.ok(chromeNet.isIP('1.2.3.4') === 4) 6 | t.ok(chromeNet.isIP('2001:0db8:3c4d:0015:0000:0000:abcd:ef12') === 6) 7 | 8 | t.ok(chromeNet.isIP('260.0.0.0') === 0) 9 | t.ok(chromeNet.isIP('2001:0db8:3c4d:0015:0000:0000:abcd:ef12:0000') === 0) 10 | t.ok(!chromeNet.isIP('')) 11 | t.ok(!chromeNet.isIP('abc')) 12 | t.ok(!chromeNet.isIP(undefined)) 13 | t.ok(!chromeNet.isIP(null)) 14 | t.ok(!chromeNet.isIP({})) 15 | t.end() 16 | }) 17 | 18 | test('net.isIPv4', function (t) { 19 | t.ok(chromeNet.isIPv4('1.2.3.4')) 20 | 21 | t.ok(!chromeNet.isIPv4('2001:0db8:3c4d:0015:0000:0000:abcd:ef12')) 22 | t.ok(!chromeNet.isIPv4('')) 23 | t.ok(!chromeNet.isIPv4('abc')) 24 | t.ok(!chromeNet.isIPv4(undefined)) 25 | t.ok(!chromeNet.isIPv4(null)) 26 | t.ok(!chromeNet.isIPv4({})) 27 | t.end() 28 | }) 29 | 30 | test('net.isIPv6', function (t) { 31 | t.ok(chromeNet.isIPv6('2001:0db8:3c4d:0015:0000:0000:abcd:ef12')) 32 | 33 | t.ok(!chromeNet.isIPv6('1.2.3.4')) 34 | t.ok(!chromeNet.isIPv6('')) 35 | t.ok(!chromeNet.isIPv6('abc')) 36 | t.ok(!chromeNet.isIPv6(undefined)) 37 | t.ok(!chromeNet.isIPv6(null)) 38 | t.ok(!chromeNet.isIPv6({})) 39 | t.end() 40 | }) 41 | -------------------------------------------------------------------------------- /test/tcp-connect.js: -------------------------------------------------------------------------------- 1 | const helper = require('./helper') 2 | const net = require('net') 3 | const portfinder = require('portfinder') 4 | const test = require('tape') 5 | 6 | test('TCP connect works (echo test)', function (t) { 7 | portfinder.getPort(function (err, port) { 8 | t.error(err, 'Found free port') 9 | let child 10 | 11 | const server = net.createServer() 12 | 13 | server.on('listening', function () { 14 | const env = { PORT: port } 15 | helper.browserify('tcp-connect.js', env, function (err) { 16 | t.error(err, 'Clean browserify build') 17 | child = helper.launchBrowser() 18 | }) 19 | }) 20 | 21 | let i = 0 22 | server.on('connection', function (c) { 23 | c.on('data', function (data) { 24 | if (i === 0) { 25 | t.equal(data.toString(), 'beep', 'Got beep') 26 | c.write('boop', 'utf8') 27 | } else if (i === 1) { 28 | t.equal(data.toString(), 'pass', 'Boop was received') 29 | c.end() 30 | server.close() 31 | child.kill() 32 | t.end() 33 | } else { 34 | t.fail('TCP client sent unexpected data') 35 | } 36 | i += 1 37 | }) 38 | }) 39 | 40 | server.listen(port) 41 | }) 42 | }) 43 | 44 | test('TCP connect works with direct API (echo test)', function (t) { 45 | portfinder.getPort(function (err, port) { 46 | t.error(err, 'Found free port') 47 | let child 48 | 49 | const server = net.createServer() 50 | 51 | server.on('listening', function () { 52 | const env = { PORT: port } 53 | helper.browserify('tcp-connect-direct.js', env, function (err) { 54 | t.error(err, 'Clean browserify build') 55 | child = helper.launchBrowser() 56 | }) 57 | }) 58 | 59 | let i = 0 60 | server.on('connection', function (c) { 61 | c.on('data', function (data) { 62 | if (i === 0) { 63 | t.equal(data.toString(), 'beep', 'Got beep') 64 | c.write('boop', 'utf8') 65 | } else if (i === 1) { 66 | t.equal(data.toString(), 'pass', 'Boop was received') 67 | c.end() 68 | server.close() 69 | child.kill() 70 | t.end() 71 | } else { 72 | t.fail('TCP client sent unexpected data') 73 | } 74 | i += 1 75 | }) 76 | }) 77 | 78 | server.listen(port) 79 | }) 80 | }) 81 | -------------------------------------------------------------------------------- /test/tcp-listen.js: -------------------------------------------------------------------------------- 1 | const auto = require('run-auto') 2 | const dgram = require('dgram') 3 | const helper = require('./helper') 4 | const net = require('net') 5 | const portfinder = require('portfinder') 6 | const test = require('tape') 7 | 8 | test('TCP listen works (echo test)', function (t) { 9 | auto({ 10 | listenPort: function (cb) { 11 | portfinder.getPort(cb) 12 | }, 13 | readyPort: function (cb) { 14 | portfinder.getPort(cb) 15 | } 16 | }, function (err, r) { 17 | t.error(err, 'Found free ports') 18 | let child 19 | 20 | // Socket for client to notify node when its TCP server is listening 21 | const readySocket = dgram.createSocket('udp4') 22 | readySocket.on('listening', function () { 23 | // Start app 24 | const env = { LISTEN_PORT: r.listenPort, READY_PORT: r.readyPort } 25 | helper.browserify('tcp-listen.js', env, function (err) { 26 | t.error(err, 'Clean browserify build') 27 | child = helper.launchBrowser() 28 | }) 29 | }) 30 | 31 | readySocket.on('message', function (message, remote) { 32 | if (message.toString() === 'listening') { 33 | t.pass('Client listens') 34 | 35 | // Do TCP echo test 36 | const socket = net.createConnection({ port: r.listenPort }) 37 | socket.on('connect', function () { 38 | socket.write('beep', 'utf8') 39 | }) 40 | 41 | let i = 0 42 | socket.on('data', function (data) { 43 | if (i === 0) { 44 | t.equal(data.toString(), 'boop', 'Beep/boop looks good') 45 | socket.end() 46 | } else { 47 | t.fail('TCP server sent unexpected data') 48 | } 49 | i += 1 50 | }) 51 | } else if (message.toString() === 'end') { 52 | t.pass('Client stream ended correctly') 53 | readySocket.close() 54 | child.kill() 55 | t.end() 56 | } 57 | }) 58 | 59 | readySocket.bind(r.readyPort) 60 | }) 61 | }) 62 | -------------------------------------------------------------------------------- /test/tcp-send-buffer.js: -------------------------------------------------------------------------------- 1 | const helper = require('./helper') 2 | const net = require('net') 3 | const portfinder = require('portfinder') 4 | const test = require('tape') 5 | 6 | test('TCP send buffer works', function (t) { 7 | portfinder.getPort(function (err, port) { 8 | t.error(err, 'Found free port') 9 | let child 10 | 11 | const server = net.createServer() 12 | 13 | server.on('listening', function () { 14 | const env = { PORT: port } 15 | helper.browserify('tcp-send-buffer.js', env, function (err) { 16 | t.error(err, 'Clean browserify build') 17 | child = helper.launchBrowser() 18 | }) 19 | }) 20 | 21 | let i = 0 22 | server.on('connection', function (c) { 23 | c.on('data', function (data) { 24 | if (i === 0) { 25 | t.equal(data.toString(), 'beep', 'Got beep') 26 | c.write('boop', 'utf8') 27 | } else if (i === 1) { 28 | t.equal(data.toString(), 'pass', 'Boop was received') 29 | c.end() 30 | server.close() 31 | child.kill() 32 | t.end() 33 | } else { 34 | t.fail('TCP client sent unexpected data') 35 | } 36 | i += 1 37 | }) 38 | }) 39 | 40 | server.listen(port) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /test/tcp-tape.js: -------------------------------------------------------------------------------- 1 | const auto = require('run-auto') 2 | const helper = require('./helper') 3 | const net = require('net') 4 | const portfinder = require('portfinder') 5 | const test = require('tape') 6 | 7 | test('tape running on Chrome App', function (t) { 8 | auto({ 9 | tapePort: function (cb) { 10 | portfinder.getPort(cb) 11 | }, 12 | port0: function (cb) { 13 | portfinder.getPort(cb) 14 | }, 15 | port1: function (cb) { 16 | portfinder.getPort(cb) 17 | } 18 | }, function (err, r) { 19 | t.error(err, 'Found free ports') 20 | let child 21 | 22 | const server = net.createServer() 23 | 24 | server.on('listening', function () { 25 | const env = { TAPE_PORT: r.tapePort, PORT0: r.port0, PORT1: r.port1 } 26 | helper.browserify('tape-helper.js', env, function (err) { 27 | t.error(err, 'Clean browserify build') 28 | child = helper.launchBrowser() 29 | }) 30 | }) 31 | 32 | server.on('connection', function (c) { 33 | console.log('\noutput from tape on Chrome ------------------------------') 34 | let rest = '' 35 | c.on('data', function (data) { 36 | data = (rest + data).split('\n') 37 | rest = data.pop() 38 | for (let i = 0; i < data.length; i++) { 39 | const msg = JSON.parse(data[i]) 40 | switch (msg.op) { 41 | case 'log': 42 | process.stdout.write(msg.log) 43 | break 44 | case 'end': 45 | console.log('end output from tape on Chrome --------------------------\n') 46 | t.ok(msg.success, 'all tests on Chrome App passed') 47 | c.end() 48 | server.close() 49 | child.kill() 50 | t.end() 51 | break 52 | } 53 | } 54 | }) 55 | }) 56 | 57 | server.listen(r.tapePort) 58 | }) 59 | }) 60 | --------------------------------------------------------------------------------