absenat
Version:
dedicated messaging service core
248 lines (245 loc) • 8.79 kB
JavaScript
/**
* @author mr-exception
* @description this method connects to a client
* @param {array} arguments
* @param {function} finished
*/
const connect = (arguments, finished) => {
// --------- validation inputs ----------
const ip = arguments[0];
if (!(new RegExp('(localhost)|(^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$)').test(ip))) {
console.log(chalk.red('Err: ip format is not correct'));
return finished();
}
const port = arguments[1];
if (parseInt(port) < 3000 || parseInt(port) > 9000) {
console.log(chalk.red('Err: port is not correct. valid port range is between 3000 and 9000'));
return finished();
}
// -------- connecting to dst client --------
console.log(`connecting to ${ip}:${port}...`);
const socket = ioClient(`http://${ip}:${port}`);
/**
* ===============================================
* Step 0: sends public key to dst node
* ===============================================
*/
socket.emit('connect_0', config.nickname, config.public_key);
// sets timeout for connection ack and ans event
const connection_timeout = setInterval(() => {
socket.removeEventListener('connect_1');
console.log(chalk.red(`NetErr: connection to ${ip}:${port} timed out`));
clearInterval(connection_timeout); // remoev the timeout
finished();
}, config.max_timeout);
/**
* ===============================================
* Step 1: recieves dst public key and dtn encrypted by src public key
* ===============================================
*/
socket.on('connect_1', (from, cipher) => {
clearInterval(connection_timeout); // cancel the timeout
const answer = JSON.parse(myPrivateKey.decrypt(cipher));
const contactPublicKey = answer.public_key;
const contactPublicKeyNode = createPublicKey(contactPublicKey);
console.log(`${socket.id} started connection as ${from}`);
connections[socket.id] = {
nickname: from,
socket,
public_key: contactPublicKey,
};
/**
* ===============================================
* Step 2: sends dtn and dst encrypted by dst public key to dst
* ===============================================
*/
const stn = parseInt(Math.random() * 99999);
const message = contactPublicKeyNode.encrypt(JSON.stringify({
dst: from,
stn,
dtn: answer.dtn,
}));
socket.emit('connect_2', config.nickname, message);
/**
* ===============================================
* Step 3: recieves stn from dst to complete the trust level
* ===============================================
*/
socket.on('connect_3', (title, cipher) => {
console.log(chalk.green('DTN is valid. finished the trusting level!'));
const answer = parseInt(myPrivateKey.decrypt(cipher).toString());
if (answer === stn) {
db.addBridge(title, 'NuLL', contactPublicKey, ip, port);
console.log(chalk.green('STN is valid.'));
// hearbeat progress
setInterval(() => {
socket.emit('hi_im_normal');
}, config.max_timeout);
socket.on('hi_im_bridge', () => {
db.bridgeHeartBeat(contactPublicKey);
});
handlers.messages(socket);
/**
* ===============================================
* Step 4: tells dst node that finished the trusing level
* ===============================================
*/
socket.emit('connect_4', config.nickname);
finished();
} else {
console.log(chalk.red('Err: invalid STN.'));
}
});
});
};
const requestContact = (arguments, finished) => {
const contact_public_key = arguments[0];
if (!contact_public_key) {
console.log(chalk.red('Err: please enter contact public key'));
return finished();
}
// generating trust string
const ts = uuidv4();
// unique universal id for message
message_id = uuidv4();
// creating message for dst node
const chunks = chunkMessage(message_id, {
a: 'indirect_connection', // action
pk: config.public_key, // source node public key
ts, // trust string
}, contact_public_key);
db.getBridges(bridges => {
let found_active_bridge = false;
for (const i in bridges) {
const bridge = bridges[i];
const bridgePublicKey = createPublicKey(bridge.public_key);
const socket = getSocket(bridge.public_key);
if (socket) {
let success_count = 0;
let count = chunks.length;
for (const i in chunks) {
socket.emit('indirect_message', config.public_key, bridgePublicKey.encrypt(chunks[i]));
socket.once('indirect_packet_recieved', () => {
success_count++;
if (success_count === count)
console.log(`\rpacket sent (c: ${count}, id: ${message_id}).`);
finished();
});
}
socket.once('indirect_packet_recieved', () => {
console.log(`\rrequested contact from bridge ${chalk.blue(bridge.title)}.`);
});
found_active_bridge = true;
}
}
if (!found_active_bridge) {
console.log(chalk.red("Err: you don't have any active bridge."));
}
return finished();
}, () => {
console.log(chalk.red("Err: you don't have any active bridge, please connect to a bridge first."));
});
}
/**
* @author mr-exception
* @description this method shows brdiges list
* @param {array} arguments
* @param {function} finished
*/
const showBridges = (arguments, finished) => {
db.getBridges(bridges => {
for (const i in bridges) {
const bridge = bridges[i];
console.log(`${bridge.id} => ${bridge.title} : (${(bridge.last_time_connected < Date.now() - config.max_timeout) ? 'offline' : 'online'})`);
}
return finished();
}, () => {
console.log(chalk.red(`Err: you don't have any bridge, plaese enter connect <ip> <port> to add new bridge`));
return finished();
})
}
/**
* @author mr-exception
* @description connects to a specified bridge by its id
* @param {array} arguments
* @param {function} finished
*/
const connectBridge = (arguments, finished) => {
const bridge_id = arguments[0];
if (!bridge_id) {
console.log(chalk.red('Err: bridge enter required.'));
return finished();
}
db.getBridge(bridge_id, bridge => {
if (bridge.last_time_connected < Date.now() - config.max_timeout) {
connect([bridge.ip, bridge.port], finished);
} else {
console.log(chalk.blue('bridge is connected!'));
return finished();
}
}, () => {
console.log(chalk.red('Err: bridge not found.'));
return finished();
});
}
/**
* @author mr-exception
* @description connects to all bridges
* @param {*} arguments
* @param {*} finished
*/
const connectAllBridges = (arguments, finished) => {
db.getBridges(bridges => {
bridges.forEach(bridge => {
if (bridge.last_time_connected < Date.now() - config.max_timeout) {
console.log(`connecting bridge ${chalk.blue(bridge.title)}...`);
connect([bridge.ip, bridge.port], finished);
} else {
console.log(`bridge ${chalk.blue(bridge.title)} is connected!`);
return finished();
}
});
}, () => {
console.log(chalk.red(`Err: you don't have any bridge, plaese enter connect <ip> <port> to add new bridge`));
return finished();
});
}
/**
* @author mr-exception
* @description gets latest information of a user by it's public key
* @param {array} arguments
* @param {function} finished
*/
const getProfile = (arguments, finished) => {
const public_key = arguments[0];
if (!public_key) {
console.log(chalk.red('Err: public key required, you have to enter the valid public key'));
return finished();
}
for (const index in connections) {
const connection = connections[index];
console.log(`asking profile from ${connection.nickname}`);
const public_key_node = createPublicKey(connection.public_key);
const cipher = public_key_node.encrypt(public_key);
connection.socket.emit('get_profile', config.public_key, cipher);
connection.socket.once('contact_not_found', (contact_public_key, cipher) => {
console.log(chalk.red(`Err: node ${connection.nickname} did not found you target.`));
finished();
});
connection.socket.once('contact_found', (contact_public_key, cipher) => {
const response = JSON.parse(myPrivateKey.decrypt(cipher));
// db.addIndirectConnection(response.nickname, response.public_key);
console.log(chalk.green(`contact found.`));
console.log(response);
finished();
});
}
}
module.exports = {
getProfile,
showBridges,
connectBridge,
connectAllBridges,
connect,
requestContact,
}