├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── package.json └── test ├── chrome-app ├── background.js ├── manifest.json └── window.html ├── client └── dgram.js ├── dgram.js └── helper.js /.gitignore: -------------------------------------------------------------------------------- 1 | bundle.js 2 | -------------------------------------------------------------------------------- /.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 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-dgram [![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-dgram/master.svg 4 | [travis-url]: https://travis-ci.org/feross/chrome-dgram 5 | [npm-image]: https://img.shields.io/npm/v/chrome-dgram.svg 6 | [npm-url]: https://npmjs.org/package/chrome-dgram 7 | [downloads-image]: https://img.shields.io/npm/dm/chrome-dgram.svg 8 | [downloads-url]: https://npmjs.org/package/chrome-dgram 9 | [standard-image]: https://img.shields.io/badge/code_style-standard-brightgreen.svg 10 | [standard-url]: https://standardjs.com 11 | 12 | ### Use the Node `dgram` API in Chrome Apps 13 | 14 | This module lets you use the Node.js [dgram](http://nodejs.org/api/dgram.html) (UDP) API in [Chrome Packaged Apps](http://developer.chrome.com/apps/about_apps.html). 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 | This module is used by [webtorrent](https://github.com/feross/webtorrent). 19 | 20 | ## install 21 | 22 | ``` 23 | npm install chrome-dgram 24 | ``` 25 | 26 | ## methods 27 | 28 | Use node's `dgram` API, including all parameter list shorthands and variations. 29 | 30 | Example UDP client/bind: 31 | 32 | ```js 33 | var dgram = require('chrome-dgram') 34 | 35 | var sock = dgram.createSocket('udp4') 36 | 37 | sock.send('beep', 0, 'beep'.length, 1337, '127.0.0.1') 38 | 39 | sock.on('message', function (data, rInfo) { 40 | console.log('Got data from ' + rInfo.address + ':' + rInfo.port) 41 | console.log(data) 42 | }) 43 | ``` 44 | 45 | See nodejs.org for full API documentation: [dgram](http://nodejs.org/api/dgram.html) 46 | 47 | ## contribute 48 | 49 | 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 Canary on Mac. If you're on Windows or Linux, feel free to send a pull request to fix this limitation. 50 | 51 | ## license 52 | 53 | MIT. Copyright (c) [Feross Aboukhadijeh](http://feross.org) & John Hiesey. 54 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! chrome-dgram. MIT License. Feross Aboukhadijeh */ 2 | /* global chrome */ 3 | 4 | /** 5 | * UDP / Datagram Sockets 6 | * ====================== 7 | * 8 | * Datagram sockets are available through require('chrome-dgram'). 9 | */ 10 | 11 | exports.Socket = Socket 12 | 13 | const EventEmitter = require('events').EventEmitter 14 | const inherits = require('inherits') 15 | const series = require('run-series') 16 | 17 | const BIND_STATE_UNBOUND = 0 18 | const BIND_STATE_BINDING = 1 19 | const BIND_STATE_BOUND = 2 20 | 21 | // Track open sockets to route incoming data (via onReceive) to the right handlers. 22 | const sockets = {} 23 | 24 | // Thorough check for Chrome App since both Edge and Chrome implement dummy chrome object 25 | if ( 26 | typeof chrome === 'object' && 27 | typeof chrome.runtime === 'object' && 28 | typeof chrome.runtime.id === 'string' && 29 | typeof chrome.sockets === 'object' && 30 | typeof chrome.sockets.udp === 'object' 31 | ) { 32 | chrome.sockets.udp.onReceive.addListener(onReceive) 33 | chrome.sockets.udp.onReceiveError.addListener(onReceiveError) 34 | } 35 | 36 | function onReceive (info) { 37 | if (info.socketId in sockets) { 38 | sockets[info.socketId]._onReceive(info) 39 | } else { 40 | console.error('Unknown socket id: ' + info.socketId) 41 | } 42 | } 43 | 44 | function onReceiveError (info) { 45 | if (info.socketId in sockets) { 46 | sockets[info.socketId]._onReceiveError(info.resultCode) 47 | } else { 48 | console.error('Unknown socket id: ' + info.socketId) 49 | } 50 | } 51 | 52 | /** 53 | * dgram.createSocket(type, [callback]) 54 | * 55 | * Creates a datagram Socket of the specified types. Valid types are `udp4` 56 | * and `udp6`. 57 | * 58 | * Takes an optional callback which is added as a listener for message events. 59 | * 60 | * Call socket.bind if you want to receive datagrams. socket.bind() will bind 61 | * to the "all interfaces" address on a random port (it does the right thing 62 | * for both udp4 and udp6 sockets). You can then retrieve the address and port 63 | * with socket.address().address and socket.address().port. 64 | * 65 | * @param {string} type Either 'udp4' or 'udp6' 66 | * @param {function} listener Attached as a listener to message events. 67 | * Optional 68 | * @return {Socket} Socket object 69 | */ 70 | exports.createSocket = function (type, listener) { 71 | return new Socket(type, listener) 72 | } 73 | 74 | inherits(Socket, EventEmitter) 75 | 76 | /** 77 | * Class: dgram.Socket 78 | * 79 | * The dgram Socket class encapsulates the datagram functionality. It should 80 | * be created via `dgram.createSocket(type, [callback])`. 81 | * 82 | * Event: 'message' 83 | * - msg Buffer object. The message 84 | * - rinfo Object. Remote address information 85 | * Emitted when a new datagram is available on a socket. msg is a Buffer and 86 | * rinfo is an object with the sender's address information and the number 87 | * of bytes in the datagram. 88 | * 89 | * Event: 'listening' 90 | * Emitted when a socket starts listening for datagrams. This happens as soon 91 | * as UDP sockets are created. 92 | * 93 | * Event: 'close' 94 | * Emitted when a socket is closed with close(). No new message events will 95 | * be emitted on this socket. 96 | * 97 | * Event: 'error' 98 | * - exception Error object 99 | * Emitted when an error occurs. 100 | */ 101 | function Socket (options, listener) { 102 | const self = this 103 | EventEmitter.call(self) 104 | if (typeof options === 'string') options = { type: options } 105 | if (options.type !== 'udp4') throw new Error('Bad socket type specified. Valid types are: udp4') 106 | 107 | if (typeof listener === 'function') self.on('message', listener) 108 | 109 | self._destroyed = false 110 | self._bindState = BIND_STATE_UNBOUND 111 | self._bindTasks = [] 112 | } 113 | 114 | /** 115 | * socket.bind(port, [address], [callback]) 116 | * 117 | * For UDP sockets, listen for datagrams on a named port and optional address. 118 | * If address is not specified, the OS will try to listen on all addresses. 119 | * After binding is done, a "listening" event is emitted and the callback(if 120 | * specified) is called. Specifying both a "listening" event listener and 121 | * callback is not harmful but not very useful. 122 | * 123 | * A bound datagram socket keeps the node process running to receive 124 | * datagrams. 125 | * 126 | * If binding fails, an "error" event is generated. In rare case (e.g. binding 127 | * a closed socket), an Error may be thrown by this method. 128 | * 129 | * @param {number} port 130 | * @param {string} address Optional 131 | * @param {function} callback Function with no parameters, Optional. Callback 132 | * when binding is done. 133 | */ 134 | Socket.prototype.bind = function (port, address, callback) { 135 | const self = this 136 | if (typeof address === 'function') { 137 | callback = address 138 | address = undefined 139 | } 140 | 141 | if (!address) address = '0.0.0.0' 142 | 143 | if (!port) port = 0 144 | 145 | if (self._bindState !== BIND_STATE_UNBOUND) throw new Error('Socket is already bound') 146 | 147 | self._bindState = BIND_STATE_BINDING 148 | 149 | if (typeof callback === 'function') self.once('listening', callback) 150 | 151 | chrome.sockets.udp.create(function (createInfo) { 152 | self.id = createInfo.socketId 153 | 154 | sockets[self.id] = self 155 | 156 | const bindFns = self._bindTasks.map(function (t) { return t.fn }) 157 | 158 | series(bindFns, function (err) { 159 | if (err) return self.emit('error', err) 160 | chrome.sockets.udp.bind(self.id, address, port, function (result) { 161 | if (result < 0) { 162 | self.emit('error', new Error('Socket ' + self.id + ' failed to bind. ' + 163 | chrome.runtime.lastError.message)) 164 | return 165 | } 166 | chrome.sockets.udp.getInfo(self.id, function (socketInfo) { 167 | if (!socketInfo.localPort || !socketInfo.localAddress) { 168 | self.emit('error', new Error('Cannot get local port/address for Socket ' + self.id)) 169 | return 170 | } 171 | 172 | self._port = socketInfo.localPort 173 | self._address = socketInfo.localAddress 174 | 175 | self._bindState = BIND_STATE_BOUND 176 | self.emit('listening') 177 | 178 | self._bindTasks.forEach(function (t) { 179 | t.callback() 180 | }) 181 | }) 182 | }) 183 | }) 184 | }) 185 | } 186 | 187 | /** 188 | * Internal function to receive new messages and emit `message` events. 189 | */ 190 | Socket.prototype._onReceive = function (info) { 191 | const self = this 192 | 193 | const buf = Buffer.from(new Uint8Array(info.data)) 194 | const rinfo = { 195 | address: info.remoteAddress, 196 | family: 'IPv4', 197 | port: info.remotePort, 198 | size: buf.length 199 | } 200 | self.emit('message', buf, rinfo) 201 | } 202 | 203 | Socket.prototype._onReceiveError = function (resultCode) { 204 | const self = this 205 | self.emit('error', new Error('Socket ' + self.id + ' receive error ' + resultCode)) 206 | } 207 | 208 | /** 209 | * socket.send(buf, offset, length, port, address, [callback]) 210 | * 211 | * For UDP sockets, the destination port and IP address must be 212 | * specified. A string may be supplied for the address parameter, and it will 213 | * be resolved with DNS. An optional callback may be specified to detect any 214 | * DNS errors and when buf may be re-used. Note that DNS lookups will delay 215 | * the time that a send takes place, at least until the next tick. The only 216 | * way to know for sure that a send has taken place is to use the callback. 217 | * 218 | * If the socket has not been previously bound with a call to bind, it's 219 | * assigned a random port number and bound to the "all interfaces" address 220 | * (0.0.0.0 for udp4 sockets, ::0 for udp6 sockets). 221 | * 222 | * @param {Buffer|Arrayish|string} buf Message to be sent 223 | * @param {number} offset Offset in the buffer where the message starts. Optional. 224 | * @param {number} length Number of bytes in the message. Optional. 225 | * @param {number} port destination port 226 | * @param {string} address destination IP 227 | * @param {function} callback Callback when message is done being delivered. 228 | * Optional. 229 | * 230 | * Valid combinations: 231 | * send(buffer, offset, length, port, address, callback) 232 | * send(buffer, offset, length, port, address) 233 | * send(buffer, offset, length, port) 234 | * send(bufferOrList, port, address, callback) 235 | * send(bufferOrList, port, address) 236 | * send(bufferOrList, port) 237 | * 238 | */ 239 | Socket.prototype.send = function (buffer, offset, length, port, address, callback) { 240 | const self = this 241 | 242 | let list 243 | 244 | if (address || (port && typeof port !== 'function')) { 245 | buffer = sliceBuffer(buffer, offset, length) 246 | } else { 247 | callback = port 248 | port = offset 249 | address = length 250 | } 251 | 252 | if (!Array.isArray(buffer)) { 253 | if (typeof buffer === 'string') { 254 | list = [Buffer.from(buffer)] 255 | } else if (!(buffer instanceof Buffer)) { 256 | throw new TypeError('First argument must be a buffer or a string') 257 | } else { 258 | list = [buffer] 259 | } 260 | } else if (!(list = fixBufferList(buffer))) { 261 | throw new TypeError('Buffer list arguments must be buffers or strings') 262 | } 263 | 264 | port = port >>> 0 265 | if (port === 0 || port > 65535) { 266 | throw new RangeError('Port should be > 0 and < 65536') 267 | } 268 | 269 | // Normalize callback so it's always a function 270 | if (typeof callback !== 'function') { 271 | callback = function () {} 272 | } 273 | 274 | if (self._bindState === BIND_STATE_UNBOUND) self.bind(0) 275 | 276 | // If the socket hasn't been bound yet, push the outbound packet onto the 277 | // send queue and send after binding is complete. 278 | if (self._bindState !== BIND_STATE_BOUND) { 279 | // If the send queue hasn't been initialized yet, do it, and install an 280 | // event handler that flishes the send queue after binding is done. 281 | if (!self._sendQueue) { 282 | self._sendQueue = [] 283 | self.once('listening', function () { 284 | // Flush the send queue. 285 | for (let i = 0; i < self._sendQueue.length; i++) { 286 | self.send.apply(self, self._sendQueue[i]) 287 | } 288 | self._sendQueue = undefined 289 | }) 290 | } 291 | self._sendQueue.push([buffer, offset, length, port, address, callback]) 292 | return 293 | } 294 | 295 | const ab = Buffer.concat(list).buffer 296 | 297 | chrome.sockets.udp.send(self.id, ab, address, port, function (sendInfo) { 298 | if (sendInfo.resultCode < 0) { 299 | const err = new Error('Socket ' + self.id + ' send error ' + sendInfo.resultCode) 300 | callback(err) 301 | self.emit('error', err) 302 | } else { 303 | callback(null) 304 | } 305 | }) 306 | } 307 | 308 | function sliceBuffer (buffer, offset, length) { 309 | if (typeof buffer === 'string') { 310 | buffer = Buffer.from(buffer) 311 | } else if (!(buffer instanceof Buffer)) { 312 | throw new TypeError('First argument must be a buffer or string') 313 | } 314 | 315 | offset = offset >>> 0 316 | length = length >>> 0 317 | 318 | // assuming buffer is browser implementation (`buffer` package on npm) 319 | let buf = buffer.buffer 320 | if (buffer.byteOffset || buffer.byteLength !== buf.byteLength) { 321 | buf = buf.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength) 322 | } 323 | if (offset || length !== buffer.length) { 324 | buf = buf.slice(offset, length) 325 | } 326 | 327 | return Buffer.from(buf) 328 | } 329 | 330 | function fixBufferList (list) { 331 | const newlist = new Array(list.length) 332 | 333 | for (let i = 0, l = list.length; i < l; i++) { 334 | const buf = list[i] 335 | if (typeof buf === 'string') { 336 | newlist[i] = Buffer.from(buf) 337 | } else if (!(buf instanceof Buffer)) { 338 | return null 339 | } else { 340 | newlist[i] = buf 341 | } 342 | } 343 | 344 | return newlist 345 | } 346 | 347 | /** 348 | * Close the underlying socket and stop listening for data on it. 349 | */ 350 | Socket.prototype.close = function () { 351 | const self = this 352 | if (self._destroyed) return 353 | 354 | delete sockets[self.id] 355 | chrome.sockets.udp.close(self.id) 356 | self._destroyed = true 357 | 358 | self.emit('close') 359 | } 360 | 361 | /** 362 | * Returns an object containing the address information for a socket. For UDP 363 | * sockets, this object will contain address, family and port. 364 | * 365 | * @return {Object} information 366 | */ 367 | Socket.prototype.address = function () { 368 | const self = this 369 | return { 370 | address: self._address, 371 | port: self._port, 372 | family: 'IPv4' 373 | } 374 | } 375 | 376 | Socket.prototype.setBroadcast = function (flag) { 377 | // No chrome.sockets equivalent 378 | } 379 | 380 | Socket.prototype.setTTL = function (ttl) { 381 | // No chrome.sockets equivalent 382 | } 383 | 384 | // NOTE: Multicast code is untested. Pull requests accepted for bug fixes and to 385 | // add tests! 386 | 387 | /** 388 | * Sets the IP_MULTICAST_TTL socket option. TTL stands for "Time to Live," but 389 | * in this context it specifies the number of IP hops that a packet is allowed 390 | * to go through, specifically for multicast traffic. Each router or gateway 391 | * that forwards a packet decrements the TTL. If the TTL is decremented to 0 392 | * by a router, it will not be forwarded. 393 | * 394 | * The argument to setMulticastTTL() is a number of hops between 0 and 255. 395 | * The default on most systems is 1. 396 | * 397 | * NOTE: The Chrome version of this function is async, whereas the node 398 | * version is sync. Keep this in mind. 399 | * 400 | * @param {number} ttl 401 | * @param {function} callback CHROME-SPECIFIC: Called when the configuration 402 | * operation is done. 403 | */ 404 | Socket.prototype.setMulticastTTL = function (ttl, callback) { 405 | const self = this 406 | if (!callback) callback = function () {} 407 | if (self._bindState === BIND_STATE_BOUND) { 408 | setMulticastTTL(callback) 409 | } else { 410 | self._bindTasks.push({ 411 | fn: setMulticastTTL, 412 | callback 413 | }) 414 | } 415 | 416 | function setMulticastTTL (callback) { 417 | chrome.sockets.udp.setMulticastTimeToLive(self.id, ttl, callback) 418 | } 419 | } 420 | 421 | /** 422 | * Sets or clears the IP_MULTICAST_LOOP socket option. When this option is 423 | * set, multicast packets will also be received on the local interface. 424 | * 425 | * NOTE: The Chrome version of this function is async, whereas the node 426 | * version is sync. Keep this in mind. 427 | * 428 | * @param {boolean} flag 429 | * @param {function} callback CHROME-SPECIFIC: Called when the configuration 430 | * operation is done. 431 | */ 432 | Socket.prototype.setMulticastLoopback = function (flag, callback) { 433 | const self = this 434 | if (!callback) callback = function () {} 435 | if (self._bindState === BIND_STATE_BOUND) { 436 | setMulticastLoopback(callback) 437 | } else { 438 | self._bindTasks.push({ 439 | fn: setMulticastLoopback, 440 | callback 441 | }) 442 | } 443 | 444 | function setMulticastLoopback (callback) { 445 | chrome.sockets.udp.setMulticastLoopbackMode(self.id, flag, callback) 446 | } 447 | } 448 | 449 | /** 450 | * Tells the kernel to join a multicast group with IP_ADD_MEMBERSHIP socket 451 | * option. 452 | * 453 | * If multicastInterface is not specified, the OS will try to add membership 454 | * to all valid interfaces. 455 | * 456 | * NOTE: The Chrome version of this function is async, whereas the node 457 | * version is sync. Keep this in mind. 458 | * 459 | * @param {string} multicastAddress 460 | * @param {string} [multicastInterface] Optional 461 | * @param {function} callback CHROME-SPECIFIC: Called when the configuration 462 | * operation is done. 463 | */ 464 | Socket.prototype.addMembership = function (multicastAddress, 465 | multicastInterface, 466 | callback) { 467 | const self = this 468 | if (!callback) callback = function () {} 469 | chrome.sockets.udp.joinGroup(self.id, multicastAddress, callback) 470 | } 471 | 472 | /** 473 | * Opposite of addMembership - tells the kernel to leave a multicast group 474 | * with IP_DROP_MEMBERSHIP socket option. This is automatically called by the 475 | * kernel when the socket is closed or process terminates, so most apps will 476 | * never need to call this. 477 | * 478 | * NOTE: The Chrome version of this function is async, whereas the node 479 | * version is sync. Keep this in mind. 480 | * 481 | * If multicastInterface is not specified, the OS will try to drop membership 482 | * to all valid interfaces. 483 | * 484 | * @param {[type]} multicastAddress 485 | * @param {[type]} multicastInterface Optional 486 | * @param {function} callback CHROME-SPECIFIC: Called when the configuration 487 | * operation is done. 488 | */ 489 | Socket.prototype.dropMembership = function (multicastAddress, 490 | multicastInterface, 491 | callback) { 492 | const self = this 493 | if (!callback) callback = function () {} 494 | chrome.sockets.udp.leaveGroup(self.id, multicastAddress, callback) 495 | } 496 | 497 | Socket.prototype.unref = function () { 498 | // No chrome.sockets equivalent 499 | } 500 | 501 | Socket.prototype.ref = function () { 502 | // No chrome.sockets equivalent 503 | } 504 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chrome-dgram", 3 | "description": "Use the Node `dgram` API in Chrome Apps", 4 | "version": "3.0.6", 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-dgram/issues" 12 | }, 13 | "contributors": [ 14 | "John Hiesey " 15 | ], 16 | "dependencies": { 17 | "inherits": "^2.0.4", 18 | "run-series": "^1.1.9" 19 | }, 20 | "devDependencies": { 21 | "browserify": "^17.0.0", 22 | "envify": "^4.1.0", 23 | "once": "1.x", 24 | "portfinder": "^1.0.28", 25 | "standard": "*", 26 | "tape": "^5.0.1" 27 | }, 28 | "homepage": "https://github.com/feross/chrome-dgram", 29 | "keywords": [ 30 | "chrome app", 31 | "chrome.socket", 32 | "socket api", 33 | "dgram", 34 | "udp", 35 | "wrapper", 36 | "client", 37 | "server" 38 | ], 39 | "license": "MIT", 40 | "main": "index.js", 41 | "repository": { 42 | "type": "git", 43 | "url": "git://github.com/feross/chrome-dgram.git" 44 | }, 45 | "scripts": { 46 | "test": "standard", 47 | "test-browser": "tape test/*.js" 48 | }, 49 | "funding": [ 50 | { 51 | "type": "github", 52 | "url": "https://github.com/sponsors/feross" 53 | }, 54 | { 55 | "type": "patreon", 56 | "url": "https://www.patreon.com/feross" 57 | }, 58 | { 59 | "type": "consulting", 60 | "url": "https://feross.org/support" 61 | } 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /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 | } 24 | 25 | } -------------------------------------------------------------------------------- /test/chrome-app/window.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/client/dgram.js: -------------------------------------------------------------------------------- 1 | const dgram = require('../../') 2 | 3 | const PORT = Number(process.env.PORT) 4 | 5 | const sock = dgram.createSocket('udp4') 6 | sock.setMulticastTTL(2) 7 | 8 | // If any errors are emitted, log them 9 | sock.on('error', function (err) { 10 | console.error(err.stack) 11 | }) 12 | 13 | sock.send('beep', 0, 'beep'.length, PORT, '127.0.0.1') 14 | 15 | sock.on('message', function (data, rInfo) { 16 | if (data.toString() === 'boop') { 17 | sock.send('pass1', 0, 'pass1'.length, rInfo.port, rInfo.address) 18 | sock.send('pass2', rInfo.port, rInfo.address) 19 | } else { 20 | sock.send('fail', 0, 'fail'.length, rInfo.port, rInfo.address) 21 | } 22 | }) 23 | -------------------------------------------------------------------------------- /test/dgram.js: -------------------------------------------------------------------------------- 1 | const dgram = require('dgram') 2 | const helper = require('./helper') 3 | const portfinder = require('portfinder') 4 | const test = require('tape') 5 | 6 | test('UDP works (echo test)', function (t) { 7 | portfinder.getPort(function (err, port) { 8 | t.error(err, 'Found free ports') 9 | const socket = dgram.createSocket('udp4') 10 | let child 11 | 12 | socket.on('listening', function () { 13 | const env = { PORT: port } 14 | helper.browserify('dgram.js', env, function (err) { 15 | t.error(err, 'Clean browserify build') 16 | child = helper.launchBrowser() 17 | }) 18 | }) 19 | 20 | let i = 0 21 | socket.on('message', function (message, remote) { 22 | if (i === 0) { 23 | t.equal(message.toString(), 'beep', 'Got beep') 24 | const boop = Buffer.from('boop') 25 | socket.send(boop, 0, boop.length, remote.port, remote.address) 26 | } else if (i === 1) { 27 | t.equal(message.toString(), 'pass1', 'Boop was received') 28 | } else if (i === 2) { 29 | t.equal(message.toString(), 'pass2', 'Omitting `offset` and `length` works') 30 | child.kill() 31 | socket.close() 32 | t.end() 33 | } else { 34 | t.fail('UDP client sent unexpected message') 35 | } 36 | i += 1 37 | }) 38 | 39 | socket.bind(port) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /test/helper.js: -------------------------------------------------------------------------------- 1 | const browserify = require('browserify') 2 | const cp = require('child_process') 3 | const envify = require('envify/custom') 4 | const fs = require('fs') 5 | const once = require('once') 6 | const path = require('path') 7 | const os = require('os') 8 | 9 | let CHROME = process.env.CHROME 10 | 11 | // locate default chromes for os 12 | switch (os.platform()) { 13 | case 'win32' : 14 | if (process.arch === 'x64') { 15 | CHROME = '"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe"' 16 | } else { 17 | CHROME = '"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"' 18 | } 19 | break 20 | case 'darwin' : 21 | CHROME = '/Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome' 22 | break 23 | case 'linux' : 24 | CHROME = '/opt/google/chrome/chrome' 25 | break 26 | default : 27 | console.log('Defaulting to process.env.CHROME `%s`', process.env.CHROME) 28 | break 29 | } 30 | 31 | const BUNDLE_PATH = path.join(__dirname, 'chrome-app/bundle.js') 32 | 33 | exports.browserify = function (filename, env, cb) { 34 | if (!env) env = {} 35 | if (!cb) cb = function () {} 36 | cb = once(cb) 37 | 38 | const b = browserify() 39 | b.add(path.join(__dirname, 'client', filename)) 40 | b.transform(envify(env)) 41 | 42 | b.bundle() 43 | .pipe(fs.createWriteStream(BUNDLE_PATH)) 44 | .on('close', cb) 45 | .on('error', cb) 46 | } 47 | 48 | exports.launchBrowser = function () { 49 | // supply full path because windows 50 | const app = path.join(__dirname, '/chrome-app') 51 | 52 | let command = CHROME + ' --load-and-launch-app=' + app 53 | if (os.platform() === 'darwin' || os.platform() === 'linux') { 54 | command += ' > /dev/null 2>&1' 55 | } 56 | const env = { cwd: path.join(__dirname, '..') } 57 | 58 | return cp.exec(command, env, function (err) { 59 | if (err) throw err 60 | }) 61 | } 62 | --------------------------------------------------------------------------------