npm-vwconnectidapi
Version:
Provides an API for We Connect ID
1,050 lines (986 loc) • 132 kB
JavaScript
"use strict";
// latest checked version of ioBroker.vw-connect: latest // https://github.com/TA2k/ioBroker.vw-connect/commit/604cdc1aacee0d13f189829aed985f35281f2016
var EventEmitter = require('events').EventEmitter;
const request = require("request");
const crypto = require("crypto");
const { Crypto } = require("@peculiar/webcrypto");
const { v4: uuidv4 } = require("uuid");
const traverse = require("traverse");
const express = require('express');
module.exports.idStatusEmitter = new EventEmitter();
module.exports.idLogEmitter = new EventEmitter();
class Log {
constructor(logLevel, external = false) {
this.logLevel = logLevel;
this.logTargetExternal = external;
if (this.logTargetExternal) {
module.exports.idLogEmitter.emit(this.logLevel, "Start logging instance");
} else {
this.debug("Start logging instance");
}
}
setLogLevel(pLogLevel, pExternal = false) {
this.logLevel = pLogLevel;
this.logTargetExternal = pExternal;
}
debug(pMessage) {
if (this.logLevel == "DEBUG") {
if (this.logTargetExternal) {
module.exports.idLogEmitter.emit("DEBUG", pMessage);
} else {
console.log("DEBUG: " + pMessage);
}
}
}
error(pMessage) {
if (this.logLevel != "NONE") {
if (this.logTargetExternal) {
module.exports.idLogEmitter.emit("ERROR", pMessage);
} else {
console.log("ERROR: " + pMessage);
}
}
}
info(pMessage) {
if (this.logLevel == "DEBUG" || this.logLevel == "INFO") {
if (this.logTargetExternal) {
module.exports.idLogEmitter.emit("INFO", pMessage);
} else {
console.log("INFO: " + pMessage);
}
}
}
warn(pMessage) {
if (this.logLevel == "DEBUG" || this.logLevel == "INFO" || this.logLevel == "WARN") {
if (this.logTargetExternal) {
module.exports.idLogEmitter.emit("WARN", pMessage);
} else {
console.log("WARN: " + pMessage);
}
}
}
}
class VwWeConnect {
config = {
userid: 0,
user: "testuser",
password: "testpass",
type: "id",
interval: 10,
forceinterval: 0,
numberOfTrips: 1,
logLevel: "ERROR",
targetTempC: -1,
targetSOC: -1,
chargeCurrent: "maximum",
autoUnlockPlug: false,
climatizationAtUnlock: true,
climatisationWindowHeating: true,
climatisationFrontLeft: true,
climatisationFrontRight: false,
unSafe: false,
noExternalPower: false,
pendingRequests: 0,
historyLimit: 100,
chargerOnly: false,
backendError: false
}
currSession = {
vin: "n/a"
}
constructor() {
this.boolFinishIdData = false;
this.boolFinishHomecharging = false;
this.boolFinishChargeAndPay = false;
this.boolFinishStations = false;
this.boolFinishVehicles = false;
this.boolFinishCarData = false;
this.log = new Log(this.config.logLevel);
this.jar = request.jar();
this.userAgent = "ioBroker v47";
this.refreshTokenInterval = null;
this.vwrefreshTokenInterval = null;
this.updateInterval = null;
this.fupdateInterval = 0; // set force update interval to 0 => deactivated;
this.refreshTokenTimeout = null;
this.homeRegion = {};
this.homeRegionSetter = {};
this.vinArray = [];
this.etags = {};
this.statesArray = [
{
url: "$homeregion/fs-car/bs/departuretimer/v1/$type/$country/vehicles/$vin/timer",
path: "timer",
element: "timer",
},
{
url: "$homeregion/fs-car/bs/climatisation/v1/$type/$country/vehicles/$vin/climater",
path: "climater",
element: "climater",
},
{
url: "$homeregion/fs-car/bs/cf/v1/$type/$country/vehicles/$vin/position",
path: "position",
element: "storedPositionResponse",
element2: "position",
element3: "findCarResponse",
element4: "Position",
},
{
url: "$homeregion/fs-car/bs/tripstatistics/v1/$type/$country/vehicles/$vin/tripdata/$tripType?type=list",
path: "tripdata",
element: "tripDataList",
},
{
url: "$homeregion/fs-car/bs/vsr/v1/$type/$country/vehicles/$vin/status",
path: "status",
element: "StoredVehicleDataResponse",
element2: "vehicleData",
},
{
url: "$homeregion/fs-car/destinationfeedservice/mydestinations/v1/$type/$country/vehicles/$vin/destinations",
path: "destinations",
element: "destinations",
},
{
url: "$homeregion/fs-car/bs/batterycharge/v1/$type/$country/vehicles/$vin/charger",
path: "charger",
element: "charger",
},
{
url: "$homeregion/fs-car/bs/rs/v1/$type/$country/vehicles/$vin/status",
path: "remoteStandheizung",
element: "statusResponse",
},
{
url: "$homeregion/fs-car/bs/dwap/v1/$type/$country/vehicles/$vin/history",
path: "history",
},
];
}
finishedReading() {
// this.log.debug(" Id/SkodaE: " + this.boolFinishIdData +
// " HomeCharge: " + this.boolFinishHomecharging +
// " ChargePay: " + this.boolFinishChargeAndPay +
// " Stat: " + this.boolFinishStations +
// " Car: " + this.boolFinishCarData +
// " Vehic: " + this.boolFinishVehicles);
return (this.boolFinishIdData || this.config.chargerOnly)
// && this.boolFinishHomecharging
// && this.boolFinishChargeAndPay
// && this.boolFinishStations
/*&& this.boolFinishCarData*/
&& this.boolFinishVehicles;
}
setCredentials(pUser, pPass) {
//this.config.userid = 0;
this.config.user = pUser;
this.config.password = pPass;
//this.config.type = "id";
this.config.interval = 0.5;
this.config.checkSafeStatusTimeout = 5; // minutes to wait for event if car is not locked or windows not closed
//this.config.forceinterval = 360; // shouldn't be smaller than 360mins, default 0 (off)
//this.config.numberOfTrips = 1;
}
setConfig(pType) {
if (pType == "idCharger") {
this.config.type = "id";
this.config.chargerOnly = true;
}
else {
this.config.type = pType;
}
}
setHistoryLimit(pLimit) {
this.config.historyLimit = pLimit;
}
setActiveVin(pVin) {
if (this.vinArray.includes(pVin)) {
this.currSession.vin = pVin;
this.log.info("Active VIN successfully set to <" + this.currSession.vin + ">.");
} else {
this.log.error("VIN <" + pVin + "> is unknown. Active VIN is still <" + this.currSession.vin + ">.");
}
}
restart() {
// dummy
}
stopCharging() {
return new Promise(async (resolve, reject) => {
this.log.debug("stopCharging >>");
this.setIdRemote(this.currSession.vin, "charging", "stop", "")
.then(() => {
this.log.debug("stopCharging successful");
resolve();
return;
})
.catch(() => {
this.log.error("stopCharging failed");
reject();
return;
});
this.log.debug("stopCharging <<");
});
}
setChargingSetting(setting, value) {
return new Promise(async (resolve, reject) => {
this.log.debug("setChargerSetting " + setting + " to " + value + " >>");
if (!this.finishedReading()) {
this.log.info("Reading necessary data not finished yet. Please try again.");
reject();
return;
}
if (!this.vinArray.includes(this.currSession.vin)) {
this.log.error("Unknown VIN, aborting. Use setActiveVin to set a valid VIN.");
reject();
return;
}
switch (setting) {
case "targetSOC":
if (value >= 50 && value <= 100) {
this.config.pendingRequests++;
this.config.targetSOC = value;
} else {
this.log.debug("setChargerSetting " + setting + " value " + value + " out of bounds >>");
}
break;
case "chargeCurrent":
if (value == "maximum" || value == "reduced") {
this.config.pendingRequests++;
this.config.chargeCurrent = value;
} else {
this.log.debug("setChargerSetting " + setting + " incorrect value " + value + " >>");
}
break;
case "autoUnlockPlug":
this.config.pendingRequests++;
this.config.autoUnlockPlug = value;
break;
default:
this.log.debug("setChargerSetting " + setting + " not implemented" + " >>");
break;
}
this.setIdRemote(this.currSession.vin, "charging", "settings")
.then(() => {
this.log.info("setIdRemote succeeded");
this.config.pendingRequests = Math.max(this.config.pendingRequests - 1, 0);
resolve();
return;
})
.catch(() => {
this.log.error("setIdRemote failed");
this.config.pendingRequests = 0;
reject();
return;
});
this.log.debug("setChargerSettings <<");
});
}
setChargingSettings(pTargetSOC, pChargeCurrent) {
return new Promise(async (resolve, reject) => {
this.log.debug("setChargerSettings TargetSOC to " + pTargetSOC + "%, chargeCurrent to " + pChargeCurrent + " >>");
if (!this.finishedReading()) {
this.log.info("Reading necessary data not finished yet. Please try again.");
reject();
return;
}
if (!this.vinArray.includes(this.currSession.vin)) {
this.log.error("Unknown VIN, aborting. Use setActiveVin to set a valid VIN.");
reject();
return;
}
if (pTargetSOC >= 50 && pTargetSOC <= 100) {
this.config.pendingRequests++;
this.config.targetSOC = pTargetSOC;
}
if (pChargeCurrent == "maximum" || pChargeCurrent == "reduced") {
this.config.pendingRequests++;
this.config.chargeCurrent = pChargeCurrent;
}
this.setIdRemote(this.currSession.vin, "charging", "settings")
.then(() => {
this.log.info("Target SOC set to " + this.config.targetSOC + "%.");
this.log.info("Charging current set to " + this.config.chargeCurrent + ".");
this.config.pendingRequests = Math.max(this.config.pendingRequests - 1, 0);
resolve();
return;
})
.catch(() => {
this.log.error("setting SOC and charge current failed");
this.config.pendingRequests = 0;
reject();
return;
});
this.log.debug("setChargerSettings <<");
});
}
startCharging() {
return new Promise(async (resolve, reject) => {
this.log.debug("startCharging >>");
if (!this.finishedReading()) {
this.log.info("Reading necessary data not finished yet. Please try again.");
reject();
return;
}
if (!this.vinArray.includes(this.currSession.vin)) {
this.log.error("Unknown VIN, aborting. Use setActiveVin to set a valid VIN.");
reject();
return;
}
this.setIdRemote(this.currSession.vin, "charging", "start")
.then(() => {
this.log.debug("startCharging successful");
resolve();
return;
})
.catch(() => {
this.log.error("startCharging failed");
reject();
return;
});
this.log.debug("startCharging <<");
});
}
stopClimatisation() {
return new Promise(async (resolve, reject) => {
this.log.debug("stopClimatisation >>");
this.setIdRemote(this.currSession.vin, "climatisation", "stop", "")
.then(() => {
this.log.debug("stopClimatisation successful");
resolve();
return;
})
.catch(() => {
this.log.error("stopClimatisation failed");
reject();
return;
});
this.log.debug("stopClimatisation <<");
});
}
setDestination(destination) {
return new Promise(async (resolve, reject) => {
this.log.debug("setDestination >>");
this.setIdRemote(this.currSession.vin, "destinations", "", destination)
.then(() => {
this.log.debug("setDestination successful");
resolve();
return;
})
.catch(() => {
this.log.error("setDestination failed");
reject();
return;
});
this.log.debug("setDestination <<");
});
}
startClimatisation() {
return new Promise(async (resolve, reject) => {
this.log.debug("startClimatisation with " + this.config.targetTempC + "°C >>");
if (!this.finishedReading()) {
this.log.info("Reading necessary data not finished yet. Please try again.");
reject();
return;
}
if (!this.vinArray.includes(this.currSession.vin)) {
this.log.error("Unknown VIN, aborting. Use setActiveVin to set a valid VIN.");
reject();
return;
}
this.setIdRemote(this.currSession.vin, "climatisation", "start", "")
.then(() => {
this.log.debug("startClimatisation successful");
resolve();
return;
})
.catch(() => {
this.log.error("startClimatisation failed");
reject();
return;
});
this.log.debug("startClimatisation <<");
});
}
setClimatisationSetting(pSetting, pValue) {
return new Promise(async (resolve, reject) => {
this.log.debug("setClimatisationSetting with " + pSetting + " " + pValue + " >>");
if (!this.finishedReading()) {
this.log.info("Reading necessary data not finished yet. Please try again.");
reject();
return;
}
if (!this.vinArray.includes(this.currSession.vin)) {
this.log.error("Unknown VIN, aborting. Use setActiveVin to set a valid VIN.");
reject();
return;
}
this.config.pendingRequests++;
switch (pSetting) {
case "climatizationAtUnlock": this.config.climatizationAtUnlock = pValue; break;
case "climatisationWindowHeating": this.config.climatisationWindowHeating = pValue; break;
case "climatisationFrontLeft": this.config.climatisationFrontLeft = pValue; break;
case "climatisationFrontRight": this.config.climatisationFrontRight = pValue; break;
default: break;
}
this.setIdRemote(this.currSession.vin, "climatisation", "settings", "")
.then(() => {
this.log.debug("setClimatisationSetting successful");
this.config.pendingRequests = Math.max(this.config.pendingRequests - 1, 0);
resolve();
return;
})
.catch(() => {
this.log.error("setClimatisationSetting failed");
this.config.pendingRequests = 0;
reject();
return;
});
this.log.debug("setClimatisationSetting <<");
});
}
setClimatisation(pTempC) {
return new Promise(async (resolve, reject) => {
this.log.debug("setClimatisation with " + pTempC + "°C >>");
if (!this.finishedReading()) {
this.log.info("Reading necessary data not finished yet. Please try again.");
reject();
return;
}
if (!this.vinArray.includes(this.currSession.vin)) {
this.log.error("Unknown VIN, aborting. Use setActiveVin to set a valid VIN.");
reject();
return;
}
if (pTempC < 16 || pTempC > 27) {
this.log.info("Invalid temperature, setting 20°C as default");
pTempC = 20;
}
this.config.pendingRequests++;
this.config.targetTempC = pTempC;
this.setIdRemote(this.currSession.vin, "climatisation", "settings", "")
.then(() => {
this.log.debug("setClimatisation successful");
this.config.pendingRequests = Math.max(this.config.pendingRequests - 1, 0);
resolve();
return;
})
.catch(() => {
this.log.error("setClimatisation failed");
this.config.pendingRequests = 0;
reject();
return;
});
this.log.debug("setClimatisation <<");
});
}
// logLevel: ERROR, INFO, DEBUG
setLogLevel(pLogLevel, pExternal = false) {
this.log.setLogLevel(pLogLevel, pExternal);
}
enableApi(port) {
const app = express();
app.get('/status', (req, res) => {
if (typeof(this.idData) != "undefiend") {
res.json(this.idData);
} else {
res.status(200).end();
}
});
app.listen(port, () => {
this.log.info(`API server is running on port ${port}`);
});
}
async getData() {
this.boolFinishIdData = false;
this.boolFinishHomecharging = false;
this.boolFinishChargeAndPay = false;
this.boolFinishStations = false;
this.boolFinishVehicles = false;
this.boolFinishCarData = false;
// resolve only after all the different calls have finished reading their data
// await promise at the end of this method
let promise = new Promise((resolve, reject) => {
const finishedReadingInterval = setInterval(() => {
if (this.finishedReading()) {
clearInterval(finishedReadingInterval)
resolve("done!");
} else {
}
}, 1000)
});
// Reset the connection indicator during startup
// this.type = "VW";
// this.country = "DE";
// this.clientId = "9496332b-ea03-4091-a224-8c746b885068%40apps_vw-dilab_com";
// this.xclientId = "38761134-34d0-41f3-9a73-c4be88d7d337";
// this.scope = "openid%20profile%20mbb%20email%20cars%20birthdate%20badge%20address%20vin";
// this.redirect = "carnet%3A%2F%2Fidentity-kit%2Flogin";
// this.xrequest = "de.volkswagen.carnet.eu.eremote";
// this.responseType = "id_token%20token%20code";
// this.xappversion = "5.1.2";
// this.xappname = "eRemote";
this.type = "Id";
this.country = "DE";
this.clientId = "a24fba63-34b3-4d43-b181-942111e6bda8@apps_vw-dilab_com";
this.xclientId = "";
this.scope = "openid profile badge cars dealers birthdate vin";
this.redirect = "weconnect://authenticated";
this.xrequest = "com.volkswagen.weconnect";
this.responseType = "code id_token token";
this.xappversion = "";
this.xappname = "";
if (this.config.interval === 0) {
this.log.info("Interval of 0 is not allowed reset to 1");
this.config.interval = 1;
}
this.tripTypes = [];
if (this.config.tripShortTerm == true) {
this.tripTypes.push("shortTerm");
}
if (this.config.tripLongTerm == true) {
this.tripTypes.push("longTerm");
}
if (this.config.tripCyclic == true) {
this.tripTypes.push("cyclic");
}
this.login()
.then(() => {
this.log.debug("Login successful");
this.getPersonalData()
.then(() => {
this.getVehicles()
.then(() => {
this.vinArray.forEach((vin) => {
this.getIdStatus(vin).catch(() => {
this.log.error("get id status Failed");
});
this.getIdParkingPosition(vin).catch(() => {
this.log.error("get id parking position Failed");
});
});
this.updateInterval = setInterval(() => {
this.updateStatus();
},
this.config.interval * 60 * 1000);
})
.catch(() => {
this.log.error("Get Vehicles Failed");
});
})
.catch(() => {
this.log.error("get personal data Failed");
});
})
.catch(() => {
this.log.error("Login Failed");
});
let result = await promise; // wait for the promise from the start to resolve
this.log.debug("getData END");
}
login() {
return new Promise(async (resolve, reject) => {
const nonce = this.getNonce();
const state = uuidv4();
let [code_verifier, codeChallenge] = this.getCodeChallenge();
const method = "GET";
const form = {};
let url =
"https://identity.vwgroup.io/oidc/v1/authorize?client_id=" +
this.clientId +
"&scope=" +
this.scope +
"&response_type=" +
this.responseType +
"&redirect_uri=" +
this.redirect +
"&nonce=" +
nonce +
"&state=" +
state;
if (this.config.type === "id" && this.type !== "Wc") {
url = await this.receiveLoginUrl().catch(() => {
this.log.warn("Failed to get login url");
});
if (!url) {
url = "https://emea.bff.cariad.digital/user-login/v1/authorize?nonce=" + this.randomString(16) + "&redirect_uri=weconnect://authenticated";
}
}
const loginRequest = request(
{
method: method,
url: url,
headers: {
"User-Agent": this.userAgent,
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate",
"x-requested-with": this.xrequest,
"upgrade-insecure-requests": 1,
},
jar: this.jar,
form: form,
gzip: true,
followAllRedirects: true,
},
(err, resp, body) => {
if (err || (resp && resp.statusCode >= 400)) {
if (this.type === "Wc") {
if (err && err.message === "Invalid protocol: wecharge:") {
this.log.debug("Found WeCharge connection");
this.getTokens(loginRequest, code_verifier, reject, resolve);
} else {
this.log.debug("No WeCharge found, cancel login");
resolve();
}
return;
}
if (err && err.message.indexOf("Invalid protocol:") !== -1) {
this.log.debug("Found Token");
this.getTokens(loginRequest, code_verifier, reject, resolve);
return;
}
this.log.error("Failed in first login step ");
err && this.log.error(err);
resp && this.log.error(resp.statusCode.toString());
body && this.log.error(JSON.stringify(body));
err && err.message && this.log.error(err.message);
loginRequest && loginRequest.uri && loginRequest.uri.query && this.log.debug(loginRequest.uri.query.toString());
reject();
return;
}
try {
let form = {};
if (body.indexOf("emailPasswordForm") !== -1) {
this.log.debug("parseEmailForm");
form = this.extractHidden(body);
form["email"] = this.config.user;
} else {
this.log.error("No Login Form found for type: " + this.type);
this.log.debug(JSON.stringify(body));
reject();
return;
}
request.post(
{
url: "https://identity.vwgroup.io/signin-service/v1/" + this.clientId + "/login/identifier",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": this.userAgent,
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate",
"x-requested-with": this.xrequest,
},
form: form,
jar: this.jar,
gzip: true,
followAllRedirects: true,
},
(err, resp, body) => {
if (err || (resp && resp.statusCode >= 400)) {
this.log.error("Failed to get login identifier");
err && this.log.error(err);
resp && this.log.error(resp.statusCode.toString());
body && this.log.error(JSON.stringify(body));
reject();
return;
}
try {
if (body.indexOf("emailPasswordForm") !== -1) {
this.log.debug("emailPasswordForm2");
/*
const stringJson =body.split("window._IDK = ")[1].split(";")[0].replace(/\n/g, "")
const json =stringJson.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": ').replace(/'/g, '"')
const jsonObj = JSON.parse(json);
*/
form = {
_csrf: body.split("csrf_token: '")[1].split("'")[0],
email: this.config.user,
password: this.config.password,
hmac: body.split('"hmac":"')[1].split('"')[0],
relayState: body.split('"relayState":"')[1].split('"')[0],
};
} else {
this.log.error("No Login Form found. Please check your E-Mail in the app.");
this.log.debug(JSON.stringify(body));
reject();
return;
}
request.post(
{
url: "https://identity.vwgroup.io/signin-service/v1/" + this.clientId + "/login/authenticate",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": this.userAgent,
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate",
"x-requested-with": this.xrequest,
},
form: form,
jar: this.jar,
gzip: true,
followAllRedirects: false,
},
(err, resp, body) => {
if (err || (resp && resp.statusCode >= 400)) {
this.log.error("Failed to get login authenticate");
err && this.log.error(err);
resp && this.log.error(resp.statusCode.toString());
body && this.log.error(JSON.stringify(body));
reject();
return;
}
try {
this.log.debug(JSON.stringify(body));
this.log.debug(JSON.stringify(resp.headers));
if (resp.headers.location.split("&").length <= 2 || resp.headers.location.indexOf("/terms-and-conditions?") !== -1) {
this.log.warn(resp.headers.location);
this.log.warn("No valid userid, please visit this link or logout and login in your app account:");
this.log.warn("https://" + resp.request.host + resp.headers.location);
this.log.warn("Try to auto accept new consent");
request.get(
{
url: "https://" + resp.request.host + resp.headers.location,
jar: this.jar,
headers: {
"User-Agent": this.userAgent,
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate",
"x-requested-with": this.xrequest,
},
followAllRedirects: true,
gzip: true,
},
(err, resp, body) => {
this.log.debug(body);
const form = this.extractHidden(body);
const url = "https://" + resp.request.host + resp.req.path.split("?")[0];
this.log.debug(JSON.stringify(form));
request.post(
{
url: url,
jar: this.jar,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": this.userAgent,
Accept:
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate",
"x-requested-with": this.xrequest,
},
form: form,
followAllRedirects: true,
gzip: true,
},
(err, resp, body) => {
if ((err && err.message.indexOf("Invalid protocol:") !== -1) || (resp && resp.statusCode >= 400)) {
this.log.warn("Failed to auto accept");
err && this.log.error(err);
resp && this.log.error(resp.statusCode.toString());
body && this.log.error(JSON.stringify(body));
reject();
return;
}
this.log.info("Auto accept succesful. Restart adapter in 10sec");
setTimeout(() => {
this.restart();
}, 10 * 1000);
}
);
}
);
reject();
return;
}
this.config.userid = resp.headers.location.split("&")[2].split("=")[1];
if (!this.stringIsAValidUrl(resp.headers.location)) {
if (resp.headers.location.indexOf("&error=") !== -1) {
const location = resp.headers.location;
this.log.error("Error: " + location.substring(location.indexOf("error="), location.length - 1));
} else {
this.log.error("No valid login url, please download the log and visit:");
this.log.error("http://" + resp.request.host + resp.headers.location);
}
reject();
return;
}
let getRequest = request.get(
{
url: resp.headers.location || "",
headers: {
"User-Agent": this.userAgent,
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate",
"x-requested-with": this.xrequest,
},
jar: this.jar,
gzip: true,
followAllRedirects: true,
},
(err, resp, body) => {
if (err) {
this.log.debug(err);
this.getTokens(getRequest, code_verifier, reject, resolve);
} else {
this.log.debug(body);
this.log.debug("No Token received visiting url and accept the permissions.");
const form = this.extractHidden(body);
getRequest = request.post(
{
url: getRequest.uri.href,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": this.userAgent,
Accept:
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate",
"x-requested-with": this.xrequest,
referer: getRequest.uri.href,
},
form: form,
jar: this.jar,
gzip: true,
followAllRedirects: true,
},
(err, resp, body) => {
if (err) {
this.getTokens(getRequest, code_verifier, reject, resolve);
} else {
this.log.error("No Token received. Please try to logout and login in the VW app or select type VWv2 in the settings");
try {
this.log.debug(JSON.stringify(body));
} catch (err) {
this.log.error(err);
reject();
}
}
}
);
}
}
);
} catch (err2) {
this.log.error("Login was not successful, please check your login credentials and selected type");
err && this.log.error(err);
this.log.error(err2);
this.log.error(err2.stack);
reject();
}
}
);
} catch (err) {
this.log.error(err);
reject();
}
}
);
} catch (err) {
this.log.error(err);
reject();
}
}
);
});
}
updateStatus() {
this.vinArray.forEach((vin) => {
this.getIdStatus(vin).catch(() => {
this.log.error("get id status Failed");
this.refreshIDToken().catch(() => { });
});
this.getIdParkingPosition(vin).catch(() => {
this.log.error("get id parking position Failed");
this.refreshIDToken().catch(() => { });
});
//this.getWcData();
});
return;
}
receiveLoginUrl() {
return new Promise((resolve, reject) => {
request(
{
method: "GET",
url: "https://emea.bff.cariad.digital/user-login/v1/authorize?nonce=" + this.randomString(16) + "&redirect_uri=weconnect://authenticated",
headers: {
Host: "emea.bff.cariad.digital",
"user-agent": this.userAgent,
accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"accept-language": "de-de",
},
jar: this.jar,
gzip: true,
followAllRedirects: false,
},
(err, resp, body) => {
if (err || (resp && resp.statusCode >= 400)) {
this.log.error("Failed in receive login url ");
err && this.log.error(err);
resp && this.log.error(resp.statusCode.toString());
body && this.log.error(JSON.stringify(body));
reject();
return;
}
resolve(resp.request.href);
}
);
});
}
replaceVarInUrl(url, vin, tripType) {
const curHomeRegion = this.homeRegion[vin];
return url
.replace("/$vin", "/" + vin + "")
.replace("$homeregion/", curHomeRegion + "/")
.replace("/$type/", "/" + this.type + "/")
.replace("/$country/", "/" + this.country + "/")
.replace("/$tripType", "/" + tripType);
}
getTokens(getRequest, code_verifier, reject, resolve) {
let hash = "";
if (getRequest.uri.hash) {
hash = getRequest.uri.hash;
} else {
hash = getRequest.uri.query;
}
const hashArray = hash.split("&");
// eslint-disable-next-line no-unused-vars
let state;
let jwtauth_code;
let jwtaccess_token;
let jwtid_token;
let jwtstate;
hashArray.forEach((hash) => {
const harray = hash.split("=");
if (harray[0] === "#state" || harray[0] === "state") {
state = harray[1];
}
if (harray[0] === "code") {
jwtauth_code = harray[1];
}
if (harray[0] === "access_token") {
jwtaccess_token = harray[1];
}
if (harray[0] === "id_token") {
jwtid_token = harray[1];
}
if (harray[0] === "#state") {
jwtstate = harray[1];
}
});
// const state = hashArray[0].substring(hashArray[0].indexOf("=") + 1);
// const jwtauth_code = hashArray[1].substring(hashArray[1].indexOf("=") + 1);
// const jwtaccess_token = hashArray[2].substring(hashArray[2].indexOf("=") + 1);
// const jwtid_token = hashArray[5].substring(hashArray[5].indexOf("=") + 1);
let method = "POST";
let body = "auth_code=" + jwtauth_code + "&id_token=" + jwtid_token;
let url = "https://emea.bff.cariad.digital/exchangeAuthCode";
let headers = {
// "user-agent": this.userAgent,
"X-App-version": this.xappversion,
"content-type": "