-
Notifications
You must be signed in to change notification settings - Fork 17
/
index.js
163 lines (133 loc) · 4.88 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
'use strict';
var _ = require('lodash');
var errorFactory = require('error-factory');
var querystring = require('querystring');
var request = require('superagent');
var Promise = require('bluebird');
var endpoints = require(__dirname + '/api-endpoints.json');
// Custom Errors
// indicates errors such as a network failure or timeout
var CommunicationError = errorFactory('CommunicationError');
// indicates errors returned by Slack e.g. for incorrect parameters supplied for an endpoint
var SlackError = errorFactory('SlackError');
// signifies that a non-200 status code was returned by Slack. This is different from
// SlackError because Slack returns a 200 status code for the kinds of errors
// identified by SlackError
var SlackServiceError = errorFactory('SlackServiceError', ['message', 'errorDetails']);
var api = _.mapValues(endpoints, function(section, sectionName) {
return _.mapValues(section, function(method, methodName) {
return apiMethod(sectionName, methodName);
});
});
api.errors = {
SlackError: SlackError,
SlackServiceError: SlackServiceError,
CommunicationError: CommunicationError
};
api.oauth.getUrl = function getUrl(options, done) {
if (typeof options === 'string') {
options = {
client_id: options
};
}
if (!options || !options.client_id) {
throw new ReferenceError('A client_id is required for this method.');
}
options.state = options.state || Math.random();
if (!done || !_.isFunction(done)) {
done = function noop() {};
}
return done(null, 'https://slack.com/oauth/authorize?' + querystring.stringify(options));
};
api.oauth.access = function authorize(options, state, done) {
// Polymorphism
if (_.isFunction(state) && _.isUndefined(done)) {
done = state;
state = null;
}
// Error Handling
if (!done || !_.isFunction(done)) {
done = function noop() {};
}
if (!options) {
throw new ReferenceError('oauth.access requires an options Object as a first argument.');
}
if (_.isArray(options) || _.isString(options) || _.isFunction(options)) {
throw new TypeError('oauth.access requires an options Object as a first argument.');
}
if (state && options.state && state !== options.state) {
return done(new SlackError('States do not match. WARNING! This could mean that the authentication was a forgery.'), null);
}
// Access authentication
return apiMethod('oauth', 'access')(options, done);
};
function promisify() {
//make sure to bubble up errors instead of console.error'ing them via Bluebird
//so that client code can add their own catch and error handling
Promise.onPossiblyUnhandledRejection(function(error) {
throw error;
});
return _.mapValues(api, function(section, sectionName) {
if (sectionName === 'errors') return section;
return _.mapValues(section, function(method, name) {
return Promise.promisify(method);
});
});
}
function apiMethod(sectionName, methodName) {
return function callApi(args, done) {
var config;
var requiredArgsList;
var url;
// Polymorphism
if (_.isUndefined(done) && _.isFunction(args)) {
done = args;
args = {};
} else if (_.isUndefined(done) && _.isUndefined(args)) {
args = {};
done = function() {};
}
if (!api.hasOwnProperty(sectionName)) {
throw new ReferenceError('API object, ' + sectionName + ', does not exist.');
}
if (!api[sectionName].hasOwnProperty(methodName)) {
throw new ReferenceError('API Method, ' + sectionName + '#' + methodName + ', does not exist.');
}
config = endpoints[sectionName][methodName];
var requiredArgsList = collectRequiredArgs(config.arguments);
_.each(requiredArgsList, function(requiredArg) {
if (_.isUndefined(args[requiredArg])) {
throw new TypeError('API Method, ' + sectionName + '#' + methodName + ', requires the following args: ' + requiredArgsList.join(', '));
}
});
url = config.url + '?' + querystring.stringify(args);
request
.get(url)
.end(function(error, response) {
if (error) {
return done(new CommunicationError('Communication error while posting message to Slack. ' + error), null);
} else {
if (response.ok) {
if (!response.body.ok) {
return done(new SlackError(config.errors[response.body.error] || response.body.error), response.body);
}
done(null, response.body);
} else {
return done(new SlackServiceError('Did not receive a successful response from Slack.', {
errorDetails: {
errorCode: response.status,
errorResponse: response.body
}
}), null);
}
}
});
}
}
function collectRequiredArgs(configuredArgs) {
return _.filter(_.keys(configuredArgs), function(key) {
return configuredArgs[key].required;
});
}
api.promisify = promisify;
module.exports = api;