diff --git a/capacitor/capacitor.config.js b/capacitor/capacitor.config.js
index 6ea64fb..a1e4760 100644
--- a/capacitor/capacitor.config.js
+++ b/capacitor/capacitor.config.js
@@ -6,6 +6,9 @@ const config = {
plugins: {
SplashScreen: {
launchShowDuration: 0
+ },
+ CapacitorHttp: {
+ enabled: false
}
},
// remove server section before making production build
@@ -13,7 +16,7 @@ const config = {
// for android only, below settings will work out of the box
// for iOS or both, change the url to http://your-device-ip
// To discover your workstation IP, just run ifconfig
- url: 'http://10.0.2.2:5001',
+ url: 'http://localhost:5001/app.html',
cleartext: true
}
}
diff --git a/capacitor/package.json b/capacitor/package.json
index 7658861..d8eeff6 100644
--- a/capacitor/package.json
+++ b/capacitor/package.json
@@ -1,26 +1,43 @@
{
+ "name": "capacitor",
+ "private": true,
"scripts": {
"build:app": "vite build",
"build:android": "run-s build:app cap-run:android",
"build:ios": "run-s build:app cap-run:ios",
- "cap-run:android": "cap sync android && cap open android",
+ "cap-run:android": "cap run android --target=Pixel_XL_API_33 --external --public-host=10.5.0.2",
"cap-run:ios": "cap sync ios && cap open ios",
"dev:ios": "run-p dev:start cap-run:ios",
- "dev:android": "run-p dev:start cap-run:android",
"dev:preview": "vite preview",
- "dev:start": "run-p dev:vite",
- "dev:vite": "vite --host --port 5001"
+ "dev:start": "run-p dev:webpack cap-run:android",
+ "dev:webpack": "webpack serve",
+ "dev:localhost-bind": "adb reverse tcp:5001 tcp:5001"
},
"devDependencies": {
- "cordova-res": "^0.15.4"
+ "assert": "^2.1.0",
+ "buffer": "^6.0.3",
+ "chrome-dgram": "^3.0.6",
+ "chrome-net": "^3.3.4",
+ "cordova-res": "^0.15.4",
+ "crypto-browserify": "^3.12.0",
+ "hybrid-chunk-store": "^1.2.2",
+ "path-esm": "^1.0.0",
+ "querystring": "^0.2.1",
+ "stream-browserify": "^3.0.0",
+ "stream-http": "^3.2.0",
+ "timers-browserify": "^2.0.12",
+ "util": "^0.12.5",
+ "webpack-cli": "^5.1.4"
},
"dependencies": {
"@capacitor/android": "^5.5.1",
"@capacitor/cli": "^5.5.1",
"@capacitor/core": "^5.5.1",
"@capacitor/ios": "^5.5.1",
+ "@superfrogbe/cordova-plugin-chrome-apps-sockets-udp": "github:superfrogbe/cordova-plugin-chrome-apps-sockets-udp",
+ "common": "workspace:*",
"cordova-plugin-chrome-apps-common": "^1.0.7",
"cordova-plugin-chrome-apps-sockets-tcp": "github:KoenLav/cordova-plugin-chrome-apps-sockets-tcp",
- "cordova-plugin-chrome-apps-sockets-udp": "^1.3.0"
+ "webpack-merge": "^5.10.0"
}
}
diff --git a/capacitor/src/App.svelte b/capacitor/src/App.svelte
deleted file mode 100644
index 01b86dc..0000000
--- a/capacitor/src/App.svelte
+++ /dev/null
@@ -1 +0,0 @@
-
uwu
diff --git a/capacitor/src/chrome-dgram.js b/capacitor/src/chrome-dgram.js
new file mode 100644
index 0000000..bddd101
--- /dev/null
+++ b/capacitor/src/chrome-dgram.js
@@ -0,0 +1,497 @@
+/*! chrome-dgram. MIT License. Feross Aboukhadijeh */
+/* global chrome */
+
+/**
+ * UDP / Datagram Sockets
+ * ======================
+ *
+ * Datagram sockets are available through require('chrome-dgram').
+ */
+
+exports.Socket = Socket
+
+const EventEmitter = require('events').EventEmitter
+const inherits = require('inherits')
+const series = require('run-series')
+
+const BIND_STATE_UNBOUND = 0
+const BIND_STATE_BINDING = 1
+const BIND_STATE_BOUND = 2
+
+// Track open sockets to route incoming data (via onReceive) to the right handlers.
+const sockets = {}
+
+// Thorough check for Chrome App since both Edge and Chrome implement dummy chrome object
+if (
+ typeof chrome === 'object' &&
+ 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) {
+ const 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) {
+ const 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
+
+ const 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) {
+ const self = this
+
+ const buf = Buffer.from(new Uint8Array(info.data))
+ const rinfo = {
+ address: info.remoteAddress,
+ family: 'IPv4',
+ port: info.remotePort,
+ size: buf.length
+ }
+ self.emit('message', buf, rinfo)
+}
+
+Socket.prototype._onReceiveError = function (resultCode) {
+ const 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) {
+ const self = this
+
+ let 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 Uint8Array)) {
+ throw new TypeError('First argument must be a buffer or a string')
+ } else {
+ list = [Buffer.from(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 (let 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
+ }
+
+ const ab = Buffer.concat(list).buffer
+
+ chrome.sockets.udp.send(self.id, ab, address, port, function (sendInfo) {
+ if (sendInfo.resultCode < 0) {
+ const err = new Error('Socket ' + self.id + ' send error ' + sendInfo.resultCode)
+ callback(err)
+ self.emit('error', err)
+ } else {
+ callback(null)
+ }
+ })
+}
+
+function sliceBuffer (buffer, offset, length) {
+ buffer = Buffer.from(buffer)
+
+ offset = offset >>> 0
+ length = length >>> 0
+
+ // assuming buffer is browser implementation (`buffer` package on npm)
+ let 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) {
+ const newlist = new Array(list.length)
+
+ for (let i = 0, l = list.length; i < l; i++) {
+ const buf = list[i]
+ if (typeof buf === 'string') {
+ newlist[i] = Buffer.from(buf)
+ } else if (!(buf instanceof Uint8Array)) {
+ return null
+ } else {
+ newlist[i] = Buffer.from(buf)
+ }
+ }
+
+ return newlist
+}
+
+/**
+ * Close the underlying socket and stop listening for data on it.
+ */
+Socket.prototype.close = function () {
+ const 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 () {
+ const 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) {
+ const 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) {
+ const 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) {
+ const 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) {
+ const 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
+}
diff --git a/capacitor/src/chrome-net.js b/capacitor/src/chrome-net.js
new file mode 100644
index 0000000..ac77cef
--- /dev/null
+++ b/capacitor/src/chrome-net.js
@@ -0,0 +1,1205 @@
+/*! chrome-net. MIT License. Feross Aboukhadijeh */
+/* global chrome */
+'use strict'
+
+/**
+ * net
+ * ===
+ *
+ * The net module provides you with an asynchronous network wrapper. It
+ * contains methods for creating both servers and clients (called streams).
+ * You can include this module with require('chrome-net')
+ */
+
+const EventEmitter = require('events')
+const inherits = require('inherits')
+const stream = require('stream')
+const deprecate = require('util').deprecate
+const timers = require('timers')
+const Buffer = require('buffer').Buffer
+
+// Track open servers and sockets to route incoming sockets (via onAccept and onReceive)
+// to the right handlers.
+const servers = {}
+const sockets = {}
+
+// Thorough check for Chrome App since both Edge and Chrome implement dummy chrome object
+if (
+ typeof chrome === 'object' &&
+ typeof chrome.sockets === 'object' &&
+ typeof chrome.sockets.tcp === 'object'
+) {
+ chrome.sockets.tcp.onReceive.addListener(onReceive)
+ chrome.sockets.tcp.onReceiveError.addListener(onReceiveError)
+}
+
+function onAccept (info) {
+ if (info.socketId in servers) {
+ servers[info.socketId]._onAccept(info.clientSocketId)
+ } else {
+ console.error('Unknown server socket id: ' + info.socketId)
+ }
+}
+
+function onAcceptError (info) {
+ if (info.socketId in servers) {
+ servers[info.socketId]._onAcceptError(info.resultCode)
+ } else {
+ console.error('Unknown server socket id: ' + info.socketId)
+ }
+}
+
+function onReceive (info) {
+ if (info.socketId in sockets) {
+ sockets[info.socketId]._onReceive(info.data)
+ } else {
+ console.error('Unknown socket id: ' + info.socketId)
+ }
+}
+
+function onReceiveError (info) {
+ if (info.socketId in sockets) {
+ sockets[info.socketId]._onReceiveError(info.resultCode)
+ } else {
+ if (info.resultCode === -100) return // net::ERR_CONNECTION_CLOSED
+ console.error('Unknown socket id: ' + info.socketId)
+ }
+}
+
+/**
+ * Creates a new TCP server. The connectionListener argument is automatically
+ * set as a listener for the 'connection' event.
+ *
+ * @param {Object} options
+ * @param {function} connectionListener
+ * @return {Server}
+ */
+exports.createServer = function (options, connectionListener) {
+ return new Server(options, connectionListener)
+}
+
+/**
+ * net.connect(options, [connectionListener])
+ * net.createConnection(options, [connectionListener])
+ *
+ * Constructs a new socket object and opens the socket to the given location.
+ * When the socket is established, the 'connect' event will be emitted.
+ *
+ * For TCP sockets, options argument should be an object which specifies:
+ *
+ * port: Port the client should connect to (Required).
+ * host: Host the client should connect to. Defaults to 'localhost'.
+ * localAddress: Local interface to bind to for network connections.
+ *
+ * ===============================================================
+ *
+ * net.connect(port, [host], [connectListener])
+ * net.createConnection(port, [host], [connectListener])
+ *
+ * Creates a TCP connection to port on host. If host is omitted,
+ * 'localhost' will be assumed. The connectListener parameter will be
+ * added as an listener for the 'connect' event.
+ *
+ * @param {Object} options
+ * @param {function} listener
+ * @return {Socket}
+ */
+exports.connect = exports.createConnection = function () {
+ const argsLen = arguments.length
+ let args = new Array(argsLen)
+ for (let i = 0; i < argsLen; i++) args[i] = arguments[i]
+ args = normalizeConnectArgs(args)
+ const s = new Socket(args[0])
+ return Socket.prototype.connect.apply(s, args)
+}
+
+inherits(Server, EventEmitter)
+
+/**
+ * Class: net.Server
+ * =================
+ *
+ * This class is used to create a TCP server.
+ *
+ * Event: 'listening'
+ * Emitted when the server has been bound after calling server.listen.
+ *
+ * Event: 'connection'
+ * - Socket object The connection object
+ * Emitted when a new connection is made. socket is an instance of net.Socket.
+ *
+ * Event: 'close'
+ * Emitted when the server closes. Note that if connections exist, this event
+ * is not emitted until all connections are ended.
+ *
+ * Event: 'error'
+ * - Error Object
+ * Emitted when an error occurs. The 'close' event will be called directly
+ * following this event. See example in discussion of server.listen.
+ */
+function Server (options, connectionListener) {
+ if (!(this instanceof Server)) return new Server(options, connectionListener)
+ EventEmitter.call(this)
+
+ if (typeof options === 'function') {
+ connectionListener = options
+ options = {}
+ this.on('connection', connectionListener)
+ } else if (options == null || typeof options === 'object') {
+ options = options || {}
+
+ if (typeof connectionListener === 'function') {
+ this.on('connection', connectionListener)
+ }
+ } else {
+ throw new TypeError('options must be an object')
+ }
+
+ this._connections = 0
+
+ Object.defineProperty(this, 'connections', {
+ get: deprecate(() => this._connections,
+ 'Server.connections property is deprecated. ' +
+ 'Use Server.getConnections method instead.'),
+ set: deprecate((val) => (this._connections = val),
+ 'Server.connections property is deprecated.'),
+ configurable: true,
+ enumerable: false
+ })
+
+ this.id = null // a number > 0
+ this.connecting = false
+
+ this.allowHalfOpen = options.allowHalfOpen || false
+ this.pauseOnConnect = !!options.pauseOnConnect
+ this._address = null
+
+ this._host = null
+ this._port = null
+ this._backlog = null
+}
+exports.Server = Server
+
+Server.prototype._usingSlaves = false // not used
+
+/**
+ * server.listen(port, [host], [backlog], [callback])
+ *
+ * Begin accepting connections on the specified port and host. If the host is
+ * omitted, the server will accept connections directed to any IPv4 address
+ * (INADDR_ANY). A port value of zero will assign a random port.
+ *
+ * Backlog is the maximum length of the queue of pending connections. The
+ * actual length will be determined by your OS through sysctl settings such as
+ * tcp_max_syn_backlog and somaxconn on linux. The default value of this
+ * parameter is 511 (not 512).
+ *
+ * This function is asynchronous. When the server has been bound, 'listening'
+ * event will be emitted. The last parameter callback will be added as an
+ * listener for the 'listening' event.
+ *
+ * @return {Socket}
+ */
+Server.prototype.listen = function (/* variable arguments... */) {
+ const lastArg = arguments[arguments.length - 1]
+ if (typeof lastArg === 'function') {
+ this.once('listening', lastArg)
+ }
+
+ let port = toNumber(arguments[0])
+
+ let address
+
+ // The third optional argument is the backlog size.
+ // When the ip is omitted it can be the second argument.
+ let backlog = toNumber(arguments[1]) || toNumber(arguments[2]) || undefined
+
+ if (arguments[0] !== null && typeof arguments[0] === 'object') {
+ const h = arguments[0]
+
+ if (h._handle || h.handle) {
+ throw new Error('handle is not supported in Chrome Apps.')
+ }
+ if (typeof h.fd === 'number' && h.fd >= 0) {
+ throw new Error('fd is not supported in Chrome Apps.')
+ }
+
+ // The first argument is a configuration object
+ if (h.backlog) {
+ backlog = h.backlog
+ }
+
+ if (typeof h.port === 'number' || typeof h.port === 'string' ||
+ (typeof h.port === 'undefined' && 'port' in h)) {
+ // Undefined is interpreted as zero (random port) for consistency
+ // with net.connect().
+ address = h.host || null
+ port = h.port
+ } else if (h.path && isPipeName(h.path)) {
+ throw new Error('Pipes are not supported in Chrome Apps.')
+ } else {
+ throw new Error('Invalid listen argument: ' + h)
+ }
+ } else if (isPipeName(arguments[0])) {
+ // UNIX socket or Windows pipe.
+ throw new Error('Pipes are not supported in Chrome Apps.')
+ } else if (arguments[1] === undefined ||
+ typeof arguments[1] === 'function' ||
+ typeof arguments[1] === 'number') {
+ // The first argument is the port, no IP given.
+ address = null
+ } else {
+ // The first argument is the port, the second an IP.
+ address = arguments[1]
+ }
+
+ // now do something with port, address, backlog
+
+ if (this.id) {
+ this.close()
+ }
+
+ // If port is invalid or undefined, bind to a random port.
+ assertPort(port)
+ this._port = port | 0
+
+ this._host = address
+
+ const isAny6 = !this._host
+ if (isAny6) {
+ this._host = '::'
+ }
+
+ this._backlog = typeof backlog === 'number' ? backlog : undefined
+
+ this.connecting = true
+
+ // chrome.sockets.tcpServer.create((createInfo) => {
+ // if (!this.connecting || this.id) {
+ // ignoreLastError()
+ // chrome.sockets.tcpServer.close(createInfo.socketId)
+ // return
+ // }
+ // if (chrome.runtime.lastError) {
+ // this.emit('error', new Error(chrome.runtime.lastError.message))
+ // return
+ // }
+
+ // const socketId = this.id = createInfo.socketId
+ // servers[this.id] = this
+
+ // const listen = () => chrome.sockets.tcpServer.listen(this.id, this._host,
+ // this._port, this._backlog,
+ // (result) => {
+ // // callback may be after close
+ // if (this.id !== socketId) {
+ // ignoreLastError()
+ // return
+ // }
+ // if (result !== 0 && isAny6) {
+ // ignoreLastError()
+ // this._host = '0.0.0.0' // try IPv4
+ // isAny6 = false
+ // return listen()
+ // }
+
+ // this._onListen(result)
+ // })
+ // listen()
+ // })
+ this._address = {}
+ this.emit('listening')
+
+ return this
+}
+
+Server.prototype._onListen = function (result) {
+ this.connecting = false
+
+ if (result === 0) {
+ const idBefore = this.id
+ chrome.sockets.tcpServer.getInfo(this.id, (info) => {
+ if (this.id !== idBefore) {
+ ignoreLastError()
+ return
+ }
+ if (chrome.runtime.lastError) {
+ this._onListen(-2) // net::ERR_FAILED
+ return
+ }
+
+ this._address = {
+ port: info.localPort,
+ family: info.localAddress &&
+ info.localAddress.indexOf(':') !== -1
+ ? 'IPv6'
+ : 'IPv4',
+ address: info.localAddress
+ }
+ this.emit('listening')
+ })
+ } else {
+ this.emit('error', exceptionWithHostPort(result, 'listen', this._host, this._port))
+ if (this.id) {
+ chrome.sockets.tcpServer.close(this.id)
+ delete servers[this.id]
+ this.id = null
+ }
+ }
+}
+
+Server.prototype._onAccept = function (clientSocketId) {
+ // Set the `maxConnections` property to reject connections when the server's
+ // connection count gets high.
+ if (this.maxConnections && this._connections >= this.maxConnections) {
+ chrome.sockets.tcp.close(clientSocketId)
+ console.warn('Rejected connection - hit `maxConnections` limit')
+ return
+ }
+
+ this._connections += 1
+
+ const acceptedSocket = new Socket({
+ server: this,
+ id: clientSocketId,
+ allowHalfOpen: this.allowHalfOpen,
+ pauseOnCreate: this.pauseOnConnect
+ })
+ acceptedSocket.on('connect', () => this.emit('connection', acceptedSocket))
+}
+
+Server.prototype._onAcceptError = function (resultCode) {
+ this.emit('error', errnoException(resultCode, 'accept'))
+ this.close()
+}
+
+/**
+ * Stops the server from accepting new connections and keeps existing
+ * connections. This function is asynchronous, the server is finally closed
+ * when all connections are ended and the server emits a 'close' event.
+ * Optionally, you can pass a callback to listen for the 'close' event.
+ * @param {function} callback
+ */
+Server.prototype.close = function (callback) {
+ if (typeof callback === 'function') {
+ if (!this.id) {
+ this.once('close', () => callback(new Error('Not running')))
+ } else {
+ this.once('close', callback)
+ }
+ }
+
+ if (this.id) {
+ chrome.sockets.tcpServer.close(this.id)
+ delete servers[this.id]
+ this.id = null
+ }
+ this._address = null
+ this.connecting = false
+
+ this._emitCloseIfDrained()
+
+ return this
+}
+
+Server.prototype._emitCloseIfDrained = function () {
+ if (this.id || this.connecting || this._connections) {
+ return
+ }
+
+ process.nextTick(emitCloseNT, this)
+}
+
+function emitCloseNT (self) {
+ if (self.id || self.connecting || self._connections) {
+ return
+ }
+ self.emit('close')
+}
+
+Object.defineProperty(Server.prototype, 'listening', {
+ get: function () {
+ return !!this._address
+ },
+ configurable: true,
+ enumerable: true
+})
+
+/**
+ * Returns the bound address, the address family name and port of the socket
+ * as reported by the operating system. Returns an object with three
+ * properties, e.g. { port: 12346, family: 'IPv4', address: '127.0.0.1' }
+ *
+ * @return {Object} information
+ */
+Server.prototype.address = function () {
+ return this._address
+}
+
+Server.prototype.unref =
+Server.prototype.ref = function () {
+ // No chrome.socket equivalent
+ return this
+}
+
+/**
+ * Asynchronously get the number of concurrent connections on the server.
+ * Works when sockets were sent to forks.
+ *
+ * Callback should take two arguments err and count.
+ *
+ * @param {function} callback
+ */
+Server.prototype.getConnections = function (callback) {
+ process.nextTick(callback, null, this._connections)
+}
+
+inherits(Socket, stream.Duplex)
+
+/**
+ * Class: net.Socket
+ * =================
+ *
+ * This object is an abstraction of a TCP or UNIX socket. net.Socket instances
+ * implement a duplex Stream interface. They can be created by the user and
+ * used as a client (with connect()) or they can be created by Node and passed
+ * to the user through the 'connection' event of a server.
+ *
+ * Construct a new socket object.
+ *
+ * options is an object with the following defaults:
+ *
+ * { fd: null // NO CHROME EQUIVALENT
+ * type: null
+ * allowHalfOpen: false // NO CHROME EQUIVALENT
+ * }
+ *
+ * `type` can only be 'tcp4' (for now).
+ *
+ * Event: 'connect'
+ * Emitted when a socket connection is successfully established. See
+ * connect().
+ *
+ * Event: 'data'
+ * - Buffer object
+ * Emitted when data is received. The argument data will be a Buffer or
+ * String. Encoding of data is set by socket.setEncoding(). (See the Readable
+ * Stream section for more information.)
+ *
+ * Note that the data will be lost if there is no listener when a Socket
+ * emits a 'data' event.
+ *
+ * Event: 'end'
+ * Emitted when the other end of the socket sends a FIN packet.
+ *
+ * By default (allowHalfOpen == false) the socket will destroy its file
+ * descriptor once it has written out its pending write queue. However,
+ * by setting allowHalfOpen == true the socket will not automatically
+ * end() its side allowing the user to write arbitrary amounts of data,
+ * with the caveat that the user is required to end() their side now.
+ *
+ * Event: 'timeout'
+ * Emitted if the socket times out from inactivity. This is only to notify
+ * that the socket has been idle. The user must manually close the connection.
+ *
+ * See also: socket.setTimeout()
+ *
+ * Event: 'drain'
+ * Emitted when the write buffer becomes empty. Can be used to throttle
+ * uploads.
+ *
+ * See also: the return values of socket.write()
+ *
+ * Event: 'error'
+ * - Error object
+ * Emitted when an error occurs. The 'close' event will be called directly
+ * following this event.
+ *
+ * Event: 'close'
+ * - had_error Boolean true if the socket had a transmission error
+ * Emitted once the socket is fully closed. The argument had_error is a
+ * boolean which says if the socket was closed due to a transmission error.
+ */
+function Socket (options) {
+ if (!(this instanceof Socket)) return new Socket(options)
+
+ if (typeof options === 'number') {
+ options = { fd: options } // Legacy interface.
+ } else if (options === undefined) {
+ options = {}
+ }
+
+ if (options.handle) {
+ throw new Error('handle is not supported in Chrome Apps.')
+ } else if (options.fd !== undefined) {
+ throw new Error('fd is not supported in Chrome Apps.')
+ }
+
+ options.decodeStrings = true
+ options.objectMode = false
+ stream.Duplex.call(this, options)
+
+ this.destroyed = false
+ this._hadError = false // Used by _http_client.js
+ this.id = null // a number > 0
+ this._parent = null
+ this._host = null
+ this._port = null
+ this._pendingData = null
+
+ this.ondata = null
+ this.onend = null
+
+ this._init()
+ this._reset()
+
+ // default to *not* allowing half open sockets
+ // Note: this is not possible in Chrome Apps, see https://crbug.com/124952
+ this.allowHalfOpen = options.allowHalfOpen || false
+
+ // shut down the socket when we're finished with it.
+ this.on('finish', this.destroy)
+
+ if (options.server) {
+ this.server = this._server = options.server
+ this.id = options.id
+ sockets[this.id] = this
+
+ if (options.pauseOnCreate) {
+ // stop the handle from reading and pause the stream
+ // (Already paused in Chrome version)
+ this._readableState.flowing = false
+ }
+
+ // For incoming sockets (from server), it's already connected.
+ this.connecting = true
+ this.writable = true
+ this._onConnect()
+ }
+}
+exports.Socket = Socket
+
+// called when creating new Socket, or when re-using a closed Socket
+Socket.prototype._init = function () {
+ // The amount of received bytes.
+ this.bytesRead = 0
+
+ this._bytesDispatched = 0
+
+ // Reserve properties
+ this.server = null
+ this._server = null
+}
+
+// called when creating new Socket, or when closing a Socket
+Socket.prototype._reset = function () {
+ this.remoteAddress = this.remotePort =
+ this.localAddress = this.localPort = null
+ this.remoteFamily = 'IPv4'
+ this.readable = this.writable = false
+ this.connecting = false
+}
+
+/**
+ * socket.connect(port, [host], [connectListener])
+ * socket.connect(options, [connectListener])
+ *
+ * Opens the connection for a given socket. If port and host are given, then
+ * the socket will be opened as a TCP socket, if host is omitted, localhost
+ * will be assumed. If a path is given, the socket will be opened as a unix
+ * socket to that path.
+ *
+ * Normally this method is not needed, as net.createConnection opens the
+ * socket. Use this only if you are implementing a custom Socket.
+ *
+ * This function is asynchronous. When the 'connect' event is emitted the
+ * socket is established. If there is a problem connecting, the 'connect'
+ * event will not be emitted, the 'error' event will be emitted with the
+ * exception.
+ *
+ * The connectListener parameter will be added as an listener for the
+ * 'connect' event.
+ *
+ * @param {Object} options
+ * @param {function} cb
+ * @return {Socket} this socket (for chaining)
+ */
+Socket.prototype.connect = function () {
+ const argsLen = arguments.length
+ let args = new Array(argsLen)
+ for (let i = 0; i < argsLen; i++) args[i] = arguments[i]
+ args = normalizeConnectArgs(args)
+ const options = args[0]
+ const cb = args[1]
+
+ if (options.path) {
+ throw new Error('Pipes are not supported in Chrome Apps.')
+ }
+
+ if (this.id) {
+ // already connected, destroy and connect again
+ this.destroy()
+ }
+
+ if (this.destroyed) {
+ this._readableState.reading = false
+ this._readableState.ended = false
+ this._readableState.endEmitted = false
+ this._writableState.ended = false
+ this._writableState.ending = false
+ this._writableState.finished = false
+ this._writableState.errorEmitted = false
+ this._writableState.length = 0
+ this.destroyed = false
+ }
+
+ this.connecting = true
+ this.writable = true
+
+ this._host = options.host || 'localhost'
+ this._port = options.port
+
+ if (typeof this._port !== 'undefined') {
+ if (typeof this._port !== 'number' && typeof this._port !== 'string') {
+ throw new TypeError('"port" option should be a number or string: ' + this._port)
+ }
+ if (!isLegalPort(this._port)) {
+ throw new RangeError('"port" option should be >= 0 and < 65536: ' + this._port)
+ }
+ }
+ this._port |= 0
+
+ this._init()
+
+ this._unrefTimer()
+
+ if (typeof cb === 'function') {
+ this.once('connect', cb)
+ }
+
+ chrome.sockets.tcp.create((createInfo) => {
+ if (!this.connecting || this.id) {
+ ignoreLastError()
+ chrome.sockets.tcp.close(createInfo.socketId)
+ return
+ }
+ if (chrome.runtime.lastError) {
+ this.destroy(new Error(chrome.runtime.lastError.message))
+ return
+ }
+
+ this.id = createInfo.socketId
+ sockets[this.id] = this
+
+ chrome.sockets.tcp.setPaused(this.id, true)
+
+ chrome.sockets.tcp.connect(this.id, this._host, this._port, (result) => {
+ // callback may come after call to destroy
+ if (this.id !== createInfo.socketId) {
+ ignoreLastError()
+ return
+ }
+ if (result !== 0) {
+ this.destroy(exceptionWithHostPort(result, 'connect', this._host, this._port))
+ return
+ }
+
+ this._unrefTimer()
+ this._onConnect()
+ })
+ })
+
+ return this
+}
+
+Socket.prototype._onConnect = function () {
+ const idBefore = this.id
+ chrome.sockets.tcp.getInfo(this.id, (result) => {
+ if (this.id !== idBefore) {
+ ignoreLastError()
+ return
+ }
+ if (chrome.runtime.lastError) {
+ this.destroy(new Error(chrome.runtime.lastError.message))
+ return
+ }
+
+ this.remoteAddress = result.peerAddress
+ this.remoteFamily = result.peerAddress &&
+ result.peerAddress.indexOf(':') !== -1
+ ? 'IPv6'
+ : 'IPv4'
+ this.remotePort = result.peerPort
+ this.localAddress = result.localAddress
+ this.localPort = result.localPort
+
+ this.connecting = false
+ this.readable = true
+
+ this.emit('connect')
+ // start the first read, or get an immediate EOF.
+ // this doesn't actually consume any bytes, because len=0
+ if (!this.isPaused()) this.read(0)
+ })
+}
+
+/**
+ * The number of characters currently buffered to be written.
+ * @type {number}
+ */
+Object.defineProperty(Socket.prototype, 'bufferSize', {
+ get: function () {
+ if (this.id) {
+ let bytes = this._writableState.length
+ if (this._pendingData) bytes += this._pendingData.length
+ return bytes
+ }
+ }
+})
+
+Socket.prototype.end = function (data, encoding) {
+ stream.Duplex.prototype.end.call(this, data, encoding)
+ this.writable = false
+}
+
+Socket.prototype._write = function (chunk, encoding, callback) {
+ if (!callback) callback = () => {}
+
+ if (this.connecting) {
+ this._pendingData = chunk
+ this.once('connect', () => this._write(chunk, encoding, callback))
+ return
+ }
+ this._pendingData = null
+
+ if (!this.id) {
+ callback(new Error('This socket is closed'))
+ return
+ }
+
+ // assuming buffer is browser implementation (`buffer` package on npm)
+ let buffer = chunk.buffer
+ if (chunk.byteLength !== buffer.byteLength) {
+ buffer = buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength)
+ }
+
+ const idBefore = this.id
+ chrome.sockets.tcp.send(this.id, buffer, (sendInfo) => {
+ if (this.id !== idBefore) {
+ ignoreLastError()
+ return
+ }
+
+ if (sendInfo.resultCode < 0) {
+ this._destroy(exceptionWithHostPort(sendInfo.resultCode, 'write', this.remoteAddress, this.remotePort), callback)
+ } else {
+ this._unrefTimer()
+ callback(null)
+ }
+ })
+
+ this._bytesDispatched += chunk.length
+}
+
+Socket.prototype._read = function (bufferSize) {
+ if (this.connecting || !this.id) {
+ this.once('connect', () => this._read(bufferSize))
+ return
+ }
+
+ chrome.sockets.tcp.setPaused(this.id, false)
+
+ const idBefore = this.id
+ chrome.sockets.tcp.getInfo(this.id, (result) => {
+ if (this.id !== idBefore) {
+ ignoreLastError()
+ return
+ }
+ if (chrome.runtime.lastError || !result.connected) {
+ this._onReceiveError(-15) // workaround for https://crbug.com/518161
+ }
+ })
+}
+
+Socket.prototype._onReceive = function (data) {
+ const buffer = Buffer.from(data)
+ const offset = this.bytesRead
+
+ this.bytesRead += buffer.length
+ this._unrefTimer()
+
+ if (this.ondata) {
+ console.error('socket.ondata = func is non-standard, use socket.on(\'data\', func)')
+ this.ondata(buffer, offset, this.bytesRead)
+ }
+ if (!this.push(buffer)) { // if returns false, then apply backpressure
+ chrome.sockets.tcp.setPaused(this.id, true)
+ }
+}
+
+Socket.prototype._onReceiveError = function (resultCode) {
+ if (resultCode === -100) { // net::ERR_CONNECTION_CLOSED
+ if (this.onend) {
+ console.error('socket.onend = func is non-standard, use socket.on(\'end\', func)')
+ this.once('end', this.onend)
+ }
+ this.push(null)
+ this.destroy()
+ } else if (resultCode < 0) {
+ this.destroy(errnoException(resultCode, 'read'))
+ }
+}
+
+function protoGetter (name, callback) {
+ Object.defineProperty(Socket.prototype, name, {
+ configurable: false,
+ enumerable: true,
+ get: callback
+ })
+}
+
+/**
+ * The amount of bytes sent.
+ * @return {number}
+ */
+protoGetter('bytesWritten', function bytesWritten () {
+ if (this.id) return this._bytesDispatched + this.bufferSize
+})
+
+Socket.prototype.destroy = function (exception) {
+ this._destroy(exception)
+}
+
+Socket.prototype._destroy = function (exception, cb) {
+ const fireErrorCallbacks = () => {
+ if (cb) cb(exception)
+ if (exception && !this._writableState.errorEmitted) {
+ process.nextTick(emitErrorNT, this, exception)
+ this._writableState.errorEmitted = true
+ }
+ }
+
+ if (this.destroyed) {
+ // already destroyed, fire error callbacks
+ fireErrorCallbacks()
+ return
+ }
+
+ if (this._server) {
+ this._server._connections -= 1
+ if (this._server._emitCloseIfDrained) this._server._emitCloseIfDrained()
+ }
+
+ this._reset()
+
+ for (let s = this; s !== null; s = s._parent) timers.unenroll(s) // eslint-disable-line node/no-deprecated-api
+
+ this.destroyed = true
+
+ // If _destroy() has been called before chrome.sockets.tcp.create()
+ // callback, we don't have an id. Therefore we don't need to close
+ // or disconnect
+ if (this.id) {
+ delete sockets[this.id]
+ chrome.sockets.tcp.close(this.id, () => {
+ if (this.destroyed) {
+ this.emit('close', !!exception)
+ }
+ })
+ this.id = null
+ }
+
+ fireErrorCallbacks()
+}
+
+Socket.prototype.destroySoon = function () {
+ if (this.writable) this.end()
+
+ if (this._writableState.finished) this.destroy()
+}
+
+/**
+ * Sets the socket to timeout after timeout milliseconds of inactivity on the socket.
+ * By default net.Socket do not have a timeout. When an idle timeout is triggered the
+ * socket will receive a 'timeout' event but the connection will not be severed. The
+ * user must manually end() or destroy() the socket.
+ *
+ * If timeout is 0, then the existing idle timeout is disabled.
+ *
+ * The optional callback parameter will be added as a one time listener for the 'timeout' event.
+ *
+ * @param {number} timeout
+ * @param {function} callback
+ */
+Socket.prototype.setTimeout = function (timeout, callback) {
+ if (timeout === 0) {
+ timers.unenroll(this) // eslint-disable-line node/no-deprecated-api
+ if (callback) {
+ this.removeListener('timeout', callback)
+ }
+ } else {
+ timers.enroll(this, timeout) // eslint-disable-line node/no-deprecated-api
+ timers._unrefActive(this)
+ if (callback) {
+ this.once('timeout', callback)
+ }
+ }
+
+ return this
+}
+
+Socket.prototype._onTimeout = function () {
+ this.emit('timeout')
+}
+
+Socket.prototype._unrefTimer = function unrefTimer () {
+ for (let s = this; s !== null; s = s._parent) {
+ timers._unrefActive(s)
+ }
+}
+
+/**
+ * Disables the Nagle algorithm. By default TCP connections use the Nagle
+ * algorithm, they buffer data before sending it off. Setting true for noDelay
+ * will immediately fire off data each time socket.write() is called. noDelay
+ * defaults to true.
+ *
+ * NOTE: The Chrome version of this function is async, whereas the node
+ * version is sync. Keep this in mind.
+ *
+ * @param {boolean} [noDelay] Optional
+ * @param {function} callback CHROME-SPECIFIC: Called when the configuration
+ * operation is done.
+ */
+Socket.prototype.setNoDelay = function (noDelay, callback) {
+ if (!this.id) {
+ this.once('connect', () => this.setNoDelay(noDelay, callback))
+ return this
+ }
+
+ // backwards compatibility: assume true when `noDelay` is omitted
+ noDelay = noDelay === undefined ? true : !!noDelay
+ chrome.sockets.tcp.setNoDelay(this.id, noDelay, chromeCallbackWrap(callback))
+
+ return this
+}
+
+/**
+ * Enable/disable keep-alive functionality, and optionally set the initial
+ * delay before the first keepalive probe is sent on an idle socket. enable
+ * defaults to false.
+ *
+ * Set initialDelay (in milliseconds) to set the delay between the last data
+ * packet received and the first keepalive probe. Setting 0 for initialDelay
+ * will leave the value unchanged from the default (or previous) setting.
+ * Defaults to 0.
+ *
+ * NOTE: The Chrome version of this function is async, whereas the node
+ * version is sync. Keep this in mind.
+ *
+ * @param {boolean} [enable] Optional
+ * @param {number} [initialDelay]
+ * @param {function} callback CHROME-SPECIFIC: Called when the configuration
+ * operation is done.
+ */
+Socket.prototype.setKeepAlive = function (enable, initialDelay, callback) {
+ if (!this.id) {
+ this.once('connect', () => this.setKeepAlive(enable, initialDelay, callback))
+ return this
+ }
+
+ chrome.sockets.tcp.setKeepAlive(this.id, !!enable, ~~(initialDelay / 1000),
+ chromeCallbackWrap(callback))
+
+ return this
+}
+
+/**
+ * Returns the bound address, the address family name and port of the socket
+ * as reported by the operating system. Returns an object with three
+ * properties, e.g. { port: 12346, family: 'IPv4', address: '127.0.0.1' }
+ *
+ * @return {Object} information
+ */
+Socket.prototype.address = function () {
+ return {
+ address: this.localAddress,
+ port: this.localPort,
+ family: this.localAddress &&
+ this.localAddress.indexOf(':') !== -1
+ ? 'IPv6'
+ : 'IPv4'
+ }
+}
+
+Object.defineProperty(Socket.prototype, '_connecting', {
+ get: function () {
+ return this.connecting
+ }
+})
+
+Object.defineProperty(Socket.prototype, 'readyState', {
+ get: function () {
+ if (this.connecting) {
+ return 'opening'
+ } else if (this.readable && this.writable) {
+ return 'open'
+ } else {
+ return 'closed'
+ }
+ }
+})
+
+Socket.prototype.unref =
+Socket.prototype.ref = function () {
+ // No chrome.socket equivalent
+ return this
+}
+
+//
+// EXPORTED HELPERS
+//
+
+// Source: https://developers.google.com/web/fundamentals/input/form/provide-real-time-validation#use-these-attributes-to-validate-input
+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]?)$/
+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]))$/
+
+exports.isIPv4 = IPv4Regex.test.bind(IPv4Regex)
+exports.isIPv6 = IPv6Regex.test.bind(IPv6Regex)
+
+exports.isIP = function (ip) {
+ return exports.isIPv4(ip) ? 4 : exports.isIPv6(ip) ? 6 : 0
+}
+
+//
+// HELPERS
+//
+
+/**
+ * Returns an array [options] or [options, cb]
+ * It is the same as the argument of Socket.prototype.connect().
+ */
+function normalizeConnectArgs (args) {
+ let options = {}
+
+ if (args[0] !== null && typeof args[0] === 'object') {
+ // connect(options, [cb])
+ options = args[0]
+ } else if (isPipeName(args[0])) {
+ // connect(path, [cb])
+ throw new Error('Pipes are not supported in Chrome Apps.')
+ } else {
+ // connect(port, [host], [cb])
+ options.port = args[0]
+ if (typeof args[1] === 'string') {
+ options.host = args[1]
+ }
+ }
+
+ const cb = args[args.length - 1]
+ return typeof cb === 'function' ? [options, cb] : [options]
+}
+
+function toNumber (x) {
+ return (x = Number(x)) >= 0 ? x : false
+}
+
+function isPipeName (s) {
+ return typeof s === 'string' && toNumber(s) === false
+}
+
+// Check that the port number is not NaN when coerced to a number,
+// is an integer and that it falls within the legal range of port numbers.
+function isLegalPort (port) {
+ if ((typeof port !== 'number' && typeof port !== 'string') ||
+ (typeof port === 'string' && port.trim().length === 0)) {
+ return false
+ }
+ return +port === (+port >>> 0) && port <= 0xFFFF
+}
+
+function assertPort (port) {
+ if (typeof port !== 'undefined' && !isLegalPort(port)) {
+ throw new RangeError('"port" argument must be >= 0 and < 65536')
+ }
+}
+
+// Call the getter function to prevent "Unchecked runtime.lastError" errors
+function ignoreLastError () {
+ void chrome.runtime.lastError // eslint-disable-line no-void
+}
+
+function chromeCallbackWrap (callback) {
+ return () => {
+ let error
+ if (chrome.runtime.lastError) {
+ console.error(chrome.runtime.lastError.message)
+ error = new Error(chrome.runtime.lastError.message)
+ }
+ if (callback) callback(error)
+ }
+}
+
+function emitErrorNT (self, err) {
+ self.emit('error', err)
+}
+
+// Full list of possible error codes: https://code.google.com/p/chrome-browser/source/browse/trunk/src/net/base/net_error_list.h
+// TODO: Try to reproduce errors in both node & Chrome Apps and extend this list
+// (what conditions lead to EPIPE?)
+const errorChromeToUv = {
+ '-10': 'EACCES',
+ '-22': 'EACCES',
+ '-138': 'EACCES',
+ '-147': 'EADDRINUSE',
+ '-108': 'EADDRNOTAVAIL',
+ '-103': 'ECONNABORTED',
+ '-102': 'ECONNREFUSED',
+ '-101': 'ECONNRESET',
+ '-16': 'EEXIST',
+ '-8': 'EFBIG',
+ '-109': 'EHOSTUNREACH',
+ '-4': 'EINVAL',
+ '-23': 'EISCONN',
+ '-6': 'ENOENT',
+ '-13': 'ENOMEM',
+ '-106': 'ENONET',
+ '-18': 'ENOSPC',
+ '-11': 'ENOSYS',
+ '-15': 'ENOTCONN',
+ '-105': 'ENOTFOUND',
+ '-118': 'ETIMEDOUT',
+ '-100': 'EOF'
+}
+function errnoException (err, syscall, details) {
+ const uvCode = errorChromeToUv[err] || 'UNKNOWN'
+ let message = syscall + ' ' + err + ' ' + details
+ if (chrome.runtime.lastError) {
+ message += ' ' + chrome.runtime.lastError.message
+ }
+ message += ' (mapped uv code: ' + uvCode + ')'
+ const e = new Error(message)
+ e.code = e.errno = uvCode
+ // TODO: expose chrome error code; what property name?
+ e.syscall = syscall
+ return e
+}
+
+function exceptionWithHostPort (err, syscall, address, port, additional) {
+ let details
+ if (port && port > 0) {
+ details = address + ':' + port
+ } else {
+ details = address
+ }
+
+ if (additional) {
+ details += ' - Local (' + additional + ')'
+ }
+ const ex = errnoException(err, syscall, details)
+ ex.address = address
+ if (port) {
+ ex.port = port
+ }
+ return ex
+}
diff --git a/capacitor/src/ipc.js b/capacitor/src/ipc.js
new file mode 100644
index 0000000..370de00
--- /dev/null
+++ b/capacitor/src/ipc.js
@@ -0,0 +1,40 @@
+import EventEmitter from 'events'
+
+const ipcRendererUI = new EventEmitter()
+
+export default {
+ emit: (event, data) => {
+ ipcRendererUI.emit(event, data)
+ },
+ on: (event, callback) => {
+ ipcRendererUI.on(event, (event, ...args) => callback(...args))
+ },
+ once: (event, callback) => {
+ ipcRendererUI.once(event, (event, ...args) => callback(...args))
+ },
+ off: event => {
+ ipcRendererUI.removeAllListeners(event)
+ }
+}
+
+ipcRendererUI.on('portRequest', async () => {
+ const { port1, port2 } = new MessageChannel()
+ window.port = {
+ onmessage: cb => {
+ port2.onmessage = ({ type, data }) => cb({ type, data })
+ },
+ postMessage: (a, b) => {
+ port2.postMessage(a, b)
+ }
+ }
+ await window.controller
+ ipcRendererWebTorrent.emit('port', { ports: [port1] })
+ ipcRendererUI.emit('port', { ports: [port2] })
+})
+
+export const ipcRendererWebTorrent = new EventEmitter()
+
+window.version = {
+ arch: 'uwu',
+ platform: 'nyaa'
+}
diff --git a/capacitor/src/main.js b/capacitor/src/main.js
deleted file mode 100644
index d8200ac..0000000
--- a/capacitor/src/main.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import App from './App.svelte'
-
-const app = new App({
- target: document.getElementById('app')
-})
-
-export default app
diff --git a/capacitor/src/webtorrent.js b/capacitor/src/webtorrent.js
new file mode 100644
index 0000000..d70829e
--- /dev/null
+++ b/capacitor/src/webtorrent.js
@@ -0,0 +1,21 @@
+import TorrentClient from 'common/modules/webtorrent.js'
+import { ipcRendererWebTorrent } from './ipc.js'
+
+globalThis.chrome.runtime = { lastError: false, id: 'something' }
+
+const controller = (async () => {
+ const reg = await navigator.serviceWorker.register('./sw.js', { scope: './' })
+
+ const worker = reg.active || reg.waiting || reg.installing
+ return new Promise(resolve => {
+ function checkState (worker) {
+ return worker.state === 'activated' && resolve(reg)
+ }
+ if (!checkState(worker)) {
+ worker.addEventListener('statechange', ({ target }) => checkState(target))
+ }
+ })
+})()
+window.controller = controller
+
+window.client = new TorrentClient(ipcRendererWebTorrent, () => ({ bsize: Infinity, bavail: Infinity }), 'browser', controller)
diff --git a/capacitor/webpack.config.cjs b/capacitor/webpack.config.cjs
index e52ba04..847858f 100644
--- a/capacitor/webpack.config.cjs
+++ b/capacitor/webpack.config.cjs
@@ -1,54 +1,57 @@
-import webpack from 'webpack'
-import TerserPlugin from 'terser-webpack-plugin'
-import info from 'webtorrent/package.json' assert { type: 'json' }
+const webpack = require('webpack')
+const commonConfig = require('common/webpack.config.cjs')
+const { merge } = require('webpack-merge')
+const { join, resolve } = require('path')
/** @type {import('webpack').Configuration} */
-export default {
- entry: './index.js',
- devtool: 'source-map',
- resolve: {
- aliasFields: ['chromeapp'],
- alias: {
- ...info.chromeapp,
- path: 'path-esm',
- stream: 'stream-browserify',
- timers: 'timers-browserify',
- crypto: 'crypto-browserify',
- buffer: 'buffer',
- querystring: 'querystring',
- zlib: '/polyfills/inflate-sync-web.js'
- }
- },
- output: {
- chunkFormat: 'module',
- filename: 'webtorrent.chromeapp.js',
- library: {
- type: 'module'
- }
- },
- mode: 'production',
- target: 'web',
- experiments: {
- outputModule: true
- },
+const capacitorConfig = {
+ entry: [join(__dirname, 'src', 'webtorrent.js')],
plugins: [
new webpack.ProvidePlugin({
- process: '/polyfills/process-fast.js',
+ process: 'webtorrent/polyfills/process-fast.js',
Buffer: ['buffer', 'Buffer']
}),
new webpack.DefinePlugin({
global: 'globalThis'
})
],
- optimization: {
- minimize: true,
- minimizer: [new TerserPlugin({
- terserOptions: {
- format: {
- comments: false
- }
- },
- extractComments: false
- })]
+ devServer: {
+ devMiddleware: {
+ writeToDisk: true
+ },
+ hot: true,
+ client: {
+ overlay: { errors: true, warnings: false, runtimeErrors: false }
+ },
+ port: 5001
}
}
+const alias = {
+ '@/modules/ipc.js': join(__dirname, 'src', 'ipc.js'),
+ 'webtorrent/lib/utp.cjs': false,
+ '@silentbot1/nat-api': false,
+ fs: false,
+ http: 'stream-http',
+ https: 'stream-http',
+ 'load-ip-set': false,
+ net: join(__dirname, 'src', 'chrome-net.js'),
+ dgram: join(__dirname, 'src', 'chrome-dgram.js'),
+ util: 'util',
+ assert: 'assert',
+ os: false,
+ ws: false,
+ ut_pex: 'ut_pex',
+ 'bittorrent-dht': false,
+ path: 'path-esm',
+ 'fs-chunk-store': 'hybrid-chunk-store',
+ stream: 'stream-browserify',
+ timers: 'timers-browserify',
+ crypto: 'crypto-browserify',
+ buffer: 'buffer',
+ 'bittorrent-tracker': 'bittorrent-tracker',
+ querystring: 'querystring',
+ zlib: 'webtorrent/polyfills/inflate-sync-web.js',
+ 'bittorrent-tracker/lib/client/http-tracker.js': resolve('../node_modules/bittorrent-tracker/lib/client/http-tracker.js')
+}
+
+module.exports = merge(commonConfig(__dirname, alias, 'chromeapp'), capacitorConfig)
diff --git a/common/App.svelte b/common/App.svelte
index 83f9767..c6bb5d1 100644
--- a/common/App.svelte
+++ b/common/App.svelte
@@ -2,6 +2,7 @@
import { setContext } from 'svelte'
import { writable } from 'simple-store-svelte'
import { alRequest } from '@/modules/anilist.js'
+ import IPC from '@/modules/ipc.js'
export const page = writable('home')
export const view = writable(null)
@@ -9,8 +10,8 @@
view.set(null)
view.set((await alRequest({ method: 'SearchIDSingle', id: anime })).data.Media)
}
- window.IPC.on('open-anime', handleAnime)
- window.IPC.on('schedule', () => {
+ IPC.on('open-anime', handleAnime)
+ IPC.on('schedule', () => {
page.set('schedule')
})
diff --git a/common/components/Menubar.svelte b/common/components/Menubar.svelte
index 685a71b..27173f3 100644
--- a/common/components/Menubar.svelte
+++ b/common/components/Menubar.svelte
@@ -1,6 +1,7 @@
diff --git a/common/views/WatchTogether/Lobby.svelte b/common/views/WatchTogether/Lobby.svelte
index 407a4a8..1ac9bf5 100644
--- a/common/views/WatchTogether/Lobby.svelte
+++ b/common/views/WatchTogether/Lobby.svelte
@@ -1,5 +1,6 @@