UNPKG

node-sparky

Version:
1,790 lines (1,653 loc) 48.5 kB
/** * @file Defines SPARK Class * @author Nicholas Marus <nmarus@gmail.com> * @license LGPL-3.0 */ 'use strict'; var EventEmitter = require('events').EventEmitter; var Bottleneck = require('bottleneck'); var crypto = require('crypto'); var debug = require('debug')('spark'); var mime = require('mime-types'); var util = require('util'); var when = require('when'); var req = require('request'); var fs = require('fs'); var _ = require('lodash'); var validator = require('./validator'); var u = require('./utils'); /** * Creates a Spark API instance that is then attached to a Spark Account. * * @constructor * @param {Object} options - Configuration object containing Spark settings * @throw {Error} Throws on spark token missing in options object. */ function Spark(options) { EventEmitter.call(this); this.id = u.genUUID64(); /** * Options Object * @memberof Spark * @namespace options * @instance * @property {string} token - Spark Token. * @property {string} webhookUrl - URL that is used for SPark API to send callbacks. * @property {string} [webhookSecret] - If specified, creates webhooks using this secret. The incoming webhook must still be authenticated. See Spark.webhookAuth(). * @property {number} [maxPageItems=50] - Max results that the paginator uses. * @property {number} [maxConcurrent=3] - Max concurrent sessions to the Spark API * @property {number} [minTime=600] - Min time between consecutive request starts. * @property {number} [requeueMinTime=minTime*10] - Min time between consecutive request starts of requests that have been re-queued. * @property {number} [requeueMaxRetry=3] - Msx number of atteempts to make for failed request. * @property {array} [requeueCodes=[429,500,503]] - Array of http result codes that should be retried. * @property {number} [requestTimeout=20000] - Timeout for an individual request recieving a response. * @property {number} [queueSize=10000] - Size of the buffer that holds outbound requests. * @property {number} [requeueSize=10000] - Size of the buffer that holds outbound re-queue requests. */ this.options = options; // config defaults this.token = this.options.token || process.env.TOKEN || null; this.webhookSecret = this.options.webhookSecret || process.env.WEBHOOK_SECRET || null; this.webhookUrl = this.options.webhookUrl || process.env.WEBHOOK_URL || null; this.maxPageItems = this.options.maxPageItems || 50; this.maxConcurrent = this.options.maxConcurrent || 3; this.minTime = this.options.minTime || 600; this.requeueMinTime = this.options.requeueMinTime || this.minTime * 10; this.requeueMaxRetry = this.options.requeueMaxRetry || 3; this.requeueCodes = this.options.requeueCodes || [ 429, 500, 503 ]; this.requestTimeout = this.options.requestTimeout || 20000; this.queueSize = this.options.queueSize || 10000; this.requeueSize = this.options.requeueSize || 10000; // api url this.apiUrl = process.env.API_URL || this.options.apiUrl || 'https://api.ciscospark.com/v1/'; // queue for API calls this.queue = new Bottleneck(this.maxConcurrent, this.minTime, this.queueSize); this.queue.on('dropped', dropped => { /** * Spark Queue Drop Event. * * @event drop * @type {object} * @property {options} request - API Request * @property {string} id - Spark UUID */ this.emit('dropped', dropped, this.id); }); // queue for API calls that fail this.requeue = new Bottleneck(this.maxConcurrent, this.requeueMinTime, this.requeueSize); this.requeue.on('dropped', dropped => { this.emit('dropped', dropped, this.id); }); // handle internal events this.on('error', err => { if(err) { debug(err.stack); } }); } util.inherits(Spark, EventEmitter); /** * Format Spark API Call, make http request, and validate response. * * @private * @param {String} method * @param {String} resource * @param {id} id * @param {query} query * @returns {Promise.<Response>} */ Spark.prototype.request = function(method, resource, id, query, maxResults) { if(!this.token) { return when.reject(new Error('token not defined')); } // parse args var args = Array.prototype.slice.call(arguments); method = args.shift(); resource = args.shift(); var url = this.apiUrl + resource; // add id to url if specified if(args.length > 0 && typeof args[0] === 'string') { url = url + '/' + args.shift(); } // get query if specified if(args.length > 0 && typeof args[0] === 'object') { query = args.shift(); } // get limit for items retrieved and convert string values to number if(args.length > 0 && typeof args[0] !== 'undefined') { maxResults = parseInt(args[0],10) ? parseInt(args.shift(),10) : null; } // headers var headers = { 'Authorization': 'Bearer ' + this.token, 'Content-Type': 'application/json' }; // default request options var requestOptions = { url: url, headers: headers, method: method, timeout: this.requestTimeout, gzip: true, time: true, json: true }; // update options for contents resource if(resource === 'contents') { requestOptions.encoding = 'binary'; requestOptions.json = false; } // update options for query if(query) { if(method === 'post' || method === 'put') { // do body query requestOptions.body = query; } else { // do query string requestOptions.qs = query; } } // perform HTTP request and structure response function request(options) { return when.promise((resolve, reject) => { req(options, (err, res, body) => { if(err) { reject(err); } else { var response = { headers: res.headers || {}, statusCode: res.statusCode || 0, code: res.statusCode || 0, body: body || res.body || {}, ok: (res.statusCode >= 200 && res.statusCode < 300) }; resolve(response); } }); }); } var errorCount = 0; var requestQueue = options => { /** * Spark request event. * * @event request * @type {object} * @property {options} request - API Request * @property {string} id - Spark UUID */ this.emit('request', options, this.id); return when(this.queue.schedule(request, options)) .then(response => { /** * Spark response event. * * @event reponse * @type {object} * @property {options} response - Response * @property {string} id - Spark UUID */ this.emit('response', response, this.id); if(_.includes(this.requeueCodes, response.statusCode)) { return when(true).delay(this.requeueMinTime).then(() => requestRequeue(options)); } else { return when(response); } }); }; var requestRequeue = options => { errorCount++; /** * Spark retry event. * * @event retry * @type {object} * @property {options} request - API Request * @property {string} id - Spark UUID */ this.emit('retry', options, this.id); debug('Retry attempt #%s for (%s) %s', errorCount, options.method, options.url); return when(this.requeue.schedule(request, options)) .then(response => { this.emit('response', response, this.id); if(errorCount >= this.requeueMaxRetry) { return when(response); } if(_.includes(this.requeueCodes, response.statusCode)) { return when(true).delay(this.requeueMinTime).then(() => requestRequeue(options)); } // return response else { return when(response); } }); }; // recursive pagination var requestPaginate = (items, link) => { // limit max results if(typeof maxResults === 'number' && items.length > maxResults) { return when(items); } var pageRequestOptions = _.clone(requestOptions); pageRequestOptions.url = link.match(/(http.*)>/)[1]; // request next page return requestQueue(pageRequestOptions) .then(response => { // response 2XX if(response.ok) { // if more items returned... if(response.body && response.body.items) { // concat next page items with current items items = _.concat(items, response.body.items); // if pagination link found in next page response, get next page if(response.headers['link']) { return requestPaginate(items, response.headers['link']); } // no more pages, return items else { return when(items); } } } // response not ok, return items retrieved else { return when(items); } }); }; return requestQueue(requestOptions) .then(response => { // response 2XX if(response.ok) { // if pagination link found in response if(response.body && response.body.items && response.headers['link']) { return requestPaginate(response.body.items, response.headers['link']) .then(allItems => { if(typeof maxResults === 'number' && allItems.length > 0) { allItems = allItems.slice(0, maxResults); } response.body.items = allItems; return when(response); }); } // return response else { return when(response); } } // everything else else { var errorMessage = util.format('received response code %s for (%s) %s body:%j qs:%j', response.statusCode, requestOptions.method, requestOptions.url, requestOptions.body || {}, requestOptions.qs || {}); debug(errorMessage); return when.reject(new Error(errorMessage)); } }); }; /** * Parse Spark API response to object. * * @private * @param {Response} response - Spark API response * @returns {Promise.<Object>} */ Spark.prototype.toObject = function(response) { // from array if(response && response.body && response.body.items instanceof Array) { if(response.body.items.length > 0) { return when(response.body.items[0]); } else { return when({}); } } // from object else if(response && !response.body.items && response.body.id) { return when(response.body); } else { return when({}); } }; /** * Parse Spark API response to array. * * @private * @param {Response} response - Spark API response * @returns {Array} */ Spark.prototype.toArray = function(response) { // from array if(response && response.body && response.body.items instanceof Array) { if(response.body.items.length > 0) { return when(response.body.items); } else { return when([]); } } // from object else if(response && !response.body.items && response.body.id) { return when([response.body]); } else { return when([]); } }; /** * Return all Spark Rooms registered to account. * * @function * @param {Integer} [max] - Number of records to return * @returns {Promise.<Array>} * * @example * spark.roomsGet(10) * .then(function(rooms) { * // process rooms as array * rooms.forEach(function(room) { * console.log(room.title); * }); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.roomsGet = function(maxResults) { return this.request('get', 'rooms', { max: this.maxPageItems }, maxResults) .then(res => this.toArray(res)); }; /** * Return all Spark 1:1 Rooms. * * @function * @param {Integer} [max] - Number of records to return * @returns {Promise.<Array>} * * @example * spark.roomsDirect(10) * .then(function(rooms) { * // process rooms as array * rooms.forEach(function(room) { * console.log(room.title); * }); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.roomsDirect = function(maxResults) { return this.request('get', 'rooms', { type: 'direct', max: this.maxPageItems }, maxResults) .then(res => this.toArray(res)); }; /** * Return all Spark Group Rooms. * * @function * @param {Integer} [max] - Number of records to return * @returns {Promise.<Array>} * * @example * spark.roomsGroup(10) * .then(function(rooms) { * // process rooms as array * rooms.forEach(function(room) { * console.log(room.title); * }); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.roomsGroup = function(maxResults) { return this.request('get', 'rooms', { type: 'group', max: this.maxPageItems }, maxResults) .then(res => this.toArray(res)); }; /** * Return all Spark Rooms for a particular Team ID. * * @function * @param {String} teamId - The Spark Team ID * @param {Integer} [max] - Number of records to return * @returns {Promise.<Array>} * * @example * spark.roomsByTeam('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u', 10) * .then(function(rooms) { * // process rooms as array * rooms.forEach(function(room) { * console.log(room.title); * }); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.roomsByTeam = function(teamId, maxResults) { return this.request('get', 'rooms', { teamId: teamId, max: this.maxPageItems }, maxResults) .then(res => this.toArray(res)); }; /** * Return details of Spark Room by ID. * * @function * @param {String} roomId - Spark Room ID * @returns {Promise.<Room>} * * @example * spark.roomGet('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u') * .then(function(room) { * console.log(room.title); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.roomGet = function(roomId) { /** * Room Object * * @namespace Room * @property {string} id - Room ID * @property {string} title - Room Title * @property {string} type - Room Type * @property {boolean} isLocked - Room Moderated/Locked * @property {string} teamId - Team ID * @property {date} lastActivity - Last Activity in Room * @property {date} created - Room Created */ return this.request('get', 'rooms', roomId) .then(res => this.toObject(res)); }; /** * Add new Spark Room. * * @function * @param {String} title - Title for new Room * @returns {Promise.<Room>} * * @example * spark.roomAdd('myroom') * .then(function(room) { * console.log(room.title); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.roomAdd = function(title) { return this.request('post', 'rooms', { title: title }) .then(res => this.toObject(res)); }; /** * Rename Spark Room. * * @function * @param {String} roomId - Spark Room ID * @param {String} title - Title for new Room * @returns {Promise.<Room>} * * @example * spark.roomRename('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u', 'myroom2') * .then(function(room) { * console.log(room.title); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.roomRename = function(roomId, title) { return this.request('put', 'rooms', roomId, { title: title }) .then(res => this.toObject(res)); }; /** * Remove Spark Room by ID. * * @function * @param {String} roomId - Spark Room ID * @returns {Promise} * * @example * spark.roomRemove('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u') * .then(function() { * console.log('Room removed.'); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.roomRemove = function(roomId) { return this.request('delete', 'rooms', roomId) .then(res => when(true)); }; /** * Search Spark for People by display name. * * @function * @param {String} displayName - Search String to find as display name * @param {Integer} [max] - Number of records to return * @returns {Promise<Array>} * * @example * spark.peopleSearch('John', 10) * .then(function(people) { * // process people as array * people.forEach(function(person) { * console.log(person.displayName); * }); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.peopleSearch = function(displayName, maxResults) { return this.request('get', 'people', { displayName: displayName, max: this.maxPageItems }, maxResults) .then(res => this.toArray(res)); }; /** * Return details of Spark User by ID. * * @function * @param {String} personId - Spark Person ID * @returns {Promise.<Person>} * * @example * spark.personGet('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u') * .then(function(person) { * console.log(person.displayName); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.personGet = function(personId) { /** * Person Object * * @namespace Person * @property {string} id - Person ID * @property {array} emails - Emails * @property {string} displayName - Display Name * @property {string} avatar - Avatar URL * @property {date} created - Date created * @property {string} email - Email * @property {string} username - Username * @property {string} domain - Domain name */ return this.request('get', 'people', personId) .then(res => this.toObject(res)); }; /** * Return details of Spark User that has authenticated. * * @function * @returns {Promise.<Person>} * * @example * spark.personMe() * .then(function(person) { * console.log(person.displayName); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.personMe = function() { return this.request('get', 'people', 'me') .then(res => this.toObject(res)); }; /** * Return details of Spark User by Email. * * @function * @param {String} email - Email address of Spark User * @returns {Promise.<Person>} * * @example * spark.personByEmail('aperson@company.com') * .then(function(person) { * console.log(person.displayName); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.personByEmail = function(email) { if(typeof email === 'string' && validator.isEmail(email)) { return this.request('get', 'people', { email: email }) .then(res => this.toObject(res)); } else { return when.reject(new Error('not a valid email')); } }; /** * @description Return details of an attachment action by ID. * * @function * @param {String} attachmentActionId AttachmentAction ID * @returns {Promise.<AttachmentAction>} AttachmentAction object * * @example * spark.attachmentActionGet('Tm90aGluZyB0byBzZWUgaGVy') * .then(attachmentAction => console.log(attachmentAction)) * .catch(err => console.error(err)); */ Spark.prototype.attachmentActionGet = function(attachmentActionId) { /** * @description attachmentActionsObject * * @property {String} id - attachmentAction ID * @property {String} roomId - Room ID * @property {String} personId - Person ID * @property {String} type - action tytpe * @property {String} messageId - Message ID * @property {Object} inputs - Card Inputs * @property {String} created - Date Message created (ISO 8601) */ if (typeof attachmentActionId !== 'string') { return when.reject(new Error('invalid arguments')); } return this.request('get', 'attachment/actions', attachmentActionId) .then(res => this.toObject(res)); }; /** * @description Create an Attachment Action. * * @memberof Spark * @function * @param {Object.<AttachmentAction>} attachmentAction Attachment Action to create * @returns {Promise.<AttachmentAction>} AttachmentAction object * * @example * const newAttachmentAction = { * type: 'submit', * messageId: 'Tm90aGluZyB0byBzZWUgaGVy', * "inputs": { * "Name": "John Andersen", * "Url": "https://example.com", * "Email": "john.andersen@example.com", * "Tel": "+1 408 526 7209" * } * } * * spark.attachmentActionCreate(newAttachmentAction) * .then(attachmentAction => console.log(attachmentAction.id)) * .catch(err => console.error(err)); */ Spark.prototype.attachmentActionCreate = function(attachmentAction) { if ((typeof attachmentAction.type !== 'string') || (typeof attachmentAction.inputs !== 'object')) { return when.reject(new Error('invalid arguments')); } return Spark.request('post', 'attachment/actions', attachmentAction); }; /** * Return messages in a Spark Room. * * @function * @param {String} roomId - Spark Room ID * @param {Integer} [max] - Number of records to return * @returns {Promise.<Array>} * * @example * spark.messagesGet('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u', 100) * .then(function(messages) { * // process messages as array * messages.forEach(function(message) { * console.log(message.text); * }); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.messagesGet = function(roomId, maxResults) { return this.request('get', 'messages', { roomId: roomId, max: this.maxPageItems }, maxResults) .then(res => this.toArray(res)); }; /** * Return details of Spark Message by ID. * * @function * @param {String} messageId - Spark Message ID * @returns {Promise.<Message>} * * @example * spark.messageGet('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u', 100) * .then(function(message) { * console.log(message.text); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.messageGet = function(messageId) { /** * Message Object * * @namespace Message * @property {string} id - Message ID * @property {string} personId - Person ID * @property {string} personEmail - Person Email * @property {string} roomId - Room ID * @property {string} text - Message text * @property {array} files - Array of File objects * @property {date} created - Date Message created */ return this.request('get', 'messages', messageId) .then(res => this.toObject(res)); }; /** * Sends 1:1 Spark message to a person. * * @function * @param {String} email - Email address of Spark User * @param {Object} message - Message Object * @returns {Promise.<Message>} * * @example * spark.messageSendPerson('aperson@company.com', { * text: 'Hello!', * files: ['http://company.com/myfile.doc'] * }) * .then(function(message) { * console.log('Message sent: %s', message.txt) ; * }) * .catch(function(err){ * console.log(err); * }); */ Spark.prototype.messageSendPerson = function(email, message) { if(typeof email === 'string' && validator.isEmail(email)) { message.toPersonEmail = email; return this.request('post', 'messages', message) .then(res => this.toObject(res)); } else { return when.reject(new Error('not a valid email')); } }; /** * Sends Spark message to a room. * * @function * @param {String} roomId - Spark Room ID * @param {Object} message - Message Object * @returns {Promise.<Message>} * * @example * spark.messageSendRoom('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u', { * text: 'Hello!', * files: ['http://company.com/myfile.doc'] * }) * .then(function(message) { * console.log('Message sent: %s', message.txt); * }) * .catch(function(err){ * console.log(err); * }); */ Spark.prototype.messageSendRoom = function(roomId, message) { message.roomId = roomId; return this.request('post', 'messages', message) .then(res => this.toObject(res)); }; /** * Streams Spark message to a room. * * @function * @param {String} roomId - Spark Room ID * @param {Object} message - Message Object * @returns {Promise.<Message>} * * @example * var roomId = 'Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u'; * var text = 'Hello'; * var filename = 'test.png'; * var stream = fs.createReadStream(filename); * var message = { 'text': text, 'filename': filename, 'stream': stream }; * spark.messageStreamRoom(roomId, message) * .then(function(message) { * console.log('Message sent: %s', message.txt); * }) * .catch(function(err){ * console.log(err); * }); */ Spark.prototype.messageStreamRoom = function(roomId, message) { if(typeof roomId !== 'string') { return when.reject(new Error('roomId invalid')); } var requestOptions = { method: 'post', url: this.apiUrl + 'messages', headers: { 'Authorization': 'Bearer ' + this.token }, formData: {'roomId': roomId } }; // if message is object... if(typeof message === 'object') { // if message.text present if(typeof message.text === 'string') requestOptions.formData.text = message.text; // if message.stream found and message.filename present if(typeof message.stream !== 'undefined' && typeof message.filename === 'string') { // regex for matching file extention var re = /\.([0-9a-z]{1,5})$/i; // if file extension found... if(re.test(message.filename) && message.filename.match(re).length > 1) { var ext = message.filename.match()[1]; requestOptions.formData.files = { value: message.stream, options: { filename: message.filename, contentType: mime.lookup(ext) } }; } // else, file extension missing else { return when.reject(new Error('message.filename missing a file extension')); } } // if message.stream found and message.filename NOT present else if(typeof message.stream !== 'undefined' && typeof message.filename === 'undefined') { requestOptions.formData.files = message.stream; } // else, message object invalid else { return when.reject(new Error('missing message object properties')); } } else { return when.reject(new Error('message is not object')); } return when.promise((resolve, reject) => { req(requestOptions, (err, response, body) => { if(err) { reject(err); } else { if(response.statusCode == 200) { resolve(response); } else { reject(util.format('received response code %s for (%s) %s body:%j qs:%j', response.statusCode, requestOptions.method, requestOptions.url, requestOptions.body || {}, requestOptions.qs || {})); } } }); }).then(res => this.toObject(res)); }; /** * Upload a file by path to Spark Room * * @function * @param {String} roomId - Spark Room ID * @param {String} filepath - path to file * @returns {Promise.<Message>} * * @example * var roomId = 'Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u'; * spark.upload(roomId, '/some/local/file.png'); */ Spark.prototype.upload = function(roomId, filepath) { // validate function parameters if(typeof roomId !== 'string' || typeof filepath !== 'string') { return when.reject(new Error('invalid function parmaeters')); } var filename = filepath.replace(/^.*[\\\/]/, ''); var stream = fs.createReadStream(filepath); var message = { 'filename': filename, 'stream': stream }; return this.messageStreamRoom(roomId, message); }; /** * Remove Spark Message by ID. * * @function * @param {String} messageId - Spark Message ID * @returns {Promise} * * @example * spark.messageRemove('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u') * .then(function() { * console.log('Message removed.'); * }) * .catch(function(err){ * console.log(err); * }); */ Spark.prototype.messageRemove = function(messageId) { return this.request('delete', 'messages', messageId) .then(res => when(true)); }; /** * Return details of Spark File by Content ID. * * @function * @param {String} id - Spark Content ID * @returns {Promise.<File>} * * @example * spark.contentGet('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u') * .then(function(file) { * console.log('File name: %s', file.name); * }) * .catch(function(err){ * console.log(err); * }); */ Spark.prototype.contentGet = function(contentId) { return this.request('get', 'contents', contentId) .then(res => { if(res && res.headers && res.headers['content-disposition']) { /** * File Object * * @namespace File * @property {string} id - Spark API Content ID * @property {string} name - File name * @property {string} ext - File extension * @property {string} type - Header [content-type] for file * @property {buffer} binary - File contents as binary * @property {string} base64 - File contents as base64 encoded string */ var file = {}; file.id = contentId; file.name = res.headers['content-disposition'].match(/"(.*)"/)[1]; file.ext = file.name.split('.').pop(); file.type = res.headers['content-type']; file.binary = new Buffer(res.body, 'binary'); file.base64 = new Buffer(res.body, 'binary').toString('base64'); return when(file); } else { return when.reject(new Error('could not retrieve file headers')); } }); }; /** * Return details of Spark File by Spark Content URL. * * @function * @param {String} url - Spark Content URL * @returns {Promise.<File>} * * @example * spark.contentByUrl('http://api.ciscospark.com/v1/contents/Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u') * .then(function(file) { * console.log('File name: %s', file.name); * }) * .catch(function(err){ * console.log(err); * }); */ Spark.prototype.contentByUrl = function(url) { var contentId = url.match(/contents\/(.*)/)[1]; return this.contentGet(contentId); }; /** * Return all Spark Teams registered to account. * * @function * @param {Integer} [max] - Number of records to return * @returns {Promise.<Array>} * * @example * spark.teamsGet(10) * .then(function(teams) { * // process teams as array * teams.forEach(function(team) { * console.log(team.name); * }); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.teamsGet = function(maxResults) { return this.request('get', 'teams', { max: this.maxPageItems }, maxResults) .then(res => this.toArray(res)); }; /** * Return details of Spark Team by ID. * * @function * @param {String} teamId - Spark Team ID * @returns {Promise.<Team>} * * @example * spark.teamGet('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u') * .then(function(team) { * console.log(team.name); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.teamGet = function(teamId) { /** * Team Object * * @namespace Team * @property {string} id - Message ID * @property {string} name - Team name * @property {date} created - Date Team created */ return this.request('get', 'teams', teamId) .then(res => this.toObject(res)); }; /** * Add new Spark Team. * * @function * @param {String} name - Name for new Team * @returns {Promise.<Team>} * * @example * spark.teamAdd('myteam') * .then(function(team) { * console.log(team.name); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.teamAdd = function(name) { return this.request('post', 'teams', { name: name }) .then(res => this.toObject(res)); }; /** * Add new Spark Team Room. * * @function * @param {String} teamId - Spark Team ID * @param {String} title - Title for new Room * @returns {Promise.<Room>} * * @example * spark.teamRoomAdd('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u', 'myroom') * .then(function(room) { * console.log(room.title); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.teamRoomAdd = function(teamId, title) { return this.request('post', 'rooms', { teamId: teamId, title: title }) .then(res => this.toObject(res)); }; /** * Rename a Spark Team. * * @function * @param {String} teamId - Spark Team ID * @param {String} name - Name for new Team * @returns {Promise.<Team>} * * @example * spark.teamRename('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u', 'myteam2') * .then(function(team) { * console.log(team.name); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.teamRename = function(teamId, name) { return this.request('put', 'teams', teamId, { name: name }) .then(res => this.toObject(res)); }; /** * Remove Spark Team by ID. * * @function * @param {String} teamId - Spark Team ID * @returns {Promise} * * @example * spark.teamRemove('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u') * .then(function() { * console.log('Team removed.'); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.teamRemove = function(teamId) { return this.request('delete', 'teams', teamId) .then(res => when(true)); }; /** * Return all Spark Team Memberships for a specific Team. * * @function * @param {String} teamId - Spark Team ID * @param {Integer} [max] - Number of records to return * @returns {Promise.<Array>} * * @example * spark.teamMembershipsGet('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u', 100) * .then(function(memberships) { * // process memberships as array * memberships.forEach(function(membership) { * console.log(membership.personEmail); * }); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.teamMembershipsGet = function(teamId, maxResults) { return this.request('get', 'team/memberships', { teamId: teamId, max: this.maxPageItems }, maxResults) .then(res => this.toArray(res)); }; /** * Return Spark Team Membership by ID. * * @function * @param {String} membershipId - Spark Membership ID * @returns {Promise.<TeamMembership>} * * @example * spark.membershipGet('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u') * .then(function(membership) { * console.log(membership.personEmail); * }) * .catch(function(err){ * console.log(err); * }); */ Spark.prototype.teamMembershipGet = function(membershipId) { /** * Team Membership Object * * @namespace TeamMembership * @property {string} id - Membership ID * @property {string} teamId - Team ID * @property {string} personId - Person ID * @property {string} personEmail - Person Email * @property {boolean} isModerator - Membership is a moderator * @property {date} created - Date Membership created */ return this.request('get', 'team/memberships', membershipId) .then(res => this.toObject(res)); }; /** * Add new Spark Team Membership. * * @function * @param {String} teamId - Spark Team ID * @param {String} email - Email address of person to add * @param {Boolean} moderator - Boolean value to add as moderator * @returns {Promise.<TeamMembership>} * * @example * spark.teamMembershipAdd('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u', 'aperson@company.com') * .then(function(membership) { * console.log(membership.id); * }) * .catch(function(err){ * console.log(err); * }); */ Spark.prototype.teamMembershipAdd = function(teamId, email, moderator) { if(typeof email === 'string' && validator.isEmail(email)) { return this.request('post', 'team/memberships', { personEmail: email, teamId: teamId, isModerator: (typeof moderator === 'boolean' && moderator) }) .then(res => this.toObject(res)); } else { return when.reject(new Error('not a valid email')); } }; /** * Set a Team Membership as moderator. * * @function * @param {String} membershipId - Spark Membership ID * @returns {Promise.<TeamMembership>} * * @example * spark.teamMembershipSetModerator('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u') * .then(function(membership) { * console.log('%s is a moderator: %s', membership.personEmail, membership.isModerator); * }) * .catch(function(err){ * console.log(err); * }); */ Spark.prototype.teamMembershipSetModerator = function(membershipId) { return this.request('put', 'team/memberships', membershipId, { isModerator: true }) .then(res => this.toObject(res)); }; /** * Remove a Team Membership as moderator. * * @function * @param {String} membershipId - Spark Membership ID * @returns {Promise.<TeamMembership>} * * @example * spark.teamMembershipClearModerator('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u') * .then(function(membership) { * console.log('%s is a moderator: %s', membership.personEmail, membership.isModerator); * }) * .catch(function(err){ * console.log(err); * }); */ Spark.prototype.teamMembershipClearModerator = function(membershipId) { return this.request('put', 'team/memberships', membershipId, { isModerator: false }) .then(res => this.toObject(res)); }; /** * Remove Spark Team Membership by ID.. * * * @function * @param {String} membershipId - Spark Membership ID * @returns {Promise} * * @example * spark.teamMembershipRemove('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u') * .then(function() { * console.log('Membership removed'); * }) * .catch(function(err){ * console.log(err); * }); */ Spark.prototype.teamMembershipRemove = function(membershipId) { return this.request('delete', 'team/memberships', membershipId) .then(res => when(true)); }; /** * Return all Spark Memberships registered to account.. * * * @function * @param {Integer} [max] - Number of records to return * @returns {Promise.<Array>} * * @example * spark.membershipsGet(100) * .then(function(memberships) { * // process memberships as array * memberships.forEach(function(membership) { * console.log(membership.personEmail); * }); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.membershipsGet = function(maxResults) { return this.request('get', 'memberships', { max: this.maxPageItems }, maxResults) .then(res => this.toArray(res)); }; /** * Return all Spark Memberships in a Spark Room.. * * * @function * @param {String} roomId - Spark Room ID * @param {Integer} [max] - Number of records to return * @returns {Promise.<Array>} * * @example * spark.membershipsByRoom('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u', 100) * .then(function(memberships) { * // process memberships as array * memberships.forEach(function(membership) { * console.log(membership.personEmail); * }); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.membershipsByRoom = function(roomId, maxResults) { return this.request('get', 'memberships', { roomId: roomId, max: this.maxPageItems }, maxResults) .then(res => this.toArray(res)); }; /** * Return Spark Membership by ID.. * * * @function * @param {String} membershipId - Spark Membership ID * @returns {Promise.<Membership>} * * @example * spark.membershipGet('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u') * .then(function(membership) { * console.log(membership.personEmail); * }) * .catch(function(err){ * console.log(err); * }); */ Spark.prototype.membershipGet = function(membershipId) { /** * Membership Object * * @namespace Membership * @property {string} id - Membership ID * @property {string} personId - Person ID * @property {string} personEmail - Person Email * @property {boolean} isModerator - Membership is a moderator * @property {boolean} isMonitor - Membership is a monitor * @property {date} created - Date Membership created */ return this.request('get', 'memberships', membershipId) .then(res => this.toObject(res)); }; /** * Return Spark Membership by Room and Email.. * * @function * @param {String} roomId - Spark Membership ID * @param {String} personEmail - Email of Person * @returns {Promise.<Membership>} * * @example * spark.membershipByRoomByEmail('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u', 'aperson@company.com') * .then(function(membership) { * console.log(membership.id); * }) * .catch(function(err){ * console.log(err); * }); */ Spark.prototype.membershipByRoomByEmail = function(roomId, personEmail) { return this.request('get', 'memberships', { roomId: roomId, personEmail: personEmail }) .then(res => this.toObject(res)); }; /** * Add new Spark Membership.. * * @function * @param {String} roomId - Spark Room ID * @param {String} email - Email address of person to add * @param {Boolean} moderator - Boolean value to add as moderator * @returns {Promise.<Membership>} * * @example * spark.membershipAdd('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u', 'aperson@company.com') * .then(function(membership) { * console.log(membership.id); * }) * .catch(function(err){ * console.log(err); * }); */ Spark.prototype.membershipAdd = function(roomId, email, moderator) { if(typeof email === 'string' && validator.isEmail(email)) { return this.request('post', 'memberships', { personEmail: email, roomId: roomId, isModerator: (typeof moderator === 'boolean' && moderator) }) .then(res => this.toObject(res)); } else { return when.reject(new Error('not a valid email')); } }; /** * Set a Membership as moderator. * * @function * @param {String} membershipId - Spark Membership ID * @returns {Promise.<Membership>} * * @example * spark.membershipSetModerator('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u') * .then(function(membership) { * console.log('%s is a moderator: %s', membership.personEmail, membership.isModerator); * }) * .catch(function(err){ * console.log(err); * }); */ Spark.prototype.membershipSetModerator = function(membershipId) { return this.request('put', 'memberships', membershipId, { isModerator: true }) .then(res => this.toObject(res)); }; /** * Remove a Membership as moderator. * * @function * @param {String} membershipId - Spark Membership ID * @returns {Promise.<Membership>} * * @example * spark.membershipClearModerator('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u') * .then(function(membership) { * console.log('%s is a moderator: %s', membership.personEmail, membership.isModerator); * }) * .catch(function(err){ * console.log(err); * }); */ Spark.prototype.membershipClearModerator = function(membershipId) { return this.request('put', 'memberships', membershipId, { isModerator: false }) .then(res => this.toObject(res)); }; /** * Remove Spark Membership by ID. * * @function * @param {String} membershipId - Spark Membership ID * @returns {Promise} * * @example * spark.membershipRemove('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u') * .then(function() { * console.log('Membership removed'); * }) * .catch(function(err){ * console.log(err); * }); */ Spark.prototype.membershipRemove = function(membershipId) { return this.request('delete', 'memberships', membershipId) .then(res => when(true)); }; /** * Return all Spark Webhooks registered to account. * * @function * @param {Integer} [max] - Number of records to return * @returns {Promise.<Array>} * * @example * spark.webhooksGet(100) * .then(function(webhooks) { * // process webhooks as array * webhooks.forEach(function(webhook) { * console.log(webhook.name); * }); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.webhooksGet = function(maxResults) { return this.request('get', 'webhooks', { max: this.maxPageItems }, maxResults) .then(res => this.toArray(res)); }; /** * Return details of Spark Webhook by ID. * * @function * @param {String} webhookId - Spark Webhook ID * @returns {Promise.<Webhook>} * * @example * spark.webhookGet('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u') * .then(function(webhook) { * console.log(webhook.name); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.webhookGet = function(webhookId) { /** * Webhook Object * * @namespace Webhook * @property {string} id - Webhook ID * @property {string} name - Webhook name * @property {string} targetUrl - Webhook target URL * @property {boolean} resource - Webhook resource * @property {boolean} event - Webhook event * @property {boolean} filter - Webhook filter * @property {date} created - Date Webhook created */ return this.request('get', 'webhooks', webhookId) .then(res => this.toObject(res)); }; /** * Add new Spark Webhook. * * @function * @param {String} resource - Resource for webhook * @param {String} event - Event for webhook * @param {String} name - Name assigned to webhook to add * @param {String} [filter] - filter * @returns {Promise.<Webhook>} * * @example * spark.webhookAdd('messages', 'created', 'mywebhook', 'Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u') * .then(function(webhook) { * console.log(webhook.name); * }) * .catch(function(err) { * // process error * console.log(err); * }); */ Spark.prototype.webhookAdd = function(resource, evt, name, filter) { var config = {}; // verify webhook url is defined if(!this.webhookUrl) { return when.reject(new Error('webhook url not specified')); } else { config.targetUrl = this.webhookUrl; } // validate required options if(typeof resource !== 'string' || typeof evt !== 'string' || typeof name !== 'string') { return when.reject(new Error('invalid parameter types passed')); } else { config.resource = resource; config.event = evt; config.name = name; } // add optional filter if(typeof filter === 'string') { config.filter = filter; } // add optional secret if(typeof this.webhookSecret === 'string') { config.secret = this.webhookSecret; } // setup webhook return this.request('post', 'webhooks', config) .then(res => this.toObject(res)); }; /** * Remove Spark Webhook by ID. * * @function * @param {String} webhookId - Spark Webhook ID. * @returns {Promise} * * @example * spark.webhookRemove('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u') * .then(function() { * console.log('Webhook removed'); * }) * .catch(function(err){ * console.log(err); * }); */ Spark.prototype.webhookRemove = function(webhookId) { return this.request('delete', 'webhooks', webhookId) .then(res => when(true)); }; /** * Authenticate X-Spark-Signature HMAC-SHA1 Hash. * * @function * @param {String} signature - Value of "X-Spark-Signature" from header * @param {(String|Object)} payload - This can either be the json object or a string representation of the webhook's body json payload * @returns {Boolen} * * @example * var sig = req.headers['x-spark-signature']; * if(spark.webhookAuth(sig, req.body)) { * // webhook is valid * } else { * // webhook is invalid * } */ Spark.prototype.webhookAuth = function(sig, payload) { // if json object... if(typeof payload === 'object') { payload = JSON.stringify(payload); } //validate if(typeof this.webhookSecret === 'string' && typeof sig === 'string') { var hmac = crypto.createHmac('sha1', this.webhookSecret); hmac.update(payload); var digest = hmac.digest('hex'); return (sig === digest); } else { return false; } }; module.exports = Spark;