comprodls-sdk 2.107.0 → 2.109.0-thor.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/comprodls-sdk.js +241 -1196
- package/dist/comprodls-sdk.min.js +1 -1
- package/lib/config/index.js +2 -0
- package/lib/helpers/lib/errors.js +3 -1
- package/lib/helpers/lib/validator.js +131 -53
- package/lib/services/spaces/index.js +36 -29
- package/lib/services/spacesextn/index.js +46 -1
- package/lib/services/superuser/index.js +21 -22
- package/package.json +2 -3
package/lib/config/index.js
CHANGED
|
@@ -154,6 +154,7 @@ exports.AUTH_API_URLS = {
|
|
|
154
154
|
validateSpaceCode: '/accounts/{accountid}/space-code/validate',
|
|
155
155
|
validateClassCode: '/accounts/{accountid}/class-code/validate',
|
|
156
156
|
joinInstituteSpace: '/accounts/{accountid}/join-institute-space',
|
|
157
|
+
provisionStudentInInstitutionalSpace: '/accounts/{accountid}/student/join-institute-space',
|
|
157
158
|
provisionSpacesToStudent: '/accounts/{accountId}/student/provision-spaces',
|
|
158
159
|
provisionSpacesToTeacher: '/accounts/{accountId}/teacher/provision-spaces',
|
|
159
160
|
shadowProvision: '/org/{orgId}/shadow-provision',
|
|
@@ -236,6 +237,7 @@ exports.AUTHEXTN_API_URLS = {
|
|
|
236
237
|
|
|
237
238
|
//Space related API
|
|
238
239
|
entitleBulkUsersToProducts: '/accounts/{accountid}/entitle-users/bulk',
|
|
240
|
+
deleteUserAccount : '/accounts/{accountid}/ext-users/{ext_user_id}',
|
|
239
241
|
|
|
240
242
|
// Assigned Path APIs
|
|
241
243
|
userAssignedPaths: '/org/{orgid}/classes/{classid}/assigned-paths/{assignedpathid}/enroll-user/multi',
|
|
@@ -27,7 +27,9 @@
|
|
|
27
27
|
var ERROR_TYPES = {
|
|
28
28
|
API_ERROR: 'API_ERROR',
|
|
29
29
|
SDK_ERROR: 'SDK_ERROR',
|
|
30
|
-
CHANNEL_SUBSCRIPTION: 'CHANNEL_SUBSCRIPTION'
|
|
30
|
+
CHANNEL_SUBSCRIPTION: 'CHANNEL_SUBSCRIPTION',
|
|
31
|
+
UNEXPECTED_ERROR: 'UNEXPECTED_ERROR',
|
|
32
|
+
POLLING_INITIATION: 'POLLING_INITIATION'
|
|
31
33
|
};
|
|
32
34
|
|
|
33
35
|
var ERROR_CATEGORY = {
|
|
@@ -22,13 +22,129 @@
|
|
|
22
22
|
* comproDLS SDK Validator Helper Module
|
|
23
23
|
* This module contains validation helper functions for comproDLS SDK
|
|
24
24
|
************************************************************/
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
var errors = require('./errors');
|
|
27
27
|
var DLSError = errors.DLSError;
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
/*********************************
|
|
30
|
+
* Core validator utilities
|
|
31
|
+
* Each validator follows the signature:
|
|
32
|
+
* fn(field, value, options) → DLSError | undefined
|
|
33
|
+
*
|
|
34
|
+
* - field : the constraint key name e.g. 'organization', 'token', 'token.access_token'
|
|
35
|
+
* - value : the value from the options object for that field
|
|
36
|
+
* - options : the rule config, can be `true` or `{ message: '...' }`
|
|
37
|
+
* For `contains`, options is a string or array of required keys.
|
|
38
|
+
*
|
|
39
|
+
* Returning a DLSError which stops validation immediately.
|
|
40
|
+
* Returning undefined means the check passed.
|
|
41
|
+
**********************************/
|
|
42
|
+
var validators = {
|
|
43
|
+
/**
|
|
44
|
+
* Fails if value is null, undefined, or blank/whitespace-only string or empty object.
|
|
45
|
+
*/
|
|
46
|
+
presence: function(field, value, options) {
|
|
47
|
+
if (value === undefined || value === null || value === '' ||
|
|
48
|
+
(typeof value == 'string' && value.trim() === '') ||
|
|
49
|
+
(typeof value == 'object' && Object.keys(value).length === 0)
|
|
50
|
+
) {
|
|
51
|
+
return createError(options.message || field + ' can\'t be blank');
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Fails if value is not a string.
|
|
57
|
+
*/
|
|
58
|
+
isString: function(field, value, options) {
|
|
59
|
+
if (typeof value !== 'string') {
|
|
60
|
+
return createError(options.message || field + ' is not a valid string');
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Fails if value is not an object.
|
|
66
|
+
*/
|
|
67
|
+
isObject: function(field, value, options) {
|
|
68
|
+
if (typeof value !== 'object' || Array.isArray(value)) {
|
|
69
|
+
return createError(options.message || field + ' is not a valid object');
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Fails if the object does not have the required key(s).
|
|
75
|
+
*/
|
|
76
|
+
contains: function(field, value, options) {
|
|
77
|
+
var keys = Array.isArray(options) ? options : typeof options === 'string' ? [options] : [];
|
|
78
|
+
|
|
79
|
+
for (var i = 0; i < keys.length; i++) {
|
|
80
|
+
var requiredKey = keys[i];
|
|
81
|
+
|
|
82
|
+
if (!value.hasOwnProperty(requiredKey)) {
|
|
83
|
+
return createError(options.message || field + ' does not contain ' + requiredKey);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/*********************************
|
|
90
|
+
* Core validate function
|
|
91
|
+
*
|
|
92
|
+
* Iterates over each field in constraints, resolves its value from options,
|
|
93
|
+
* then runs each rule's validator function in order.
|
|
94
|
+
* Returns the first error encountered or undefined if all pass.
|
|
95
|
+
*
|
|
96
|
+
* @param {Object} options - The input object to validate
|
|
97
|
+
* @param {Object} constraints - Map of field names to their rule constraints
|
|
98
|
+
* @returns {DLSError | undefined}
|
|
99
|
+
**********************************/
|
|
100
|
+
function validate(options, constraints) {
|
|
101
|
+
for (var field in constraints) {
|
|
102
|
+
var fieldConstraintsObj = constraints[field];
|
|
103
|
+
var value = getValue(options, field);
|
|
104
|
+
|
|
105
|
+
for (var ruleName in fieldConstraintsObj) {
|
|
106
|
+
var ruleOptions = fieldConstraintsObj[ruleName];
|
|
107
|
+
var validatorFn = validators[ruleName];
|
|
108
|
+
|
|
109
|
+
if (!validatorFn) { continue; }
|
|
110
|
+
|
|
111
|
+
// Pass field name, field value, and field's rule constraint value to the validator function
|
|
112
|
+
var error = validatorFn(field, value, ruleOptions);
|
|
113
|
+
if (error) { return error; }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/*********************************
|
|
121
|
+
* Private helpers
|
|
122
|
+
**********************************/
|
|
123
|
+
function getValue(obj, path) {
|
|
124
|
+
var keys = path.split('.');
|
|
125
|
+
var result = obj;
|
|
126
|
+
|
|
127
|
+
for (var i = 0; i < keys.length; i++) {
|
|
128
|
+
if (result === undefined || result === null) { return undefined; }
|
|
129
|
+
result = result[keys[i]];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function createError(message) {
|
|
136
|
+
var msg = message || 'Validation error';
|
|
137
|
+
msg = msg.charAt(0).toUpperCase() + msg.slice(1);
|
|
138
|
+
|
|
139
|
+
return new DLSError(errors.ERROR_TYPES.SDK_ERROR, {
|
|
140
|
+
message: msg,
|
|
141
|
+
description: msg
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/*********************************
|
|
146
|
+
* Exported auth validators
|
|
147
|
+
**********************************/
|
|
32
148
|
|
|
33
149
|
/**
|
|
34
150
|
* This function validates if the SDK instance is authenticated.
|
|
@@ -45,22 +161,10 @@ function validateIsAuthenticatedV2(orgId, token) {
|
|
|
45
161
|
return new DLSError(errors.ERROR_TYPES.SDK_ERROR, errObj);
|
|
46
162
|
};
|
|
47
163
|
|
|
48
|
-
function validate(options, constraints) {
|
|
49
|
-
var err = {};
|
|
50
|
-
var validation_errors = validator(options, constraints);
|
|
51
|
-
if (validation_errors) {
|
|
52
|
-
for (var validation_error in validation_errors) {
|
|
53
|
-
err.message = err.description = validation_errors[validation_error][0];
|
|
54
|
-
err = new DLSError(errors.ERROR_TYPES.SDK_ERROR, err);
|
|
55
|
-
return err;
|
|
56
|
-
}
|
|
57
|
-
} else {
|
|
58
|
-
return undefined;
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
|
|
62
164
|
/**
|
|
63
|
-
*
|
|
165
|
+
* Validates if SDK instance is authenticated by checking presence of orgId and token.
|
|
166
|
+
* @param {String} orgId - Organization ID
|
|
167
|
+
* @param {Object} token - Token object { access_token: 'string' }
|
|
64
168
|
*/
|
|
65
169
|
function validateIsAuthenticated(orgId, token) {
|
|
66
170
|
var validate_options = {
|
|
@@ -68,47 +172,21 @@ function validateIsAuthenticated(orgId, token) {
|
|
|
68
172
|
'token': token
|
|
69
173
|
};
|
|
70
174
|
|
|
175
|
+
var errMsg = 'SDK Instance does not have valid orgid or token. ' +
|
|
176
|
+
'Please authenticate using authWithCredentials or authWithToken method.';
|
|
177
|
+
|
|
71
178
|
var validate_constraints = {
|
|
72
179
|
'organization': {
|
|
73
|
-
'presence': {
|
|
74
|
-
'message' : '^SDK Instance does not have valid orgid or token. Please authenticate using authWithCredentials or authWithToken method.'
|
|
75
|
-
}
|
|
180
|
+
'presence': { 'message': errMsg }
|
|
76
181
|
},
|
|
77
182
|
'token': {
|
|
78
|
-
'presence': {
|
|
79
|
-
'message' : '^SDK Instance does not have valid orgid or token. Please authenticate using authWithCredentials or authWithToken method.'
|
|
80
|
-
}
|
|
183
|
+
'presence': { 'message': errMsg }
|
|
81
184
|
}
|
|
82
185
|
};
|
|
83
186
|
|
|
84
187
|
return validate(validate_options, validate_constraints);
|
|
85
188
|
};
|
|
86
189
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
validator.validators.isString = function(value, options) {
|
|
91
|
-
if (!validator.isString(value)) {
|
|
92
|
-
return options.message || 'is not a valid string';
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
validator.validators.isObject = function(value, options) {
|
|
97
|
-
if (!validator.isObject(value)) {
|
|
98
|
-
return options.message || 'is not a valid object';
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
validator.validators.contains = function(value, options) {
|
|
103
|
-
if (validator.isArray(options)) {
|
|
104
|
-
for (var key in options) {
|
|
105
|
-
if (!value || !value.hasOwnProperty(options[key])) {
|
|
106
|
-
return options.message || 'does not contain ' + options[key];
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
} else if (validator.isString(options)) {
|
|
110
|
-
if (!value || !value.hasOwnProperty(options)) {
|
|
111
|
-
return options.message || 'does not contain ' + options;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
};
|
|
190
|
+
exports.validate = validate;
|
|
191
|
+
exports.isAuthenticated = validateIsAuthenticated;
|
|
192
|
+
exports.isAuthenticatedV2 = validateIsAuthenticatedV2;
|
|
@@ -277,39 +277,45 @@ function changeSpaceCode(options) {
|
|
|
277
277
|
*/
|
|
278
278
|
function joinInstituteSpace(options) {
|
|
279
279
|
var self = this;
|
|
280
|
-
// Initializing promise
|
|
281
|
-
var dfd = q.defer();
|
|
282
|
-
|
|
283
|
-
if(options && options.ext_user_id &&
|
|
284
|
-
options.ext_role && options.space_code)
|
|
285
|
-
{
|
|
286
|
-
// Passed all validations, Contruct API url
|
|
287
|
-
var url = self.config.DEFAULT_HOSTS.AUTH +
|
|
288
|
-
self.config.AUTH_API_URLS.joinInstituteSpace;
|
|
289
|
-
url = helpers.api.constructAPIUrl(url, { accountid : self.accountId });
|
|
290
280
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
.set('Accept', 'application/json')
|
|
295
|
-
.send(options);
|
|
296
|
-
if(self.traceid) { requestAPI.set('X-Amzn-Trace-Id', self.traceid); }
|
|
281
|
+
return new Promise(function(resolve, reject) {
|
|
282
|
+
if (options && options.ext_user_id && options.ext_role) {
|
|
283
|
+
// Passed all validations, Contruct API url
|
|
297
284
|
|
|
298
|
-
|
|
299
|
-
if(
|
|
300
|
-
|
|
301
|
-
|
|
285
|
+
var url;
|
|
286
|
+
if (options.ext_role === 'student') {
|
|
287
|
+
url = self.config.DEFAULT_HOSTS.AUTH +
|
|
288
|
+
self.config.AUTH_API_URLS.provisionStudentInInstitutionalSpace;
|
|
302
289
|
}
|
|
303
|
-
else {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
err.message = err.description = 'ext_user_id or ext_role or space_code not found in request options.';
|
|
308
|
-
err = new DLSError(helpers.errors.ERROR_TYPES.SDK_ERROR, err);
|
|
309
|
-
dfd.reject(err);
|
|
310
|
-
}
|
|
290
|
+
else {
|
|
291
|
+
url = self.config.DEFAULT_HOSTS.AUTH + self.config.AUTH_API_URLS.joinInstituteSpace;
|
|
292
|
+
}
|
|
293
|
+
url = helpers.api.constructAPIUrl(url, { accountid : self.accountId });
|
|
311
294
|
|
|
312
|
-
|
|
295
|
+
// Setup request with URL and Params
|
|
296
|
+
var requestAPI = request.post(url)
|
|
297
|
+
.set('Content-Type', 'application/json')
|
|
298
|
+
.set('Accept', 'application/json');
|
|
299
|
+
if(self.traceid) { requestAPI.set('X-Amzn-Trace-Id', self.traceid); }
|
|
300
|
+
|
|
301
|
+
requestAPI
|
|
302
|
+
.send(options)
|
|
303
|
+
.agent(keepaliveAgent)
|
|
304
|
+
.end(function(error, response) {
|
|
305
|
+
if (error) {
|
|
306
|
+
var err = new DLSError(helpers.errors.ERROR_TYPES.API_ERROR, error);
|
|
307
|
+
reject(err);
|
|
308
|
+
}
|
|
309
|
+
else { resolve(response.body); }
|
|
310
|
+
});
|
|
311
|
+
} else {
|
|
312
|
+
var err = {};
|
|
313
|
+
err.message = err.description = 'ext_user_id or ext_role not found' +
|
|
314
|
+
' in request options.';
|
|
315
|
+
err = new DLSError(helpers.errors.ERROR_TYPES.SDK_ERROR, err);
|
|
316
|
+
reject(err);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
313
319
|
}
|
|
314
320
|
|
|
315
321
|
/**
|
|
@@ -849,6 +855,7 @@ function updateUserInformation(options) {
|
|
|
849
855
|
};
|
|
850
856
|
if(options.ref_id) { bodyParams.ref_id = options.ref_id; }
|
|
851
857
|
if(options.ext_email) { bodyParams.ext_email = options.ext_email; }
|
|
858
|
+
if(options.ext_username) { bodyParams.ext_username = options.ext_username; }
|
|
852
859
|
if(options.ext_first_name) { bodyParams.ext_first_name = options.ext_first_name; }
|
|
853
860
|
if(options.ext_last_name) { bodyParams.ext_last_name = options.ext_last_name; }
|
|
854
861
|
if(options.address) { bodyParams.address = options.address; }
|
|
@@ -42,7 +42,8 @@ function spacesextn(accountId) {
|
|
|
42
42
|
this.accountId = accountId;
|
|
43
43
|
return {
|
|
44
44
|
entitleBulkUsersToProducts: entitleBulkUsersToProducts.bind(this),
|
|
45
|
-
setupUser: setupUser.bind(this)
|
|
45
|
+
setupUser: setupUser.bind(this),
|
|
46
|
+
deleteUserAccount : deleteUserAccount.bind(this)
|
|
46
47
|
};
|
|
47
48
|
}
|
|
48
49
|
|
|
@@ -129,3 +130,47 @@ function setupUser(options) {
|
|
|
129
130
|
}
|
|
130
131
|
return deferred.promise;
|
|
131
132
|
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Wiki Link for SDK params
|
|
136
|
+
* https://github.com/comprodls/comprodls-sdk-js/wiki/04_AUTH-Adapter#getuserprofileparams
|
|
137
|
+
*/
|
|
138
|
+
function deleteUserAccount(options) {
|
|
139
|
+
var self = this;
|
|
140
|
+
return new Promise(function(resolve, reject) {
|
|
141
|
+
|
|
142
|
+
if (options.ext_user_id && options.body) {
|
|
143
|
+
//Passed all validations, Contruct API url
|
|
144
|
+
var url = self.config.DEFAULT_HOSTS.AUTHEXTN +
|
|
145
|
+
self.config.AUTHEXTN_API_URLS.deleteUserAccount;
|
|
146
|
+
url = helpers.api.constructAPIUrl(url, {
|
|
147
|
+
accountid: self.accountId,
|
|
148
|
+
ext_user_id: options.ext_user_id,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Setup request
|
|
152
|
+
var requestAPI = request.delete(url)
|
|
153
|
+
.set('Content-Type', 'application/json')
|
|
154
|
+
.set('Accept', 'application/json');
|
|
155
|
+
if (self.traceid) { requestAPI.set('X-Amzn-Trace-Id', self.traceid); }
|
|
156
|
+
|
|
157
|
+
requestAPI
|
|
158
|
+
.send(options.body)
|
|
159
|
+
.agent(keepaliveAgent)
|
|
160
|
+
.end(function(error, response) {
|
|
161
|
+
if (error) {
|
|
162
|
+
var err = new DLSError(helpers.errors.ERROR_TYPES.API_ERROR, error);
|
|
163
|
+
reject(err);
|
|
164
|
+
} else {
|
|
165
|
+
resolve(response.body);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
} else {
|
|
169
|
+
var err = {};
|
|
170
|
+
err.description = 'Mandatory field \'ext_user_id\' or \'body\' in request options.';
|
|
171
|
+
err.message = err.description;
|
|
172
|
+
err = new DLSError(helpers.errors.ERROR_TYPES.SDK_ERROR, err);
|
|
173
|
+
reject(err);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
@@ -27,6 +27,7 @@ var q = require('q');
|
|
|
27
27
|
var request = require('superagent');
|
|
28
28
|
|
|
29
29
|
var helpers = require('../../helpers');
|
|
30
|
+
var requestLayer = require('../../helpers/lib/requestLayer');
|
|
30
31
|
|
|
31
32
|
var DLSError = helpers.errors.DLSError;
|
|
32
33
|
|
|
@@ -48,34 +49,32 @@ function superuser(accountId) {
|
|
|
48
49
|
*/
|
|
49
50
|
function getAllInstitutions(options) {
|
|
50
51
|
var self = this;
|
|
51
|
-
// Initializing promise
|
|
52
|
-
var dfd = q.defer();
|
|
53
52
|
|
|
54
|
-
var url =
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
var url = helpers.api.constructAPIUrl(
|
|
54
|
+
self.config.DEFAULT_HOSTS.AUTH + self.config.AUTH_API_URLS.getAllInstitutions,
|
|
55
|
+
{ accountid : self.accountId }
|
|
56
|
+
);
|
|
57
57
|
|
|
58
58
|
var params = {};
|
|
59
|
-
if(options) {
|
|
60
|
-
if(options.lookup) { params.lookup = options.lookup; }
|
|
61
|
-
if(options.cursor) { params.cursor = options.cursor; }
|
|
62
|
-
if(options.
|
|
59
|
+
if (options) {
|
|
60
|
+
if (options.lookup) { params.lookup = options.lookup; }
|
|
61
|
+
if (options.cursor) { params.cursor = options.cursor; }
|
|
62
|
+
if (options.start) { params.start = options.start; }
|
|
63
|
+
if (options.end) { params.end = options.end; }
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
//
|
|
66
|
-
var
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
66
|
+
// Prepare request parameters & execute request
|
|
67
|
+
var reqOptions = {
|
|
68
|
+
params: params,
|
|
69
|
+
headers: { traceid: self.traceid }
|
|
70
|
+
};
|
|
71
|
+
return requestLayer.get(url, reqOptions)
|
|
72
|
+
.then(function (response) {
|
|
73
|
+
return response.body;
|
|
74
|
+
})
|
|
75
|
+
.catch(function (err) {
|
|
76
|
+
throw new DLSError(helpers.errors.ERROR_TYPES.API_ERROR, err);
|
|
76
77
|
});
|
|
77
|
-
|
|
78
|
-
return dfd.promise;
|
|
79
78
|
}
|
|
80
79
|
|
|
81
80
|
/**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "comprodls-sdk",
|
|
3
3
|
"description": "comproDLS SDK for JavaScript",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.109.0-thor.1",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Compro Technologies Private Limited",
|
|
7
7
|
"url": "http://www.comprotechnologies.com/"
|
|
@@ -16,8 +16,7 @@
|
|
|
16
16
|
"pubnub": "7.5.0",
|
|
17
17
|
"q": "~1.4.1",
|
|
18
18
|
"string-template": "^1.0.0",
|
|
19
|
-
"superagent": "3.8.3"
|
|
20
|
-
"validate.js": "^0.9.0"
|
|
19
|
+
"superagent": "3.8.3"
|
|
21
20
|
},
|
|
22
21
|
"devDependencies": {
|
|
23
22
|
"grunt": "^0.4.5",
|