mirror of
https://github.com/DanielnetoDotCom/YouPHPTube
synced 2025-10-03 09:49:28 +02:00
503 lines
15 KiB
JavaScript
503 lines
15 KiB
JavaScript
/*! chrome-dgram. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
|
/* global chrome */
|
|
|
|
/**
|
|
* UDP / Datagram Sockets
|
|
* ======================
|
|
*
|
|
* Datagram sockets are available through require('chrome-dgram').
|
|
*/
|
|
|
|
exports.Socket = Socket
|
|
|
|
var EventEmitter = require('events').EventEmitter
|
|
var inherits = require('inherits')
|
|
var series = require('run-series')
|
|
|
|
var BIND_STATE_UNBOUND = 0
|
|
var BIND_STATE_BINDING = 1
|
|
var BIND_STATE_BOUND = 2
|
|
|
|
// Track open sockets to route incoming data (via onReceive) to the right handlers.
|
|
var sockets = {}
|
|
|
|
// Thorough check for Chrome App since both Edge and Chrome implement dummy chrome object
|
|
if (
|
|
typeof chrome === 'object' &&
|
|
typeof chrome.runtime === 'object' &&
|
|
typeof chrome.runtime.id === 'string' &&
|
|
typeof chrome.sockets === 'object' &&
|
|
typeof chrome.sockets.udp === 'object'
|
|
) {
|
|
chrome.sockets.udp.onReceive.addListener(onReceive)
|
|
chrome.sockets.udp.onReceiveError.addListener(onReceiveError)
|
|
}
|
|
|
|
function onReceive (info) {
|
|
if (info.socketId in sockets) {
|
|
sockets[info.socketId]._onReceive(info)
|
|
} else {
|
|
console.error('Unknown socket id: ' + info.socketId)
|
|
}
|
|
}
|
|
|
|
function onReceiveError (info) {
|
|
if (info.socketId in sockets) {
|
|
sockets[info.socketId]._onReceiveError(info.resultCode)
|
|
} else {
|
|
console.error('Unknown socket id: ' + info.socketId)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* dgram.createSocket(type, [callback])
|
|
*
|
|
* Creates a datagram Socket of the specified types. Valid types are `udp4`
|
|
* and `udp6`.
|
|
*
|
|
* Takes an optional callback which is added as a listener for message events.
|
|
*
|
|
* Call socket.bind if you want to receive datagrams. socket.bind() will bind
|
|
* to the "all interfaces" address on a random port (it does the right thing
|
|
* for both udp4 and udp6 sockets). You can then retrieve the address and port
|
|
* with socket.address().address and socket.address().port.
|
|
*
|
|
* @param {string} type Either 'udp4' or 'udp6'
|
|
* @param {function} listener Attached as a listener to message events.
|
|
* Optional
|
|
* @return {Socket} Socket object
|
|
*/
|
|
exports.createSocket = function (type, listener) {
|
|
return new Socket(type, listener)
|
|
}
|
|
|
|
inherits(Socket, EventEmitter)
|
|
|
|
/**
|
|
* Class: dgram.Socket
|
|
*
|
|
* The dgram Socket class encapsulates the datagram functionality. It should
|
|
* be created via `dgram.createSocket(type, [callback])`.
|
|
*
|
|
* Event: 'message'
|
|
* - msg Buffer object. The message
|
|
* - rinfo Object. Remote address information
|
|
* Emitted when a new datagram is available on a socket. msg is a Buffer and
|
|
* rinfo is an object with the sender's address information and the number
|
|
* of bytes in the datagram.
|
|
*
|
|
* Event: 'listening'
|
|
* Emitted when a socket starts listening for datagrams. This happens as soon
|
|
* as UDP sockets are created.
|
|
*
|
|
* Event: 'close'
|
|
* Emitted when a socket is closed with close(). No new message events will
|
|
* be emitted on this socket.
|
|
*
|
|
* Event: 'error'
|
|
* - exception Error object
|
|
* Emitted when an error occurs.
|
|
*/
|
|
function Socket (options, listener) {
|
|
var self = this
|
|
EventEmitter.call(self)
|
|
if (typeof options === 'string') options = { type: options }
|
|
if (options.type !== 'udp4') throw new Error('Bad socket type specified. Valid types are: udp4')
|
|
|
|
if (typeof listener === 'function') self.on('message', listener)
|
|
|
|
self._destroyed = false
|
|
self._bindState = BIND_STATE_UNBOUND
|
|
self._bindTasks = []
|
|
}
|
|
|
|
/**
|
|
* socket.bind(port, [address], [callback])
|
|
*
|
|
* For UDP sockets, listen for datagrams on a named port and optional address.
|
|
* If address is not specified, the OS will try to listen on all addresses.
|
|
* After binding is done, a "listening" event is emitted and the callback(if
|
|
* specified) is called. Specifying both a "listening" event listener and
|
|
* callback is not harmful but not very useful.
|
|
*
|
|
* A bound datagram socket keeps the node process running to receive
|
|
* datagrams.
|
|
*
|
|
* If binding fails, an "error" event is generated. In rare case (e.g. binding
|
|
* a closed socket), an Error may be thrown by this method.
|
|
*
|
|
* @param {number} port
|
|
* @param {string} address Optional
|
|
* @param {function} callback Function with no parameters, Optional. Callback
|
|
* when binding is done.
|
|
*/
|
|
Socket.prototype.bind = function (port, address, callback) {
|
|
var self = this
|
|
if (typeof address === 'function') {
|
|
callback = address
|
|
address = undefined
|
|
}
|
|
|
|
if (!address) address = '0.0.0.0'
|
|
|
|
if (!port) port = 0
|
|
|
|
if (self._bindState !== BIND_STATE_UNBOUND) throw new Error('Socket is already bound')
|
|
|
|
self._bindState = BIND_STATE_BINDING
|
|
|
|
if (typeof callback === 'function') self.once('listening', callback)
|
|
|
|
chrome.sockets.udp.create(function (createInfo) {
|
|
self.id = createInfo.socketId
|
|
|
|
sockets[self.id] = self
|
|
|
|
var bindFns = self._bindTasks.map(function (t) { return t.fn })
|
|
|
|
series(bindFns, function (err) {
|
|
if (err) return self.emit('error', err)
|
|
chrome.sockets.udp.bind(self.id, address, port, function (result) {
|
|
if (result < 0) {
|
|
self.emit('error', new Error('Socket ' + self.id + ' failed to bind. ' +
|
|
chrome.runtime.lastError.message))
|
|
return
|
|
}
|
|
chrome.sockets.udp.getInfo(self.id, function (socketInfo) {
|
|
if (!socketInfo.localPort || !socketInfo.localAddress) {
|
|
self.emit('error', new Error('Cannot get local port/address for Socket ' + self.id))
|
|
return
|
|
}
|
|
|
|
self._port = socketInfo.localPort
|
|
self._address = socketInfo.localAddress
|
|
|
|
self._bindState = BIND_STATE_BOUND
|
|
self.emit('listening')
|
|
|
|
self._bindTasks.map(function (t) {
|
|
t.callback()
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Internal function to receive new messages and emit `message` events.
|
|
*/
|
|
Socket.prototype._onReceive = function (info) {
|
|
var self = this
|
|
|
|
var buf = Buffer.from(new Uint8Array(info.data))
|
|
var rinfo = {
|
|
address: info.remoteAddress,
|
|
family: 'IPv4',
|
|
port: info.remotePort,
|
|
size: buf.length
|
|
}
|
|
self.emit('message', buf, rinfo)
|
|
}
|
|
|
|
Socket.prototype._onReceiveError = function (resultCode) {
|
|
var self = this
|
|
self.emit('error', new Error('Socket ' + self.id + ' receive error ' + resultCode))
|
|
}
|
|
|
|
/**
|
|
* 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 {Buffer|Arrayish|string} buf Message to be sent
|
|
* @param {number} offset Offset in the buffer where the message starts. Optional.
|
|
* @param {number} length Number of bytes in the message. Optional.
|
|
* @param {number} port destination port
|
|
* @param {string} address destination IP
|
|
* @param {function} callback Callback when message is done being delivered.
|
|
* Optional.
|
|
*
|
|
* Valid combinations:
|
|
* send(buffer, offset, length, port, address, callback)
|
|
* send(buffer, offset, length, port, address)
|
|
* send(buffer, offset, length, port)
|
|
* send(bufferOrList, port, address, callback)
|
|
* send(bufferOrList, port, address)
|
|
* send(bufferOrList, port)
|
|
*
|
|
*/
|
|
Socket.prototype.send = function (buffer, offset, length, port, address, callback) {
|
|
var self = this
|
|
|
|
var list
|
|
|
|
if (address || (port && typeof port !== 'function')) {
|
|
buffer = sliceBuffer(buffer, offset, length)
|
|
} else {
|
|
callback = port
|
|
port = offset
|
|
address = length
|
|
}
|
|
|
|
if (!Array.isArray(buffer)) {
|
|
if (typeof buffer === 'string') {
|
|
list = [Buffer.from(buffer)]
|
|
} else if (!(buffer instanceof Buffer)) {
|
|
throw new TypeError('First argument must be a buffer or a string')
|
|
} else {
|
|
list = [buffer]
|
|
}
|
|
} else if (!(list = fixBufferList(buffer))) {
|
|
throw new TypeError('Buffer list arguments must be buffers or strings')
|
|
}
|
|
|
|
port = port >>> 0
|
|
if (port === 0 || port > 65535) {
|
|
throw new RangeError('Port should be > 0 and < 65536')
|
|
}
|
|
|
|
// Normalize callback so it's always a function
|
|
if (typeof callback !== 'function') {
|
|
callback = function () {}
|
|
}
|
|
|
|
if (self._bindState === BIND_STATE_UNBOUND) self.bind(0)
|
|
|
|
// If the socket hasn't been bound yet, push the outbound packet onto the
|
|
// send queue and send after binding is complete.
|
|
if (self._bindState !== BIND_STATE_BOUND) {
|
|
// If the send queue hasn't been initialized yet, do it, and install an
|
|
// event handler that flishes the send queue after binding is done.
|
|
if (!self._sendQueue) {
|
|
self._sendQueue = []
|
|
self.once('listening', function () {
|
|
// Flush the send queue.
|
|
for (var i = 0; i < self._sendQueue.length; i++) {
|
|
self.send.apply(self, self._sendQueue[i])
|
|
}
|
|
self._sendQueue = undefined
|
|
})
|
|
}
|
|
self._sendQueue.push([buffer, offset, length, port, address, callback])
|
|
return
|
|
}
|
|
|
|
var ab = Buffer.concat(list).buffer
|
|
|
|
chrome.sockets.udp.send(self.id, ab, address, port, function (sendInfo) {
|
|
if (sendInfo.resultCode < 0) {
|
|
var err = new Error('Socket ' + self.id + ' send error ' + sendInfo.resultCode)
|
|
callback(err)
|
|
self.emit('error', err)
|
|
} else {
|
|
callback(null)
|
|
}
|
|
})
|
|
}
|
|
|
|
function sliceBuffer (buffer, offset, length) {
|
|
if (typeof buffer === 'string') {
|
|
buffer = Buffer.from(buffer)
|
|
} else if (!(buffer instanceof Buffer)) {
|
|
throw new TypeError('First argument must be a buffer or string')
|
|
}
|
|
|
|
offset = offset >>> 0
|
|
length = length >>> 0
|
|
|
|
// assuming buffer is browser implementation (`buffer` package on npm)
|
|
var buf = buffer.buffer
|
|
if (buffer.byteOffset || buffer.byteLength !== buf.byteLength) {
|
|
buf = buf.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)
|
|
}
|
|
if (offset || length !== buffer.length) {
|
|
buf = buf.slice(offset, length)
|
|
}
|
|
|
|
return Buffer.from(buf)
|
|
}
|
|
|
|
function fixBufferList (list) {
|
|
var newlist = new Array(list.length)
|
|
|
|
for (var i = 0, l = list.length; i < l; i++) {
|
|
var buf = list[i]
|
|
if (typeof buf === 'string') {
|
|
newlist[i] = Buffer.from(buf)
|
|
} else if (!(buf instanceof Buffer)) {
|
|
return null
|
|
} else {
|
|
newlist[i] = buf
|
|
}
|
|
}
|
|
|
|
return newlist
|
|
}
|
|
|
|
/**
|
|
* Close the underlying socket and stop listening for data on it.
|
|
*/
|
|
Socket.prototype.close = function () {
|
|
var self = this
|
|
if (self._destroyed) return
|
|
|
|
delete sockets[self.id]
|
|
chrome.sockets.udp.close(self.id)
|
|
self._destroyed = true
|
|
|
|
self.emit('close')
|
|
}
|
|
|
|
/**
|
|
* Returns an object containing the address information for a socket. For UDP
|
|
* sockets, this object will contain address, family and port.
|
|
*
|
|
* @return {Object} information
|
|
*/
|
|
Socket.prototype.address = function () {
|
|
var self = this
|
|
return {
|
|
address: self._address,
|
|
port: self._port,
|
|
family: 'IPv4'
|
|
}
|
|
}
|
|
|
|
Socket.prototype.setBroadcast = function (flag) {
|
|
// No chrome.sockets equivalent
|
|
}
|
|
|
|
Socket.prototype.setTTL = function (ttl) {
|
|
// No chrome.sockets equivalent
|
|
}
|
|
|
|
// NOTE: Multicast code is untested. Pull requests accepted for bug fixes and to
|
|
// add tests!
|
|
|
|
/**
|
|
* Sets the IP_MULTICAST_TTL socket option. TTL stands for "Time to Live," but
|
|
* in this context it specifies the number of IP hops that a packet is allowed
|
|
* to go through, specifically for multicast traffic. Each router or gateway
|
|
* that forwards a packet decrements the TTL. If the TTL is decremented to 0
|
|
* by a router, it will not be forwarded.
|
|
*
|
|
* The argument to setMulticastTTL() is a number of hops between 0 and 255.
|
|
* The default on most systems is 1.
|
|
*
|
|
* NOTE: The Chrome version of this function is async, whereas the node
|
|
* version is sync. Keep this in mind.
|
|
*
|
|
* @param {number} ttl
|
|
* @param {function} callback CHROME-SPECIFIC: Called when the configuration
|
|
* operation is done.
|
|
*/
|
|
Socket.prototype.setMulticastTTL = function (ttl, callback) {
|
|
var self = this
|
|
if (!callback) callback = function () {}
|
|
if (self._bindState === BIND_STATE_BOUND) {
|
|
setMulticastTTL(callback)
|
|
} else {
|
|
self._bindTasks.push({
|
|
fn: setMulticastTTL,
|
|
callback
|
|
})
|
|
}
|
|
|
|
function setMulticastTTL (callback) {
|
|
chrome.sockets.udp.setMulticastTimeToLive(self.id, ttl, callback)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets or clears the IP_MULTICAST_LOOP socket option. When this option is
|
|
* set, multicast packets will also be received on the local interface.
|
|
*
|
|
* NOTE: The Chrome version of this function is async, whereas the node
|
|
* version is sync. Keep this in mind.
|
|
*
|
|
* @param {boolean} flag
|
|
* @param {function} callback CHROME-SPECIFIC: Called when the configuration
|
|
* operation is done.
|
|
*/
|
|
Socket.prototype.setMulticastLoopback = function (flag, callback) {
|
|
var self = this
|
|
if (!callback) callback = function () {}
|
|
if (self._bindState === BIND_STATE_BOUND) {
|
|
setMulticastLoopback(callback)
|
|
} else {
|
|
self._bindTasks.push({
|
|
fn: setMulticastLoopback,
|
|
callback
|
|
})
|
|
}
|
|
|
|
function setMulticastLoopback (callback) {
|
|
chrome.sockets.udp.setMulticastLoopbackMode(self.id, flag, callback)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tells the kernel to join a multicast group with IP_ADD_MEMBERSHIP socket
|
|
* option.
|
|
*
|
|
* If multicastInterface is not specified, the OS will try to add membership
|
|
* to all valid interfaces.
|
|
*
|
|
* NOTE: The Chrome version of this function is async, whereas the node
|
|
* version is sync. Keep this in mind.
|
|
*
|
|
* @param {string} multicastAddress
|
|
* @param {string} [multicastInterface] Optional
|
|
* @param {function} callback CHROME-SPECIFIC: Called when the configuration
|
|
* operation is done.
|
|
*/
|
|
Socket.prototype.addMembership = function (multicastAddress,
|
|
multicastInterface,
|
|
callback) {
|
|
var self = this
|
|
if (!callback) callback = function () {}
|
|
chrome.sockets.udp.joinGroup(self.id, multicastAddress, callback)
|
|
}
|
|
|
|
/**
|
|
* Opposite of addMembership - tells the kernel to leave a multicast group
|
|
* with IP_DROP_MEMBERSHIP socket option. This is automatically called by the
|
|
* kernel when the socket is closed or process terminates, so most apps will
|
|
* never need to call this.
|
|
*
|
|
* NOTE: The Chrome version of this function is async, whereas the node
|
|
* version is sync. Keep this in mind.
|
|
*
|
|
* If multicastInterface is not specified, the OS will try to drop membership
|
|
* to all valid interfaces.
|
|
*
|
|
* @param {[type]} multicastAddress
|
|
* @param {[type]} multicastInterface Optional
|
|
* @param {function} callback CHROME-SPECIFIC: Called when the configuration
|
|
* operation is done.
|
|
*/
|
|
Socket.prototype.dropMembership = function (multicastAddress,
|
|
multicastInterface,
|
|
callback) {
|
|
var self = this
|
|
if (!callback) callback = function () {}
|
|
chrome.sockets.udp.leaveGroup(self.id, multicastAddress, callback)
|
|
}
|
|
|
|
Socket.prototype.unref = function () {
|
|
// No chrome.sockets equivalent
|
|
}
|
|
|
|
Socket.prototype.ref = function () {
|
|
// No chrome.sockets equivalent
|
|
}
|