UNPKG

@iobroker/adapter-core

Version:

Core module to be used in ioBroker adapters. Acts as the bridge to js-controller.

204 lines (203 loc) 7.68 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var TokenRefresher_exports = {}; __export(TokenRefresher_exports, { TokenRefresher: () => TokenRefresher }); module.exports = __toCommonJS(TokenRefresher_exports); var import_node_https = require("node:https"); class TokenRefresher { static { __name(this, "TokenRefresher"); } adapter; stateName; refreshTokenTimeout; accessToken; url; readyPromise; name; /** Threshold in milliseconds before the access token expires to trigger a refresh */ static TOKEN_REFRESH_THRESHOLD_MS = 18e4; // 3 minutes in milliseconds /** * Creates an instance of TokenRefresher. * * @param adapter Instance of ioBroker adapter * @param serviceName Name of the service for which the tokens are managed, e.g., 'spotify', 'dropbox', etc. * @param stateName Optional name of the state where tokens are stored. Defaults to 'oauth2Tokens' and that will store tokens in `ADAPTER.X.oauth2Tokens`. */ constructor(adapter, serviceName, stateName) { this.adapter = adapter; this.stateName = stateName || "oauth2Tokens"; this.url = `https://oauth2.iobroker.in/${serviceName}`; this.name = serviceName || adapter.name; if (this.name === "oauth2") { this.name = adapter.name; } this.readyPromise = this.init(); } httpPost(url, data, timeout = 2e4) { return new Promise((resolve, reject) => { const req = (0, import_node_https.request)(url, { method: "POST", headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(JSON.stringify(data)) }, timeout }, (res) => { let responseData = ""; res.on("data", (chunk) => { responseData += chunk; }); res.on("end", () => { if (res.statusCode === 200 || res.statusCode === 201) { resolve(JSON.parse(responseData)); } else { reject(new Error(`HTTP ${res.statusCode}: ${responseData}`)); } }); res.on("error", reject); }); req.on("error", (error) => reject(error)); req.write(JSON.stringify(data)); req.end(); }); } async init() { const obj = await this.adapter.getObjectAsync(this.stateName); if (!obj) { await this.adapter.setObjectAsync(this.stateName, { type: "state", common: { name: this.stateName, expert: true, type: "string", role: "json", read: true, write: true, def: "" }, native: {} }); this.adapter.log.debug(`State ${this.stateName} created for ${this.name}`); } const state = await this.adapter.getStateAsync(this.stateName); if (state) { try { this.accessToken = JSON.parse(state.val); } catch (error) { this.adapter.log.error(`Cannot parse tokens: ${state.val}: ${error.message}`); this.accessToken = void 0; } } else { this.adapter.log.error(`No tokens for ${this.name} found`); } this.adapter.subscribeStatesAsync(this.stateName).catch((error) => this.adapter.log.error(`Cannot read tokens: ${error}`)); return this.refreshTokens().catch((error) => this.adapter.log.error(`Cannot refresh tokens: ${error}`)); } /** * Destroys the TokenRefresher instance, clearing any timeouts and stopping state subscriptions. */ destroy() { if (this.refreshTokenTimeout) { this.adapter.clearTimeout(this.refreshTokenTimeout); this.refreshTokenTimeout = void 0; } } /** * This method is called when the state changes for the token. * * @param id ID of the state that changed * @param state Value */ onStateChange(id, state) { if (state?.ack && id === `${this.adapter.namespace}.${this.stateName}`) { if (JSON.stringify(this.accessToken) !== state.val) { try { this.accessToken = JSON.parse(state.val); this.refreshTokens().catch((error) => this.adapter.log.error(`Cannot refresh tokens: ${error}`)); } catch (error) { this.adapter.log.error(`Cannot parse tokens: ${error}`); this.accessToken = void 0; } } } } /** Returns the access token if it is valid and not expired.*/ async getAccessToken() { await this.readyPromise; if (!this.accessToken?.access_token) { this.adapter.log.error(`No tokens for ${this.name} found`); return void 0; } if (!this.accessToken.access_token_expires_on || new Date(this.accessToken.access_token_expires_on).getTime() < Date.now()) { this.adapter.log.error("Access token is expired. Please authorize with your credentials via Admin interface again"); return void 0; } return this.accessToken.access_token; } async refreshTokens() { if (this.refreshTokenTimeout) { this.adapter.clearTimeout(this.refreshTokenTimeout); this.refreshTokenTimeout = void 0; } if (!this.accessToken?.refresh_token) { this.adapter.log.error(`No tokens for ${this.name} found. Please authorize anew with your credentials via Admin interface.`); return; } if (!this.accessToken.access_token_expires_on || new Date(this.accessToken.access_token_expires_on).getTime() < Date.now()) { this.adapter.log.debug("Access token is expired. Retrying to refresh tokens..."); } let expiresIn = new Date(this.accessToken.access_token_expires_on).getTime() - Date.now() - TokenRefresher.TOKEN_REFRESH_THRESHOLD_MS; if (expiresIn <= 0) { try { this.accessToken = await this.httpPost(this.url, this.accessToken); } catch (error) { this.accessToken = void 0; this.adapter.log.error(`Cannot refresh tokens: ${error}`); } if (this.accessToken) { this.accessToken.access_token_expires_on = new Date(Date.now() + this.accessToken.expires_in * 1e3).toISOString(); expiresIn = new Date(this.accessToken.access_token_expires_on).getTime() - Date.now() - 18e4; await this.adapter.setState(this.stateName, JSON.stringify(this.accessToken), true); this.adapter.log.debug(`Tokens for ${this.name} updated`); } else { expiresIn = 6e5; this.adapter.log.error(`No tokens for ${this.name} could be refreshed`); } } if (expiresIn > 6e5) { expiresIn = 6e5; } else if (expiresIn < 6e4) { expiresIn = 6e4; } this.refreshTokenTimeout = this.adapter.setTimeout(() => { this.refreshTokenTimeout = void 0; this.refreshTokens().catch((error) => this.adapter.log.error(`Cannot refresh tokens: ${error}`)); }, expiresIn); } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { TokenRefresher }); //# sourceMappingURL=TokenRefresher.js.map