iobroker.vw-connect
Version:
Adapter for VW Connect
1,232 lines (1,199 loc) • 253 kB
JavaScript
// @ts-nocheck
"use strict";
/*
* Created with @iobroker/create-adapter v1.17.0
*/
// The adapter-core module gives you access to the core ioBroker functions
// you need to create an adapter
const utils = require("@iobroker/adapter-core");
const request = require("request");
const qs = require("qs");
const crypto = require("crypto");
const { Crypto } = require("@peculiar/webcrypto");
const { v4: uuidv4 } = require("uuid");
const traverse = require("traverse");
const geohash = require("ngeohash");
const { extractKeys } = require("./lib/extractKeys");
const axios = require("axios").default;
const Json2iob = require("json2iob");
const mqtt = require("mqtt");
const uuid = require("uuid");
class VwWeconnect extends utils.Adapter {
/**
* @param {Partial<ioBroker.AdapterOptions>} [options={}]
*/
constructor(options) {
super({
...options,
name: "vw-connect",
});
this.on("ready", this.onReady.bind(this));
// this.on("objectChange", this.onObjectChange.bind(this));
this.on("stateChange", this.onStateChange.bind(this));
// this.on("message", this.onMessage.bind(this));
this.on("unload", this.onUnload.bind(this));
this.extractKeys = extractKeys;
this.json2iob = new Json2iob(this);
this.jar = request.jar();
this.userAgent = "iobroker v";
this.skodaUserAgent = "MySkoda/Android/8.0.0/250203003";
this.refreshTokenInterval = null;
this.vwrefreshTokenInterval = null;
this.updateInterval = null;
this.fupdateInterval = null;
this.refreshTokenTimeout = null;
this.homeRegion = {};
this.homeRegionSetter = {};
this.secondAccessToken = null;
this.ignoredPaths = {};
this.vinArray = [];
this.etags = {};
this.hasRemoteLock = false;
this.isFirstLocation = true;
this.lastTripCheck = 0;
this.firstStart = true;
this.blockTrip = {};
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/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",
},
];
}
/**
* Is called when databases are connected and adapter received configuration.
*/
async onReady() {
// Initialize your adapter here
this.setState("info.connection", false, true);
if (!this.config.password) {
this.log.warn("Please enter password");
return;
}
this.userAgent += this.version;
// 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";
if (this.config.type === "vw") {
this.log.info("WeConnect App is disabled switch to ID/Volkswagen App");
this.config.type = "id";
}
if (this.config.type === "skoda") {
this.type = "Skoda";
this.country = "CZ";
this.clientId = "f9a2359a-b776-46d9-bd0c-db1904343117@apps_vw-dilab_com";
this.xclientId = "afb0473b-6d82-42b8-bfea-cead338c46ef";
this.scope = "openid mbb profile";
this.redirect = "skodaconnect://oidc.login/";
this.xrequest = "cz.skodaauto.connect";
this.responseType = "code%20id_token";
this.xappversion = "3.2.6";
this.xappname = "cz.skodaauto.connect";
}
if (this.config.type === "skodae") {
this.type = "Skoda";
this.country = "CZ";
this.clientId = "7f045eee-7003-4379-9968-9355ed2adb06@apps_vw-dilab_com";
this.xclientId = "afb0473b-6d82-42b8-bfea-cead338c46ef";
this.scope =
"address badge birthdate cars driversLicense dealers email mileage mbb nationalIdentifier openid phone profession profile vin";
this.redirect = "myskoda%3A%2F%2Fredirect%2Flogin%2F";
this.xrequest = "cz.skodaauto.connect";
this.responseType = "code id_token";
this.xappversion = "8.0.0";
this.xappname = "cz.skodaauto.connect";
this.xbrand = "skoda";
}
if (this.config.type === "seat") {
this.type = "Seat";
this.country = "ES";
this.clientId = "3c8e98bc-3ae9-4277-a563-d5ee65ddebba@apps_vw-dilab_com";
this.xclientId = "9dcc70f0-8e79-423a-a3fa-4065d99088b4";
this.scope = "openid profile address phone email birthdate nationalIdentifier cars mbb dealers badge nationality";
this.redirect = "seatconnect://identity-kit/login";
// this.xrequest = "cz.skodaauto.connect";
this.responseType = "code%20id_token%20token";
this.xappversion = "1.11.2";
this.xappname = "SEAT Connect";
}
if (this.config.type === "seatcupra") {
this.type = "Seat";
// this.clientId = "3c756d46-f1ba-4d78-9f9a-cff0d5292d51@apps_vw-dilab_com";
this.clientId = "99a5b77d-bd88-4d53-b4e5-a539c60694a3@apps_vw-dilab_com";
this.scope = "openid profile nickname birthdate phone";
// this.redirect = "cupra://oauth-callback";
this.redirect = "seat://oauth-callback";
this.responseType = "code";
this.xappversion = "1.1.29";
this.xappname = "SEATConnect";
}
if (this.config.type === "seatcupra2") {
this.type = "Seat";
this.clientId = "30e33736-c537-4c72-ab60-74a7b92cfe83@apps_vw-dilab_com";
this.scope = "openid profile address phone email birthdate nationalIdentifier cars mbb dealers badge nationality";
this.redirect = "cupraconnect://identity-kit/login";
this.responseType = "code id_token token";
this.xappversion = "1.1.29";
this.xclientId = "9d183b70-d129-424f-9a26-c3778edf95e1";
this.xappname = "SEATConnect";
}
if (this.config.type === "vwv2") {
this.log.info("WeConnect App is disabled switch to ID/Volkswagen App");
this.config.type = "id";
this.type = "VW";
this.country = "DE";
this.clientId = "9496332b-ea03-4091-a224-8c746b885068@apps_vw-dilab_com";
this.xclientId = "89312f5d-b853-4965-a471-b0859ee468af";
this.scope = "openid profile mbb cars birthdate nickname address phone";
this.redirect = "carnet://identity-kit/login";
this.xrequest = "de.volkswagen.car-net.eu.e-remote";
this.responseType = "id_token%20token%20code";
this.xappversion = "5.6.7";
this.xappname = "We Connect";
}
if (this.config.type === "id") {
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 = "";
this.xbrand = "volkswagen";
}
if (this.config.type === "audi") {
this.log.info("Login in with audi as audietron");
this.config.type = "audietron";
// this.type = "Audi";
// this.country = "DE";
// this.clientId = "09b6cbec-cd19-4589-82fd-363dfa8c24da@apps_vw-dilab_com";
// this.xclientId = "77869e21-e30a-4a92-b016-48ab7d3db1d8";
// this.scope =
// "address profile badge birthdate birthplace nationalIdentifier nationality profession email vin phone nickname name picture mbb gallery openid";
// this.redirect = "myaudi:///";
// this.xrequest = "de.myaudi.mobile.assistant";
// this.responseType = "token%20id_token";
// // this.responseType = "code";
// this.xappversion = "3.22.0";
// this.xappname = "myAudi";
}
if (this.config.type === "audietron") {
this.type = "Audi";
this.country = "DE";
this.clientId = "f4d0934f-32bf-4ce4-b3c4-699a7049ad26@apps_vw-dilab_com";
this.scope =
"address badge birthdate birthplace email gallery mbb name nationalIdentifier nationality nickname phone picture profession profile vin openid";
this.redirect = "myaudi:///";
this.responseType = "code";
this.xappversion = "4.14.1";
this.xappname = "myAudi";
this.xclientId = "59edf286-a9ca-4d34-9421-68da00f72dc8";
}
if (this.config.type === "audidata") {
this.type = "Audi";
this.country = "DE";
this.clientId = "ec6198b1-b31e-41ec-9a69-95d42d6497ed@apps_vw-dilab_com";
this.scope = "openid profile address email phone";
this.redirect = "acpp://de.audi.connectplugandplay/oauth2redirect/identitykit";
this.responseType = "code";
}
if (this.config.type === "go") {
this.type = "";
this.country = "";
this.clientId = "ac42b0fa-3b11-48a0-a941-43a399e7ef84@apps_vw-dilab_com";
this.xclientId = "";
this.scope = "openid%20profile%20address%20email%20phone";
this.redirect = "vwconnect%3A%2F%2Fde.volkswagen.vwconnect%2Foauth2redirect%2Fidentitykit";
this.xrequest = "";
this.responseType = "code";
this.xappversion = "";
this.xappname = "";
}
if (this.config.type === "seatelli") {
this.type = "";
this.country = "";
this.clientId = "d940d794-5945-48a3-84b1-44222c387800@apps_vw-dilab_com";
this.xclientId = "";
this.scope = "openid profile";
this.redirect = "Seat-elli-hub://opid";
this.xrequest = "";
this.responseType = "code";
this.xappversion = "";
this.xappname = "";
}
if (this.config.type === "skodapower") {
this.type = "";
this.country = "";
this.clientId = "b84ba8a1-7925-43c9-9963-022587faaac5@apps_vw-dilab_com";
this.xclientId = "";
this.scope = "openid profile";
this.redirect = "skoda-hub://opid";
this.xrequest = "";
this.responseType = "code";
this.xappversion = "";
this.xappname = "";
}
if (!this.config.interval || this.config.interval < 0.5) {
this.log.info("Interval of 0 is not allowed reset to 1");
this.config.interval = 1;
}
// if (this.config.type === "skodae") {
// // this.log.info("Parking Postion is temporary disabled for Skoda E");
// if (this.config.interval < 10) {
// this.log.info("Interval under 10min is temporary not allowed for Skoda E reset to 10min");
// this.config.interval = 10;
// }
// }
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.info("Login successful");
this.setState("info.connection", true, true);
this.extendObject("refresh", {
type: "state",
common: {
name: "Refresh All States",
type: "boolean",
role: "button",
write: true,
},
native: {},
});
this.getPersonalData().then(() => {
this.getVehicles()
.then(() => {
if (this.config.type !== "go") {
this.vinArray.forEach((vin) => {
if (this.config.type === "id" || this.config.type === "audietron") {
this.getHomeRegion(vin);
this.getIdStatus(vin).catch(() => {
this.log.error("get id status Failed");
});
} else if (this.config.type === "seatcupra") {
this.getSeatCupraStatus(vin);
} else if (this.config.type === "audidata") {
this.getAudiDataStatus(vin).catch(() => {
this.log.error("get audi data status Failed");
});
} else if (this.config.type === "skodae") {
this.getSkodaEStatus(vin);
} else {
this.getHomeRegion(vin)
.catch(() => {
this.log.debug("get home region Failed " + vin);
})
.finally(() => {
this.getVehicleData(vin).catch(() => {
this.log.error("get vehicle data Failed");
});
this.getVehicleRights(vin).catch(() => {
this.log.error("get vehicle rights Failed");
});
this.requestStatusUpdate(vin)
.finally(() => {
this.statesArray.forEach((state) => {
if (state.path == "tripdata") {
this.tripTypes.forEach((tripType) => {
this.getVehicleStatus(
vin,
state.url,
state.path,
state.element,
state.element2,
state.element3,
state.element4,
tripType,
).catch(() => {
this.log.debug("error while getting " + state.url);
});
});
} else {
this.getVehicleStatus(
vin,
state.url,
state.path,
state.element,
state.element2,
state.element3,
state.element4,
).catch(() => {
this.log.debug("error while getting " + state.url);
});
}
});
})
.catch(() => {
this.log.error("status update Failed " + vin);
});
})
.catch(() => {
this.log.error("Error getting home region");
});
}
});
}
if (this.config.type !== "skodae" && this.config.type !== "seatcupra") {
this.updateStatus();
}
this.updateInterval && clearInterval(this.updateInterval);
this.updateInterval = setInterval(() => {
this.updateStatus();
}, this.config.interval * 60 * 1000);
if (this.config.type !== "id" && this.config.type !== "skodae" && this.config.type !== "audietron") {
if (this.config.forceinterval > 0) {
this.fupdateInterval = setInterval(() => {
if (this.config.type === "go") {
this.getVehicles();
return;
}
this.vinArray.forEach((vin) => {
this.requestStatusUpdate(vin).catch(() => {
this.log.error("force status update Failed");
});
});
}, this.config.forceinterval * 60 * 1000);
}
}
if (this.config.type === "seatelli" || this.config.type === "skodapower") {
this.getElliData(this.config.type).catch(() => {
this.log.error("get elli Failed");
});
}
})
.catch(() => {
this.log.error("Get Vehicles Failed");
});
});
})
.catch(() => {
this.log.error("Login Failed");
this.log.error("Restart Adapter in 30min");
setTimeout(() => {
this.log.error("Restart adapter");
this.restart();
}, 30 * 60 * 1000);
});
this.subscribeStates("*");
}
login() {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
const nonce = this.getNonce();
const state = uuidv4();
this.log.info(`Login in with ${this.config.type}`);
let [code_verifier, codeChallenge] = this.getCodeChallenge();
if (this.config.type === "seatelli" || this.config.type === "skodapower") {
[code_verifier, codeChallenge] = this.getCodeChallengev2();
}
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 === "vw" ||
this.config.type === "vwv2" ||
this.config.type === "go" ||
this.config.type === "seatelli" ||
this.config.type === "skodae" ||
this.config.type === "skodapower" ||
this.config.type === "audidata" ||
this.config.type === "audietron" ||
this.config.type === "seatcupra" ||
this.config.type === "seatcupra2"
) {
url += "&code_challenge=" + codeChallenge + "&code_challenge_method=S256";
}
if (this.config.type === "audi") {
url += "&ui_locales=de-DE%20de&prompt=login";
}
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 && 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 && 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 {
if (this.type === "Wc") {
resolve();
return;
}
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 check username and password or visit this link or logout and login in your app account:",
);
this.log.warn("Bitte in die App einloggen und die Nutzungsbedingungen akzeptieren.");
this.log.warn("https://" + resp.request.host + resp.headers.location);
this.log.warn("For Skoda: https://skodaid.vwgroup.io/landing-page");
this.log.warn("For VW: https://vwid.vwgroup.io/landing-page");
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);
//check for empty form object
if (Object.keys(form).length === 0 && form.constructor === Object) {
try {
const stringJson = body.split("window._IDK = ")[1].split("</")[0];
let json = stringJson.replace(/([{,]\s*)(\w+)\s*:/g, '$1"$2":'); // Add quotes around property names
json = json.replace(/'/g, '"');
json = json.replace(/,\s*}/g, "}"); // Remove trailing commas
const parsedJson = JSON.parse(json);
form._csrf = parsedJson.csrf_token;
form.hmac = parsedJson.templateModel.hmac;
form.relayState = parsedJson.templateModel.relayState;
form.legalDocuments = parsedJson.templateModel.legalDocuments;
form.countryOfResidence = "DE";
form.countryOfJurisdiction = "DE";
} catch (error) {
this.log.error(
"Error in consent form. Please accept the Data Privacy Statement in the app after relogin",
);
this.log.error(error);
reject();
return;
}
}
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: qs
.stringify(form)
.replace(/true/g, "yes")
.replace(/false/g, "no")
.replace(/%5D%5B/g, "%5D.")
.replace(/%5D=/g, "="),
followAllRedirects: true,
gzip: true,
},
(err, resp, body) => {
if (err && err.message.indexOf("Invalid protocol:") === 0) {
this.log.info("Auto accept succesful. Restart adapter in 10sec");
setTimeout(() => {
this.restart();
}, 10 * 1000);
return;
}
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.warn(
"No Token received visiting url and accept the permissions or login in the App and accept manually",
);
this.log.info(getRequest.uri.href);
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 latest MySkoda or MySeat App",
);
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();
}
},
);
});
}
async cleanObjects(vin) {
let remoteState = await this.getObjectAsync(vin + ".general.systemId");
if (remoteState) {
this.log.info("clean old states" + vin);
await this.delObjectAsync(vin, { recursive: true });
}
remoteState = await this.getObjectAsync(vin + ".general.capabilities");
if (remoteState) {
this.log.info("clean old states" + vin);
await this.delObjectAsync(vin, { recursive: true });
}
if (this.config.type === "seatcupra") {
remoteState = await this.getObjectAsync(vin + ".remote.targetTemperatureInCelsius");
if (!remoteState) {
this.log.info("clean old states" + vin);
await this.delObjectAsync(vin, { recursive: true });
}
}
}
updateStatus() {
if (this.config.type === "go") {
this.getVehicles();
return;
} else if (this.config.type === "skodae") {
this.vinArray.forEach((vin) => {
this.getSkodaEStatus(vin);
});
if (this.pairedWallbox) {
this.getWcData(this.config.historyLimit);
}
} else if (this.config.type === "audidata") {
this.vinArray.forEach((vin) => {
this.getAudiDataStatus(vin).catch(() => {
this.log.error("get audi data status Failed");
});
});
} else if (this.config.type === "id") {
this.vinArray.forEach((vin) => {
this.getIdStatus(vin).catch(() => {
this.log.error("get id status Failed");
this.refreshIDToken().catch(() => {});
});
if (this.config.type === "id" && this.pairedWallbox) {
this.getWcData(this.config.historyLimit);
}
});
return;
} else if (this.config.type === "audietron") {
this.vinArray.forEach((vin) => {
this.getIdStatus(vin).catch(() => {
this.log.error("get id status Failed");
this.refreshTokenv2().catch(() => {});
});
});
return;
} else if (this.config.type === "seatcupra") {
this.vinArray.forEach((vin) => {
this.getSeatCupraStatus(vin);
});
return;
} else if (this.config.type === "seatelli" || this.config.type === "skodapower") {
this.getElliData(this.config.type).catch(() => {
this.log.error("get elli Failed");
});
return;
} else {
this.vinArray.forEach((vin) => {
this.statesArray.forEach((state) => {
if (state.path == "tripdata") {
this.tripTypes.forEach((tripType) => {
this.getVehicleStatus(
vin,
state.url,
state.path,
state.element,
state.element2,
null,
null,
tripType,
).catch(() => {
this.log.debug("error while getting " + state.url);
});
});
} else {
this.getVehicleStatus(vin, state.url, state.path, state.element, state.element2).catch(() => {
this.log.debug("error while getting " + state.url);
});
}
});
});
}
}
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: {
"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] || "https://msg.volkswagen.de";
return url
.replace("/$vin", "/" + vin + "")
.replace("$homeregion/", curHomeRegion + "/")
.replace("/$type/", "/" + this.type + "/")
.replace("/$country/", "/" + this.country + "/")
.replace("/$tripType", "/" + tripType);
}
getQmauth() {
const timestamp = parseInt(Date.now() / 100000);
this.log.debug(timestamp.toString());
//credits to https://github.com/arjenvrh/audi_connect_ha/blob/master/custom_components/audiconnect/audi_services.py
const xqmauth_secret = Buffer.from([
26,
256 - 74,
256 - 103,
37,
256 - 84,
23,
256 - 102,
256 - 86,
78,
256 - 125,
256 - 85,
256 - 26,
113,
256 - 87,
71,
109,
23,
100,
24,
256 - 72,
91,
256 - 41,
6,
256 - 15,
67,
108,
256 - 95,
91,
256 - 26,
71,
256 - 104,
256 - 100,
]);
const xqmauth_val = crypto.createHmac("sha256", xqmauth_secret).update(timestamp.toString()).digest("hex");
this.log.debug(timestamp.toString());
return "v1:01da27b0:" + xqmauth_val;
}
getTokensv2(getRequest, code_verifier, reject, resolve) {
const url = getRequest.uri.query;
this.log.debug(url);
const queries = qs.parse(url);
const body = {
client_id: this.clientId,
grant_type: "authorization_code",
code: queries.code,
redirect_uri: "myaudi:///",
response_type: "token id_token",
code_verifier: code_verifier,
};
const qmAuth = this.getQmauth();
this.log.debug(qmAuth);
this.log.debug(JSON.stringify(body));
request(
{
method: "POST",
url: "https://emea.bff.cariad.digital/login/v1/idk/token",
headers: {
accept: "application/json",
"content-type": "application/x-www-form-urlencoded; charset=utf-8",
"accept-charset": "utf-8",
"x-qmauth": qmAuth,
"accept-language": "de-de",
"user-agent": this.userAgent,
},
jar: this.jar,
gzip: true,
followAllRedirects: true,
body: qs.stringify(body),
},
(err, resp) => {
if (err || (resp && resp.statusCode >= 400)) {
this.log.error("Failed get tokensv2. Please check your if your local time is correct");
err && this.log.error(err);
resp && this.log.error(resp.statusCode.toString());
resp && resp.body && this.log.error(JSON.stringify(resp.body));
reject();
return;
}
const idktokens = JSON.parse(resp.body);
this.config.atoken = idktokens.access_token;
this.config.rtoken = idktokens.refresh_token;
request(
{
method: "POST",
url: "https://emea.bff.cariad.digital/login/v1/audi/token",
headers: {
accept: "application/json",
"content-type": "application/json; charset=utf-8",
"accept-charset": "utf-8",
"x-app-version": "4.13.0",
"x-app-name": "myAudi",
"accept-language": "de-de",
"user-agent": this.userAgent,
},
jar: this.jar,
gzip: true,
followAllRedirects: false,
body: JSON.stringify({
token: this.config.atoken,
grant_type: "id_token",
stage: "live",
config: "myaudi",
}),
},
(err, resp) => {
if (err || (resp && resp.statusCode >= 400)) {
this.log.error("failed get audi token");
err && this.log.error(err);
resp && this.log.error(resp.statusCode.toString());
body && this.log.error(JSON.stringify(body));
reject();
return;
}
if (this.config.type === "audi") {
// eslint-disable-next-line
this.getVWToken({}, jwtid_token, reject, resolve);
return;
}
this.aaztoken = JSON.parse(resp.body);
this.refreshTokenInterval && clearInterval(this.refreshTokenInterval);
this.refreshTokenInterval = setInterval(() => {
this.refreshTokenv2().catch(() => {});
}, 0.9 * 60 * 60 * 1000); // 0.9hours
this.getVWToken({}, idktokens.id_token, reject, resolve);
},
);
},
);
}
getTokens(getRequest, code_verifier, reject, resolve) {
if (this.config.type === "audietron") {
this.getTokensv2(getRequest, code_verifier, reject, resolve);
return;
}
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://tokenrefreshservice.apps.emea.vwapps.io/exchangeAuthCode";
let headers = {
// "user-agent": this.userAgent,
"X-App-version": this.xappversion,
"content-type": "application/x-www-form-urlencoded",
"x-app-name": this.xappname,
accept: "application/json",
};
if (this.config.type === "vw" || this.config.type === "vwv2") {
body += "&code_verifier=" + code_verifier;
} else {
let brand = this.config.type === "skodae" ? "skoda" : this.config.type;
if (this.config.type === "seatcupra2") {
brand = "cupra";
}
body += "&brand=" + brand;
}
if (this.config.type === "seatcupra2") {
body += "&code_verifier=" + code_verifier;
}
if (this.config.type === "skodae") {
const parsedParameters = qs.parse(hash);
// this.config.atoken = parsedParameters.access_token;
let systemId = "TECHNICAL";
if (this.clientId === "7f045eee-7003-4379-9968-9355ed2adb06@apps_vw-dilab_com") {
systemId = "CONNECT";
}
method = "POST";
url =
"https://mysmob.api.connect.skoda-auto.cz/api/v1/authentication/exchange-authorization-code?tokenType=" +
systemId;
body = JSON.stringify({
code: parsedParameters.code,
redirectUri: "myskoda://redirect/login/",
verifier: code_verifier,
});
headers = {
accept: "*/*",
// authorization: "Bearer " + parsedParameters.id_token,
"content-type": "application/json",
"user-agent": this.skodaUserAgent,
"accept-language": "de-de",
};
}
if (this.config.type === "go") {
url = "https://dmp.apps.emea.vwapps.io/mobility-platform/token";
body =
"code=" +
jwtauth_code +
"&client_id=" +
this.clientId +
"&redirect_uri=vwconnect://de.volkswagen.vwconnect/oa