homebridge-hilo
Version:
Plugin Homebridge (non officiel) pour la passerelle et les appareils Hilo de Hydro-Québec | Unofficial Homebridge plugin for Hydro-Québec Hilo bridge and devices
176 lines (159 loc) • 4.7 kB
text/typescript
import axios, { AxiosHeaders, InternalAxiosRequestConfig } from "axios";
import fs from "fs";
import { getConfig } from "./config";
import { getLogger } from "./logger";
import { getApi } from "./api";
import { TypedDocumentString } from "./graphql/graphql";
let accessToken: string | undefined;
let refreshToken: string | undefined;
const clientId = "1ca9f585-4a55-4085-8e30-9746a65fa561";
const authServer = "https://connexion.hiloenergie.com";
const renewTokens = async ({
newRefreshToken,
newAccessToken,
expiresIn,
}: {
newRefreshToken: string;
newAccessToken?: string;
expiresIn?: number;
}) => {
const configPath = getApi().user.configPath();
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
const hilo = config.platforms.find(
(p: { platform: string }) => p.platform === "Hilo",
);
hilo.refreshToken = newRefreshToken;
fs.writeFileSync(configPath, JSON.stringify(config, null, 4));
accessToken = newAccessToken;
refreshToken = newRefreshToken;
await setupAutoRefreshToken(expiresIn);
};
async function login() {
getLogger().debug("Logging in");
const config = getConfig();
const newRefreshToken = config.refreshToken;
await renewTokens({
newRefreshToken,
});
}
type RefreshTokenResponse = {
access_token: string;
id_token: string;
token_type: "Bearer";
not_before: number;
expires_in: number;
resource: string;
id_token_expires_in: number;
profile_info: string;
scope: string;
refresh_token: string;
refresh_token_expires_in: number;
};
async function refreshTokenRequest() {
getLogger().debug("Refreshing token");
const data = new URLSearchParams();
data.append("grant_type", "refresh_token");
data.append("client_id", clientId);
data.append("refresh_token", refreshToken!);
data.append(
"scope",
"openid https://HiloDirectoryB2C.onmicrosoft.com/hiloapis/user_impersonation offline_access",
);
data.append("redirect_uri", "https://my.home-assistant.io/redirect/oauth");
const response = await axios.post<RefreshTokenResponse>(
`${authServer}/HiloDirectoryB2C.onmicrosoft.com/B2C_1A_SIGN_IN/oauth2/v2.0/token?p=b2c_1a_sign_in`,
data,
);
await renewTokens({
newAccessToken: response.data.access_token,
newRefreshToken: response.data.refresh_token,
expiresIn: response.data.expires_in,
});
}
export const getAccessToken = () => accessToken || "";
export async function setupAutoRefreshToken(expiresIn: number | undefined) {
if (!expiresIn) {
getLogger().debug("Refreshing token");
try {
await refreshTokenRequest();
} catch (e) {
unableToLogin(e);
}
} else {
getLogger().debug("Setting up auto refresh token");
setTimeout(
async () => {
getLogger().debug("Refreshing token automatically");
try {
await refreshTokenRequest();
} catch (_e) {
try {
await login();
} catch (e) {
unableToLogin(e);
}
}
},
expiresIn * 1000 - 1000 * 60 * 5,
); // 5 minutes before expiration
}
}
export const hiloApi = axios.create({
baseURL: "https://api.hiloenergie.com",
});
export const graphqlApi = axios.create({
baseURL: "https://platform.hiloenergie.com",
});
export function execute<TResult, TVariables>(
query: TypedDocumentString<TResult, TVariables>,
...[variables]: TVariables extends Record<string, never> ? [] : [TVariables]
) {
return graphqlApi.post(
"/api/digital-twin/v3/graphql",
{
query: query.toString(),
variables,
},
{
headers: {
"Content-Type": "application/json",
},
},
);
}
const authInterceptor = async (config: InternalAxiosRequestConfig) => {
if (!accessToken) {
if (refreshToken) {
try {
await refreshTokenRequest();
} catch (_e) {
try {
await login();
} catch (e) {
unableToLogin(e);
}
}
} else {
try {
await login();
} catch (e) {
unableToLogin(e);
}
}
}
config.headers = new AxiosHeaders({
...config.headers,
"Ocp-Apim-Subscription-Key": "20eeaedcb86945afa3fe792cea89b8bf",
Authorization: accessToken ? `Bearer ${accessToken}` : "",
});
return config;
};
hiloApi.interceptors.request.use(authInterceptor);
graphqlApi.interceptors.request.use(authInterceptor);
const unableToLogin = (e: unknown) => {
const logger = getLogger();
logger.error("Unable to login", axios.isAxiosError(e) ? e.response?.data : e);
logger.error(
"You can try to refresh your login credentials by using the 'Login with Hilo' button in the plugin configuration in homebridge UI",
);
};