Socket
Socket
Sign inDemoInstall

snoowrap

Package Overview
Dependencies
53
Maintainers
1
Versions
65
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.2.0 to 0.3.0

lib/actions.js

6

lib/constants.js
module.exports = {
ENDPOINT_DOMAIN: 'reddit.com',
MODULE_NAME: 'snoowrap',
ISSUE_REPORT_LINK: require('../package.json').bugs.url,
API_RULES_LINK: 'https://github.com/reddit/reddit/wiki/API',
USER_KEYS: ['author', 'approved_by', 'banned_by'],

@@ -15,5 +16,6 @@ SUBREDDIT_KEYS: ['subreddit'],

Listing: 'Listing',
more: 'more',
UserList: 'UserList'
},
username_regex: /^[\w-]{1,20}$/
USERNAME_REGEX: /^[\w-]{1,20}$/
};
'use strict';
let constants = require('./constants');
let e = ''; // Used for newlines in template strings without breaking indentation
let errors = {

@@ -9,4 +8,4 @@ RateLimitError: class extends Error {

super();
this.message = `ERROR: ${ constants.MODULE_NAME }.errors.ratelimit_exceeded: ${ e }${ constants.MODULE_NAME } refused to continue because reddit's ratelimit was exceeded. ${ e }For more information about reddit\'s ratelimit, please consult reddit\'s API rules at ${ e }https://github.com/reddit/reddit/wiki/API. To avoid hitting the ratelimit again, you should probably ${ e }wait at least ${ expiry_time_from_now } seconds before making any more requests.`;
this.name = 'RateLimitError';
this.message = `${ constants.MODULE_NAME }.errors.${ this.name }: ${ constants.MODULE_NAME } refused to continue because reddit's ratelimit was exceeded. For more information about reddit's ratelimit, please consult reddit's API rules at ${ constants.API_RULES_LINK }. To avoid hitting the ratelimit again, you should probably wait at least ${ expiry_time_from_now } seconds before making any more requests.`;
}

@@ -17,7 +16,15 @@ },

super();
this.message = `Cannot fetch information on the user '${ username }'. Please be sure you have the right username.`;
this.name = 'InvalidUserError';
this.message = `${ constants.MODULE_NAME }.errors.${ this.name }: Cannot fetch information on the user '${ username }'. Please be sure you have the right username.`;
}
}
},
InvalidMethodCallError: class extends Error {
constructor(reason) {
super();
this.name = 'InvalidMethodCallError';
this.message = `${ constants.MODULE_NAME }.errors.${ this.name }: ${ reason }`;
}
},
RateLimitWarning: time_until_reset => `Warning: ${ constants.MODULE_NAME } temporarily stopped sending requests because reddit's ratelimit was exceeded. The request you attempted to send was queued, and will be sent to reddit when the current ratelimit period expires in ${ time_until_reset } seconds.`
};
module.exports = errors;

@@ -5,4 +5,5 @@ 'use strict';

require('harmony-reflect'); // temp dependency until node implements Proxies properly
require('harmony-reflect'); // temp dependency until v8 implements Proxies properly
let Promise = require('bluebird');
Promise.config({ longStackTraces: true });
let _ = require('lodash');

@@ -15,2 +16,4 @@ let request = require('request-promise').defaults({ json: true });

let errors = require('./errors');
let default_config = require('./default_config');
let actions = require('./actions');
let objects = {};

@@ -29,5 +32,13 @@ let helpers = {};

this.ratelimit_reset_point = options.ratelimit_reset_point;
this.config = default_config;
this.throttle = Promise.resolve();
}
get_me() {
return this.get('api/v1/me').then(result => {
this.own_user_info = new objects.RedditUser(result, this, true);
return this.own_user_info;
});
}
get_user(name) {
return new objects.RedditUser({ name: name }, this);
return new objects.RedditUser({ name }, this);
}

@@ -38,4 +49,7 @@ get_comment(comment_id) {

get_subreddit(display_name) {
return new objects.Subreddit({ display_name: display_name }, this);
return new objects.Subreddit({ display_name }, this);
}
get_submission(submission_id) {
return new objects.Submission({ name: `t3_${ submission_id }` }, this);
}
_update_access_token() {

@@ -45,30 +59,18 @@ var _this = this;

return _asyncToGenerator(function* () {
let token_response = yield request.post({
url: `https://www.${ constants.ENDPOINT_DOMAIN }/api/v1/access_token`,
headers: {
Authorization: `Basic ${ Buffer(`${ _this.client_id }:${ _this.client_secret }`).toString('base64') }`,
'User-Agent': _this.user_agent
},
let token_info = yield request.post({
url: `https://www.${ _this.config.endpoint_domain }/api/v1/access_token`,
auth: { user: _this.client_id, pass: _this.client_secret },
headers: { 'user-agent': _this.user_agent },
form: { grant_type: 'refresh_token', refresh_token: _this.refresh_token }
});
_this.access_token = token_response.access_token;
_this.token_expiration = moment().add(token_response.expires_in, 'seconds');
_this.scopes = token_response.scope.split(' ');
_this.access_token = token_info.access_token;
_this.token_expiration = moment().add(token_info.expires_in, 'seconds');
_this.scopes = token_info.scope.split(' ');
})();
}
get oauth_requester() {
let request_filter_proxy = (requester, thisArg, args) => {
if (this.ratelimit_remaining < 1 && this.ratelimit_reset_point.isAfter()) {
throw new errors.RateLimitError(this.ratelimit_reset_point.diff(moment(), 'seconds'));
}
let needs_refresh = !this.token_expiration || moment(this.token_expiration).subtract(10, 'seconds').isBefore();
return promise_wrap((needs_refresh ? this._update_access_token() : Promise.resolve()).then(() => {
return requester.defaults({ headers: { Authorization: `bearer ${ this.access_token }` } }).apply(thisArg, args);
}));
};
get _oauth_requester() {
let default_requester = request.defaults({
headers: { 'User-Agent': this.user_agent },
baseUrl: `https://oauth.${ constants.ENDPOINT_DOMAIN }`,
qs: { raw_json: 1 }, // This tells reddit to unescape html characters, so that it sends '<' instead of '&lt;'
headers: { 'user-agent': this.user_agent },
baseUrl: `https://oauth.${ this.config.endpoint_domain }`,
qs: { raw_json: 1 }, // This tells reddit to unescape html characters, e.g. it will send '<' instead of '&lt;'
resolveWithFullResponse: true,

@@ -81,33 +83,109 @@ transform: (body, response) => {

});
return new Proxy(default_requester, {
apply: request_filter_proxy,
get: (target, key) => {
// Allow both request(args) and request.post(args)
if (_.includes(['get', 'head', 'post', 'put', 'patch', 'del'], key)) {
return new Proxy(target.defaults({ method: key }), { apply: request_filter_proxy });
let handle_request = (() => {
var _this2 = this;
var ref = _asyncToGenerator(function* (requester, self, args) {
let attempts = arguments.length <= 3 || arguments[3] === undefined ? 0 : arguments[3];
if (_this2.ratelimit_remaining < 1 && _this2.ratelimit_reset_point.isAfter()) {
let seconds_until_expiry = _this2.ratelimit_reset_point.diff(moment(), 'seconds');
if (_this2.config.continue_after_ratelimit_error) {
_this2.warn(errors.RateLimitWarning(seconds_until_expiry));
yield Promise.delay(_this2.ratelimit_reset_point.diff());
} else {
throw new errors.RateLimitError(seconds_until_expiry);
}
}
return target[key];
/* this.throttle is a timer that gets reset to this.config.request_delay whenever a request is sent.
This ensures that requests are ratelimited and that no requests are lost. The await statement is wrapped
in a loop to make sure that if the throttle promise resolves while multiple requests are pending, only
one of the requests will be sent, and the others will await the throttle again. (The loop is non-blocking
due to its await statement.) */
while (!_this2.throttle.isFulfilled()) {
yield _this2.throttle;
}
_this2.throttle = Promise.delay(_this2.config.request_delay);
// If the access token has expired (or will expire in the next 10 seconds), refresh it.
if (!_this2.token_expiration || moment(_this2.token_expiration).subtract(10, 'seconds').isBefore()) {
yield _this2._update_access_token();
}
// Send the request and return the response.
return yield requester.defaults({ auth: { bearer: _this2.access_token } }).apply(self, args).catch(function (err) {
if (attempts < _this2.config.max_retry_attempts && _.includes(_this2.config.retry_error_codes, err.statusCode)) {
return handle_request(requester, self, args, attempts + 1);
}
throw err;
});
}),
_this = this;
return function handle_request(_x, _x2, _x3, _x4) {
return ref.apply(_this, arguments);
};
})();
return new Proxy(default_requester, { apply: handle_request });
}
inspect() {
// Hide confidential information (tokens, client IDs, etc.) from the console.log output.
// Also, hide some things that aren't converted to text well.
let keys_for_hidden_values = ['client_secret', 'refresh_token', 'access_token'];
let hidden_keys = ['throttle'];
let formatted = util.inspect(_(this).omit(hidden_keys).mapValues((value, key) => {
if (_.includes(keys_for_hidden_values, key)) {
return value && '(redacted)';
}
});
if (value instanceof moment) {
return value.format();
}
return value;
}).value());
return `<${ constants.MODULE_NAME }.objects.${ this.constructor.name }> ${ formatted }`;
}
/*gotta*/get get() {
return this._oauth_requester.defaults({ method: 'get' });
}
get post() {
return this._oauth_requester.defaults({ method: 'post' });
}
get patch() {
return this._oauth_requester.defaults({ method: 'patch' });
}
get put() {
return this._oauth_requester.defaults({ method: 'put' });
}
get delete() {
return this._oauth_requester.defaults({ method: 'delete' });
}
// TODO: probably combine the above getters to avoid repetition, though that would require deleting the 'get get' joke :\
warn() {
if (!this.config.suppress_warnings) {
console.warn(...arguments);
}
}
};
objects.RedditContent = class RedditContent {
constructor(options, _fetcher, has_fetched) {
var _this2 = this;
this._fetcher = _fetcher;
constructor(options, _ac, has_fetched) {
this._ac = _ac;
this.has_fetched = !!has_fetched;
_.assign(this, options);
this._fetch = _.once(_asyncToGenerator(function* () {
let response = yield _this2._fetcher.oauth_requester.get({ uri: _this2._uri });
let transformed = _this2._transform_api_response(response);
_.assign(_this2, transformed);
_this2.has_fetched = true;
return _this2;
}));
_.assignIn(this, options);
this.fetch = this.fetch || _.once(() => {
return promise_wrap(this._ac.get({ uri: this._uri }).then(this._transform_api_response.bind(this)).then(response => {
/* The line below is equivalent to _.assign(this, response);, but _.assign ends up triggering warning messages when
used on Proxies, since the patched globals from harmony-reflect aren't applied to lodash. This won't be a problem once
Proxies are correctly implemented natively. https://github.com/tvcutsem/harmony-reflect#dependencies */
_.forIn(response, (value, key) => {
this[key] = value;
});
this.has_fetched = true;
return this;
}));
});
return new Proxy(this, { get: (target, key) => {
if (key in target || key === 'length' || key in Promise.prototype || this.has_fetched) {
if (key in target || key === 'length' || key in Promise.prototype || target.has_fetched) {
return target[key];
}
if (key === '_raw') {
return target;
}
return this.fetch()[key];

@@ -117,78 +195,91 @@ } });

inspect() {
// TODO: Simplify this when lodash fixes _.pickBy
let property_display = _.pick(this, _.filter(_.keys(this), key => {
return key.indexOf('_') !== 0 && typeof this[key] !== 'function';
}));
return `<${ constants.MODULE_NAME }.objects.${ this.constructor.name }> ${ util.inspect(property_display) }`;
let public_properties = _.omitBy(this, (value, key) => key.startsWith('_') || typeof value === 'function');
return `<${ constants.MODULE_NAME }.objects.${ this.constructor.name }> ${ util.inspect(public_properties) }`;
}
get oauth_requester() {
return this._fetcher.oauth_requester;
_transform_api_response(response_object) {
return response_object;
}
fetch() {
if (this.has_fetched) {
return this;
}
return promise_wrap(this._fetch());
}
_transform_api_response(response_obj) {
return response_obj;
}
};
objects.Comment = class Comment extends objects.RedditContent {
constructor(options, _fetcher, has_fetched) {
super(options, _fetcher, has_fetched);
constructor(options, _ac, has_fetched) {
super(options, _ac, has_fetched);
}
_transform_api_response(response_object) {
return response_object.children[0];
_transform_api_response(response_obj) {
let replies_uri = `comments/${ response_obj[0].link_id.slice(3) }`;
let replies_query = { comment: this.name.slice(3) };
let _transform = item => item[1][0].replies;
response_obj[0].replies = new objects.Listing({ _uri: replies_uri, query: replies_query, _transform }, this._ac);
return response_obj[0];
}
get _uri() {
return `/api/info?id=${ this.name }`;
return `api/info?id=${ this.name }`;
}
static get inherited_action_categories() {
return ['reply', 'vote', 'moderate', 'more_comments'];
}
};
objects.RedditUser = class RedditUser extends objects.RedditContent {
constructor(options, _fetcher, has_fetched) {
super(options, _fetcher, has_fetched);
constructor(options, _ac, has_fetched) {
super(options, _ac, has_fetched);
}
get _uri() {
if (typeof this.name !== 'string' || !constants.username_regex.test(this.name)) {
if (typeof this.name !== 'string' || !constants.USERNAME_REGEX.test(this.name)) {
throw new errors.InvalidUserError(this.name);
}
return `/user/${ this.name }/about`;
return `user/${ this.name }/about`;
}
static get inherited_action_categories() {
return [];
}
};
objects.Submission = class Submission extends objects.RedditContent {
constructor(options, _fetcher, has_fetched) {
super(options, _fetcher, has_fetched);
constructor(options, _ac, has_fetched) {
super(options, _ac, has_fetched);
let _transform = response => response[1];
this.comments = new objects.Listing({ _uri: `comments/${ this.name.slice(3) }`, _transform }, _ac);
}
get _uri() {
return `/api/info?id=${ this.name }`;
return `api/info?id=${ this.name }`;
}
_transform_api_response(response_object) {
return response_object[0];
}
static get inherited_action_categories() {
return ['reply', 'vote', 'moderate', 'more_comments'];
}
};
objects.PrivateMessage = class PrivateMessage extends objects.RedditContent {
constructor(options, _fetcher, has_fetched) {
super(options, _fetcher, has_fetched);
constructor(options, _ac, has_fetched) {
super(options, _ac, has_fetched);
}
get _uri() {
return `/message/messages/${ this.id }`;
return `message/messages/${ this.id }`;
}
static get inherited_action_categories() {
return ['reply', 'moderate']; // more_comments? Need to check whether this ever applies with PMs
}
};
objects.Subreddit = class Subreddit extends objects.RedditContent {
constructor(options, _fetcher, has_fetched) {
super(options, _fetcher, has_fetched);
constructor(options, _ac, has_fetched) {
super(options, _ac, has_fetched);
}
get _uri() {
return `/r/${ this.display_name }/about`;
return `r/${ this.display_name }/about`;
}
get_moderators() {
return this._fetcher.oauth_requester.get(`/r/${ this.display_name }/about/moderators`);
return this._ac.get(`r/${ this.display_name }/about/moderators`);
}
static get inherited_action_categories() {
return [];
}
};
objects.Trophy = class Trophy extends objects.RedditContent {
constructor(options, _fetcher, has_fetched) {
super(options, _fetcher, has_fetched);
constructor(options, _ac, has_fetched) {
super(options, _ac, has_fetched);
}

@@ -198,17 +289,147 @@ };

objects.PromoCampaign = class PromoCampaign extends objects.RedditContent {
constructor(options, _fetcher, has_fetched) {
super(options, _fetcher, has_fetched);
constructor(options, _ac, has_fetched) {
super(options, _ac, has_fetched);
}
};
objects.Listing = class Listing extends objects.RedditContent {
constructor(options, _fetcher, has_fetched) {
super(options, _fetcher, has_fetched);
objects.Listing = class Listing extends Array {
constructor() {
var _ref = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
var _ref$children = _ref.children;
let children = _ref$children === undefined ? [] : _ref$children;
var _ref$query = _ref.query;
let query = _ref$query === undefined ? {} : _ref$query;
var _ref$show_all = _ref.show_all;
let show_all = _ref$show_all === undefined ? true : _ref$show_all;
let limit = _ref.limit;
var _ref$_transform = _ref._transform;
let _transform = _ref$_transform === undefined ? _.identity : _ref$_transform;
let _uri = _ref._uri;
let method = _ref.method;
let after = _ref.after;
let before = _ref.before;
var _ref$_is_comment_list = _ref._is_comment_list;
let _is_comment_list = _ref$_is_comment_list === undefined ? false : _ref$_is_comment_list;
let _ac = arguments[1];
super();
_.assign(this, children);
let constant_params = _.assign(query, { show: show_all ? 'all' : undefined, limit });
this._ac = _ac;
this._requester = _ac._oauth_requester.defaults({ uri: _uri, method, qs: constant_params });
this._transform = _transform;
this.limit = limit;
this.after = after;
this.before = before;
this._is_comment_list = _is_comment_list;
return new Proxy(this, { get: (target, key, thisArg) => {
if (!isNaN(key) && key >= target.length) {
return promise_wrap(target.fetch({ amount: key - target.length + 1 }).then(_.last));
}
return Reflect.get(target, key, thisArg);
} });
}
get is_finished() {
if (this._is_comment_list) {
return !this._more || !this._more.children.length;
}
return !!this.uri && this.after === null && this.before === null;
}
fetch(_ref2) {
var _ref2$amount = _ref2.amount;
let amount = _ref2$amount === undefined ? this.limit : _ref2$amount;
if (typeof amount !== 'number') {
throw new errors.InvalidMethodCallError('Failed to fetch listing. (amount must be a Number.)');
}
if (amount <= 0 || this.is_finished) {
return [];
}
if (this._is_comment_list) {
return promise_wrap(this._fetch_more_comments({ amount }));
}
return promise_wrap(this._fetch_more_regular({ amount }));
}
_fetch_more_regular(_ref3) {
var _this3 = this;
let amount = _ref3.amount;
return _asyncToGenerator(function* () {
let limit_for_request = Math.min(amount, _this3.limit || 0);
let request_params = { qs: { after: _this3.after, before: _this3.before, limit: limit_for_request } };
let response = yield _this3._requester(request_params).then(_this3._transform);
if (_this3.length === 0 && _.last(response) instanceof objects.more) {
_this3._more = response.pop();
_this3._is_comment_list = true;
}
_this3.push(..._.toArray(response));
_this3.before = response.before;
_this3.after = response.after;
return _this3.slice(0, amount).concat((yield _this3.fetch({ amount: amount - response.length })));
})();
}
/* Pagination for comments works differently than it does for most other things; rather than sending a link to the next page
within a listing, reddit sends the last comment in the list as as a `more` object, with links to all the remaining comments
in the thread. */
_fetch_more_comments() {
var _this4 = this;
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _asyncToGenerator(function* () {
let new_comments = _this4._more ? yield _this4._more.fetch(...args) : [];
_this4.push(..._.toArray(new_comments));
return new_comments;
})();
}
fetch_all() {
return this.fetch({ amount: Infinity });
}
inspect() {
return util.inspect(_.omitBy(this, (value, key) => key.startsWith('_')));
}
};
objects.UserList = class UserList extends objects.RedditContent {
constructor(options, _fetcher) {
objects.more = class more extends objects.RedditContent {
constructor(properties, _ac) {
super(properties, _ac);
}
/* Requests to /api/morechildren are capped at 20 comments at a time, but requests to /api/info are capped at 100, so
it's easier to send to the latter. The disadvantage is that comment replies are not automatically sent from requests
to /api/info. */
fetch(_ref4) {
var _this5 = this;
var _ref4$amount = _ref4.amount;
let amount = _ref4$amount === undefined ? Infinity : _ref4$amount;
return _asyncToGenerator(function* () {
if (isNaN(amount)) {
throw new errors.InvalidMethodCallError('Failed to fetch listing. (`amount` must be a Number.)');
}
if (amount <= 0 || _this5.children.length === 0) {
return [];
}
let ids_for_this_request = _this5.children.splice(0, Math.min(amount, 100)).map(function (id) {
return `t1_${ id }`;
});
// Requests are capped at 100 comments. Send lots of requests recursively to get the comments, then concatenate them.
// (This speed-requesting is only possible with comment listings since the entire list of ids is present initially.)
let promise_for_this_batch = _this5._ac.get({ uri: 'api/info', qs: { id: ids_for_this_request.join(',') } });
let promise_for_remaining_items = _this5.fetch({ amount: amount - ids_for_this_request.length });
return _.toArray((yield promise_for_this_batch)).concat((yield promise_for_remaining_items));
})();
}
};
objects.UserList = class UserList {
constructor(options, _ac) {
return options.children.map(user => {
return new objects.RedditUser(user, _fetcher);
return new objects.RedditUser(user, _ac);
});

@@ -218,8 +439,11 @@ }

helpers._populate = (response_tree, _fetcher) => {
helpers._populate = (response_tree, _ac) => {
if (typeof response_tree === 'object' && response_tree !== null) {
// Map {kind: 't2', data: {name: 'some_username', ... }} to a RedditUser (e.g.) with the same properties
if (_.keys(response_tree).length === 2 && response_tree.kind && constants.KINDS[response_tree.kind]) {
let remainder_of_tree = helpers._populate(response_tree.data, _fetcher);
return new objects[constants.KINDS[response_tree.kind]](remainder_of_tree, _fetcher, true);
if (_.keys(response_tree).length === 2 && response_tree.kind) {
let remainder_of_tree = helpers._populate(response_tree.data, _ac);
if (constants.KINDS[response_tree.kind]) {
return new objects[constants.KINDS[response_tree.kind]](remainder_of_tree, _ac, true);
}
_ac.warn(`Unknown type ${ response_tree.kind }. This may be a bug; please report it at ${ constants.ISSUE_REPORT_LINK }.`);
}

@@ -230,8 +454,8 @@ let mapFunction = Array.isArray(response_tree) ? _.map : _.mapValues;

if (_.includes(constants.USER_KEYS, key)) {
return new objects.RedditUser({ name: value }, _fetcher);
return new objects.RedditUser({ name: value }, _ac);
}
if (_.includes(constants.SUBREDDIT_KEYS, key)) {
return new objects.Subreddit({ display_name: value }, _fetcher);
return new objects.Subreddit({ display_name: value }, _ac);
}
return helpers._populate(value, _fetcher);
return helpers._populate(value, _ac);
});

@@ -242,4 +466,13 @@ }

/* Assign all the action functions to the class prototypes. Actions are split into categories (moderation, voting, etc.),
which are defined in actions.js. Each class should have the inherited_action_categories property, which is a list of strings
corresponding to the action categories which apply to objects of that class. */
_(objects).forOwn(object_class => {
_(actions).pick(object_class.inherited_action_categories || []).forOwn(action_category => {
_.assign(object_class.prototype, action_category);
});
});
snoowrap.objects = objects;
snoowrap.helpers = helpers;
module.exports = snoowrap;
{
"name": "snoowrap",
"version": "0.2.0",
"version": "0.3.0",
"license": "Apache-2.0",

@@ -9,4 +9,5 @@ "description": "A Node.js wrapper for the reddit API",

"compile": "babel -d lib/ src/",
"prepublish": "npm run compile",
"test": "eslint --ignore-path .gitignore . && mocha --harmony_proxies --compilers js:babel-core/register"
"pretest": "eslint --ignore-path .gitignore . && npm run compile",
"test": "mocha --harmony_proxies --compilers js:babel-core/register",
"prepublish": "npm test"
},

@@ -17,3 +18,9 @@ "repository": {

},
"keywords": [],
"keywords": [
"reddit",
"api",
"wrapper",
"praw",
"snoo"
],
"author": "not-an-aardvark <not-an-aardvark@users.noreply.github.com>",

@@ -25,3 +32,5 @@ "bugs": {

"plugins": [
"transform-async-to-generator"
"transform-async-to-generator",
"transform-es2015-destructuring",
"transform-es2015-parameters"
]

@@ -31,18 +40,20 @@ },

"dependencies": {
"bluebird": "^3.1.1",
"bluebird": "^3.1.5",
"harmony-reflect": "^1.4.2",
"lodash": "^4.0.0",
"lodash": "^4.1.0",
"moment": "^2.11.1",
"promise-chains": "^0.1.1",
"promise-chains": "^0.1.2",
"request-promise": "^2.0.0"
},
"devDependencies": {
"babel-cli": "^6.4.0",
"babel-core": "^6.4.0",
"babel-eslint": "^4.1.6",
"babel-plugin-transform-async-to-generator": "^6.4.0",
"chai": "^3.4.1",
"babel-cli": "^6.4.5",
"babel-core": "^6.4.5",
"babel-eslint": "^5.0.0-beta8",
"babel-plugin-transform-async-to-generator": "^6.4.6",
"babel-plugin-transform-es2015-destructuring": "^6.4.0",
"babel-plugin-transform-es2015-parameters": "^6.4.5",
"chai": "^3.5.0",
"chai-as-promised": "^5.2.0",
"mocha": "^2.3.4"
"mocha": "^2.4.5"
}
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc