From fe7b340eb4bd7498e27a6f43a6d0ddde025d8723 Mon Sep 17 00:00:00 2001 From: Matthisk Heimensen Date: Fri, 23 Oct 2015 16:21:22 +0200 Subject: [PATCH] Added jscs styleguide, fixed all styleguide issues --- .jscsrc | 8 + .jshintrc | 4 +- gulpfile.js | 12 +- package.json | 2 + src/getstream.js | 54 ++-- src/lib/batch_operations.js | 26 +- src/lib/client.js | 500 ++++++++++++++++++------------------ src/lib/errors.js | 26 +- src/lib/feed.js | 366 +++++++++++++------------- src/lib/parse_request.js | 36 +-- src/lib/signing.js | 96 +++---- src/lib/utils.js | 65 ++--- 12 files changed, 620 insertions(+), 575 deletions(-) create mode 100644 .jscsrc diff --git a/.jscsrc b/.jscsrc new file mode 100644 index 00000000..90a5aa8e --- /dev/null +++ b/.jscsrc @@ -0,0 +1,8 @@ +{ + "preset": "airbnb", + "requirePaddingNewLinesBeforeLineComments": null, + "disallowQuotedKeysInObjects": false, + "disallowMultipleVarDecl": false, + "requireDotNotation": false, + "safeContextKeyword": null +} diff --git a/.jshintrc b/.jshintrc index 7a48c71d..a945566e 100644 --- a/.jshintrc +++ b/.jshintrc @@ -21,7 +21,7 @@ "nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters. "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) "plusplus" : false, // true: Prohibit use of `++` and `--` - "quotmark" : false, // Quotation mark consistency: + "quotmark" : "single", // Quotation mark consistency: // false : do nothing (default) // true : ensure whatever is used is consistent // "single" : require single quotes @@ -62,7 +62,7 @@ "proto" : false, // true: Tolerate using the `__proto__` property "scripturl" : false, // true: Tolerate script-targeted URLs "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` - "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation + "sub" : true, // true: Tolerate using `[]` notation when it can still be expressed in dot notation "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` "validthis" : false, // true: Tolerate using this in a non-constructor function diff --git a/gulpfile.js b/gulpfile.js index 78f699b8..f196dc01 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,6 +1,8 @@ var gulp = require('gulp'); var gutil = require('gulp-util'); var jshint = require('gulp-jshint'); +var jscs = require('gulp-jscs'); +var stylish = require('gulp-jscs-stylish'); var mocha = require('gulp-mocha'); var fs = require('fs'); var git = require('gulp-git'); @@ -38,12 +40,12 @@ function runSynchronized(tasks, callback){ */ -// check for jshint errors +// check for jshint and jscs errors gulp.task('lint', function() { - return gulp.src('./src/lib/*.js') - .pipe(jshint({ - lookup: true - })) + return gulp.src('./src/**/*.js') + .pipe(jshint({ lookup: true })) + .pipe(jscs()) + .pipe(stylish.combineWithHintResults()) .pipe(jshint.reporter('jshint-stylish')); }); diff --git a/package.json b/package.json index f7d213b9..cdc0b2bb 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,8 @@ "gulp-browserify": "^0.5.0", "gulp-bump": "^0.1.8", "gulp-git": "git://github.com/stevelacy/gulp-git.git", + "gulp-jscs": "^3.0.1", + "gulp-jscs-stylish": "^1.2.1", "gulp-jshint": "^1.6.3", "gulp-mocha": "^0.4.1", "gulp-shell": "^0.2.7", diff --git a/src/getstream.js b/src/getstream.js index 26b0e477..b4ad453a 100644 --- a/src/getstream.js +++ b/src/getstream.js @@ -8,32 +8,34 @@ var errors = require('./lib/errors'); var request = require('request'); function connect(apiKey, apiSecret, appId, options) { - /* - * Usage - * stream.connect(apiKey, apiSecret) - * or if you want to be able to subscribe and listen - * for changes - * stream.connect(apiKey, apiSecret, appId) - * or on heroku - * stream.connect(streamURL) - * where streamURL looks like this - * https://thierry:pass@getstream.io/?app=1 - * - */ - if (typeof(process) != "undefined" && process.env.STREAM_URL && !apiKey) { - var parts = /https\:\/\/(\w+)\:(\w+)\@([\w-]*).*\?app_id=(\d+)/.exec(process.env.STREAM_URL); - apiKey = parts[1]; - apiSecret = parts[2]; - var location = parts[3]; - appId = parts[4]; - if (options === undefined) { - options = {}; - } - if (location != 'getstream') { - options.location = location; - } - } - return new StreamClient(apiKey, apiSecret, appId, options); + /* + * Usage + * stream.connect(apiKey, apiSecret) + * or if you want to be able to subscribe and listen + * for changes + * stream.connect(apiKey, apiSecret, appId) + * or on heroku + * stream.connect(streamURL) + * where streamURL looks like this + * https://thierry:pass@getstream.io/?app=1 + * + */ + if (typeof (process) !== 'undefined' && process.env.STREAM_URL && !apiKey) { + var parts = /https\:\/\/(\w+)\:(\w+)\@([\w-]*).*\?app_id=(\d+)/.exec(process.env.STREAM_URL); + apiKey = parts[1]; + apiSecret = parts[2]; + var location = parts[3]; + appId = parts[4]; + if (options === undefined) { + options = {}; + } + + if (location !== 'getstream') { + options.location = location; + } + } + + return new StreamClient(apiKey, apiSecret, appId, options); } module.exports.connect = connect; diff --git a/src/lib/batch_operations.js b/src/lib/batch_operations.js index c04b7a22..8c23b55b 100644 --- a/src/lib/batch_operations.js +++ b/src/lib/batch_operations.js @@ -3,29 +3,29 @@ var request = require('request'); var errors = require('./errors'); module.exports = { - addToMany: function(activity, feeds, callback) { + addToMany: function(activity, feeds, callback) { var req = this.makeSignedRequest({ url: 'feed/add_to_many/', body: { - 'activity': activity, - 'feeds': feeds - } + activity: activity, + feeds: feeds, + }, }, callback); return req; }, - followMany: function(follows, callback) { + followMany: function(follows, callback) { var req = this.makeSignedRequest({ url: 'follow_many/', - body: follows - }, callback); + body: follows, + }, callback); return req; }, - makeSignedRequest: function(kwargs, cb) { - if(!this.apiSecret) { + makeSignedRequest: function(kwargs, cb) { + if (!this.apiSecret) { throw new errors.SiteError('Missing secret, which is needed to perform signed requests, use var client = stream.connect(key, secret);'); } @@ -34,7 +34,7 @@ module.exports = { kwargs.url = this.enrichUrl(kwargs.url); kwargs.json = true; kwargs.method = 'POST'; - kwargs.headers = { 'X-Api-Key' : this.apiKey }; + kwargs.headers = { 'X-Api-Key': this.apiKey }; var callback = this.wrapCallback(cb); var req = request(kwargs, callback); @@ -42,9 +42,9 @@ module.exports = { httpSignature.sign(req, { algorithm: 'hmac-sha256', key: this.apiSecret, - keyId: this.apiKey + keyId: this.apiKey, }); - + return request; - } + }, }; diff --git a/src/lib/client.js b/src/lib/client.js index 36e5a0ab..84c165a5 100644 --- a/src/lib/client.js +++ b/src/lib/client.js @@ -6,295 +6,307 @@ var utils = require('./utils'); var BatchOperations = require('./batch_operations'); var StreamClient = function() { - this.initialize.apply(this, arguments); + this.initialize.apply(this, arguments); }; StreamClient.prototype = { - baseUrl: 'https://api.getstream.io/api/', - - initialize: function(apiKey, apiSecret, appId, options) { - /* - * initialize is not directly called by via stream.connect, ie: - * stream.connect(apiKey, apiSecret) - * secret is optional and only used in server side mode - * stream.connect(apiKey, null, appId); - */ - this.apiKey = apiKey; - this.apiSecret = apiSecret; - this.appId = appId; - this.options = options || {}; - this.version = this.options.version || 'v1.0'; - this.fayeUrl = this.options.fayeUrl || 'https://faye.getstream.io/faye'; - this.fayeClient = null; - // track a source name for the api calls, ie get started or databrowser - this.group = this.options.group || 'unspecified'; - // track subscriptions made on feeds created by this client - this.subscriptions = {}; - // which data center to use - this.location = this.options.location; - if (this.location) { - this.baseUrl = 'https://' + this.location + '-api.getstream.io/api/'; - } - if (typeof(process) !== "undefined" && process.env.LOCAL) { - this.baseUrl = 'http://localhost:8000/api/'; - } - if (typeof(process) !== "undefined" && process.env.LOCAL_FAYE) { - this.fayeUrl = 'http://localhost:9999/faye/'; - } - this.handlers = {}; - this.browser = typeof(window) !== 'undefined'; - this.node = !this.browser; - - if (this.browser && this.apiSecret) { - throw new errors.FeedError('You are publicly sharing your private key. Dont use the private key while in the browser.'); - } - }, + baseUrl: 'https://api.getstream.io/api/', - on: function(event, callback) { - /* - * Support for global event callbacks - * This is useful for generic error and loading handling - * - * client.on('request', callback); - * client.on('response', callback); - * - */ - this.handlers[event] = callback; - }, + initialize: function(apiKey, apiSecret, appId, options) { + /* + * initialize is not directly called by via stream.connect, ie: + * stream.connect(apiKey, apiSecret) + * secret is optional and only used in server side mode + * stream.connect(apiKey, null, appId); + */ + this.apiKey = apiKey; + this.apiSecret = apiSecret; + this.appId = appId; + this.options = options || {}; + this.version = this.options.version || 'v1.0'; + this.fayeUrl = this.options.fayeUrl || 'https://faye.getstream.io/faye'; + this.fayeClient = null; + // track a source name for the api calls, ie get started or databrowser + this.group = this.options.group || 'unspecified'; + // track subscriptions made on feeds created by this client + this.subscriptions = {}; + // which data center to use + this.location = this.options.location; + if (this.location) { + this.baseUrl = 'https://' + this.location + '-api.getstream.io/api/'; + } - off: function(key) { - /* - * client.off() removes all handlers - * client.off(name) removes the specified handler - */ - if (key === undefined) { - this.handlers = {}; - } else { - delete this.handlers[key]; - } - }, + if (typeof (process) !== 'undefined' && process.env.LOCAL) { + this.baseUrl = 'http://localhost:8000/api/'; + } - send: function() { - /* - * Call the given handler with the arguments - */ - var args = Array.prototype.slice.call(arguments); - var key = args[0]; - args = args.slice(1); - if (this.handlers[key]) { - this.handlers[key].apply(this, args); - } - }, + if (typeof (process) !== 'undefined' && process.env.LOCAL_FAYE) { + this.fayeUrl = 'http://localhost:9999/faye/'; + } + this.handlers = {}; + this.browser = typeof (window) !== 'undefined'; + this.node = !this.browser; - wrapCallback: function(cb) { - var client = this; + if (this.browser && this.apiSecret) { + throw new errors.FeedError('You are publicly sharing your private key. Dont use the private key while in the browser.'); + } + }, - function callback() { - // first hit the global callback, subsequently forward - var args = Array.prototype.slice.call(arguments); - var sendArgs = ['response'].concat(args); - client.send.apply(client, sendArgs); - if (cb !== undefined) { - cb.apply(client, args); - } - } - return callback; - }, + on: function(event, callback) { + /* + * Support for global event callbacks + * This is useful for generic error and loading handling + * + * client.on('request', callback); + * client.on('response', callback); + * + */ + this.handlers[event] = callback; + }, - userAgent: function() { - var description = (this.node) ? 'node' : 'browser'; - // TODO: get the version here in a way which works in both and browserify - var version = 'unknown'; - return 'stream-javascript-client-' + description + '-' + version; - }, + off: function(key) { + /* + * client.off() removes all handlers + * client.off(name) removes the specified handler + */ + if (key === undefined) { + this.handlers = {}; + } else { + delete this.handlers[key]; + } + }, - getReadOnlyToken: function(feedSlug, userId) { - /* - * Returns a token that allows only read operations - * - * client.getReadOnlyToken('user', '1'); - */ - var feedId = '' + feedSlug + userId; - return signing.JWTScopeToken(this.apiSecret, feedId, '*', 'read'); - }, + send: function() { + /* + * Call the given handler with the arguments + */ + var args = Array.prototype.slice.call(arguments); + var key = args[0]; + args = args.slice(1); + if (this.handlers[key]) { + this.handlers[key].apply(this, args); + } + }, + + wrapCallback: function(cb) { + var client = this; + + function callback() { + // first hit the global callback, subsequently forward + var args = Array.prototype.slice.call(arguments); + var sendArgs = ['response'].concat(args); + client.send.apply(client, sendArgs); + if (cb !== undefined) { + cb.apply(client, args); + } + } - getReadWriteToken: function(feedSlug, userId) { - /* - * Returns a token that allows read and write operations - * - * client.getReadWriteToken('user', '1'); - */ - var feedId = '' + feedSlug + userId; - return signing.JWTScopeToken(this.apiSecret, feedId, '*', '*'); - }, + return callback; + }, - feed: function(feedSlug, userId, token, siteId, options) { - /* - * Returns a feed object for the given feed id and token - * Example: - * - * client.feed('user', '1', 'token2'); - */ - - options = options || {}; - - if (!feedSlug || !userId) { - throw new errors.FeedError('Please provide a feed slug and user id, ie client.feed("user", "1")'); - } - - if (feedSlug.indexOf(':') !== -1) { - throw new errors.FeedError('Please initialize the feed using client.feed("user", "1") not client.feed("user:1")'); - } - - utils.validateFeedSlug(feedSlug); - utils.validateUserId(userId); - - // raise an error if there is no token - if (!this.apiSecret && !token) { - throw new errors.FeedError('Missing token, in client side mode please provide a feed secret'); - } - - // create the token in server side mode - if (this.apiSecret && !token) { - var feedId = '' + feedSlug + userId; - // use scoped token if read-only access is necessary - token = options.readOnly ? this.getReadOnlyToken(feedSlug, userId) : signing.sign(this.apiSecret, feedId); - } - - var feed = new StreamFeed(this, feedSlug, userId, token, siteId); - return feed; - }, + userAgent: function() { + var description = (this.node) ? 'node' : 'browser'; + // TODO: get the version here in a way which works in both and browserify + var version = 'unknown'; + return 'stream-javascript-client-' + description + '-' + version; + }, - enrichUrl: function(relativeUrl) { - /* - * Combines the base url with version and the relative url - */ - var url = this.baseUrl + this.version + '/' + relativeUrl; - return url; - }, + getReadOnlyToken: function(feedSlug, userId) { + /* + * Returns a token that allows only read operations + * + * client.getReadOnlyToken('user', '1'); + */ + var feedId = '' + feedSlug + userId; + return signing.JWTScopeToken(this.apiSecret, feedId, '*', 'read'); + }, - enrichKwargs: function(kwargs) { - /* - * Adds the API key and the signature - */ - kwargs.url = this.enrichUrl(kwargs.url); - if (kwargs.qs === undefined) { - kwargs.qs = {}; - } - kwargs.qs.api_key = this.apiKey; - kwargs.qs.location = this.group; - kwargs.json = true; - var signature = kwargs.signature || this.signature; - kwargs.headers = {}; - - // auto-detect authentication type and set HTTP headers accordingly - if (signing.isJWTSignature(signature)) { - kwargs.headers['stream-auth-type'] = 'jwt'; - signature = signature.split(' ').reverse()[0]; - } else { - kwargs.headers['stream-auth-type'] = 'simple'; - } - - kwargs.headers.Authorization = signature; - kwargs.headers['X-Stream-Client'] = this.userAgent(); - return kwargs; - }, + getReadWriteToken: function(feedSlug, userId) { + /* + * Returns a token that allows read and write operations + * + * client.getReadWriteToken('user', '1'); + */ + var feedId = '' + feedSlug + userId; + return signing.JWTScopeToken(this.apiSecret, feedId, '*', '*'); + }, - signActivity: function(activity) { - return this.signActivities([activity])[0]; - }, + feed: function(feedSlug, userId, token, siteId, options) { + /* + * Returns a feed object for the given feed id and token + * Example: + * + * client.feed('user', '1', 'token2'); + */ - signActivities: function(activities) { - /* - * We automatically sign the to parameter when in server side mode - */ - if (!this.apiSecret) { - return activities; - } - - for (var i = 0; i < activities.length; i++) { - var activity = activities[i]; - var to = activity.to || []; - var signedTo = []; - for (var j = 0; j < to.length; j++) { - var feedId = to[j]; - var feedSlug = feedId.split(':')[0]; - var userId = feedId.split(':')[1]; - var token = this.feed(feedSlug, userId).token; - var signedFeed = feedId + ' ' + token; - signedTo.push(signedFeed); - } - activity.to = signedTo; - } - return activities; - }, + options = options || {}; + + if (!feedSlug || !userId) { + throw new errors.FeedError('Please provide a feed slug and user id, ie client.feed("user", "1")'); + } + + if (feedSlug.indexOf(':') !== -1) { + throw new errors.FeedError('Please initialize the feed using client.feed("user", "1") not client.feed("user:1")'); + } + + utils.validateFeedSlug(feedSlug); + utils.validateUserId(userId); + + // raise an error if there is no token + if (!this.apiSecret && !token) { + throw new errors.FeedError('Missing token, in client side mode please provide a feed secret'); + } - getFayeAuthorization : function() { + // create the token in server side mode + if (this.apiSecret && !token) { + var feedId = '' + feedSlug + userId; + // use scoped token if read-only access is necessary + token = options.readOnly ? this.getReadOnlyToken(feedSlug, userId) : signing.sign(this.apiSecret, feedId); + } + + var feed = new StreamFeed(this, feedSlug, userId, token, siteId); + return feed; + }, + + enrichUrl: function(relativeUrl) { + /* + * Combines the base url with version and the relative url + */ + var url = this.baseUrl + this.version + '/' + relativeUrl; + return url; + }, + + enrichKwargs: function(kwargs) { + /* + * Adds the API key and the signature + */ + kwargs.url = this.enrichUrl(kwargs.url); + if (kwargs.qs === undefined) { + kwargs.qs = {}; + } + + kwargs.qs['api_key'] = this.apiKey; + kwargs.qs.location = this.group; + kwargs.json = true; + var signature = kwargs.signature || this.signature; + kwargs.headers = {}; + + // auto-detect authentication type and set HTTP headers accordingly + if (signing.isJWTSignature(signature)) { + kwargs.headers['stream-auth-type'] = 'jwt'; + signature = signature.split(' ').reverse()[0]; + } else { + kwargs.headers['stream-auth-type'] = 'simple'; + } + + kwargs.headers.Authorization = signature; + kwargs.headers['X-Stream-Client'] = this.userAgent(); + return kwargs; + }, + + signActivity: function(activity) { + return this.signActivities([activity])[0]; + }, + + signActivities: function(activities) { + /* + * We automatically sign the to parameter when in server side mode + */ + if (!this.apiSecret) { + return activities; + } + + for (var i = 0; i < activities.length; i++) { + var activity = activities[i]; + var to = activity.to || []; + var signedTo = []; + for (var j = 0; j < to.length; j++) { + var feedId = to[j]; + var feedSlug = feedId.split(':')[0]; + var userId = feedId.split(':')[1]; + var token = this.feed(feedSlug, userId).token; + var signedFeed = feedId + ' ' + token; + signedTo.push(signedFeed); + } + + activity.to = signedTo; + } + + return activities; + }, + + getFayeAuthorization: function() { var apiKey = this.apiKey, self = this; + return { - incoming : function(message, callback) { + incoming: function(message, callback) { callback(message); }, - outgoing : function(message, callback) { - if( message.subscription && self.subscriptions[message.subscription] ) { + + outgoing: function(message, callback) { + if (message.subscription && self.subscriptions[message.subscription]) { var subscription = self.subscriptions[message.subscription]; message.ext = { - 'user_id' : subscription.userId, - 'api_key' : apiKey, - 'signature' : subscription.token + 'user_id': subscription.userId, + 'api_key': apiKey, + 'signature': subscription.token, }; } + callback(message); - } + }, }; }, - getFayeClient : function() { + getFayeClient: function() { var Faye = require('faye'); if (this.fayeClient === null) { this.fayeClient = new Faye.Client(this.fayeUrl); var authExtension = this.getFayeAuthorization(); this.fayeClient.addExtension(authExtension); } + return this.fayeClient; }, - /* - * Shortcuts for post, get and delete HTTP methods - * - */ - - get: function(kwargs, cb) { - this.send('request', 'get', kwargs, cb); - kwargs = this.enrichKwargs(kwargs); - kwargs.method = 'GET'; - var callback = this.wrapCallback(cb); - return request(kwargs, callback); - }, - post: function(kwargs, cb) { - this.send('request', 'post', kwargs, cb); - kwargs = this.enrichKwargs(kwargs); - kwargs.method = 'POST'; - var callback = this.wrapCallback(cb); - return request(kwargs, callback); - }, - delete: function(kwargs, cb) { - this.send('request', 'delete', kwargs, cb); - kwargs = this.enrichKwargs(kwargs); - kwargs.method = 'DELETE'; - var callback = this.wrapCallback(cb); - return request(kwargs, callback); - }, + /* + * Shortcuts for post, get and delete HTTP methods + * + */ + + get: function(kwargs, cb) { + this.send('request', 'get', kwargs, cb); + kwargs = this.enrichKwargs(kwargs); + kwargs.method = 'GET'; + var callback = this.wrapCallback(cb); + return request(kwargs, callback); + }, + + post: function(kwargs, cb) { + this.send('request', 'post', kwargs, cb); + kwargs = this.enrichKwargs(kwargs); + kwargs.method = 'POST'; + var callback = this.wrapCallback(cb); + return request(kwargs, callback); + }, + + delete: function(kwargs, cb) { + this.send('request', 'delete', kwargs, cb); + kwargs = this.enrichKwargs(kwargs); + kwargs.method = 'DELETE'; + var callback = this.wrapCallback(cb); + return request(kwargs, callback); + }, }; // If we are in a node environment and batchOperations is available add the methods to the prototype of StreamClient -if(BatchOperations) { - for(var key in BatchOperations) { - if(BatchOperations.hasOwnProperty(key)) { +if (BatchOperations) { + for (var key in BatchOperations) { + if (BatchOperations.hasOwnProperty(key)) { StreamClient.prototype[key] = BatchOperations[key]; } } diff --git a/src/lib/errors.js b/src/lib/errors.js index b1443400..2e57ec95 100644 --- a/src/lib/errors.js +++ b/src/lib/errors.js @@ -1,20 +1,20 @@ var errors = module.exports; -var canCapture = ( typeof Error.captureStackTrace === 'function'); +var canCapture = (typeof Error.captureStackTrace === 'function'); var canStack = !!(new Error()).stack; function ErrorAbstract(msg, constructor) { - this.message = msg; + this.message = msg; - Error.call(this, this.message); + Error.call(this, this.message); - if (canCapture) { - Error.captureStackTrace(this, constructor); - } else if (canStack) { - this.stack = (new Error()).stack; - } else { - this.stack = ''; - } + if (canCapture) { + Error.captureStackTrace(this, constructor); + } else if (canStack) { + this.stack = (new Error()).stack; + } else { + this.stack = ''; + } } errors._Abstract = ErrorAbstract; @@ -25,12 +25,14 @@ ErrorAbstract.prototype = new Error(); * @param {String} [msg] - An error message that will probably end up in a log. */ errors.FeedError = function FeedError(msg) { - ErrorAbstract.call(this, msg); + ErrorAbstract.call(this, msg); }; + errors.FeedError.prototype = new ErrorAbstract(); errors.SiteError = function SiteError(msg) { - ErrorAbstract.call(this, msg); + ErrorAbstract.call(this, msg); }; + errors.SiteError.prototype = new ErrorAbstract(); diff --git a/src/lib/feed.js b/src/lib/feed.js index 8115b8fc..82f73acc 100644 --- a/src/lib/feed.js +++ b/src/lib/feed.js @@ -2,207 +2,219 @@ var errors = require('./errors'); var utils = require('./utils'); var StreamFeed = function() { - this.initialize.apply(this, arguments); + this.initialize.apply(this, arguments); }; StreamFeed.prototype = { - /* - * The feed object contains convenience functions such add activity - * remove activity etc - * - */ - initialize : function(client, feedSlug, userId, token, siteId) { - this.client = client; - this.slug = feedSlug; - this.userId = userId; - this.id = this.slug + ':' + this.userId; - this.token = token; - - this.feedUrl = this.id.replace(':', '/'); - this.feedTogether = this.id.replace(':', ''); - this.signature = this.feedTogether + ' ' + this.token; - - // faye setup - this.notificationChannel = 'site-' + this.client.appId + '-feed-' + this.feedTogether; - }, - addActivity : function(activity, callback) { - /* - * Adds the given activity to the feed and - * calls the specified callback - */ - activity = this.client.signActivity(activity); - var xhr = this.client.post({ - 'url' : 'feed/' + this.feedUrl + '/', - 'body' : activity, - 'signature' : this.signature - }, callback); - return xhr; - }, - removeActivity : function(activityId, callback) { - /* - * Removes the activity by activityId - * feed.removeActivity(activityId); - * Or - * feed.removeActivity({'foreign_id': foreignId}); - */ - var identifier = (activityId.foreignId) ? activityId.foreignId : activityId; - var params = {}; - if (activityId.foreignId) { - params.foreign_id = '1'; - } - var xhr = this.client.delete( { - 'url' : 'feed/' + this.feedUrl + '/' + identifier + '/', - 'qs' : params, - 'signature' : this.signature - }, callback); - return xhr; - }, - addActivities : function(activities, callback) { - /* - * Adds the given activities to the feed and - * calls the specified callback - */ - activities = this.client.signActivities(activities); - var data = { - activities : activities - }; - var xhr = this.client.post({ - 'url' : 'feed/' + this.feedUrl + '/', - 'body' : data, - 'signature' : this.signature - }, callback); - return xhr; - }, - follow : function(targetSlug, targetUserId, options, callback) { - /* - * feed.follow('user', '1'); - * or - * feed.follow('user', '1', callback); + /* + * The feed object contains convenience functions such add activity + * remove activity etc + * + */ + initialize: function(client, feedSlug, userId, token, siteId) { + this.client = client; + this.slug = feedSlug; + this.userId = userId; + this.id = this.slug + ':' + this.userId; + this.token = token; + + this.feedUrl = this.id.replace(':', '/'); + this.feedTogether = this.id.replace(':', ''); + this.signature = this.feedTogether + ' ' + this.token; + + // faye setup + this.notificationChannel = 'site-' + this.client.appId + '-feed-' + this.feedTogether; + }, + + addActivity: function(activity, callback) { + /* + * Adds the given activity to the feed and + * calls the specified callback + */ + activity = this.client.signActivity(activity); + var xhr = this.client.post({ + url: 'feed/' + this.feedUrl + '/', + body: activity, + signature: this.signature, + }, callback); + return xhr; + }, + + removeActivity: function(activityId, callback) { + /* + * Removes the activity by activityId + * feed.removeActivity(activityId); + * Or + * feed.removeActivity({'foreign_id': foreignId}); + */ + var identifier = (activityId.foreignId) ? activityId.foreignId : activityId; + var params = {}; + if (activityId.foreignId) { + params['foreign_id'] = '1'; + } + + var xhr = this.client.delete({ + url: 'feed/' + this.feedUrl + '/' + identifier + '/', + qs: params, + signature: this.signature, + }, callback); + return xhr; + }, + + addActivities: function(activities, callback) { + /* + * Adds the given activities to the feed and + * calls the specified callback + */ + activities = this.client.signActivities(activities); + var data = { + activities: activities, + }; + var xhr = this.client.post({ + url: 'feed/' + this.feedUrl + '/', + body: data, + signature: this.signature, + }, callback); + return xhr; + }, + + follow: function(targetSlug, targetUserId, options, callback) { + /* + * feed.follow('user', '1'); + * or + * feed.follow('user', '1', callback); * or * feed.follow('user', '1', options, callback); - */ - utils.validateFeedSlug(targetSlug); - utils.validateUserId(targetUserId); + */ + utils.validateFeedSlug(targetSlug); + utils.validateUserId(targetUserId); - var activityCopyLimit; - var last = arguments[arguments.length - 1]; - // callback is always the last argument - callback = (last.call) ? last : undefined; - var target = targetSlug + ':' + targetUserId; + var activityCopyLimit; + var last = arguments[arguments.length - 1]; + // callback is always the last argument + callback = (last.call) ? last : undefined; + var target = targetSlug + ':' + targetUserId; // check for additional options - if(options && !options.call) { - if(options.limit) { + if (options && !options.call) { + if (options.limit) { activityCopyLimit = options.limit; } } var body = { - 'target': target + target: target, }; - if(activityCopyLimit) { + if (activityCopyLimit) { body['activity_copy_limit'] = activityCopyLimit; } - var xhr = this.client.post({ - 'url' : 'feed/' + this.feedUrl + '/following/', - 'body' : body, - 'signature' : this.signature - }, callback); - return xhr; - }, - unfollow : function(targetSlug, targetUserId, callback) { - /* - * Unfollow the given feed, ie: - * feed.unfollow('user', '2', callback); - */ - utils.validateFeedSlug(targetSlug); - utils.validateUserId(targetUserId); - var targetFeedId = targetSlug + ':' + targetUserId; - var xhr = this.client.delete( { - 'url' : 'feed/' + this.feedUrl + '/following/' + targetFeedId + '/', - 'signature' : this.signature - }, callback); - return xhr; - }, - following : function(options, callback) { - /* - * List which feeds this feed is following - * - * feed.following({limit:10, filter: ['user:1', 'user:2']}, callback); - */ - if (options !== undefined && options.filter) { - options.filter = options.filter.join(','); - } - var xhr = this.client.get({ - 'url' : 'feed/' + this.feedUrl + '/following/', - 'qs' : options, - 'signature' : this.signature - }, callback); - return xhr; - }, - followers : function(options, callback) { - /* - * List the followers of this feed - * - * feed.followers({limit:10, filter: ['user:1', 'user:2']}, callback); - */ - if (options !== undefined && options.filter) { - options.filter = options.filter.join(','); - } - var xhr = this.client.get({ - 'url' : 'feed/' + this.feedUrl + '/followers/', - 'qs' : options, - 'signature' : this.signature - }, callback); - return xhr; - }, - get : function(options, callback) { - /* - * Reads the feed - * - * feed.get({limit: 10, id_lte: 'activity-id'}) - * or - * feed.get({limit: 10, mark_seen: true}) - */ - if (options && options.mark_read && options.mark_read.join) { - options.mark_read = options.mark_read.join(','); - } - if (options && options.mark_seen && options.mark_seen.join) { - options.mark_seen = options.mark_seen.join(','); - } - - var xhr = this.client.get({ - 'url' : 'feed/' + this.feedUrl + '/', - 'qs' : options, - 'signature' : this.signature - }, callback); - return xhr; - }, - - getFayeClient : function() { + var xhr = this.client.post({ + url: 'feed/' + this.feedUrl + '/following/', + body: body, + signature: this.signature, + }, callback); + return xhr; + }, + + unfollow: function(targetSlug, targetUserId, callback) { + /* + * Unfollow the given feed, ie: + * feed.unfollow('user', '2', callback); + */ + utils.validateFeedSlug(targetSlug); + utils.validateUserId(targetUserId); + var targetFeedId = targetSlug + ':' + targetUserId; + var xhr = this.client.delete({ + url: 'feed/' + this.feedUrl + '/following/' + targetFeedId + '/', + signature: this.signature, + }, callback); + return xhr; + }, + + following: function(options, callback) { + /* + * List which feeds this feed is following + * + * feed.following({limit:10, filter: ['user:1', 'user:2']}, callback); + */ + if (options !== undefined && options.filter) { + options.filter = options.filter.join(','); + } + + var xhr = this.client.get({ + url: 'feed/' + this.feedUrl + '/following/', + qs: options, + signature: this.signature, + }, callback); + return xhr; + }, + + followers: function(options, callback) { + /* + * List the followers of this feed + * + * feed.followers({limit:10, filter: ['user:1', 'user:2']}, callback); + */ + if (options !== undefined && options.filter) { + options.filter = options.filter.join(','); + } + + var xhr = this.client.get({ + url: 'feed/' + this.feedUrl + '/followers/', + qs: options, + signature: this.signature, + }, callback); + return xhr; + }, + + get: function(options, callback) { + /* + * Reads the feed + * + * feed.get({limit: 10, id_lte: 'activity-id'}) + * or + * feed.get({limit: 10, mark_seen: true}) + */ + if (options && options['mark_read'] && options['mark_read'].join) { + options['mark_read'] = options['mark_read'].join(','); + } + + if (options && options['mark_seen'] && options['mark_seen'].join) { + options['mark_seen'] = options['mark_seen'].join(','); + } + + var xhr = this.client.get({ + url: 'feed/' + this.feedUrl + '/', + qs: options, + signature: this.signature, + }, callback); + return xhr; + }, + + getFayeClient: function() { return this.client.getFayeClient(); }, - subscribe : function(callback) { - /* - * subscribes to any changes in the feed, return a promise - * feed.subscribe(callback).then(function(){ - * console.log('we are now listening to changes'); - * }); - */ - if (!this.client.appId) { - throw new errors.SiteError('Missing app id, which is needed to subscribe, use var client = stream.connect(key, secret, appId);'); - } + subscribe: function(callback) { + /* + * subscribes to any changes in the feed, return a promise + * feed.subscribe(callback).then(function(){ + * console.log('we are now listening to changes'); + * }); + */ + if (!this.client.appId) { + throw new errors.SiteError('Missing app id, which is needed to subscribe, use var client = stream.connect(key, secret, appId);'); + } this.client.subscriptions['/' + this.notificationChannel] = { - 'token': this.token, - 'userId': this.notificationChannel + token: this.token, + userId: this.notificationChannel, }; - return this.getFayeClient().subscribe('/' + this.notificationChannel, callback); - } + return this.getFayeClient().subscribe('/' + this.notificationChannel, callback); + }, }; -module.exports = StreamFeed; +module.exports = StreamFeed; diff --git a/src/lib/parse_request.js b/src/lib/parse_request.js index ca01349e..a0569403 100644 --- a/src/lib/parse_request.js +++ b/src/lib/parse_request.js @@ -1,26 +1,28 @@ /* - * Simple wrapper to make make parse httprequest look + * Simple wrapper to make make parse httprequest look * somewhat like request library */ function request(options, callback) { - // first difference with request, qs is called params - options.params = options.qs; - // next up we need to support json for complex body params - options.body = JSON.stringify(options.body); - // also the callback is somewhat different - function callbackWrapperSuccess(httpResponse) { - callback(httpResponse); - } - function callbackWrapperFailure(httpResponse) { - callback(httpResponse); - } - options.success = callbackWrapperSuccess; - options.error = callbackWrapperFailure; - // add the json header - options.headers['Content-Type'] = 'application/json;charset=utf-8'; - Parse.Cloud.httpRequest(options); + // first difference with request, qs is called params + options.params = options.qs; + // next up we need to support json for complex body params + options.body = JSON.stringify(options.body); + // also the callback is somewhat different + function callbackWrapperSuccess(httpResponse) { + callback(httpResponse); + } + + function callbackWrapperFailure(httpResponse) { + callback(httpResponse); + } + + options.success = callbackWrapperSuccess; + options.error = callbackWrapperFailure; + // add the json header + options.headers['Content-Type'] = 'application/json;charset=utf-8'; + Parse.Cloud.httpRequest(options); } module.exports = request; diff --git a/src/lib/signing.js b/src/lib/signing.js index 8b447fe4..0e341385 100644 --- a/src/lib/signing.js +++ b/src/lib/signing.js @@ -4,27 +4,27 @@ var JWS_REGEX = /^[a-zA-Z0-9\-_]+?\.[a-zA-Z0-9\-_]+?\.([a-zA-Z0-9\-_]+)?$/; var Base64 = require('Base64'); function makeUrlSafe(s) { - /* - * Makes the given base64 encoded string urlsafe - */ - var escaped = s.replace(/\+/g, '-').replace(/\//g, '_'); - return escaped.replace(/^=+/, '').replace(/=+$/, ''); + /* + * Makes the given base64 encoded string urlsafe + */ + var escaped = s.replace(/\+/g, '-').replace(/\//g, '_'); + return escaped.replace(/^=+/, '').replace(/=+$/, ''); } function decodeBase64Url(base64UrlString) { try { return Base64.atob(toBase64(base64UrlString)); - } catch(e) { - if(e.name === 'InvalidCharacterError') { + } catch (e) { + if (e.name === 'InvalidCharacterError') { return undefined; } else { throw e; } - } + } } function safeJsonParse(thing) { - if (typeof(thing) === "object") return thing; + if (typeof (thing) === 'object') return thing; try { return JSON.parse(thing); } catch (e) { @@ -33,15 +33,15 @@ function safeJsonParse(thing) { } function padString(string) { - var segmentLength = 4; - var diff = string.length % segmentLength; - if (!diff) - return string; - var padLength = segmentLength - diff; + var segmentLength = 4; + var diff = string.length % segmentLength; + if (!diff) + return string; + var padLength = segmentLength - diff; - while (padLength--) - string += '='; - return string; + while (padLength--) + string += '='; + return string; } function toBase64(base64UrlString) { @@ -59,41 +59,41 @@ function headerFromJWS(jwsSig) { exports.headerFromJWS = headerFromJWS; exports.sign = function(apiSecret, feedId) { - /* - * Setup sha1 based on the secret - * Get the digest of the value - * Base64 encode the result - * - * Also see - * https://github.com/tbarbugli/stream-ruby/blob/master/lib/stream/signer.rb - * https://github.com/tschellenbach/stream-python/blob/master/stream/signing.py - * - * Steps - * apiSecret: tfq2sdqpj9g446sbv653x3aqmgn33hsn8uzdc9jpskaw8mj6vsnhzswuwptuj9su - * feedId: flat1 - * digest: Q\xb6\xd5+\x82\xd58\xdeu\x80\xc5\xe3\xb8\xa5bL1\xf1\xa3\xdb - * token: UbbVK4LVON51gMXjuKViTDHxo9s - */ - var hashedSecret = new crypto.createHash('sha1').update(apiSecret).digest(); - var hmac = crypto.createHmac('sha1', hashedSecret); - var digest = hmac.update(feedId).digest('base64'); - var token = makeUrlSafe(digest); - return token; + /* + * Setup sha1 based on the secret + * Get the digest of the value + * Base64 encode the result + * + * Also see + * https://github.com/tbarbugli/stream-ruby/blob/master/lib/stream/signer.rb + * https://github.com/tschellenbach/stream-python/blob/master/stream/signing.py + * + * Steps + * apiSecret: tfq2sdqpj9g446sbv653x3aqmgn33hsn8uzdc9jpskaw8mj6vsnhzswuwptuj9su + * feedId: flat1 + * digest: Q\xb6\xd5+\x82\xd58\xdeu\x80\xc5\xe3\xb8\xa5bL1\xf1\xa3\xdb + * token: UbbVK4LVON51gMXjuKViTDHxo9s + */ + var hashedSecret = new crypto.createHash('sha1').update(apiSecret).digest(); + var hmac = crypto.createHmac('sha1', hashedSecret); + var digest = hmac.update(feedId).digest('base64'); + var token = makeUrlSafe(digest); + return token; }; exports.JWTScopeToken = function(apiSecret, feedId, resource, action) { - /* - * Creates the JWT token for feedId, resource and action using the apiSecret - */ - var payload = {feed_id:feedId, resource:resource, action:action}; - var token = jwt.sign(payload, apiSecret, {algorithm: 'HS256'}); - return token; + /* + * Creates the JWT token for feedId, resource and action using the apiSecret + */ + var payload = {'feed_id':feedId, resource:resource, action:action}; + var token = jwt.sign(payload, apiSecret, {algorithm: 'HS256'}); + return token; }; exports.isJWTSignature = function(signature) { - /* - * check if token is a valid JWT token - */ - var token = signature.split(' ')[1]; - return JWS_REGEX.test(token) && !!headerFromJWS(token); + /* + * check if token is a valid JWT token + */ + var token = signature.split(' ')[1]; + return JWS_REGEX.test(token) && !!headerFromJWS(token); }; diff --git a/src/lib/utils.js b/src/lib/utils.js index 0c5fa13c..b5711109 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -1,46 +1,49 @@ var errors = require('./errors'); var validRe = /^[\w-]+$/; - function validateFeedId(feedId) { - /* - * Validate that the feedId matches the spec user:1 - */ - var parts = feedId.split(':'); - if (parts.length !== 2) { - throw new errors.FeedError('Invalid feedId, expected something like user:1 got ' + feedId); - } - var feedSlug = parts[0]; - var userId = parts[1]; - validateFeedSlug(feedSlug); - validateUserId(userId); - return feedId; + /* + * Validate that the feedId matches the spec user:1 + */ + var parts = feedId.split(':'); + if (parts.length !== 2) { + throw new errors.FeedError('Invalid feedId, expected something like user:1 got ' + feedId); + } + + var feedSlug = parts[0]; + var userId = parts[1]; + validateFeedSlug(feedSlug); + validateUserId(userId); + return feedId; } -exports.validateFeedId = validateFeedId; +exports.validateFeedId = validateFeedId; function validateFeedSlug(feedSlug) { - /* - * Validate that the feedSlug matches \w - */ - var valid = validRe.test(feedSlug); - if (!valid) { - throw new errors.FeedError('Invalid feedSlug, please use letters, numbers or _ got: ' + feedSlug); - } - return feedSlug; + /* + * Validate that the feedSlug matches \w + */ + var valid = validRe.test(feedSlug); + if (!valid) { + throw new errors.FeedError('Invalid feedSlug, please use letters, numbers or _ got: ' + feedSlug); + } + + return feedSlug; } -exports.validateFeedSlug = validateFeedSlug; +exports.validateFeedSlug = validateFeedSlug; function validateUserId(userId) { - /* - * Validate the userId matches \w - */ - var valid = validRe.test(userId); - if (!valid) { - throw new errors.FeedError('Invalid feedSlug, please use letters, numbers or _ got: ' + userId); - } - return userId; + /* + * Validate the userId matches \w + */ + var valid = validRe.test(userId); + if (!valid) { + throw new errors.FeedError('Invalid feedSlug, please use letters, numbers or _ got: ' + userId); + } + + return userId; } + exports.validateUserId = validateUserId;