Convert network module to typescript

Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
This commit is contained in:
Cameron Diver 2018-11-27 13:43:06 +00:00
parent 4bcdc83850
commit 6317b16138
No known key found for this signature in database
GPG Key ID: 49690ED87032539F
2 changed files with 156 additions and 107 deletions

View File

@ -1,107 +0,0 @@
Promise = require 'bluebird'
_ = require 'lodash'
url = require 'url'
networkCheck = require 'network-checker'
os = require 'os'
fs = Promise.promisifyAll(require('fs'))
constants = require './lib/constants'
{ checkTruthy } = require './lib/validation'
blink = require './lib/blink'
{ EEXIST } = require './lib/errors'
networkPattern =
blinks: 4
pause: 1000
pauseConnectivityCheck = false
enableConnectivityCheck = true
# options: An object of net.connect options, with the addition of:
# timeout: 10s
checkHost = (options) ->
if !enableConnectivityCheck or pauseConnectivityCheck
return true
else
return networkCheck.checkHost(options)
# Custom monitor that uses checkHost function above.
customMonitor = (options, fn) ->
networkCheck.monitor(checkHost, options, fn)
# enable: A Boolean to enable/disable the connectivity checks
exports.enableCheck = enableCheck = (enable) ->
enableConnectivityCheck = enable
# Call back for inotify triggered when the VPN status is changed.
vpnStatusInotifyCallback = ->
fs.lstatAsync(constants.vpnStatusPath + '/active')
.then ->
pauseConnectivityCheck = true
.catch ->
pauseConnectivityCheck = false
exports.startConnectivityCheck = _.once (apiEndpoint, enable, onChangeCallback) ->
exports.enableConnectivityCheck(enable)
if !apiEndpoint?
console.log('No API endpoint specified, skipping connectivity check')
return
parsedUrl = url.parse(apiEndpoint)
fs.mkdirAsync(constants.vpnStatusPath)
.catch EEXIST, (err) ->
console.log('VPN status path exists.')
.then ->
fs.watch(constants.vpnStatusPath, vpnStatusInotifyCallback)
# Manually trigger the call back to detect cases when VPN was switched on before the supervisor starts.
vpnStatusInotifyCallback() if enable
customMonitor
host: parsedUrl.hostname
port: parsedUrl.port ? (if parsedUrl.protocol is 'https:' then 443 else 80)
interval: 10 * 1000
(connected) ->
onChangeCallback?(connected)
if connected
console.log('Internet Connectivity: OK')
blink.pattern.stop()
else
console.log('Waiting for connectivity...')
blink.pattern.start(networkPattern)
# Callback function to enable/disable tcp pings
exports.enableConnectivityCheck = (val) ->
enabled = checkTruthy(val) ? true
enableCheck(enabled)
console.log("Connectivity check enabled: #{enabled}")
exports.connectivityCheckEnabled = Promise.method ->
return enableConnectivityCheck
exports.getIPAddresses = ->
# We get IP addresses but ignore:
# - docker and balena bridges (docker0, docker1, balena0, etc)
# - legacy rce bridges (rce0, etc)
# - tun interfaces like the legacy vpn
# - the resin VPN interface (resin-vpn)
# - loopback interface (lo)
# - the bridge for dnsmasq (resin-dns)
# - the docker network for the supervisor API (supervisor0)
# - custom docker network bridges (br- + 12 hex characters)
_.flatten(_.map(_.omitBy(os.networkInterfaces(), (interfaceFields, interfaceName) ->
/^(?:balena|docker|rce|tun)[0-9]+|tun[0-9]+|resin-vpn|lo|resin-dns|supervisor0|balena-redsocks|resin-redsocks|br-[0-9a-f]{12}$/.test(interfaceName))
, (validInterfaces) ->
_.map(_.pickBy(validInterfaces, family: 'IPv4'), 'address'))
)
exports.startIPAddressUpdate = do ->
_lastIPValues = null
return (callback, interval) ->
getAndReportIP = ->
ips = exports.getIPAddresses()
if !_.isEmpty(_.xor(ips , _lastIPValues))
_lastIPValues = ips
callback(ips)
setInterval( ->
getAndReportIP()
, interval)
getAndReportIP()

156
src/network.ts Normal file
View File

@ -0,0 +1,156 @@
import * as Bluebird from 'bluebird';
import * as _ from 'lodash';
import { fs } from 'mz';
import * as networkCheck from 'network-checker';
import * as os from 'os';
import * as url from 'url';
import * as constants from './lib/constants';
import { EEXIST } from './lib/errors';
import { checkTruthy } from './lib/validation';
import blink = require('./lib/blink');
const networkPattern = {
blinks: 4,
pause: 1000,
};
let isConnectivityCheckPaused = false;
let isConnectivityCheckEnabled = true;
function checkHost(
opts: networkCheck.ConnectOptions,
): boolean | PromiseLike<boolean> {
return (
!isConnectivityCheckEnabled ||
isConnectivityCheckPaused ||
networkCheck.checkHost(opts)
);
}
function customMonitor(
options: networkCheck.ConnectOptions,
fn: networkCheck.MonitorChangeFunction,
) {
return networkCheck.monitor(checkHost, options, fn);
}
export function enableCheck(enable: boolean) {
isConnectivityCheckEnabled = enable;
}
async function vpnStatusInotifyCallback(): Promise<void> {
try {
await fs.lstat(constants.vpnStatusPath);
isConnectivityCheckPaused = true;
} catch {
isConnectivityCheckPaused = false;
}
}
export const startConnectivityCheck = _.once(
async (
apiEndpoint: string,
enable: boolean,
onChangeCallback?: networkCheck.MonitorChangeFunction,
) => {
enableConnectivityCheck(enable);
if (!apiEndpoint) {
console.log('No API endpoint specified, skipping connectivity check');
return;
}
await Bluebird.resolve(fs.mkdir(constants.vpnStatusPath))
.catch(EEXIST, () => {
console.log('VPN status path exists.');
})
.then(() => {
fs.watch(constants.vpnStatusPath, vpnStatusInotifyCallback);
});
if (enable) {
vpnStatusInotifyCallback();
}
const parsedUrl = url.parse(apiEndpoint);
const port = parseInt(parsedUrl.port!, 10);
customMonitor(
{
host: parsedUrl.hostname,
port: port || (parsedUrl.protocol === 'https' ? 443 : 80),
path: parsedUrl.path || '/',
interval: 10 * 1000,
},
connected => {
if (_.isFunction(onChangeCallback)) {
onChangeCallback(connected);
}
if (connected) {
console.log('Internet Connectivity: OK');
blink.pattern.stop();
} else {
console.log('Waiting for connectivity...');
blink.pattern.start(networkPattern);
}
},
);
},
);
export function enableConnectivityCheck(enable: boolean) {
const boolEnable = checkTruthy(enable);
enable = boolEnable != null ? boolEnable : true;
enableCheck(enable);
console.log(`Connectivity check enabled: ${enable}`);
}
export const connectivityCheckEnabled = Bluebird.method(
() => isConnectivityCheckEnabled,
);
const IP_REGEX = /^(?:balena|docker|rce|tun)[0-9]+|tun[0-9]+|resin-vpn|lo|resin-dns|supervisor0|balena-redsocks|resin-redsocks|br-[0-9a-f]{12}$/;
export function getIPAddresses(): string[] {
// We get IP addresses but ignore:
// - docker and balena bridges (docker0, docker1, balena0, etc)
// - legacy rce bridges (rce0, etc)
// - tun interfaces like the legacy vpn
// - the resin VPN interface (resin-vpn)
// - loopback interface (lo)
// - the bridge for dnsmasq (resin-dns)
// - the docker network for the supervisor API (supervisor0)
// - custom docker network bridges (br- + 12 hex characters)
return _(os.networkInterfaces())
.omitBy((_interfaceFields, interfaceName) => IP_REGEX.test(interfaceName))
.flatMap(validInterfaces => {
return _(validInterfaces)
.pickBy({ family: 'IPv4' })
.map('address')
.value();
})
.value();
}
export function startIPAddressUpdate(): (
callback: (ips: string[]) => void,
interval: number,
) => void {
let lastIPValues: string[] | null = null;
return (cb, interval) => {
const getAndReportIP = () => {
const ips = getIPAddresses();
if (
!_(ips)
.xor(lastIPValues)
.isEmpty()
) {
lastIPValues = ips;
cb(ips);
}
};
setInterval(getAndReportIP, interval);
getAndReportIP();
};
}