UNPKG

xcoobee-sdk

Version:

The XcooBee SDK is a facility to abstract lower level calls and implement standard behaviors. The XcooBee team is providing this to improve the speed of implementation and show the best practices while interacting with XcooBee.

456 lines (400 loc) 16.8 kB
const createHmac = require('crypto-js/hmac-sha1'); const EventsApi = require('../api/EventsApi'); const EventSubscriptionsApi = require('../api/EventSubscriptionsApi'); const XcooBeeError = require('../core/XcooBeeError'); const { decryptWithEncryptedPrivateKey } = require('../core/EncryptionUtils'); const ErrorResponse = require('./ErrorResponse'); const SdkUtils = require('./SdkUtils'); const SuccessResponse = require('./SuccessResponse'); /** * The System SDK service. * Instances are not created directly. An {@link Sdk} instance will have a * reference to a `System` SDK instance through the {@link Sdk#system system} * property. * * ```js * const XcooBee = require('xcoobee-sdk'); * * const sdk = new XcooBee.Sdk(...); * sdk.system.ping(...).then(...); * ``` * * @param {Config} config * @param {ApiAccessTokenCache} apiAccessTokenCache * @param {UsersCache} usersCache */ class System { /* eslint-disable-next-line valid-jsdoc */ /** * Constructs a `System` SDK service instance. */ constructor(config, apiAccessTokenCache, usersCache) { this._ = { apiAccessTokenCache, config: config || null, usersCache, }; } /** * @protected * @param {Config} config */ set config(config) { this._.config = config; } /** * @protected */ _assertValidState() { if (!this._.config) { throw TypeError('Illegal State: Default config has not been set yet.'); } } /** * Adds event subscriptions * * @async * @param {EventSubscription[]} eventSubscriptions - List of subscriptions to create. * @param {Config} [config] - The configuration to use instead of the default. * * @returns {Promise<SuccessResponse | ErrorResponse>} * @property {number} code - The response status code. * @property {Error} [error] - The response error if status is not successful. * @property {string} [error.message] - The error message. * @property {string} request_id - The ID of the request generated by the XcooBee * system. * @property {Object} [result] - The result of the response if status is successful. * @property {EventSubscription[]} result.data - A page of the newly added event * subscriptions. * * @throws {XcooBeeError} */ async addEventSubscriptions(eventSubscriptions, config = null) { this._assertValidState(); const apiCfg = SdkUtils.resolveApiCfg(config, this._.config); const { apiKey, apiSecret, apiUrlRoot } = apiCfg; try { const apiAccessToken = await this._.apiAccessTokenCache.get(apiUrlRoot, apiKey, apiSecret); const eventSubscriptionsPage = await EventSubscriptionsApi.addEventSubscriptions( apiUrlRoot, apiAccessToken, eventSubscriptions ); const response = new SuccessResponse(eventSubscriptionsPage); return response; } catch (err) { throw new ErrorResponse(400, err); } } /** * Deletes event subscriptions for passed topics and channels. * * @async * @param {EventSubscription[]} eventSubscriptions - List of subscriptions to delete. * @param {Config} [config] - The configuration to use instead of the default. * * @returns {Promise<SuccessResponse | ErrorResponse>} * @property {number} code - The response status code. * @property {Error} [error] - The response error if status is not successful. * @property {string} [error.message] - The error message. * @property {string} request_id - The ID of the request generated by the XcooBee * system. * @property {Object} [result] - The result of the response if status is successful. * @property {number} result.deleted_number - The number of event subscriptions * deleted. * * @throws {XcooBeeError} */ async deleteEventSubscriptions(eventSubscriptions, config = null) { this._assertValidState(); const apiCfg = SdkUtils.resolveApiCfg(config, this._.config); const { apiKey, apiSecret, apiUrlRoot } = apiCfg; try { const apiAccessToken = await this._.apiAccessTokenCache.get(apiUrlRoot, apiKey, apiSecret); const result = await EventSubscriptionsApi.deleteEventSubscriptions( apiUrlRoot, apiAccessToken, eventSubscriptions ); const response = new SuccessResponse(result); return response; } catch (err) { throw new ErrorResponse(400, err); } } /** * Fetches a page of the user's events. * * @async * @param {Config} [config] - If specified, the configuration to use instead of the * default. * * @returns {Promise<PagingResponse, ErrorResponse>} * @property {number} code - The response status code. * @property {Error} [error] - The response error if status is not successful. * @property {string} [error.message] - The error message. * @property {string} request_id - The ID of the request generated by the XcooBee * system. * @property {Object} [result] - The result of the response if status is successful. * @property {Events[]} result.data - A page of events. * @property {Object} [result.page_info] - The page information. * @property {boolean} result.page_info.has_next_page - Flag indicating whether there * is another page of data to may be fetched. * @property {string} result.page_info.end_cursor - The end cursor. * * @throws {XcooBeeError} */ async getEvents(config = null) { this._assertValidState(); const fetchPage = async (sdkCfg, params) => { const { apiKey, apiSecret, apiUrlRoot, pgpPassword, pgpSecret, } = sdkCfg; const { after, limit } = params; const apiAccessToken = await this._.apiAccessTokenCache.get(apiUrlRoot, apiKey, apiSecret); const user = await this._.usersCache.get(apiUrlRoot, apiKey, apiSecret); const userCursor = user.cursor; const eventsPage = await EventsApi.getEvents(apiUrlRoot, apiAccessToken, userCursor, pgpSecret, pgpPassword, after, limit); return eventsPage; }; const sdkCfg = SdkUtils.resolveSdkCfg(config, this._.config); return SdkUtils.startPaging(fetchPage, sdkCfg, {}); } /** * Triggers test event to webhook of campaign. * * @async * @param {string} topic - Event topic to trigger. * @param {Config} [config] - If specified, the configuration to use instead of the * default. * * @returns {Promise<SuccessResponse | ErrorResponse>} * @property {number} code - The response status code. * @property {Error} [error] - The response error if status is not successful. * @property {string} [error.message] - The error message. * @property {string} request_id - The ID of the request generated by the XcooBee * system. * @property {Object} [result] - The result of the response if status is successful. * @property {Event} result - Event data. */ async triggerEvent(topic, config = null) { this._assertValidState(); const resolvedCampaignId = SdkUtils.resolveCampaignId(null, config, this._.config); const apiCfg = SdkUtils.resolveApiCfg(config, this._.config); const { apiKey, apiSecret, apiUrlRoot } = apiCfg; try { const apiAccessToken = await this._.apiAccessTokenCache.get(apiUrlRoot, apiKey, apiSecret); const result = await EventsApi.triggerEvent(apiUrlRoot, apiAccessToken, resolvedCampaignId, topic); return new SuccessResponse(result); } catch (err) { throw new ErrorResponse(400, err); } } /** * Lists the current event subscriptions for the specified campaign. * * @async * @param {string} [referenceId] - ID of related enyity (i.e. campaignId). * @param {string} [referenceType] - Type of related entity (i.e. campaign, funding_panel). * @param {Config} [config] - The configuration to use instead of the default. * * @returns {Promise<PagingResponse, ErrorResponse>} * @property {number} code - The response status code. * @property {Error} [error] - The response error if status is not successful. * @property {string} [error.message] - The error message. * @property {string} request_id - The ID of the request generated by the XcooBee * system. * @property {Object} [result] - The result of the response if status is successful. * @property {EventSubscription[]} result.data - A page of event subscriptions for the * * @throws {XcooBeeError} */ async listEventSubscriptions(referenceId = null, referenceType = null, config = null) { this._assertValidState(); const apiCfg = SdkUtils.resolveApiCfg(config, this._.config); const { apiKey, apiSecret, apiUrlRoot } = apiCfg; try { const apiAccessToken = await this._.apiAccessTokenCache.get(apiUrlRoot, apiKey, apiSecret); const result = await EventSubscriptionsApi.listEventSubscriptions(apiUrlRoot, apiAccessToken, referenceId, referenceType); return new SuccessResponse(result); } catch (err) { throw new ErrorResponse(400, err); } } /** * Lists available event subscriptions for the specified reference. * * @async * @param {string} [referenceId] - ID of related enyity (i.e. campaignId). * @param {string} [referenceType] - Type of related entity (i.e. campaign, funding_panel). * @param {Config} [config] - The configuration to use instead of the default. * * @returns {Promise<SuccessResponse, ErrorResponse>} * @property {number} code - The response status code. * @property {Error} [error] - The response error if status is not successful. * @property {string} [error.message] - The error message. * @property {string} request_id - The ID of the request generated by the XcooBee * system. * @property {Object} [result] - The result of the response if status is successful. * @property {Object[]} result - A list of available topic to subscribe * * @throws {XcooBeeError} */ async getAvailableSubscriptions(referenceId = null, referenceType = null, config = null) { this._assertValidState(); const apiCfg = SdkUtils.resolveApiCfg(config, this._.config); const { apiKey, apiSecret, apiUrlRoot } = apiCfg; try { const apiAccessToken = await this._.apiAccessTokenCache.get(apiUrlRoot, apiKey, apiSecret); const result = await EventSubscriptionsApi.getAvailableSubscriptions(apiUrlRoot, apiAccessToken, referenceId, referenceType); return new SuccessResponse(result); } catch (err) { throw new ErrorResponse(400, err); } } /** * Unsuspends event subscription by topic and channel. * * @async * @param {string} topic - Topic of subscription. * @param {string} channel - Channel of subscription. * @param {Config} [config] - The configuration to use instead of the default. * * @returns {Promise<SuccessResponse, ErrorResponse>} * @property {number} code - The response status code. * @property {Error} [error] - The response error if status is not successful. * @property {string} [error.message] - The error message. * @property {string} request_id - The ID of the request generated by the XcooBee * system. * @property {Object} [result] - The result of the response if status is successful. * @property {EventSubscription} result - An updated event subscription * * @throws {XcooBeeError} */ async unsuspendEventSubscription(topic, channel, config = null) { this._assertValidState(); const apiCfg = SdkUtils.resolveApiCfg(config, this._.config); const { apiKey, apiSecret, apiUrlRoot } = apiCfg; try { const apiAccessToken = await this._.apiAccessTokenCache.get(apiUrlRoot, apiKey, apiSecret); const result = await EventSubscriptionsApi.unsuspendEventSubscription(apiUrlRoot, apiAccessToken, topic, channel); return new SuccessResponse(result); } catch (err) { throw new ErrorResponse(400, err); } } /** * Delete event by provided ids. * * @async * @param {number[]} eventIds - Ids of events to delete. * @param {Config} [config] - The configuration to use instead of the default. * * @returns {Promise<SuccessResponse, ErrorResponse>} * @property {number} code - The response status code. * @property {Error} [error] - The response error if status is not successful. * @property {string} [error.message] - The error message. * @property {string} request_id - The ID of the request generated by the XcooBee * system. * @property {Object} [result] - The result of the response if status is successful. * @property {Event} result - Deleted events * * @throws {XcooBeeError} */ async deleteEvents(eventIds, config = null) { this._assertValidState(); const apiCfg = SdkUtils.resolveApiCfg(config, this._.config); const { apiKey, apiSecret, apiUrlRoot } = apiCfg; try { const apiAccessToken = await this._.apiAccessTokenCache.get(apiUrlRoot, apiKey, apiSecret); const result = await EventsApi.deleteEvents(apiUrlRoot, apiAccessToken, eventIds); return new SuccessResponse(result); } catch (err) { throw new ErrorResponse(400, err); } } /** * Can be called to check whether the current configuration will connect to the XcooBee system. * * @async * @param {Config} [config] - The configuration to use instead of the default. * * @returns {Promise<SuccessResponse | ErrorResponse>} * @property {number} code - The response status code. * @property {Error} [error] - The response error if status is not successful. * @property {string} [error.message] - The error message. * @property {string} request_id - The ID of the request generated by the XcooBee * system. * @property {true} result - Flag indicating that the ping was successful. * * @throws {XcooBeeError} */ async ping(config = null) { this._assertValidState(); const apiCfg = SdkUtils.resolveApiCfg(config, this._.config); const { apiKey, apiSecret, apiUrlRoot } = apiCfg; try { const user = await this._.usersCache.get(apiUrlRoot, apiKey, apiSecret); if (user) { return new SuccessResponse(true); } throw new XcooBeeError('User not found.'); } catch (err) { throw new ErrorResponse(400, err); } } /** * Method for hadling either incoming events or events listed by `getEvents` method * * @async * @param {function[]} handlers - list of handlers. i.e. { myHandler: (payload) => { ... } } * @param {Object[]} [events] - events listed by `getEvents` method. Should be empty array if payload and headers procided * @param {string} [payload] - decrypted body of webhook event. Should be `null` if list of events provided * @param {Object} [headers] - headers of webhook event. Should be `null` if list of events provided * * @returns {Promise} * * @throws {TypeError} * @throws {XcooBeeError} */ async handleEvents(handlers, events = [], payload = null, headers = null) { if (!Object.keys(handlers).length) { throw new TypeError('At least one handler should be passed'); } if (payload !== null && headers !== null) { const hmac = headers['XBEE-SIGNATURE'] || headers['xbee-signature'] || null; const handler = headers['XBEE-HANDLER'] || headers['xbee-handler'] || null; const sdkCfg = SdkUtils.resolveSdkCfg(null, this._.config); const { apiKey, apiSecret, apiUrlRoot, pgpPassword, pgpSecret, } = sdkCfg; const user = await this._.usersCache.get(apiUrlRoot, apiKey, apiSecret); const calculatedHmac = createHmac(payload, user.xcoobee_id).toString(); if (calculatedHmac !== hmac) { throw new XcooBeeError('Invalid signature'); } const event = { payload, handler, }; const payloadJson = await decryptWithEncryptedPrivateKey( payload, pgpSecret, pgpPassword ); event.payload = payloadJson; events.push(event); } return Promise.all(events.map((event) => { if (!event.handler || typeof handlers[event.handler] !== 'function') { throw new XcooBeeError(`Handler '${event.handler}' is not defined`); } return handlers[event.handler](event.payload); })); } } module.exports = System;