// // react-native-udp // // Created by Mark Vayngrib on 05/10/15. // Copyright (c) 2015 Tradle, Inc. All rights reserved. // /** * @providesModule UdpSocket * @flow */ 'use strict'; var React = require('react-native') var mixInEventEmitter = require('mixInEventEmitter') var DeviceEventEmitter = require('RCTDeviceEventEmitter') var Sockets = require('NativeModules').UdpSockets var noop = function () {} var instances = 0 var STATE = { UNBOUND: 0, BINDING: 1, BOUND: 2 } function UdpSocket(type) { this._id = instances++ this._state = STATE.UNBOUND this._subscriptiom = DeviceEventEmitter.addListener( 'udp-' + this._id + '-data', this._onReceive.bind(this) ); // ensure compatibility with node's EventEmitter if (!this.on) this.on = this.addListener.bind(this) Sockets.createSocket(this._id, { type: type || 'udp4' }) // later } UdpSocket.prototype._debug = function() { // for now var args = [].slice.call(arguments) args.unshift(this._id) console.log.apply(console, args) } UdpSocket.prototype.bind = function(port, address, callback) { var self = this if (this._state !== STATE.UNBOUND) throw new Error('Socket is already bound') if (typeof address === 'function') { callback = address address = undefined } if (!address) address = '0.0.0.0' if (!port) port = 0 if (callback) this.once('listening', callback.bind(this)) this._state = STATE.BINDING this._debug('binding, address:', address, 'port:', port) Sockets.bind(this._id, port, address, function(err, addr) { if (err) { // questionable: may want to self-destruct and // force user to create a new socket self._state = STATE.UNBOUND self._debug('failed to bind', err) return self.emit('error', err) } self._debug('bound to address:', addr.address, 'port:', addr.port) self._address = addr.address self._port = addr.port self._state = STATE.BOUND self.emit('listening') }) } UdpSocket.prototype.close = function() { if (this._destroyed) return this._destroyed = true this._debug('closing') this._subscription.remove(); Sockets.close(this._id, this._debug.bind(this, 'closed')) this.emit('close') } UdpSocket.prototype._onReceive = function(info) { this._debug('received', info) var buf = toByteArray(info.data) var rinfo = { address: info.address, port: info.port, family: 'IPv4', // not necessarily size: buf.length } if (typeof Buffer !== 'undefined') buf = new Buffer(buf) this.emit('message', buf, rinfo) } /** * socket.send(buf, offset, length, port, address, [callback]) * * For UDP sockets, the destination port and IP address must be * specified. A string may be supplied for the address parameter, and it will * be resolved with DNS. An optional callback may be specified to detect any * DNS errors and when buf may be re-used. Note that DNS lookups will delay * the time that a send takes place, at least until the next tick. The only * way to know for sure that a send has taken place is to use the callback. * * If the socket has not been previously bound with a call to bind, it's * assigned a random port number and bound to the "all interfaces" address * (0.0.0.0 for udp4 sockets, ::0 for udp6 sockets). * * @param {Array|string} message to be sent * @param {number} offset Offset in the buffer where the message starts. * @param {number} length Number of bytes in the message. * @param {number} port destination port * @param {string} address destination IP * @param {function} callback Callback when message is done being delivered. * Optional. */ // UdpSocket.prototype.send = function (buf, host, port, cb) { UdpSocket.prototype.send = function(buffer, offset, length, port, address, callback) { var self = this if (offset !== 0) throw new Error('Non-zero offset not supported yet') if (this._state === STATE.UNBOUND) { throw new Error('bind before sending, seriously dude') } else if (this._state === STATE.BINDING) { // we're ok, GCDAsync(Udp)Socket handles queueing internally } callback = callback || noop if (typeof buffer === 'string') { buffer = toByteArray(buffer) } else if (typeof Buffer !== 'undefined' && Buffer.isBuffer(buffer)) { buffer = buffer.toJSON().data } self._debug('sending', buffer) Sockets.send(this._id, buffer, +port, address, function(err) { if (err) { self._debug('send failed', err) return callback(err) } self._debug('sent') callback() }) } UdpSocket.prototype.address = function() { if (this._state !== STATE.BOUND) { throw new Error('socket is not bound yet') } return { address: this._address, port: this._port, family: 'IPv4' } } UdpSocket.prototype.setBroadcast = function(flag) { // nothing yet } UdpSocket.prototype.setTTL = function(ttl) { // nothing yet } UdpSocket.prototype.setMulticastTTL = function(ttl, callback) { // nothing yet } UdpSocket.prototype.setMulticastLoopback = function(flag, callback) { // nothing yet } UdpSocket.prototype.addMembership = function(multicastAddress, multicastInterface, callback) { // nothing yet } UdpSocket.prototype.dropMembership = function(multicastAddress, multicastInterface, callback) { // nothing yet } UdpSocket.prototype.ref = function() { // anything? } UdpSocket.prototype.unref = function() { // anything? } mixInEventEmitter(UdpSocket, { 'listening': true, 'message': true, 'close': true, 'error': true }) function toByteArray(obj) { if (typeof obj === 'object') { var i = 0 var arr = [] while (true) { if (!(i in obj)) break arr.push(+obj[i]) i++ } return new Uint8Array(arr) } else if (typeof obj !== 'string') { throw new Error('unsupported format') } var uint = new Uint8Array(obj.length); for (var i = 0, l = obj.length; i < l; i++){ uint[i] = obj.charCodeAt(i); } return new Uint8Array(uint); } module.exports = UdpSocket