UNPKG

npm-vwconnectidapi

Version:

Provides an API for We Connect ID

1,050 lines (986 loc) 132 kB
"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": "