yale-doorman
Version:
SDK for Yale Doorman through the Yale cloud API
201 lines (200 loc) • 10.9 kB
JavaScript
;
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var _YaleDoorman_accessToken, _YaleDoorman_refreshToken, _YaleDoorman_accessTokenExpiresAt, _YaleDoorman_configuration, _YaleDoorman_email, _YaleDoorman_password;
Object.defineProperty(exports, "__esModule", { value: true });
exports.YaleDoorman = void 0;
const deepMerge_js_1 = require("./misc/deepMerge.js");
const default_js_1 = __importDefault(require("./config/default.js"));
const url_1 = require("url");
const assert_1 = __importDefault(require("assert"));
const got_1 = __importDefault(require("got"));
/**
* SDK class that expose the Yale doorman SDK.
*
* Note that `login()` does not need to be called explicitly.
* The SDK will lazily call `login()` to (re)authenticate when necessary.
*/
class YaleDoorman {
/** The generic type parameter should be either omitted or `false` in production. */
constructor(email, password, configuration) {
_YaleDoorman_accessToken.set(this, void 0);
_YaleDoorman_refreshToken.set(this, void 0);
_YaleDoorman_accessTokenExpiresAt.set(this, void 0);
_YaleDoorman_configuration.set(this, void 0);
_YaleDoorman_email.set(this, void 0);
_YaleDoorman_password.set(this, void 0);
__classPrivateFieldSet(this, _YaleDoorman_configuration, (!configuration ? default_js_1.default : (0, deepMerge_js_1.deepMerge)(default_js_1.default, configuration)), "f");
__classPrivateFieldSet(this, _YaleDoorman_email, email, "f");
__classPrivateFieldSet(this, _YaleDoorman_password, password, "f");
}
/** Send a REST request to the Sector API */
async httpRequest({ endpoint, method = "GET", form, isRetry = false, query }) {
if (isRetry || !__classPrivateFieldGet(this, _YaleDoorman_accessTokenExpiresAt, "f") || (__classPrivateFieldGet(this, _YaleDoorman_accessTokenExpiresAt, "f").valueOf() - __classPrivateFieldGet(this, _YaleDoorman_configuration, "f").clock.Date.now()) < 1000 * 60) {
await this.login();
}
(0, assert_1.default)(__classPrivateFieldGet(this, _YaleDoorman_accessToken, "f"), "Expect access token to be defined");
const { yale: { host, endpoints } } = __classPrivateFieldGet(this, _YaleDoorman_configuration, "f");
const response = await (0, got_1.default)(new url_1.URL(endpoints[endpoint], host), {
method,
form,
searchParams: query,
headers: { authorization: `Bearer ${__classPrivateFieldGet(this, _YaleDoorman_accessToken, "f")}` },
resolveBodyOnly: false,
throwHttpErrors: false,
retry: { limit: 3 },
responseType: "json"
});
__classPrivateFieldGet(this, _YaleDoorman_configuration, "f").logger.debug(`Request ${method} ${endpoints[endpoint]} -> HTTP${response.statusCode}`);
if (response.statusCode === 401 && !isRetry) {
return await this.httpRequest({
endpoint, method, form, isRetry: true, query
});
}
else if (response.statusCode === 401) {
throw new Error(`Authentication error (${method}). Body: ${response.rawBody.toString()}`);
}
else if (response.statusCode >= 400) {
throw new Error(`HTTP error ${method} ${endpoint}: ${response.statusCode} ${response.rawBody.toString()}`);
}
return response.body;
}
/** Returns a boolean indicating success */
async loginWithRefreshToken() {
if (!__classPrivateFieldGet(this, _YaleDoorman_refreshToken, "f")) {
return false;
}
const { yale: { host, endpoints: { token } } } = __classPrivateFieldGet(this, _YaleDoorman_configuration, "f");
// Perform the actual login
const response = await (0, got_1.default)(new url_1.URL(token, host), {
method: "POST",
resolveBodyOnly: false,
throwHttpErrors: false,
username: __classPrivateFieldGet(this, _YaleDoorman_configuration, "f").yale.clientId,
password: __classPrivateFieldGet(this, _YaleDoorman_configuration, "f").yale.clientSecret,
form: {
grant_type: "refresh_token",
refresh_token: __classPrivateFieldGet(this, _YaleDoorman_refreshToken, "f")
},
responseType: "json",
retry: { limit: 3 }
});
if (response.statusCode < 300) {
__classPrivateFieldSet(this, _YaleDoorman_accessToken, response.body.access_token, "f");
__classPrivateFieldSet(this, _YaleDoorman_accessTokenExpiresAt, new Date(response.body.expires_in * 1000), "f");
__classPrivateFieldSet(this, _YaleDoorman_refreshToken, response.body.refresh_token, "f");
__classPrivateFieldGet(this, _YaleDoorman_configuration, "f").logger.info("Successfully authenticated with Yale API using refresh token");
return true;
}
__classPrivateFieldGet(this, _YaleDoorman_configuration, "f").logger.debug("Failed to authenticate with using refresh token");
return false;
}
/** Authenticate using the credentials specified in the constructor.
*
* This function is used internally and does not need to be called directly.
*/
async login() {
if (await this.loginWithRefreshToken()) {
return;
}
const { yale: { host, endpoints: { token } } } = __classPrivateFieldGet(this, _YaleDoorman_configuration, "f");
// Perform the actual login
const response = await (0, got_1.default)(new url_1.URL(token, host), {
method: "POST",
resolveBodyOnly: false,
throwHttpErrors: false,
username: __classPrivateFieldGet(this, _YaleDoorman_configuration, "f").yale.clientId,
password: __classPrivateFieldGet(this, _YaleDoorman_configuration, "f").yale.clientSecret,
form: {
grant_type: "password",
username: __classPrivateFieldGet(this, _YaleDoorman_email, "f"),
password: __classPrivateFieldGet(this, _YaleDoorman_password, "f")
},
responseType: "json",
retry: { limit: 3 }
});
if (response.statusCode === 401) {
throw new Error(`Authentication error on login. Maybe the credentials are incorrect? Body: ${response.rawBody.toString()}`);
}
else if (response.statusCode >= 400) {
throw new Error(`HTTP error: ${response.statusCode} ${response.rawBody.toString()}`);
}
__classPrivateFieldSet(this, _YaleDoorman_accessToken, response.body.access_token, "f");
__classPrivateFieldSet(this, _YaleDoorman_accessTokenExpiresAt, new Date(response.body.expires_in * 1000), "f");
__classPrivateFieldSet(this, _YaleDoorman_refreshToken, response.body.refresh_token, "f");
__classPrivateFieldGet(this, _YaleDoorman_configuration, "f").logger.info("Successfully authenticated with Yale API using credentials");
}
/** Fetch a list of devices connected to the Yale hub. */
async getDevices() {
return this.httpRequest({ endpoint: "getDevices" });
}
/** Convenience method to filter the set of Yale devices down the only doors. Only returns the metadata required for changing door states. */
async getDoors() {
const devices = await this.httpRequest({ endpoint: "getDevices" });
const doors = devices.data
.filter(device => device.type === "device_type.door_lock")
.map(({ address, area, no, name, status_open }) => ({
name,
zone: no,
area,
address,
state: status_open === null || status_open === void 0 ? void 0 : status_open[0]
}));
return doors;
}
/** Fetch events from the Yale hub. */
async getEventHistory() {
return this.httpRequest({ endpoint: "getEventHistory" });
}
/** Locks a specific door. This request tends to take ~10 sec.
The Zone, Area and RfAddress arguments may be retrieved using the getDevices() method. */
async lockDoor(zone, area, rfAddress) {
const response = await this.httpRequest({
endpoint: "lockDoor",
method: "POST",
form: {
area,
zone,
device_sid: rfAddress,
device_type: "device_type.door_lock",
request_value: "1"
}
});
if (response.code !== "000") {
throw new Error(`Failed to lock the door. Response code: ${response.code} Body: ${JSON.stringify(response)}`);
}
}
/** Unlocks a specific door. This request tends to take ~10 sec.
The Zone and Area arguments may be retrieved using the getDevices() method. */
async unlockDoor(zone, area, pincode) {
const response = await this.httpRequest({
endpoint: "unlockDoor",
method: "POST",
form: {
area,
zone,
pincode
}
});
if (response.code === "996") {
throw new Error(`Failed to unlock the door. The door pin might be incorrect. Response body: ${JSON.stringify(response)}`);
}
else if (response.code !== "000") {
throw new Error(`Failed to unlock the door. Response code: ${response.code} Body: ${JSON.stringify(response)}`);
}
}
}
exports.YaleDoorman = YaleDoorman;
_YaleDoorman_accessToken = new WeakMap(), _YaleDoorman_refreshToken = new WeakMap(), _YaleDoorman_accessTokenExpiresAt = new WeakMap(), _YaleDoorman_configuration = new WeakMap(), _YaleDoorman_email = new WeakMap(), _YaleDoorman_password = new WeakMap();