101 lines
3.0 KiB
JavaScript
101 lines
3.0 KiB
JavaScript
const crypto = require('crypto');
|
|
const fetch = require('node-fetch');
|
|
const Vehicle = require('../models/vehicle');
|
|
const PubNub = require('pubnub');
|
|
|
|
const baseUrl = 'https://avtocod.ru/api/v3';
|
|
let deviceToken = crypto.createHash('sha256').update(Date.now().toString()).digest().toString('hex');
|
|
|
|
function fromBase64(data) {
|
|
return Buffer.from(data, 'base64').toString('binary');
|
|
}
|
|
|
|
async function getPage(url) {
|
|
let result = await fetch(url);
|
|
return await result.text();
|
|
}
|
|
|
|
async function getJson(url) {
|
|
let result = await fetch(url);
|
|
return await result.json();
|
|
}
|
|
|
|
function decryptReport(report, hash) {
|
|
let dataLength = report.length/2;
|
|
let encryptedObject = JSON.parse(fromBase64(report.slice(-dataLength).concat(report.slice(0, -dataLength))));
|
|
|
|
let iv = Buffer.from(encryptedObject.iv, 'base64');
|
|
let data = Buffer.from(encryptedObject.value, 'base64');
|
|
let key = crypto.createHash('sha256').update(hash).digest();
|
|
|
|
let decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
|
|
let decrypted = decipher.update(data);
|
|
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
|
|
|
return JSON.parse(decrypted.toString());
|
|
}
|
|
|
|
function waitForReport(pubnubConfig, channel) {
|
|
return new Promise((resolve, reject) => {
|
|
let report = null;
|
|
let retry = 0;
|
|
|
|
let timeout = setTimeout(() => {
|
|
pubnub.unsubscribeAll();
|
|
if(report != null) {
|
|
resolve(report);
|
|
} else {
|
|
reject(new Error('Request timed out'));
|
|
}
|
|
}, 15000);
|
|
|
|
let pubnub = new PubNub(pubnubConfig);
|
|
pubnub.addListener({
|
|
status: function(event) {
|
|
if(event.error == true && event.operation == 'PNSubscribeOperation' && event.category == 'PNAccessDeniedCategory' && ++retry < 5) {
|
|
setTimeout(() => {
|
|
event.errorData.payload.channels.forEach(c => pubnub.subscribe({ channels: [c] }));
|
|
}, 1000);
|
|
}
|
|
},
|
|
message: function(message) {
|
|
if(message.message.event == 'report-ready') {
|
|
clearTimeout(timeout);
|
|
pubnub.unsubscribeAll();
|
|
resolve(message.message.data);
|
|
} else if(message.message.event == 'report-updated') {
|
|
report = message.message.data;
|
|
}
|
|
}
|
|
});
|
|
pubnub.subscribe({ channels: [channel] });
|
|
});
|
|
}
|
|
|
|
class AvtocodProvider {
|
|
static async getReport(number) {
|
|
let url = `${baseUrl}/auto/generate?number=${encodeURIComponent(number)}&device_token=${deviceToken}`;
|
|
let resp = await getJson(url);
|
|
|
|
console.log('URL: ', resp.report_uri);
|
|
let html = await getPage(resp.report_uri);
|
|
let result = html.match(/<meta name="app-version-hash" content="(.*?)" \/>/);
|
|
if(result == null) {
|
|
throw Error('Error getting api version hash');
|
|
}
|
|
let hash = result[1];
|
|
|
|
result = html.match(/value:\s+(\{"cloud_payments_public_key".*?\})\s/);
|
|
if(result == null) {
|
|
throw Error('Error getting report data');
|
|
}
|
|
let mainData = JSON.parse(result[1]);
|
|
let report = decryptReport(mainData.report_container.report, hash);
|
|
if(report == null) {
|
|
report = await waitForReport(mainData.pubnub, mainData.report_container.channel);
|
|
}
|
|
return Vehicle.fromAvtocod(report);
|
|
}
|
|
}
|
|
|
|
module.exports = AvtocodProvider; |