workspace-integrations
Version:
Webex Workspace Integrations NodeJS SDK
176 lines (175 loc) • 6.96 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const util_1 = require("./util");
const http_1 = require("./http");
const devices_1 = require("./apis/devices");
const workspaces_1 = require("./apis/workspaces");
const xapi_1 = require("./apis/xapi");
const logger_1 = require("./logger");
const jwt_1 = require("./jwt");
function validateConfig(config) {
if (!config.clientId || !config.clientSecret || !config.activationCode) {
throw new TypeError('Missing clientId, clientSecret or activationCode in config');
}
const jwt = config.activationCode;
if (!jwt.oauthUrl || !jwt.webexapisBaseUrl) {
throw new TypeError('activationCode does not contain the expected data. Please decode it and provide the required attributes as a JSON object.');
}
const known = ['clientId', 'clientSecret', 'activationCode', 'notifications', 'logLevel', 'webhook', 'actionsUrl'];
Object.keys(config).forEach((key) => {
if (!known.includes(key)) {
logger_1.default.error('Unknown config: ' + key);
}
});
}
class IntegrationImpl {
constructor(appConfig) {
this.actionHandler = null;
this.errorHandler = null;
this.appConfig = appConfig;
const { activationCode, accessToken, pollUrl } = appConfig;
this.http = new http_1.default(activationCode.webexapisBaseUrl, accessToken);
this.devices = new devices_1.default(this.http);
this.workspaces = new workspaces_1.default(this.http);
this.xapi = new xapi_1.default(this.http);
this.appUrl = activationCode.appUrl;
this.tokenExpiryTime = appConfig.tokenExpiryTime;
const timeToExpiry = (new Date(this.tokenExpiryTime).getTime() - Date.now()) / 1000;
const timeToRefresh = timeToExpiry - 60 * 15;
logger_1.default.verbose(`Fetching new token on ${new Date(Date.now() + timeToRefresh * 1000)}`);
setTimeout(() => this.refreshToken(), timeToRefresh * 1000);
if (pollUrl) {
this.pollData(pollUrl);
}
}
onError(handler) {
this.errorHandler = handler;
}
onAction(handler) {
this.actionHandler = handler;
}
async getAppInfo() {
return this.ping();
}
webexApi(partialUrl, method, body, contentType) {
return this.http.webexApi(partialUrl, method, body, contentType);
}
ping() {
return this.http.ping(this.appUrl);
}
decodeJwt(jwt) {
return (0, jwt_1.decodeAndVerify)(jwt);
}
serialize() {
return this.appConfig;
}
static async deserialize(obj) {
const { tokenExpiryTime, activationCode } = obj;
const expired = new Date(tokenExpiryTime).getTime() <= Date.now();
if (expired) {
const { access_token, expires_in } = await http_1.default.createAccessToken({
clientId: obj.appId,
clientSecret: obj.appSecret,
oauthUrl: activationCode.oauthUrl,
refreshToken: activationCode.refreshToken,
});
obj.accessToken = access_token;
obj.tokenExpiryTime = new Date(Date.now() + (expires_in * 1000)).toISOString();
}
return new IntegrationImpl(obj);
}
async pollData(pollUrl) {
if (!pollUrl)
return;
while (true) {
let data;
try {
data = await this.http.pollDeviceData(pollUrl);
}
catch (e) {
console.log('Error polling', e);
const WaitAfterError = 5000;
await (0, util_1.sleep)(WaitAfterError);
}
if (data) {
this.processNotifications(data === null || data === void 0 ? void 0 : data.messages);
}
}
}
processNotifications(notifications) {
logger_1.default.verbose(`Got ${notifications.length} notifications`);
notifications.forEach((n) => {
if (n.type === 'action') {
this.decodeAndNotifyAction(n);
}
else {
this.xapi.processNotification(n);
}
});
}
async decodeAndNotifyAction(action) {
if (!this.actionHandler)
return;
const { jwt } = action;
const res = await (0, jwt_1.decodeAndVerify)(jwt);
if (res) {
this.actionHandler(res);
}
else {
logger_1.default.error('Not able to verify action message!');
}
}
static async connect(options) {
var _a;
validateConfig(options);
const { clientId, clientSecret, notifications, webhook, actionsUrl, activationCode } = options;
const { oauthUrl, refreshToken, appUrl } = activationCode;
const tokenData = await http_1.default.createAccessToken({ clientId, clientSecret, oauthUrl, refreshToken });
logger_1.default.info('Got initial access token');
const appInfo = await http_1.default.initIntegration({
accessToken: tokenData.access_token,
appUrl,
notifications,
webhook,
actionsUrl,
});
logger_1.default.info('Successfully initiated integration');
const { access_token, expires_in } = tokenData;
const tokenExpiryTime = new Date(Date.now() + (expires_in * 1000)).toISOString();
const config = {
appId: clientId,
appSecret: clientSecret,
accessToken: access_token,
activationCode, tokenExpiryTime,
};
if (notifications === 'longpolling') {
config.pollUrl = (_a = appInfo.queue) === null || _a === void 0 ? void 0 : _a.pollUrl;
}
const integration = new IntegrationImpl(config);
return integration;
}
async refreshToken() {
const { oauthUrl, refreshToken } = this.appConfig.activationCode;
try {
const { access_token, expires_in } = await http_1.default.createAccessToken({
clientId: this.appConfig.appId,
clientSecret: this.appConfig.appSecret,
oauthUrl,
refreshToken,
});
this.http.setAccessToken(access_token);
const nextTime = expires_in - 60 * 15;
this.tokenExpiryTime = new Date(Date.now() + (expires_in * 1000)).toISOString();
logger_1.default.info('Fetched new access token');
logger_1.default.verbose(`Fetching new token on ${new Date(Date.now() + nextTime * 1000)}`);
setTimeout(() => this.refreshToken(), nextTime * 1000);
}
catch (e) {
logger_1.default.error('Unable to refresh token');
if (this.errorHandler) {
this.errorHandler('Not able to refresh token. ' + (e instanceof Error && e.message));
}
}
}
}
exports.default = IntegrationImpl;