node is set to true node is set to true
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * @module stream | |
3 | * @author Thierry Schellenbach | |
4 | * BSD License | |
5 | */ | |
6 | 1 | var StreamClient = require('./lib/client'); |
7 | 1 | var errors = require('./lib/errors'); |
8 | 1 | var request = require('request'); |
9 | ||
10 | 1 | function connect(apiKey, apiSecret, appId, options) { |
11 | /** | |
12 | * Create StreamClient | |
13 | * @method connect | |
14 | * @param {string} apiKey API key | |
15 | * @param {string} [apiSecret] API secret (only use this on the server) | |
16 | * @param {string} [appId] Application identifier | |
17 | * @param {object} [options] Additional options | |
18 | * @param {string} [options.location] Datacenter location | |
19 | * @return {StreamClient} StreamClient | |
20 | * @example <caption>Basic usage</caption> | |
21 | * stream.connect(apiKey, apiSecret); | |
22 | * @example <caption>or if you want to be able to subscribe and listen</caption> | |
23 | * stream.connect(apiKey, apiSecret, appId); | |
24 | * @example <caption>or on Heroku</caption> | |
25 | * stream.connect(streamURL); | |
26 | * @example <caption>where streamURL looks like</caption> | |
27 | * "https://thierry:pass@gestream.io/?app=1" | |
28 | */ | |
29 | 126 | if (typeof (process) !== 'undefined' && process.env.STREAM_URL && !apiKey) { |
30 | 3 | var parts = /https\:\/\/(\w+)\:(\w+)\@([\w-]*).*\?app_id=(\d+)/.exec(process.env.STREAM_URL); |
31 | 3 | apiKey = parts[1]; |
32 | 3 | apiSecret = parts[2]; |
33 | 3 | var location = parts[3]; |
34 | 3 | appId = parts[4]; |
35 | 3 | if (options === undefined) { |
36 | 3 | options = {}; |
37 | } | |
38 | ||
39 | 3 | if (location !== 'getstream') { |
40 | 1 | options.location = location; |
41 | } | |
42 | } | |
43 | ||
44 | 126 | return new StreamClient(apiKey, apiSecret, appId, options); |
45 | } | |
46 | ||
47 | 1 | module.exports.connect = connect; |
48 | 1 | module.exports.errors = errors; |
49 | 1 | module.exports.request = request; |
50 | 1 | module.exports.Client = StreamClient; |
51 |
Line | Hits | Source |
---|---|---|
1 | 1 | var httpSignature = require('http-signature'); |
2 | 1 | var request = require('request'); |
3 | 1 | var errors = require('./errors'); |
4 | 1 | var Promise = require('./promise'); |
5 | ||
6 | 1 | module.exports = { |
7 | addToMany: function(activity, feeds, callback) { | |
8 | /** | |
9 | * Add one activity to many feeds | |
10 | * @method addToMany | |
11 | * @memberof StreamClient.prototype | |
12 | * @since 2.3.0 | |
13 | * @param {object} activity The activity to add | |
14 | * @param {Array} feeds Array of objects describing the feeds to add to | |
15 | * @param {requestCallback} callback Callback called on completion | |
16 | * @return {Promise} Promise object | |
17 | */ | |
18 | 3 | return this.makeSignedRequest({ |
19 | url: 'feed/add_to_many/', | |
20 | body: { | |
21 | activity: activity, | |
22 | feeds: feeds, | |
23 | }, | |
24 | }, callback); | |
25 | }, | |
26 | ||
27 | followMany: function(follows, callbackOrActivityCopyLimit, callback) { | |
28 | /** | |
29 | * Follow multiple feeds with one API call | |
30 | * @method followMany | |
31 | * @memberof StreamClient.prototype | |
32 | * @since 2.3.0 | |
33 | * @param {Array} follows The follow relations to create | |
34 | * @param {number} [activityCopyLimit] How many activities should be copied from the target feed | |
35 | * @param {requestCallback} [callback] Callback called on completion | |
36 | * @return {Promise} Promise object | |
37 | */ | |
38 | 4 | var activityCopyLimit, qs = {}; |
39 | ||
40 | 4 | if (callbackOrActivityCopyLimit && typeof callbackOrActivityCopyLimit === 'number') { |
41 | 2 | activityCopyLimit = callbackOrActivityCopyLimit; |
42 | } | |
43 | ||
44 | 4 | if (callbackOrActivityCopyLimit && typeof callbackOrActivityCopyLimit === 'function') { |
45 | 1 | callback = callbackOrActivityCopyLimit; |
46 | } | |
47 | ||
48 | 4 | if (activityCopyLimit) { |
49 | 2 | qs['activity_copy_limit'] = activityCopyLimit; |
50 | } | |
51 | ||
52 | 4 | return this.makeSignedRequest({ |
53 | url: 'follow_many/', | |
54 | body: follows, | |
55 | qs: qs, | |
56 | }, callback); | |
57 | }, | |
58 | ||
59 | makeSignedRequest: function(kwargs, cb) { | |
60 | /** | |
61 | * Method to create request to api with application level authentication | |
62 | * @method makeSignedRequest | |
63 | * @memberof StreamClient.prototype | |
64 | * @since 2.3.0 | |
65 | * @access private | |
66 | * @param {object} kwargs Arguments for the request | |
67 | * @param {requestCallback} cb Callback to call on completion | |
68 | * @return {Promise} Promise object | |
69 | */ | |
70 | 9 | if (!this.apiSecret) { |
71 | 1 | throw new errors.SiteError('Missing secret, which is needed to perform signed requests, use var client = stream.connect(key, secret);'); |
72 | } | |
73 | ||
74 | 8 | return new Promise(function(fulfill, reject) { |
75 | 8 | this.send('request', 'post', kwargs, cb); |
76 | ||
77 | 8 | kwargs.url = this.enrichUrl(kwargs.url); |
78 | 8 | kwargs.json = true; |
79 | 8 | kwargs.method = 'POST'; |
80 | 8 | kwargs.headers = { 'X-Api-Key': this.apiKey }; |
81 | ||
82 | 8 | var callback = this.wrapPromiseTask(cb, fulfill, reject); |
83 | 8 | var req = request(kwargs, callback); |
84 | ||
85 | 8 | httpSignature.sign(req, { |
86 | algorithm: 'hmac-sha256', | |
87 | key: this.apiSecret, | |
88 | keyId: this.apiKey, | |
89 | }); | |
90 | }.bind(this)); | |
91 | }, | |
92 | }; | |
93 |
Line | Hits | Source |
---|---|---|
1 | 1 | var request = require('request'); |
2 | 1 | var StreamFeed = require('./feed'); |
3 | 1 | var signing = require('./signing'); |
4 | 1 | var errors = require('./errors'); |
5 | 1 | var utils = require('./utils'); |
6 | 1 | var BatchOperations = require('./batch_operations'); |
7 | 1 | var Promise = require('./promise'); |
8 | 1 | var qs = require('qs'); |
9 | 1 | var url = require('url'); |
10 | 1 | var Faye = require('faye'); |
11 | ||
12 | /** | |
13 | * @callback requestCallback | |
14 | * @param {object} [errors] | |
15 | * @param {object} response | |
16 | * @param {object} body | |
17 | */ | |
18 | ||
19 | 1 | var StreamClient = function() { |
20 | /** | |
21 | * Client to connect to Stream api | |
22 | * @class StreamClient | |
23 | */ | |
24 | 126 | this.initialize.apply(this, arguments); |
25 | }; | |
26 | ||
27 | 1 | StreamClient.prototype = { |
28 | baseUrl: 'https://api.getstream.io/api/', | |
29 | baseAnalyticsUrl: 'https://analytics.getstream.io/analytics/', | |
30 | ||
31 | initialize: function(apiKey, apiSecret, appId, options) { | |
32 | /** | |
33 | * Initialize a client | |
34 | * @method intialize | |
35 | * @memberof StreamClient.prototype | |
36 | * @param {string} apiKey - the api key | |
37 | * @param {string} [apiSecret] - the api secret | |
38 | * @param {string} [appId] - id of the app | |
39 | * @param {object} [options] - additional options | |
40 | * @param {string} [options.location] - which data center to use | |
41 | * @param {boolean} [options.expireTokens=false] - whether to use a JWT timestamp field (i.e. iat) | |
42 | * @example <caption>initialize is not directly called by via stream.connect, ie:</caption> | |
43 | * stream.connect(apiKey, apiSecret) | |
44 | * @example <caption>secret is optional and only used in server side mode</caption> | |
45 | * stream.connect(apiKey, null, appId); | |
46 | */ | |
47 | 126 | this.apiKey = apiKey; |
48 | 126 | this.apiSecret = apiSecret; |
49 | 126 | this.appId = appId; |
50 | 126 | this.options = options || {}; |
51 | 126 | this.version = this.options.version || 'v1.0'; |
52 | 126 | this.fayeUrl = this.options.fayeUrl || 'https://faye.getstream.io/faye'; |
53 | 126 | this.fayeClient = null; |
54 | // track a source name for the api calls, ie get started or databrowser | |
55 | 126 | this.group = this.options.group || 'unspecified'; |
56 | // track subscriptions made on feeds created by this client | |
57 | 126 | this.subscriptions = {}; |
58 | 126 | this.expireTokens = this.options.expireTokens ? this.options.expireTokens : false; |
59 | // which data center to use | |
60 | 126 | this.location = this.options.location; |
61 | 126 | if (this.location) { |
62 | 54 | this.baseUrl = 'https://' + this.location + '-api.getstream.io/api/'; |
63 | } | |
64 | ||
65 | 126 | if (typeof (process) !== 'undefined' && process.env.LOCAL) { |
66 | 0 | this.baseUrl = 'http://localhost:8000/api/'; |
67 | } | |
68 | ||
69 | 126 | if (typeof (process) !== 'undefined' && process.env.LOCAL_FAYE) { |
70 | 0 | this.fayeUrl = 'http://localhost:9999/faye/'; |
71 | } | |
72 | ||
73 | 126 | if (typeof (process) !== 'undefined' && process.env.STREAM_BASE_URL) { |
74 | 0 | this.baseUrl = process.env.STREAM_BASE_URL; |
75 | } | |
76 | ||
77 | 126 | this.handlers = {}; |
78 | 126 | this.browser = typeof (window) !== 'undefined'; |
79 | 126 | this.node = !this.browser; |
80 | ||
81 | 126 | if (this.browser && this.apiSecret) { |
82 | 0 | throw new errors.FeedError('You are publicly sharing your private key. Dont use the private key while in the browser.'); |
83 | } | |
84 | }, | |
85 | ||
86 | on: function(event, callback) { | |
87 | /** | |
88 | * Support for global event callbacks | |
89 | * This is useful for generic error and loading handling | |
90 | * @method on | |
91 | * @memberof StreamClient.prototype | |
92 | * @param {string} event - Name of the event | |
93 | * @param {function} callback - Function that is called when the event fires | |
94 | * @example | |
95 | * client.on('request', callback); | |
96 | * client.on('response', callback); | |
97 | */ | |
98 | 2 | this.handlers[event] = callback; |
99 | }, | |
100 | ||
101 | off: function(key) { | |
102 | /** | |
103 | * Remove one or more event handlers | |
104 | * @method off | |
105 | * @memberof StreamClient.prototype | |
106 | * @param {string} [key] - Name of the handler | |
107 | * @example | |
108 | * client.off() removes all handlers | |
109 | * client.off(name) removes the specified handler | |
110 | */ | |
111 | 1 | if (key === undefined) { |
112 | 1 | this.handlers = {}; |
113 | } else { | |
114 | 0 | delete this.handlers[key]; |
115 | } | |
116 | }, | |
117 | ||
118 | send: function() { | |
119 | /** | |
120 | * Call the given handler with the arguments | |
121 | * @method send | |
122 | * @memberof StreamClient.prototype | |
123 | * @access private | |
124 | */ | |
125 | 148 | var args = Array.prototype.slice.call(arguments); |
126 | 148 | var key = args[0]; |
127 | 148 | args = args.slice(1); |
128 | 148 | if (this.handlers[key]) { |
129 | 2 | this.handlers[key].apply(this, args); |
130 | } | |
131 | }, | |
132 | ||
133 | wrapPromiseTask: function(cb, fulfill, reject) { | |
134 | /** | |
135 | * Wrap a task to be used as a promise | |
136 | * @method wrapPromiseTask | |
137 | * @memberof StreamClient.prototype | |
138 | * @private | |
139 | * @param {requestCallback} cb | |
140 | * @param {function} fulfill | |
141 | * @param {function} reject | |
142 | * @return {function} | |
143 | */ | |
144 | 74 | var client = this; |
145 | ||
146 | 74 | var callback = this.wrapCallback(cb); |
147 | 74 | return function task(error, response, body) { |
148 | 74 | if (error) { |
149 | 2 | reject({ |
150 | error: error, | |
151 | response: response, | |
152 | }); | |
153 | 72 | } else if (!/^2/.test('' + response.statusCode)) { |
154 | 4 | reject({ |
155 | error: body, | |
156 | response: response, | |
157 | }); | |
158 | } else { | |
159 | 68 | fulfill(body); |
160 | } | |
161 | ||
162 | 74 | callback.apply(client, arguments); |
163 | }; | |
164 | }, | |
165 | ||
166 | wrapCallback: function(cb) { | |
167 | /** | |
168 | * Wrap callback for HTTP request | |
169 | * @method wrapCallBack | |
170 | * @memberof StreamClient.prototype | |
171 | * @access private | |
172 | */ | |
173 | 74 | var client = this; |
174 | ||
175 | 74 | function callback() { |
176 | // first hit the global callback, subsequently forward | |
177 | 74 | var args = Array.prototype.slice.call(arguments); |
178 | 74 | var sendArgs = ['response'].concat(args); |
179 | 74 | client.send.apply(client, sendArgs); |
180 | 74 | if (cb !== undefined) { |
181 | 55 | cb.apply(client, args); |
182 | } | |
183 | } | |
184 | ||
185 | 74 | return callback; |
186 | }, | |
187 | ||
188 | userAgent: function() { | |
189 | /** | |
190 | * Get the current user agent | |
191 | * @method userAgent | |
192 | * @memberof StreamClient.prototype | |
193 | * @return {string} current user agent | |
194 | */ | |
195 | 66 | var description = (this.node) ? 'node' : 'browser'; |
196 | // TODO: get the version here in a way which works in both and browserify | |
197 | 66 | var version = 'unknown'; |
198 | 66 | return 'stream-javascript-client-' + description + '-' + version; |
199 | }, | |
200 | ||
201 | getReadOnlyToken: function(feedSlug, userId) { | |
202 | /** | |
203 | * Returns a token that allows only read operations | |
204 | * | |
205 | * @method getReadOnlyToken | |
206 | * @memberof StreamClient.prototype | |
207 | * @param {string} feedSlug - The feed slug to get a read only token for | |
208 | * @param {string} userId - The user identifier | |
209 | * @return {string} token | |
210 | * @example | |
211 | * client.getReadOnlyToken('user', '1'); | |
212 | */ | |
213 | 105 | return this.feed(feedSlug, userId).getReadOnlyToken(); |
214 | }, | |
215 | ||
216 | getReadWriteToken: function(feedSlug, userId) { | |
217 | /** | |
218 | * Returns a token that allows read and write operations | |
219 | * | |
220 | * @method getReadWriteToken | |
221 | * @memberof StreamClient.prototype | |
222 | * @param {string} feedSlug - The feed slug to get a read only token for | |
223 | * @param {string} userId - The user identifier | |
224 | * @return {string} token | |
225 | * @example | |
226 | * client.getReadWriteToken('user', '1'); | |
227 | */ | |
228 | 1 | return this.feed(feedSlug, userId).getReadWriteToken(); |
229 | }, | |
230 | ||
231 | feed: function(feedSlug, userId, token, siteId, options) { | |
232 | /** | |
233 | * Returns a feed object for the given feed id and token | |
234 | * @method feed | |
235 | * @memberof StreamClient.prototype | |
236 | * @param {string} feedSlug - The feed slug | |
237 | * @param {string} userId - The user identifier | |
238 | * @param {string} [token] - The token | |
239 | * @param {string} [siteId] - The site identifier | |
240 | * @param {object} [options] - Additional function options | |
241 | * @param {boolean} [options.readOnly] - A boolean indicating whether to generate a read only token for this feed | |
242 | * @return {StreamFeed} | |
243 | * @example | |
244 | * client.feed('user', '1', 'token2'); | |
245 | */ | |
246 | ||
247 | 549 | options = options || {}; |
248 | ||
249 | 549 | if (!feedSlug || !userId) { |
250 | 1 | throw new errors.FeedError('Please provide a feed slug and user id, ie client.feed("user", "1")'); |
251 | } | |
252 | ||
253 | 548 | if (feedSlug.indexOf(':') !== -1) { |
254 | 1 | throw new errors.FeedError('Please initialize the feed using client.feed("user", "1") not client.feed("user:1")'); |
255 | } | |
256 | ||
257 | 547 | utils.validateFeedSlug(feedSlug); |
258 | 546 | utils.validateUserId(userId); |
259 | ||
260 | // raise an error if there is no token | |
261 | 545 | if (!this.apiSecret && !token) { |
262 | 0 | throw new errors.FeedError('Missing token, in client side mode please provide a feed secret'); |
263 | } | |
264 | ||
265 | // create the token in server side mode | |
266 | 545 | if (this.apiSecret && !token) { |
267 | 538 | var feedId = '' + feedSlug + userId; |
268 | // use scoped token if read-only access is necessary | |
269 | 538 | token = options.readOnly ? this.getReadOnlyToken(feedSlug, userId) : signing.sign(this.apiSecret, feedId); |
270 | } | |
271 | ||
272 | 545 | var feed = new StreamFeed(this, feedSlug, userId, token, siteId); |
273 | 545 | return feed; |
274 | }, | |
275 | ||
276 | enrichUrl: function(relativeUrl) { | |
277 | /** | |
278 | * Combines the base url with version and the relative url | |
279 | * @method enrichUrl | |
280 | * @memberof StreamClient.prototype | |
281 | * @private | |
282 | * @param {string} relativeUrl | |
283 | */ | |
284 | 74 | var url = this.baseUrl + this.version + '/' + relativeUrl; |
285 | 74 | return url; |
286 | }, | |
287 | ||
288 | enrichKwargs: function(kwargs) { | |
289 | /** | |
290 | * Adds the API key and the signature | |
291 | * @method enrichKwargs | |
292 | * @memberof StreamClient.prototype | |
293 | * @param {object} kwargs | |
294 | * @private | |
295 | */ | |
296 | 66 | kwargs.url = this.enrichUrl(kwargs.url); |
297 | 66 | if (kwargs.qs === undefined) { |
298 | 38 | kwargs.qs = {}; |
299 | } | |
300 | ||
301 | 66 | kwargs.qs['api_key'] = this.apiKey; |
302 | 66 | kwargs.qs.location = this.group; |
303 | 66 | kwargs.json = true; |
304 | 66 | var signature = kwargs.signature || this.signature; |
305 | 66 | kwargs.headers = {}; |
306 | ||
307 | // auto-detect authentication type and set HTTP headers accordingly | |
308 | 66 | if (signing.isJWTSignature(signature)) { |
309 | 12 | kwargs.headers['stream-auth-type'] = 'jwt'; |
310 | 12 | signature = signature.split(' ').reverse()[0]; |
311 | } else { | |
312 | 54 | kwargs.headers['stream-auth-type'] = 'simple'; |
313 | } | |
314 | ||
315 | 66 | kwargs.headers.Authorization = signature; |
316 | 66 | kwargs.headers['X-Stream-Client'] = this.userAgent(); |
317 | 66 | return kwargs; |
318 | }, | |
319 | ||
320 | signActivity: function(activity) { | |
321 | /** | |
322 | * We automatically sign the to parameter when in server side mode | |
323 | * @method signActivities | |
324 | * @memberof StreamClient.prototype | |
325 | * @private | |
326 | * @param {object} [activity] Activity to sign | |
327 | */ | |
328 | 21 | return this.signActivities([activity])[0]; |
329 | }, | |
330 | ||
331 | signActivities: function(activities) { | |
332 | /** | |
333 | * We automatically sign the to parameter when in server side mode | |
334 | * @method signActivities | |
335 | * @memberof StreamClient.prototype | |
336 | * @private | |
337 | * @param {array} Activities | |
338 | */ | |
339 | 25 | if (!this.apiSecret) { |
340 | 0 | return activities; |
341 | } | |
342 | ||
343 | 25 | for (var i = 0; i < activities.length; i++) { |
344 | 37 | var activity = activities[i]; |
345 | 37 | var to = activity.to || []; |
346 | 37 | var signedTo = []; |
347 | 37 | for (var j = 0; j < to.length; j++) { |
348 | 2 | var feedId = to[j]; |
349 | 2 | var feedSlug = feedId.split(':')[0]; |
350 | 2 | var userId = feedId.split(':')[1]; |
351 | 2 | var token = this.feed(feedSlug, userId).token; |
352 | 2 | var signedFeed = feedId + ' ' + token; |
353 | 2 | signedTo.push(signedFeed); |
354 | } | |
355 | ||
356 | 37 | activity.to = signedTo; |
357 | } | |
358 | ||
359 | 25 | return activities; |
360 | }, | |
361 | ||
362 | getFayeAuthorization: function() { | |
363 | /** | |
364 | * Get the authorization middleware to use Faye with getstream.io | |
365 | * @method getFayeAuthorization | |
366 | * @memberof StreamClient.prototype | |
367 | * @private | |
368 | * @return {object} Faye authorization middleware | |
369 | */ | |
370 | 6 | var apiKey = this.apiKey, |
371 | self = this; | |
372 | ||
373 | 6 | return { |
374 | incoming: function(message, callback) { | |
375 | 30 | callback(message); |
376 | }, | |
377 | ||
378 | outgoing: function(message, callback) { | |
379 | 21 | if (message.subscription && self.subscriptions[message.subscription]) { |
380 | 8 | var subscription = self.subscriptions[message.subscription]; |
381 | ||
382 | 8 | message.ext = { |
383 | 'user_id': subscription.userId, | |
384 | 'api_key': apiKey, | |
385 | 'signature': subscription.token, | |
386 | }; | |
387 | } | |
388 | ||
389 | 21 | callback(message); |
390 | }, | |
391 | }; | |
392 | }, | |
393 | ||
394 | getFayeClient: function() { | |
395 | /** | |
396 | * Returns this client's current Faye client | |
397 | * @method getFayeClient | |
398 | * @memberof StreamClient.prototype | |
399 | * @private | |
400 | * @return {object} Faye client | |
401 | */ | |
402 | 11 | if (this.fayeClient === null) { |
403 | 6 | this.fayeClient = new Faye.Client(this.fayeUrl); |
404 | 6 | var authExtension = this.getFayeAuthorization(); |
405 | 6 | this.fayeClient.addExtension(authExtension); |
406 | } | |
407 | ||
408 | 11 | return this.fayeClient; |
409 | }, | |
410 | ||
411 | get: function(kwargs, cb) { | |
412 | /** | |
413 | * Shorthand function for get request | |
414 | * @method get | |
415 | * @memberof StreamClient.prototype | |
416 | * @private | |
417 | * @param {object} kwargs | |
418 | * @param {requestCallback} cb Callback to call on completion | |
419 | * @return {Promise} Promise object | |
420 | */ | |
421 | 24 | return new Promise(function(fulfill, reject) { |
422 | 24 | this.send('request', 'get', kwargs, cb); |
423 | 24 | kwargs = this.enrichKwargs(kwargs); |
424 | 24 | kwargs.method = 'GET'; |
425 | 24 | var callback = this.wrapPromiseTask(cb, fulfill, reject); |
426 | 24 | request(kwargs, callback); |
427 | }.bind(this)); | |
428 | }, | |
429 | ||
430 | post: function(kwargs, cb) { | |
431 | /** | |
432 | * Shorthand function for post request | |
433 | * @method post | |
434 | * @memberof StreamClient.prototype | |
435 | * @private | |
436 | * @param {object} kwargs | |
437 | * @param {requestCallback} cb Callback to call on completion | |
438 | * @return {Promise} Promise object | |
439 | */ | |
440 | 38 | return new Promise(function(fulfill, reject) { |
441 | 38 | this.send('request', 'post', kwargs, cb); |
442 | 38 | kwargs = this.enrichKwargs(kwargs); |
443 | 38 | kwargs.method = 'POST'; |
444 | 38 | var callback = this.wrapPromiseTask(cb, fulfill, reject); |
445 | 38 | request(kwargs, callback); |
446 | }.bind(this)); | |
447 | }, | |
448 | ||
449 | delete: function(kwargs, cb) { | |
450 | /** | |
451 | * Shorthand function for delete request | |
452 | * @method delete | |
453 | * @memberof StreamClient.prototype | |
454 | * @private | |
455 | * @param {object} kwargs | |
456 | * @param {requestCallback} cb Callback to call on completion | |
457 | * @return {Promise} Promise object | |
458 | */ | |
459 | 4 | return new Promise(function(fulfill, reject) { |
460 | 4 | this.send('request', 'delete', kwargs, cb); |
461 | 4 | kwargs = this.enrichKwargs(kwargs); |
462 | 4 | kwargs.method = 'DELETE'; |
463 | 4 | var callback = this.wrapPromiseTask(cb, fulfill, reject); |
464 | 4 | request(kwargs, callback); |
465 | }.bind(this)); | |
466 | }, | |
467 | ||
468 | updateActivities: function(activities, callback) { | |
469 | /** | |
470 | * Updates all supplied activities on the getstream-io api | |
471 | * @since 3.1.0 | |
472 | * @param {array} activities list of activities to update | |
473 | * @return {Promise} | |
474 | */ | |
475 | 8 | if (! (activities instanceof Array)) { |
476 | 1 | throw new TypeError('The activities argument should be an Array'); |
477 | } | |
478 | ||
479 | 7 | var authToken = signing.JWTScopeToken(this.apiSecret, 'activities', '*', { feedId: '*', expireTokens: this.expireTokens }); |
480 | ||
481 | 7 | var data = { |
482 | activities: activities, | |
483 | }; | |
484 | ||
485 | 7 | return this.post({ |
486 | url: 'activities/', | |
487 | body: data, | |
488 | signature: authToken, | |
489 | }, callback); | |
490 | }, | |
491 | ||
492 | updateActivity: function(activity) { | |
493 | /** | |
494 | * Updates one activity on the getstream-io api | |
495 | * @since 3.1.0 | |
496 | * @param {object} activity The activity to update | |
497 | * @return {Promise} | |
498 | */ | |
499 | 5 | return this.updateActivities([activity]); |
500 | }, | |
501 | ||
502 | }; | |
503 | ||
504 | 1 | if (qs) { |
505 | 1 | StreamClient.prototype.createRedirectUrl = function(targetUrl, userId, events) { |
506 | /** | |
507 | * Creates a redirect url for tracking the given events in the context of | |
508 | * an email using Stream's analytics platform. Learn more at | |
509 | * getstream.io/personalization | |
510 | * @method createRedirectUrl | |
511 | * @memberof StreamClient.prototype | |
512 | * @param {string} targetUrl Target url | |
513 | * @param {string} userId User id to track | |
514 | * @param {array} events List of events to track | |
515 | * @return {string} The redirect url | |
516 | */ | |
517 | 3 | var uri = url.parse(targetUrl); |
518 | ||
519 | 3 | if (!(uri.host || (uri.hostname && uri.port)) && !uri.isUnix) { |
520 | 1 | throw new errors.MissingSchemaError('Invalid URI: "' + url.format(uri) + '"'); |
521 | } | |
522 | ||
523 | 2 | var authToken = signing.JWTScopeToken(this.apiSecret, 'redirect_and_track', '*', { userId: userId, expireTokens: this.expireTokens }); |
524 | 2 | var analyticsUrl = this.baseAnalyticsUrl + 'redirect/'; |
525 | 2 | var kwargs = { |
526 | 'auth_type': 'jwt', | |
527 | 'authorization': authToken, | |
528 | 'url': targetUrl, | |
529 | 'api_key': this.apiKey, | |
530 | 'events': JSON.stringify(events), | |
531 | }; | |
532 | ||
533 | 2 | var qString = utils.rfc3986(qs.stringify(kwargs, null, null, {})); |
534 | ||
535 | 2 | return analyticsUrl + '?' + qString; |
536 | }; | |
537 | } | |
538 | ||
539 | // If we are in a node environment and batchOperations is available add the methods to the prototype of StreamClient | |
540 | 1 | if (BatchOperations) { |
541 | 1 | for (var key in BatchOperations) { |
542 | 3 | if (BatchOperations.hasOwnProperty(key)) { |
543 | 3 | StreamClient.prototype[key] = BatchOperations[key]; |
544 | } | |
545 | } | |
546 | } | |
547 | ||
548 | 1 | module.exports = StreamClient; |
549 |
Line | Hits | Source |
---|---|---|
1 | 1 | var errors = module.exports; |
2 | ||
3 | 1 | var canCapture = (typeof Error.captureStackTrace === 'function'); |
4 | 1 | var canStack = !!(new Error()).stack; |
5 | ||
6 | /** | |
7 | * Abstract error object | |
8 | * @class ErrorAbstract | |
9 | * @access private | |
10 | * @param {string} [msg] Error message | |
11 | * @param {function} constructor | |
12 | */ | |
13 | 1 | function ErrorAbstract(msg, constructor) { |
14 | 14 | this.message = msg; |
15 | ||
16 | 14 | Error.call(this, this.message); |
17 | ||
18 | 14 | if (canCapture) { |
19 | 14 | Error.captureStackTrace(this, constructor); |
20 | 0 | } else if (canStack) { |
21 | 0 | this.stack = (new Error()).stack; |
22 | } else { | |
23 | 0 | this.stack = ''; |
24 | } | |
25 | } | |
26 | ||
27 | 1 | errors._Abstract = ErrorAbstract; |
28 | 1 | ErrorAbstract.prototype = new Error(); |
29 | ||
30 | /** | |
31 | * FeedError | |
32 | * @class FeedError | |
33 | * @access private | |
34 | * @extends ErrorAbstract | |
35 | * @memberof Stream.errors | |
36 | * @param {String} [msg] - An error message that will probably end up in a log. | |
37 | */ | |
38 | 1 | errors.FeedError = function FeedError(msg) { |
39 | 7 | ErrorAbstract.call(this, msg); |
40 | }; | |
41 | ||
42 | 1 | errors.FeedError.prototype = new ErrorAbstract(); |
43 | ||
44 | /** | |
45 | * SiteError | |
46 | * @class SiteError | |
47 | * @access private | |
48 | * @extends ErrorAbstract | |
49 | * @memberof Stream.errors | |
50 | * @param {string} [msg] An error message that will probably end up in a log. | |
51 | */ | |
52 | 1 | errors.SiteError = function SiteError(msg) { |
53 | 2 | ErrorAbstract.call(this, msg); |
54 | }; | |
55 | ||
56 | 1 | errors.SiteError.prototype = new ErrorAbstract(); |
57 | ||
58 | /** | |
59 | * MissingSchemaError | |
60 | * @method MissingSchema | |
61 | * @access private | |
62 | * @extends ErrorAbstract | |
63 | * @memberof Stream.errors | |
64 | * @param {string} msg | |
65 | */ | |
66 | 1 | errors.MissingSchemaError = function MissingSchemaError(msg) { |
67 | 2 | ErrorAbstract.call(this, msg); |
68 | }; | |
69 | ||
70 | 1 | errors.MissingSchemaError.prototype = new ErrorAbstract(); |
71 |
Line | Hits | Source |
---|---|---|
1 | 1 | var errors = require('./errors'); |
2 | 1 | var utils = require('./utils'); |
3 | 1 | var signing = require('./signing'); |
4 | ||
5 | 1 | var StreamFeed = function() { |
6 | /** | |
7 | * Manage api calls for specific feeds | |
8 | * The feed object contains convenience functions such add activity, remove activity etc | |
9 | * @class StreamFeed | |
10 | */ | |
11 | 545 | this.initialize.apply(this, arguments); |
12 | }; | |
13 | ||
14 | 1 | StreamFeed.prototype = { |
15 | initialize: function(client, feedSlug, userId, token) { | |
16 | /** | |
17 | * Initialize a feed object | |
18 | * @method intialize | |
19 | * @memberof StreamFeed.prototype | |
20 | * @param {StreamClient} client - The stream client this feed is constructed from | |
21 | * @param {string} feedSlug - The feed slug | |
22 | * @param {string} userId - The user id | |
23 | * @param {string} [token] - The authentication token | |
24 | */ | |
25 | 545 | this.client = client; |
26 | 545 | this.slug = feedSlug; |
27 | 545 | this.userId = userId; |
28 | 545 | this.id = this.slug + ':' + this.userId; |
29 | 545 | this.token = token; |
30 | ||
31 | 545 | this.feedUrl = this.id.replace(':', '/'); |
32 | 545 | this.feedTogether = this.id.replace(':', ''); |
33 | 545 | this.signature = this.feedTogether + ' ' + this.token; |
34 | ||
35 | // faye setup | |
36 | 545 | this.notificationChannel = 'site-' + this.client.appId + '-feed-' + this.feedTogether; |
37 | }, | |
38 | ||
39 | addActivity: function(activity, callback) { | |
40 | /** | |
41 | * Adds the given activity to the feed and | |
42 | * calls the specified callback | |
43 | * @method addActivity | |
44 | * @memberof StreamFeed.prototype | |
45 | * @param {object} activity - The activity to add | |
46 | * @param {requestCallback} callback - Callback to call on completion | |
47 | * @return {Promise} Promise object | |
48 | */ | |
49 | 21 | activity = this.client.signActivity(activity); |
50 | ||
51 | 21 | return this.client.post({ |
52 | url: 'feed/' + this.feedUrl + '/', | |
53 | body: activity, | |
54 | signature: this.signature, | |
55 | }, callback); | |
56 | }, | |
57 | ||
58 | removeActivity: function(activityId, callback) { | |
59 | /** | |
60 | * Removes the activity by activityId | |
61 | * @method removeActivity | |
62 | * @memberof StreamFeed.prototype | |
63 | * @param {string} activityId Identifier of activity to remove | |
64 | * @param {requestCallback} callback Callback to call on completion | |
65 | * @return {Promise} Promise object | |
66 | * @example | |
67 | * feed.removeActivity(activityId); | |
68 | * @example | |
69 | * feed.removeActivity({'foreign_id': foreignId}); | |
70 | */ | |
71 | 2 | var identifier = (activityId.foreignId) ? activityId.foreignId : activityId; |
72 | 2 | var params = {}; |
73 | 2 | if (activityId.foreignId) { |
74 | 1 | params['foreign_id'] = '1'; |
75 | } | |
76 | ||
77 | 2 | return this.client.delete({ |
78 | url: 'feed/' + this.feedUrl + '/' + identifier + '/', | |
79 | qs: params, | |
80 | signature: this.signature, | |
81 | }, callback); | |
82 | }, | |
83 | ||
84 | addActivities: function(activities, callback) { | |
85 | /** | |
86 | * Adds the given activities to the feed and calls the specified callback | |
87 | * @method addActivities | |
88 | * @memberof StreamFeed.prototype | |
89 | * @param {Array} activities Array of activities to add | |
90 | * @param {requestCallback} callback Callback to call on completion | |
91 | * @return {Promise} XHR request object | |
92 | */ | |
93 | 4 | activities = this.client.signActivities(activities); |
94 | 4 | var data = { |
95 | activities: activities, | |
96 | }; | |
97 | 4 | var xhr = this.client.post({ |
98 | url: 'feed/' + this.feedUrl + '/', | |
99 | body: data, | |
100 | signature: this.signature, | |
101 | }, callback); | |
102 | 4 | return xhr; |
103 | }, | |
104 | ||
105 | follow: function(targetSlug, targetUserId, options, callback) { | |
106 | /** | |
107 | * Follows the given target feed | |
108 | * @method follow | |
109 | * @memberof StreamFeed.prototype | |
110 | * @param {string} targetSlug Slug of the target feed | |
111 | * @param {string} targetUserId User identifier of the target feed | |
112 | * @param {object} options Additional options | |
113 | * @param {number} options.activityCopyLimit Limit the amount of activities copied over on follow | |
114 | * @param {requestCallback} callback Callback to call on completion | |
115 | * @return {Promise} Promise object | |
116 | * @example feed.follow('user', '1'); | |
117 | * @example feed.follow('user', '1', callback); | |
118 | * @example feed.follow('user', '1', options, callback); | |
119 | */ | |
120 | 8 | utils.validateFeedSlug(targetSlug); |
121 | 7 | utils.validateUserId(targetUserId); |
122 | ||
123 | 6 | var activityCopyLimit; |
124 | 6 | var last = arguments[arguments.length - 1]; |
125 | // callback is always the last argument | |
126 | 6 | callback = (last.call) ? last : undefined; |
127 | 6 | var target = targetSlug + ':' + targetUserId; |
128 | ||
129 | // check for additional options | |
130 | 6 | if (options && !options.call) { |
131 | 1 | if (typeof options.limit !== "undefined" && options.limit !== null) { |
132 | 1 | activityCopyLimit = options.limit; |
133 | } | |
134 | } | |
135 | ||
136 | 6 | var body = { |
137 | target: target, | |
138 | }; | |
139 | ||
140 | 6 | if (typeof activityCopyLimit !== "undefined" && activityCopyLimit !== null) { |
141 | 1 | body['activity_copy_limit'] = activityCopyLimit; |
142 | } | |
143 | ||
144 | 6 | return this.client.post({ |
145 | url: 'feed/' + this.feedUrl + '/following/', | |
146 | body: body, | |
147 | signature: this.signature, | |
148 | }, callback); | |
149 | }, | |
150 | ||
151 | unfollow: function(targetSlug, targetUserId, optionsOrCallback, callback) { | |
152 | /** | |
153 | * Unfollow the given feed | |
154 | * @method unfollow | |
155 | * @memberof StreamFeed.prototype | |
156 | * @param {string} targetSlug Slug of the target feed | |
157 | * @param {string} targetUserId [description] | |
158 | * @param {requestCallback|object} optionsOrCallback | |
159 | * @param {boolean} optionOrCallback.keepHistory when provided the activities from target | |
160 | * feed will not be kept in the feed | |
161 | * @param {requestCallback} callback Callback to call on completion | |
162 | * @return {object} XHR request object | |
163 | * @example feed.unfollow('user', '2', callback); | |
164 | */ | |
165 | 2 | var options = {}, qs = {}; |
166 | 3 | if (typeof optionsOrCallback === 'function') callback = optionsOrCallback; |
167 | 3 | if (typeof optionsOrCallback === 'object') options = optionsOrCallback; |
168 | 3 | if (typeof options.keepHistory === 'boolean' && options.keepHistory) qs['keep_history'] = '1'; |
169 | ||
170 | 2 | utils.validateFeedSlug(targetSlug); |
171 | 2 | utils.validateUserId(targetUserId); |
172 | 2 | var targetFeedId = targetSlug + ':' + targetUserId; |
173 | 2 | var xhr = this.client.delete({ |
174 | url: 'feed/' + this.feedUrl + '/following/' + targetFeedId + '/', | |
175 | qs: qs, | |
176 | signature: this.signature, | |
177 | }, callback); | |
178 | 2 | return xhr; |
179 | }, | |
180 | ||
181 | following: function(options, callback) { | |
182 | /** | |
183 | * List which feeds this feed is following | |
184 | * @method following | |
185 | * @memberof StreamFeed.prototype | |
186 | * @param {object} options Additional options | |
187 | * @param {string} options.filter Filter to apply on search operation | |
188 | * @param {requestCallback} callback Callback to call on completion | |
189 | * @return {Promise} Promise object | |
190 | * @example feed.following({limit:10, filter: ['user:1', 'user:2']}, callback); | |
191 | */ | |
192 | 2 | if (options !== undefined && options.filter) { |
193 | 1 | options.filter = options.filter.join(','); |
194 | } | |
195 | ||
196 | 2 | return this.client.get({ |
197 | url: 'feed/' + this.feedUrl + '/following/', | |
198 | qs: options, | |
199 | signature: this.signature, | |
200 | }, callback); | |
201 | }, | |
202 | ||
203 | followers: function(options, callback) { | |
204 | /** | |
205 | * List the followers of this feed | |
206 | * @method followers | |
207 | * @memberof StreamFeed.prototype | |
208 | * @param {object} options Additional options | |
209 | * @param {string} options.filter Filter to apply on search operation | |
210 | * @param {requestCallback} callback Callback to call on completion | |
211 | * @return {Promise} Promise object | |
212 | * @example | |
213 | * feed.followers({limit:10, filter: ['user:1', 'user:2']}, callback); | |
214 | */ | |
215 | 1 | if (options !== undefined && options.filter) { |
216 | 0 | options.filter = options.filter.join(','); |
217 | } | |
218 | ||
219 | 1 | return this.client.get({ |
220 | url: 'feed/' + this.feedUrl + '/followers/', | |
221 | qs: options, | |
222 | signature: this.signature, | |
223 | }, callback); | |
224 | }, | |
225 | ||
226 | get: function(options, callback) { | |
227 | /** | |
228 | * Reads the feed | |
229 | * @method get | |
230 | * @memberof StreamFeed.prototype | |
231 | * @param {object} options Additional options | |
232 | * @param {requestCallback} callback Callback to call on completion | |
233 | * @return {Promise} Promise object | |
234 | * @example feed.get({limit: 10, id_lte: 'activity-id'}) | |
235 | * @example feed.get({limit: 10, mark_seen: true}) | |
236 | */ | |
237 | 21 | if (options && options['mark_read'] && options['mark_read'].join) { |
238 | 0 | options['mark_read'] = options['mark_read'].join(','); |
239 | } | |
240 | ||
241 | 21 | if (options && options['mark_seen'] && options['mark_seen'].join) { |
242 | 0 | options['mark_seen'] = options['mark_seen'].join(','); |
243 | } | |
244 | ||
245 | 21 | return this.client.get({ |
246 | url: 'feed/' + this.feedUrl + '/', | |
247 | qs: options, | |
248 | signature: this.signature, | |
249 | }, callback); | |
250 | }, | |
251 | ||
252 | getFayeClient: function() { | |
253 | /** | |
254 | * Returns the current faye client object | |
255 | * @method getFayeClient | |
256 | * @memberof StreamFeed.prototype | |
257 | * @access private | |
258 | * @return {object} Faye client | |
259 | */ | |
260 | 11 | return this.client.getFayeClient(); |
261 | }, | |
262 | ||
263 | subscribe: function(callback) { | |
264 | /** | |
265 | * Subscribes to any changes in the feed, return a promise | |
266 | * @method subscribe | |
267 | * @memberof StreamFeed.prototype | |
268 | * @param {function} callback Callback to call on completion | |
269 | * @return {Promise} Promise object | |
270 | * @example | |
271 | * feed.subscribe(callback).then(function(){ | |
272 | * console.log('we are now listening to changes'); | |
273 | * }); | |
274 | */ | |
275 | 9 | if (!this.client.appId) { |
276 | 1 | throw new errors.SiteError('Missing app id, which is needed to subscribe, use var client = stream.connect(key, secret, appId);'); |
277 | } | |
278 | ||
279 | 8 | this.client.subscriptions['/' + this.notificationChannel] = { |
280 | token: this.token, | |
281 | userId: this.notificationChannel, | |
282 | }; | |
283 | ||
284 | 8 | return this.getFayeClient().subscribe('/' + this.notificationChannel, callback); |
285 | }, | |
286 | ||
287 | getReadOnlyToken: function() { | |
288 | /** | |
289 | * Returns a token that allows only read operations | |
290 | * | |
291 | * @method getReadOnlyToken | |
292 | * @memberof StreamClient.prototype | |
293 | * @param {string} feedSlug - The feed slug to get a read only token for | |
294 | * @param {string} userId - The user identifier | |
295 | * @return {string} token | |
296 | * @example | |
297 | * client.getReadOnlyToken('user', '1'); | |
298 | */ | |
299 | 106 | var feedId = '' + this.slug + this.userId; |
300 | 106 | return signing.JWTScopeToken(this.client.apiSecret, '*', 'read', { feedId: feedId, expireTokens: this.client.expireTokens }); |
301 | }, | |
302 | ||
303 | getReadWriteToken: function() { | |
304 | /** | |
305 | * Returns a token that allows read and write operations | |
306 | * | |
307 | * @method getReadWriteToken | |
308 | * @memberof StreamClient.prototype | |
309 | * @param {string} feedSlug - The feed slug to get a read only token for | |
310 | * @param {string} userId - The user identifier | |
311 | * @return {string} token | |
312 | * @example | |
313 | * client.getReadWriteToken('user', '1'); | |
314 | */ | |
315 | 2 | var feedId = '' + this.slug + this.userId; |
316 | 2 | return signing.JWTScopeToken(this.client.apiSecret, '*', '*', { feedId: feedId, expireTokens: this.client.expireTokens }); |
317 | }, | |
318 | }; | |
319 | ||
320 | 1 | module.exports = StreamFeed; |
Line | Hits | Source |
---|---|---|
1 | 1 | var Promise = require('faye/src/util/promise'); |
2 | ||
3 | 1 | module.exports = Promise; |
Line | Hits | Source |
---|---|---|
1 | 1 | var crypto = require('crypto'); |
2 | 1 | var jwt = require('jsonwebtoken'); |
3 | 1 | var JWS_REGEX = /^[a-zA-Z0-9\-_]+?\.[a-zA-Z0-9\-_]+?\.([a-zA-Z0-9\-_]+)?$/; |
4 | 1 | var Base64 = require('Base64'); |
5 | ||
6 | 1 | function makeUrlSafe(s) { |
7 | /* | |
8 | * Makes the given base64 encoded string urlsafe | |
9 | */ | |
10 | 434 | var escaped = s.replace(/\+/g, '-').replace(/\//g, '_'); |
11 | 434 | return escaped.replace(/^=+/, '').replace(/=+$/, ''); |
12 | } | |
13 | ||
14 | 1 | function decodeBase64Url(base64UrlString) { |
15 | 14 | try { |
16 | 14 | return Base64.atob(toBase64(base64UrlString)); |
17 | } catch (e) { | |
18 | 1 | if (e.name === 'InvalidCharacterError') { |
19 | 1 | return undefined; |
20 | } else { | |
21 | 0 | throw e; |
22 | } | |
23 | } | |
24 | } | |
25 | ||
26 | 1 | function safeJsonParse(thing) { |
27 | 14 | if (typeof (thing) === 'object') return thing; |
28 | 14 | try { |
29 | 14 | return JSON.parse(thing); |
30 | } catch (e) { | |
31 | 1 | return undefined; |
32 | } | |
33 | } | |
34 | ||
35 | 1 | function padString(string) { |
36 | 14 | var segmentLength = 4; |
37 | 14 | var diff = string.length % segmentLength; |
38 | 14 | if (!diff) |
39 | 13 | return string; |
40 | 1 | var padLength = segmentLength - diff; |
41 | ||
42 | 1 | while (padLength--) |
43 | 3 | string += '='; |
44 | 1 | return string; |
45 | } | |
46 | ||
47 | 1 | function toBase64(base64UrlString) { |
48 | 14 | var b64str = padString(base64UrlString) |
49 | .replace(/\-/g, '+') | |
50 | .replace(/_/g, '/'); | |
51 | 14 | return b64str; |
52 | } | |
53 | ||
54 | 1 | function headerFromJWS(jwsSig) { |
55 | 14 | var encodedHeader = jwsSig.split('.', 1)[0]; |
56 | 14 | return safeJsonParse(decodeBase64Url(encodedHeader)); |
57 | } | |
58 | ||
59 | 1 | exports.headerFromJWS = headerFromJWS; |
60 | ||
61 | 1 | exports.sign = function(apiSecret, feedId) { |
62 | /* | |
63 | * Setup sha1 based on the secret | |
64 | * Get the digest of the value | |
65 | * Base64 encode the result | |
66 | * | |
67 | * Also see | |
68 | * https://github.com/tbarbugli/stream-ruby/blob/master/lib/stream/signer.rb | |
69 | * https://github.com/tschellenbach/stream-python/blob/master/stream/signing.py | |
70 | * | |
71 | * Steps | |
72 | * apiSecret: tfq2sdqpj9g446sbv653x3aqmgn33hsn8uzdc9jpskaw8mj6vsnhzswuwptuj9su | |
73 | * feedId: flat1 | |
74 | * digest: Q\xb6\xd5+\x82\xd58\xdeu\x80\xc5\xe3\xb8\xa5bL1\xf1\xa3\xdb | |
75 | * token: UbbVK4LVON51gMXjuKViTDHxo9s | |
76 | */ | |
77 | 434 | var hashedSecret = new crypto.createHash('sha1').update(apiSecret).digest(); |
78 | 434 | var hmac = crypto.createHmac('sha1', hashedSecret); |
79 | 434 | var digest = hmac.update(feedId).digest('base64'); |
80 | 434 | var token = makeUrlSafe(digest); |
81 | 434 | return token; |
82 | }; | |
83 | ||
84 | 1 | exports.JWTScopeToken = function(apiSecret, resource, action, opts) { |
85 | /** | |
86 | * Creates the JWT token for feedId, resource and action using the apiSecret | |
87 | * @method JWTScopeToken | |
88 | * @memberof signing | |
89 | * @private | |
90 | * @param {string} apiSecret - API Secret key | |
91 | * @param {string} resource - JWT payload resource | |
92 | * @param {string} action - JWT payload action | |
93 | * @param {object} [options] - Optional additional options | |
94 | * @param {string} [options.feedId] - JWT payload feed identifier | |
95 | * @param {string} [options.userId] - JWT payload user identifier | |
96 | * @return {string} JWT Token | |
97 | */ | |
98 | 121 | var options = opts || {}, |
99 | noTimestamp = options.expireTokens ? !options.expireTokens : true; | |
100 | 121 | var payload = { |
101 | resource: resource, | |
102 | action: action, | |
103 | }; | |
104 | ||
105 | 121 | if (options.feedId) { |
106 | 119 | payload['feed_id'] = options.feedId; |
107 | } | |
108 | ||
109 | 121 | if (options.userId) { |
110 | 2 | payload['user_id'] = options.userId; |
111 | } | |
112 | ||
113 | 121 | var token = jwt.sign(payload, apiSecret, { algorithm: 'HS256', noTimestamp: noTimestamp }); |
114 | 121 | return token; |
115 | }; | |
116 | ||
117 | 1 | exports.isJWTSignature = function(signature) { |
118 | /** | |
119 | * check if token is a valid JWT token | |
120 | * @method isJWTSignature | |
121 | * @memberof signing | |
122 | * @private | |
123 | * @param {string} signature - Signature to check | |
124 | * @return {boolean} | |
125 | */ | |
126 | 68 | var token = signature.split(' ')[1] || signature; |
127 | 68 | return JWS_REGEX.test(token) && !!headerFromJWS(token); |
128 | }; | |
129 |
Line | Hits | Source |
---|---|---|
1 | 1 | var errors = require('./errors'); |
2 | 1 | var validRe = /^[\w-]+$/; |
3 | ||
4 | 1 | function validateFeedId(feedId) { |
5 | /* | |
6 | * Validate that the feedId matches the spec user:1 | |
7 | */ | |
8 | 2 | var parts = feedId.split(':'); |
9 | 2 | if (parts.length !== 2) { |
10 | 1 | throw new errors.FeedError('Invalid feedId, expected something like user:1 got ' + feedId); |
11 | } | |
12 | ||
13 | 1 | var feedSlug = parts[0]; |
14 | 1 | var userId = parts[1]; |
15 | 1 | validateFeedSlug(feedSlug); |
16 | 1 | validateUserId(userId); |
17 | 1 | return feedId; |
18 | } | |
19 | ||
20 | 1 | exports.validateFeedId = validateFeedId; |
21 | ||
22 | 1 | function validateFeedSlug(feedSlug) { |
23 | /* | |
24 | * Validate that the feedSlug matches \w | |
25 | */ | |
26 | 558 | var valid = validRe.test(feedSlug); |
27 | 558 | if (!valid) { |
28 | 2 | throw new errors.FeedError('Invalid feedSlug, please use letters, numbers or _ got: ' + feedSlug); |
29 | } | |
30 | ||
31 | 556 | return feedSlug; |
32 | } | |
33 | ||
34 | 1 | exports.validateFeedSlug = validateFeedSlug; |
35 | ||
36 | 1 | function validateUserId(userId) { |
37 | /* | |
38 | * Validate the userId matches \w | |
39 | */ | |
40 | 556 | var valid = validRe.test(userId); |
41 | 556 | if (!valid) { |
42 | 2 | throw new errors.FeedError('Invalid feedSlug, please use letters, numbers or _ got: ' + userId); |
43 | } | |
44 | ||
45 | 554 | return userId; |
46 | } | |
47 | ||
48 | 1 | exports.validateUserId = validateUserId; |
49 | ||
50 | 1 | function rfc3986(str) { |
51 | 2 | return str.replace(/[!'()*]/g, function(c) { |
52 | 0 | return '%' + c.charCodeAt(0).toString(16).toUpperCase(); |
53 | }); | |
54 | } | |
55 | ||
56 | 1 | exports.rfc3986 = rfc3986; |
57 |