UNPKG

vw-services-client

Version:

Partial port of https://github.com/robinostlund/volkswagencarnet for node

707 lines 36.2 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); var jssoup_1 = __importDefault(require("jssoup")); var node_fetch_1 = __importDefault(require("node-fetch")); // @ts-ignore // tslint:disable-next-line:no-var-requires var fetch = require('fetch-cookie/node-fetch')(node_fetch_1.default); // tslint:disable:no-console var HEADERS_SESSION = { Accept: 'application/json, text/plain, */*', 'Content-Type': 'application/json;charset=UTF-8', 'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0.1; D5803 Build/23.5.A.1.291; wv) \ AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36', }; var HEADERS_AUTH = { Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,\ image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0.1; D5803 Build/23.5.A.1.291; wv) \ AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36', }; var BASE_SESSION = 'https://www.portal.volkswagen-we.com/'; var BASE_AUTH = 'https://identity.vwgroup.io'; function makeCopy(obj) { var newMap = {}; // tslint:disable-next-line:forin for (var i in obj) { newMap[i] = obj[i]; } return newMap; } function getJsonFromUrl(url) { if (!url) { url = location.href; } var question = url.indexOf('?'); var hash = url.indexOf('#'); if (hash === -1 && question === -1) { return {}; } if (hash === -1) { hash = url.length; } var query = question === -1 || hash === question + 1 ? url.substring(hash) : url.substring(question + 1, hash); var result = {}; query.split('&').forEach(function (part) { if (!part) { return; } part = part.split('+').join(' '); // replace every + with space, regexp-free version var eq = part.indexOf('='); var key = eq > -1 ? part.substr(0, eq) : part; var val = eq > -1 ? decodeURIComponent(part.substr(eq + 1)) : ''; var from = key.indexOf('['); if (from === -1) { result[decodeURIComponent(key)] = val; } else { var to = key.indexOf(']', from); var index = decodeURIComponent(key.substring(from + 1, to)); key = decodeURIComponent(key.substring(0, from)); if (!result[key]) { result[key] = []; } if (!index) { result[key].push(val); } else { result[key][index] = val; } } }); return result; } var Connection = /** @class */ (function () { function Connection(agent, username, password) { this.agent = agent; this.sessionHeaders = makeCopy(HEADERS_SESSION); this.sessionBase = BASE_SESSION; this.sessionAuthHeaders = makeCopy(HEADERS_AUTH); this.sessionAuthBase = BASE_AUTH; this.sessionAuthRefUrl = ''; this.sessionFirstUpdate = false; this.sessionAuthUsername = username; this.sessionAuthPassword = password; this.state = {}; } Connection.prototype.login = function () { return __awaiter(this, void 0, void 0, function () { var extractCSRF, req, text, csrf, responseData, lgUrl, refUrl1, content, getInput, getForm, loginCsrf, loginToken, loginHmac, loginFormAction, loginUrl, postData, toUrlEncoded, refUrl2, authRelayUrl, authCsrf, authToken, authHmac, authFormAction, authUrl, refUrl3, refUrl4, refUrl5, refUrl6, parsedUrl, state, code, url, url2, refUrl7, _a, e_1; return __generator(this, function (_b) { switch (_b.label) { case 0: // """ Reset session in case we would like to login again """ this.sessionHeaders = makeCopy(HEADERS_SESSION); this.sessionAuthHeaders = makeCopy(HEADERS_AUTH); extractCSRF = function (req) { var r = /<meta name="_csrf" content="([^"]*)?"\/>/i; var items = req.match(r); return items[1]; }; _b.label = 1; case 1: _b.trys.push([1, 19, , 20]); return [4 /*yield*/, fetch(this.sessionBase + '/portal/en_GB/web/guest/home', { method: 'GET', })]; case 2: req = _b.sent(); if (req.status !== 200) { return [2 /*return*/, false]; } return [4 /*yield*/, req.text()]; case 3: text = _b.sent(); csrf = extractCSRF(text); // Request login page and get CSRF this.sessionAuthHeaders.Referer = this.sessionBase + 'portal'; return [4 /*yield*/, fetch(this.sessionBase + 'portal/web/guest/home/-/csrftokenhandling/get-login-url', { method: 'POST', headers: this.sessionAuthHeaders, redirect: 'follow', })]; case 4: req = _b.sent(); if (req.status !== 200) { return [2 /*return*/, false]; } return [4 /*yield*/, req.json()]; case 5: responseData = _b.sent(); lgUrl = responseData.loginURL.path; return [4 /*yield*/, fetch(lgUrl, { method: 'GET', redirect: 'manual', headers: this.sessionAuthHeaders, })]; case 6: // no redirect so we can get values we look for req = _b.sent(); if (req.status !== 302) { return [2 /*return*/, false]; } refUrl1 = req.headers.get('location'); return [4 /*yield*/, fetch(refUrl1, { method: 'GET', headers: this.sessionAuthHeaders, })]; case 7: // now get actual login page and get session id and ViewState req = _b.sent(); if (req.status !== 200) { return [2 /*return*/, false]; } return [4 /*yield*/, req.text()]; case 8: content = _b.sent(); getInput = function (data, name) { var soup = new jssoup_1.default(data); var items = soup.findAll('input'); for (var _i = 0, items_1 = items; _i < items_1.length; _i++) { var item = items_1[_i]; if (item.attrs.name === name) { return item.attrs.value; } } return ''; }; getForm = function (data, name) { var soup = new jssoup_1.default(data); var items = soup.findAll('form'); for (var _i = 0, items_2 = items; _i < items_2.length; _i++) { var item = items_2[_i]; if (item.attrs.id === name) { return item.attrs.action; } } return ''; }; loginCsrf = getInput(content, '_csrf'); loginToken = getInput(content, 'relayState'); loginHmac = getInput(content, 'hmac'); loginFormAction = getForm(content, 'emailPasswordForm'); loginUrl = this.sessionAuthBase + loginFormAction; // post login this.sessionAuthHeaders.Referer = refUrl1; postData = { email: this.sessionAuthUsername, password: this.sessionAuthPassword, relayState: loginToken, hmac: loginHmac, _csrf: loginCsrf, }; toUrlEncoded = function (obj) { return Object.keys(obj) .map(function (k) { return encodeURIComponent(k) + '=' + encodeURIComponent(obj[k]); }) .join('&'); }; return [4 /*yield*/, fetch(loginUrl, { method: 'POST', redirect: 'manual', body: toUrlEncoded(postData), headers: this.sessionAuthHeaders, })]; case 9: req = _b.sent(); if (req.status !== 303) { return [2 /*return*/, false]; } refUrl2 = req.headers.get('location'); authRelayUrl = refUrl2; return [4 /*yield*/, fetch(authRelayUrl, { method: 'GET', redirect: 'manual', // body: toUrlEncoded(postData), headers: this.sessionAuthHeaders, })]; case 10: // get: https://identity.vwgroup.io/signin-service/v1/xxx@apps_vw-dilab_com/login/authenticate?relayState=xxx&email=xxx req = _b.sent(); if (req.status !== 200) { return [2 /*return*/, false]; } return [4 /*yield*/, req.text()]; case 11: content = _b.sent(); authCsrf = getInput(content, '_csrf'); authToken = getInput(content, 'relayState'); authHmac = getInput(content, 'hmac'); authFormAction = getForm(content, 'credentialsForm'); authUrl = this.sessionAuthBase + authFormAction; // post login this.sessionAuthHeaders.Referer = authRelayUrl; postData = { email: this.sessionAuthUsername, password: this.sessionAuthPassword, relayState: authToken, hmac: authHmac, _csrf: authCsrf, }; return [4 /*yield*/, fetch(authUrl, { method: 'POST', headers: this.sessionAuthHeaders, redirect: 'manual', body: toUrlEncoded(postData), })]; case 12: // post: https://identity.vwgroup.io/signin-service/v1/xxx@apps_vw-dilab_com/login/authenticate req = _b.sent(); if (req.status !== 302) { return [2 /*return*/, false]; } refUrl3 = req.headers.get('location'); return [4 /*yield*/, fetch(refUrl3, { method: 'GET', headers: this.sessionAuthHeaders, redirect: 'manual', })]; case 13: req = _b.sent(); if (req.status !== 302) { return [2 /*return*/, false]; } refUrl4 = req.headers.get('location'); return [4 /*yield*/, fetch(refUrl4, { method: 'GET', headers: this.sessionAuthHeaders, redirect: 'manual', })]; case 14: req = _b.sent(); if (req.status !== 302) { return [2 /*return*/, false]; } refUrl5 = req.headers.get('location'); return [4 /*yield*/, fetch(refUrl5, { method: 'GET', headers: this.sessionAuthHeaders, redirect: 'manual', })]; case 15: // user_id = parse_qs(urlparse(refUrl5).query).get('user_id')[0] req = _b.sent(); if (req.status !== 302) { return [2 /*return*/, false]; } refUrl6 = req.headers.get('location'); parsedUrl = getJsonFromUrl(refUrl6); state = parsedUrl.state; code = parsedUrl.code; // post: // https://www.portal.volkswagen-we.com/portal/web/guest/complete-login?p_auth=xxx&p_p_id=33_WAR_cored5portlet&p_p_lifecycle=1&p_pstate=normal&p_p_mode=view&p_p_col_id=column-1&p_p_col_count=1&_33_WAR_cored5portlet_javax.portlet.action=getLoginStatus this.sessionAuthHeaders.Referer = refUrl6; postData = { _33_WAR_cored5portlet_code: code, _33_WAR_cored5portlet_landingPageUrl: '', }; url = refUrl6.split('?')[0]; url2 = url + '?p_auth=' + state + '&p_p_id=33_WAR_cored5portlet&p_p_lifecycle=1&p_p_state=normal&p_p_mode=view&p_p_col_id=column-1&p_p_col_count=1&_33_WAR_cored5portlet_javax.portlet.action=getLoginStatus'; return [4 /*yield*/, fetch(url2, { method: 'POST', headers: this.sessionAuthHeaders, redirect: 'manual', body: toUrlEncoded(postData), timeout: 10000, })]; case 16: req = _b.sent(); if (req.status !== 302) { return [2 /*return*/, false]; } refUrl7 = req.headers.get('location'); return [4 /*yield*/, fetch(refUrl7, { method: 'GET', headers: this.sessionAuthHeaders, redirect: 'manual', })]; case 17: req = _b.sent(); if (req.status !== 200) { return [2 /*return*/, false]; } _a = extractCSRF; return [4 /*yield*/, req.text()]; case 18: // We have a new CSRF csrf = _a.apply(void 0, [_b.sent()]); // cookie = this.session.cookies.get_dict() // cookie = req.cookies // this.session_guest_language_id = extract_guest_language_id(req.cookies.get('GUEST_LANGUAGE_ID').value) // Update headers for requests this.sessionHeaders.Referer = refUrl7; this.sessionHeaders['X-CSRF-Token'] = csrf; this.sessionAuthRefUrl = refUrl7 + '/'; return [2 /*return*/, true]; case 19: e_1 = _b.sent(); console.log(e_1); return [2 /*return*/, false]; case 20: return [2 /*return*/]; } }); }); }; Connection.prototype.logout = function () { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.post('-/logout/revoke', undefined, undefined)]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }; Connection.prototype.update = function () { return __awaiter(this, void 0, void 0, function () { var loadedVehicles, _i, _a, vehicle, vehicleVin, _b, _c, vehicle, vehicleUrl, _d, _e, key, _f, _g, vehicle, e_2; return __generator(this, function (_h) { switch (_h.label) { case 0: _h.trys.push([0, 17, , 18]); if (!this.sessionFirstUpdate) return [3 /*break*/, 4]; return [4 /*yield*/, this.validate_login()]; case 1: if (!!(_h.sent())) return [3 /*break*/, 3]; console.warn('Session expired, creating new login session to carnet.'); return [4 /*yield*/, this.login()]; case 2: _h.sent(); _h.label = 3; case 3: return [3 /*break*/, 5]; case 4: this.sessionFirstUpdate = true; _h.label = 5; case 5: return [4 /*yield*/, this.post('-/mainnavigation/get-fully-loaded-cars', undefined, undefined)]; case 6: loadedVehicles = _h.sent(); console.log(loadedVehicles); if (!(loadedVehicles.fullyLoadedVehiclesResponse && loadedVehicles.fullyLoadedVehiclesResponse.vehiclesNotFullyLoaded)) return [3 /*break*/, 12]; _i = 0, _a = loadedVehicles.fullyLoadedVehiclesResponse.vehiclesNotFullyLoaded; _h.label = 7; case 7: if (!(_i < _a.length)) return [3 /*break*/, 10]; vehicle = _a[_i]; vehicleVin = vehicle.vin; return [4 /*yield*/, this.post('-/mainnavigation/load-car-details/' + vehicleVin, undefined, undefined)]; case 8: _h.sent(); _h.label = 9; case 9: _i++; return [3 /*break*/, 7]; case 10: return [4 /*yield*/, this.post('-/mainnavigation/get-fully-loaded-cars', undefined, undefined)]; case 11: loadedVehicles = _h.sent(); _h.label = 12; case 12: if (loadedVehicles.fullyLoadedVehiclesResponse && loadedVehicles.fullyLoadedVehiclesResponse.completeVehicles) { // // update vehicles for (_b = 0, _c = loadedVehicles.fullyLoadedVehiclesResponse.completeVehicles; _b < _c.length; _b++) { vehicle = _c[_b]; vehicleUrl = this.sessionBase + vehicle.dashboardUrl; if (!this.state[vehicleUrl]) { this.state[vehicleUrl] = vehicle; } else { for (_d = 0, _e = Object.keys(vehicle); _d < _e.length; _d++) { key = _e[_d]; this.state[vehicleUrl][key] = vehicle[key]; } } } } _f = 0, _g = Object.keys(this.state); _h.label = 13; case 13: if (!(_f < _g.length)) return [3 /*break*/, 16]; vehicle = _g[_f]; return [4 /*yield*/, this.update_vehicle(vehicle)]; case 14: _h.sent(); _h.label = 15; case 15: _f++; return [3 /*break*/, 13]; case 16: return [2 /*return*/, true]; case 17: e_2 = _h.sent(); console.log(e_2); return [3 /*break*/, 18]; case 18: return [2 /*return*/]; } }); }); }; Connection.prototype.request = function (method, url, data) { return __awaiter(this, void 0, void 0, function () { var options, req, res; return __generator(this, function (_a) { switch (_a.label) { case 0: options = { method: method, headers: this.sessionHeaders, redirect: 'manual', }; if (data) { options.body = JSON.stringify(data); } return [4 /*yield*/, fetch(url, options)]; case 1: req = _a.sent(); return [4 /*yield*/, req.json()]; case 2: res = _a.sent(); // console.log(`Received [{response.status}] response: {res}`) return [2 /*return*/, res]; } }); }); }; Connection.prototype.make_url = function (ref, rel) { console.log("MAKE_URL ref=" + ref + " rel=" + rel); if (rel) { console.log('relative'); return rel + '/' + ref; } console.log('add abs'); return this.sessionAuthRefUrl + '/' + ref; }; Connection.prototype.get = function (url, rel) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.request('GET', this.make_url(url, rel), undefined)]; case 1: // """Perform a get query to the online service.""" return [2 /*return*/, _a.sent()]; } }); }); }; Connection.prototype.post = function (url, rel, data) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: if (!data) return [3 /*break*/, 2]; return [4 /*yield*/, this.request('POST', this.make_url(url, rel), data)]; case 1: return [2 /*return*/, _a.sent()]; case 2: return [4 /*yield*/, this.request('POST', this.make_url(url, rel), undefined)]; case 3: return [2 /*return*/, _a.sent()]; } }); }); }; Connection.prototype.validate_login = function () { return __awaiter(this, void 0, void 0, function () { var messages; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.post('-/msgc/get-new-messages', undefined, undefined)]; case 1: messages = _a.sent(); return [2 /*return*/, messages.errorCode === '0']; } }); }); }; Connection.prototype.update_vehicle = function (vehurl) { return __awaiter(this, void 0, void 0, function () { var url, response, e_3, response, err_1, response, err_2, response, err_3, response, err_4, response, err_5, response, err_6, response, err_7; return __generator(this, function (_a) { switch (_a.label) { case 0: url = vehurl; _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, this.post('-/msgc/get-new-messages', url, undefined)]; case 2: response = _a.sent(); // messageList if (response.errorCode === '0') { this.state[url].vehicleMessagesNew = response.response; } else { console.log("Could not fetch new messages: " + response); } return [3 /*break*/, 4]; case 3: e_3 = _a.sent(); // except Exception as err: console.log("Could not fetch new messages, error: " + e_3); return [3 /*break*/, 4]; case 4: _a.trys.push([4, 6, , 7]); return [4 /*yield*/, this.post('-/msgc/get-latest-messages', url, undefined)]; case 5: response = _a.sent(); // messageList if (response.errorCode === '0') { this.state[url].vehicleMessagesLatest = response.messageList; } else { console.log("Could not fetch latest messages: " + response); } return [3 /*break*/, 7]; case 6: err_1 = _a.sent(); console.log("Could not fetch latest messages, error: " + err_1); return [3 /*break*/, 7]; case 7: _a.trys.push([7, 9, , 10]); return [4 /*yield*/, this.post('-/vsr/get-vsr', url, undefined)]; case 8: response = _a.sent(); // messageList if (response.errorCode === '0') { this.state[url].vehicleStatus = response.vehicleStatusData; } else { console.log("Could not fetch vsr: " + response); } return [3 /*break*/, 10]; case 9: err_2 = _a.sent(); console.log("Could not fetch vsr messages, error: " + err_2); return [3 /*break*/, 10]; case 10: _a.trys.push([10, 12, , 13]); return [4 /*yield*/, this.post('-/emanager/get-emanager', url, undefined)]; case 11: response = _a.sent(); // messageList if (response.errorCode === '0') { this.state[url].EManager = response.EManager; } else { console.log("Could not fetch e manager: " + response); } return [3 /*break*/, 13]; case 12: err_3 = _a.sent(); console.log("Could not fetch e manager messages, error: " + err_3); return [3 /*break*/, 13]; case 13: _a.trys.push([13, 15, , 16]); return [4 /*yield*/, this.post('-/cf/get-location', url, undefined)]; case 14: response = _a.sent(); // messageList if (response.errorCode === '0') { this.state[url].vehiclePosition = response.position; } else { console.log("Could not fetch position: " + response); } return [3 /*break*/, 16]; case 15: err_4 = _a.sent(); console.log("Could not fetch position messages, error: " + err_4); return [3 /*break*/, 16]; case 16: _a.trys.push([16, 18, , 19]); return [4 /*yield*/, this.post('-/vehicle-info/get-vehicle-details', url, undefined)]; case 17: response = _a.sent(); // messageList if (response.errorCode === '0') { this.state[url].vehicleDetails = response.vehicleDetails; } else { console.log("Could not fetch vehicle details: " + response); } return [3 /*break*/, 19]; case 18: err_5 = _a.sent(); console.log("Could not fetch vehicle details messages, error: " + err_5); return [3 /*break*/, 19]; case 19: _a.trys.push([19, 21, , 22]); return [4 /*yield*/, this.post('-/rah/get-status', url, undefined)]; case 20: response = _a.sent(); // messageList if (response.errorCode === '0') { this.state[url].vehicleRemoteAuxiliaryHeating = response.remoteAuxiliaryHeating; } else { console.log("Could not fetch vehicle vehicleRemoteAuxiliaryHeating: " + response); } return [3 /*break*/, 22]; case 21: err_6 = _a.sent(); console.log("Could not fetch vehicle vehicleRemoteAuxiliaryHeating messages, error: " + err_6); return [3 /*break*/, 22]; case 22: _a.trys.push([22, 24, , 25]); return [4 /*yield*/, this.post('-/rts/get-latest-trip-statistics', url, undefined)]; case 23: response = _a.sent(); // messageList if (response.errorCode === '0') { this.state[url].vehicleLastTrips = response.rtsViewModel; } else { console.log("Could not fetch vehicle vehicleLastTrips: " + response); } return [3 /*break*/, 25]; case 24: err_7 = _a.sent(); console.log("Could not fetch vehicle vehicleLastTrips messages, error: " + err_7); return [3 /*break*/, 25]; case 25: return [2 /*return*/]; } }); }); }; return Connection; }()); //# sourceMappingURL=main.js.map