@luminati-io/luminati-proxy
Version:
A configurable local proxy for luminati.io
216 lines (213 loc) • 7.76 kB
JavaScript
// LICENSE_CODE ZON ISC
; /*jslint node:true, esnext:true*/
const _ = require('lodash');
const events = require('events');
const dns = require('dns');
const http = require('http');
const rand = require('../util/rand.js');
const etask = require('../util/etask.js');
const zws = require('../util/ws.js');
const date = require('../util/date.js');
const zerr = require('../util/zerr.js');
const logger = require('./logger.js').child({category: 'MNGR: lpm_f'});
const util_lib = require('./util.js');
const perr = require('./perr.js');
const Lpm_f = etask._class(class Lpm_f extends events.EventEmitter {
constructor(mgr){
super();
this.mgr = mgr;
this.argv = mgr.argv;
this.errors = 0;
this.ever_connected = false;
this.sync_recent_stats = _.throttle(this.update_stats, date.ms.MIN);
}
*init(_this){
_this.uri_ws = `ws://zagent75.${_this.mgr._defaults.api_domain}`;
_this.ws = new zws.Client(_this.uri_ws, {
label: 'lpm_f',
agent: new http.Agent({
lookup: (hostname, opt, cb)=>{
const _opt = Object.assign({}, opt,
{family: 4, all: true});
dns.lookup(hostname, _opt, (err, res)=>{
if (err)
return cb(err);
const {address, family=4} = rand.rand_element(res)||{};
cb(undefined, address, family);
});
},
}),
ipc_client: {
hello: 'post',
reset_auth: 'post',
auth: {type: 'call', timeout: 30*date.ms.SEC},
update_conf: {type: 'call', timeout: 30*date.ms.SEC},
get_conf: {type: 'call', timeout: 30*date.ms.SEC},
get_meta_conf: {type: 'call', timeout: 30*date.ms.SEC},
get_server_conf: {type: 'call', timeout: 30*date.ms.SEC},
update_stats: {type: 'call', timeout: 30*date.ms.SEC},
send_stat: {type: 'call', timeout: 30*date.ms.SEC},
resolve_proxies: {type: 'call', timeout: 30*date.ms.SEC},
},
})
.on('connected', ()=>_this.on_connected(this))
.on('disconnected', _this.on_disconnected.bind(_this))
.on('json', _this.on_json.bind(_this));
yield this.wait();
_this.ever_connected = true;
}
*on_connected(_this, et){
this.on('uncaught', e=>{
logger.error('on_connected: %s', zerr.e2s(e));
et.throw(e);
});
logger.notice('Connection established');
yield _this.ws.ipc.hello();
if (_this.ever_connected)
{
let auth_conf;
if (auth_conf = yield _this.login())
yield _this.mgr.apply_cloud_config(auth_conf);
}
et.continue();
}
*on_json(_this, data){
this.on('uncaught', e=>logger.error('json %s', zerr.e2s(e)));
if (!data || !data.msg)
return;
if (data.msg=='new_conf')
yield _this.mgr.apply_cloud_config(data.new_conf);
else if (data.msg=='zones')
_this.mgr.apply_zones_config(data.zones);
else if (data.msg=='server_conf' && data.server_conf)
_this.emit('server_conf', data.server_conf);
}
on_disconnected(){
logger.warn('Connection failed');
this.errors++;
if (this.errors>1 && !this.ever_connected)
{
logger.warn('Could not establish WS connection to '+this.uri_ws);
this.ws.close();
}
}
close(){
if (this.ws)
this.ws.close();
if (this.sync_recent_stats)
this.sync_recent_stats.cancel();
}
connected(){
return this.ws && this.ws.connected;
}
*login(_this){
this.on('uncaught', e=>{
logger.error('login %s', zerr.e2s(e));
util_lib.perr('error', {error: zerr.e2s(e), ctx: 'lpm_f login'});
});
const lpm_token = _this.mgr._defaults.lpm_token;
if (!_this.connected() || !lpm_token)
return;
const auth_res = yield _this.ws.ipc.auth({lpm_token});
if (auth_res.err)
{
logger.warn('Authentication failed: '+auth_res.err);
return false;
}
logger.notice('Authentication success');
return auth_res.config;
}
*logout(_this){
this.on('uncaught', e=>logger.error('logout %s', zerr.e2s(e)));
if (!_this.connected())
return;
yield _this.ws.ipc.reset_auth();
}
*update_conf(_this, config){
this.on('uncaught', e=>logger.warn('update_conf %s', e.message));
const lpm_token = _this.mgr._defaults.lpm_token;
if (!_this.connected() || !lpm_token)
return;
const changes = _this.mgr.config_changes;
_this.mgr.config_changes = [];
const resp = yield _this.ws.ipc.update_conf({
lpm_token,
config,
changes,
});
if (resp && resp.err)
throw new Error(resp.err);
}
*get_conf(_this, opt={}){
this.on('uncaught', e=>logger.error('get_conf %s', zerr.e2s(e)));
const lpm_token = _this.mgr._defaults.lpm_token;
if (!_this.connected() || !lpm_token)
return;
const resp = yield _this.ws.ipc.get_conf({lpm_token});
if (!opt.retried && resp && resp.err=='not_authorized')
{
const auth_conf = yield _this.login();
return auth_conf||{};
}
return resp && resp.config || {};
}
*get_meta_conf(_this){
if (!_this.connected())
throw new Error('no_lpm_f_conn');
const opt = {zagent: _this.argv.zagent};
const resp = yield _this.ws.ipc.get_meta_conf(opt);
if (!resp || !resp.config)
throw new Error(resp && resp.err || 'unknown err from lpm_f');
return resp.config;
}
*get_server_conf(_this){
// XXX krzysztof: consider throwing an error and mock in the tests
if (!_this.connected())
return;
const resp = yield _this.ws.ipc.get_server_conf();
if (!resp || !resp.server_conf)
throw new Error(resp && resp.err || 'unknown err from lpm_f');
_this.emit('server_conf', resp.server_conf);
}
*send_stat(_this, data){
if (!_this.connected())
throw new Error('no_lpm_f_conn');
const resp = yield _this.ws.ipc.send_stat({data});
if (resp && resp.err)
throw new Error(resp.err);
return resp;
}
*update_stats(_this){
this.on('uncaught', e=>{
logger.error('update_stats %s', zerr.e2s(e));
});
if (!_this.argv.sync_stats)
return;
const stats = _this.mgr.loki.stats_get();
const lpm_token = _this.mgr._defaults.lpm_token;
if (!_this.connected() || !lpm_token)
return;
const resp = yield _this.ws.ipc.update_stats({
lpm_token,
stats,
uuid: perr.uuid,
});
if (resp && resp.err)
throw new Error(resp.err);
}
*resolve_proxies(_this, opt={}){
this.on('uncaught', e=>{
logger.error('update_stats %s', zerr.e2s(e));
this.return([]);
});
const resp = yield _this.ws.ipc.resolve_proxies({
limit: 20,
cn: !!opt.cn,
});
if (!resp || !resp.proxies)
throw new Error(resp && resp.err || 'unknown err from lpm_f');
return resp.proxies;
}
});
module.exports = Lpm_f;