Skip to content

Commit

Permalink
Add support for Bluelink and Kia Connect(only signin tested) China ve…
Browse files Browse the repository at this point in the history
…rsion. (#243)
  • Loading branch information
toraidl authored Apr 20, 2023
1 parent cf10ab8 commit f23687b
Show file tree
Hide file tree
Showing 10 changed files with 1,217 additions and 4 deletions.
4 changes: 2 additions & 2 deletions debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const askForRegionInput = () => {
type: 'list',
name: 'region',
message: 'What Region are you in?',
choices: ['US', 'EU', 'CA'],
choices: ['CN','US', 'EU', 'CA'],
},
{
type: 'list',
Expand Down Expand Up @@ -157,7 +157,7 @@ async function performCommand(command) {
const startRes = await vehicle.start({
airCtrl: false,
igniOnDuration: 10,
airTempvalue: 70,
airTempvalue: 24,
defrost: false,
heating1: false,
});
Expand Down
10 changes: 9 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,30 @@ import {
getBrandEnvironment as getEUBrandEnvironment,
EuropeanBrandEnvironment,
} from './constants/europe';
import {
getBrandEnvironment as getCNBrandEnvironment,
ChineseBrandEnvironment,
} from './constants/china';

import { Brand, VehicleStatusOptions } from './interfaces/common.interfaces';

export const ALL_ENDPOINTS = {
CA: (brand: Brand): CanadianBrandEnvironment['endpoints'] =>
getCABrandEnvironment(brand).endpoints,
EU: (brand: Brand): EuropeanBrandEnvironment['endpoints'] =>
getEUBrandEnvironment({ brand }).endpoints,
CN: (brand: Brand): ChineseBrandEnvironment['endpoints'] =>
getCNBrandEnvironment({ brand }).endpoints,
};

export const GEN2 = 2;
export const GEN1 = 1;
export type REGION = 'US' | 'CA' | 'EU';
export type REGION = 'US' | 'CA' | 'EU' | 'CN';
export enum REGIONS {
US = 'US',
CA = 'CA',
EU = 'EU',
CN = 'CN',
}

// ev stuffz
Expand Down
90 changes: 90 additions & 0 deletions src/constants/china.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { ChineseBlueLinkConfig } from '../controllers/chinese.controller';
import { Brand } from '../interfaces/common.interfaces';

export interface ChineseBrandEnvironment {
brand: Brand;
host: string;
baseUrl: string;
clientId: string;
appId: string;
endpoints: {
integration: string;
silentSignIn: string;
session: string;
login: string;
language: string;
redirectUri: string;
token: string;
};
basicToken: string;
GCMSenderID: string;
providerDeviceId: string;
pushRegId: string;
}

const getEndpoints = (
baseUrl: string,
clientId: string
): ChineseBrandEnvironment['endpoints'] => ({
session: `${baseUrl}/api/v1/user/oauth2/authorize?response_type=code&state=test&client_id=${clientId}&redirect_uri=${baseUrl}:443/api/v1/user/oauth2/redirect`,
login: `${baseUrl}/api/v1/user/signin`,
language: `${baseUrl}/api/v1/user/language`,
redirectUri: `${baseUrl}:443/api/v1/user/oauth2/redirect`,
token: `${baseUrl}/api/v1/user/oauth2/token`,
integration: `${baseUrl}/api/v1/user/integrationinfo`,
silentSignIn: `${baseUrl}/api/v1/user/silentsignin`,
});

type BrandEnvironmentConfig = Pick<ChineseBlueLinkConfig, 'brand'>;

const getHyundaiEnvironment = (): ChineseBrandEnvironment => {
const host = 'prd.cn-ccapi.hyundai.com';
const baseUrl = `https://${host}`;
const clientId = '72b3d019-5bc7-443d-a437-08f307cf06e2';
const appId = 'ed01581a-380f-48cd-83d4-ed1490c272d0';
return {
brand: 'hyundai',
host,
baseUrl,
clientId,
appId,
endpoints: Object.freeze(getEndpoints(baseUrl, clientId)),
basicToken:
'Basic NzJiM2QwMTktNWJjNy00NDNkLWE0MzctMDhmMzA3Y2YwNmUyOnNlY3JldA==',
GCMSenderID: '414998006775',
providerDeviceId: '59af09e554a9442ab8589c9500d04d2e',
pushRegId: '1',
};
};

const getKiaEnvironment = (): ChineseBrandEnvironment => {
const host = 'prd.cn-ccapi.kia.com';
const baseUrl = `https://${host}`;
const clientId = '9d5df92a-06ae-435f-b459-8304f2efcc67';
const appId = 'eea8762c-adfc-4ee4-8d7a-6e2452ddf342';
return {
brand: 'kia',
host,
baseUrl,
clientId,
appId,
endpoints: Object.freeze(getEndpoints(baseUrl, clientId)),
basicToken: 'Basic OWQ1ZGY5MmEtMDZhZS00MzVmLWI0NTktODMwNGYyZWZjYzY3OnRzWGRrVWcwOEF2MlpaelhPZ1d6Snl4VVQ2eWVTbk5OUWtYWFBSZEtXRUFOd2wxcA==',
GCMSenderID: '345127537656',
providerDeviceId: '32dedba78045415b92db816e805ed47b',
pushRegId: 'ogc+GB5gom7zDEQjPhb3lP+bjjM=DG2rQ9Zuq0otwOU7n9y08LKjYpo=',
};
};

export const getBrandEnvironment = ({
brand,
}: BrandEnvironmentConfig): ChineseBrandEnvironment => {
switch (brand) {
case 'hyundai':
return Object.freeze(getHyundaiEnvironment());
case 'kia':
return Object.freeze(getKiaEnvironment());
default:
throw new Error(`Constructor ${brand} is not managed.`);
}
};
27 changes: 27 additions & 0 deletions src/controllers/authStrategies/china.authStrategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import got from 'got';
import { CookieJar } from 'tough-cookie';
import {ChineseBrandEnvironment } from '../../constants/china';

export type Code = string;

export interface AuthStrategy {
readonly name: string;
login(
user: { username: string; password: string },
options?: { cookieJar?: CookieJar }
): Promise<{ code: Code; cookies: CookieJar }>;
}

export async function initSession(
environment: ChineseBrandEnvironment,
cookies?: CookieJar
): Promise<CookieJar> {
const cookieJar = cookies ?? new CookieJar();
await got(environment.endpoints.session, { cookieJar });
await got(environment.endpoints.language, {
method: 'POST',
body: `{"lang":"zh"}`,
cookieJar,
});
return cookieJar;
}
48 changes: 48 additions & 0 deletions src/controllers/authStrategies/chinese.legacyAuth.strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import got from 'got';
import { CookieJar } from 'tough-cookie';
import { ChineseBrandEnvironment } from '../../constants/china';
import { AuthStrategy, Code, initSession } from './china.authStrategy';
import Url from 'url';

export class ChineseLegacyAuthStrategy implements AuthStrategy {
constructor(
private readonly environment: ChineseBrandEnvironment,
) {}

public get name(): string {
return 'ChineseLegacyAuthStrategy';
}

async login(
user: { username: string; password: string },
options?: { cookieJar: CookieJar }
): Promise<{ code: Code; cookies: CookieJar }> {
const cookieJar = await initSession(this.environment, options?.cookieJar);
const { body, statusCode } = await got(this.environment.endpoints.login, {
method: 'POST',
json: true,
body: {
'email': user.username,
'password': user.password,
},
cookieJar,
});
if (!body.redirectUrl) {
throw new Error(
`@ChineseLegacyAuthStrategy.login: sign In didn't work, could not retrieve auth code. status: ${statusCode}, body: ${JSON.stringify(
body
)}`
);
}
const { code } = Url.parse(body.redirectUrl, true).query;
if (!code) {
throw new Error(
'@ChineseLegacyAuthStrategy.login: AuthCode was not found, you probably need to migrate your account.'
);
}
return {
code: code as Code,
cookies: cookieJar,
};
}
}
Loading

0 comments on commit f23687b

Please sign in to comment.