bluelinky2
Version:
An unofficial nodejs API wrapper for Hyundai bluelink
141 lines (115 loc) • 4.53 kB
text/typescript
import got from 'got';
import { BlueLinkyConfig } from '../interfaces/common.interfaces';
import { CanadianBrandEnvironment, getBrandEnvironment } from '../constants/canada';
import { Vehicle } from '../vehicles/vehicle';
import CanadianVehicle from '../vehicles/canadian.vehicle';
import { SessionController } from './controller';
import logger from '../logger';
import { VehicleRegisterOptions } from '../interfaces/common.interfaces';
import { manageBluelinkyError } from '../tools/common.tools';
export interface CanadianBlueLinkyConfig extends BlueLinkyConfig {
region: 'CA';
}
export class CanadianController extends SessionController<CanadianBlueLinkyConfig> {
private _environment: CanadianBrandEnvironment;
constructor(userConfig: CanadianBlueLinkyConfig) {
super(userConfig);
logger.debug('CA Controller created');
this._environment = getBrandEnvironment(userConfig.brand);
}
public get environment() : CanadianBrandEnvironment {
return this._environment;
}
private vehicles: Array<CanadianVehicle> = [];
private timeOffset = -(new Date().getTimezoneOffset() / 60);
public async refreshAccessToken(): Promise<string> {
const shouldRefreshToken = Math.floor(Date.now() / 1000 - this.session.tokenExpiresAt) >= -10;
logger.debug('shouldRefreshToken: ' + shouldRefreshToken.toString());
if (this.session.refreshToken && shouldRefreshToken) {
// TODO: someone should find the refresh token API url then we dont have to do this hack
// the previously used CA_ENDPOINTS.verifyToken did not refresh it only provided if the token was valid
await this.login();
logger.debug('Token refreshed');
return 'Token refreshed';
}
logger.debug('Token not expired, no need to refresh');
return 'Token not expired, no need to refresh';
}
public async login(): Promise<string> {
logger.info('Begin login request');
try {
const response = await this.request(this.environment.endpoints.login, {
loginId: this.userConfig.username,
password: this.userConfig.password,
});
logger.debug(response.result);
this.session.accessToken = response.result.accessToken;
this.session.refreshToken = response.result.refreshToken;
this.session.tokenExpiresAt = Math.floor(+new Date() / 1000 + response.result.expireIn);
return 'login good';
} catch (err) {
return 'error: ' + err;
}
}
async logout(): Promise<string> {
return 'OK';
}
async getVehicles(): Promise<Array<Vehicle>> {
logger.info('Begin getVehicleList request');
try {
const response = await this.request(this.environment.endpoints.vehicleList, {});
const data = response.result;
if (data.vehicles === undefined) {
this.vehicles = [];
return this.vehicles;
}
data.vehicles.forEach(vehicle => {
const vehicleConfig = {
nickname: vehicle.nickName,
name: vehicle.nickName,
vin: vehicle.vin,
regDate: vehicle.enrollmentDate,
brandIndicator: vehicle.brandIndicator,
regId: vehicle.regid,
id: vehicle.vehicleId,
generation: vehicle.genType,
} as VehicleRegisterOptions;
this.vehicles.push(new CanadianVehicle(vehicleConfig, this));
});
return this.vehicles;
} catch (err) {
logger.debug(err);
return this.vehicles;
}
}
//////////////////////////////////////////////////////////////////////////////
// Internal
//////////////////////////////////////////////////////////////////////////////
// TODO: not quite sure how to type this if it's dynamic?
/* eslint-disable @typescript-eslint/no-explicit-any */
private async request(endpoint, body: any, headers: any = {}): Promise<any | null> {
logger.debug(`[${endpoint}] ${JSON.stringify(headers)} ${JSON.stringify(body)}`);
try {
const response = await got(endpoint, {
method: 'POST',
json: true,
headers: {
from: this.environment.origin,
language: 1,
offset: this.timeOffset,
accessToken: this.session.accessToken,
...headers,
},
body: {
...body,
},
});
if (response.body.responseHeader.responseCode != 0) {
throw response.body.responseHeader.responseDesc;
}
return response.body;
} catch (err) {
throw manageBluelinkyError(err, 'CanadianController');
}
}
}