twilio-sync
Advanced tools
Comparing version 0.3.7-dev-build.162 to 0.3.7-dev-build.177
@@ -47,4 +47,2 @@ 'use strict'; | ||
var _twilioTransport2 = _interopRequireDefault(_twilioTransport); | ||
var _twilioNotifications = require('twilio-notifications'); | ||
@@ -169,3 +167,3 @@ | ||
options.twilsockClient = options.twilsockClient || new _twilsock2.default(token, options); | ||
options.transport = options.transport || new _twilioTransport2.default(options.twilsockClient); | ||
options.transport = options.transport || new _twilioTransport.Transport(options.twilsockClient); | ||
options.notificationsClient = options.notificationsClient || new _twilioNotifications2.default(token, options); | ||
@@ -178,3 +176,3 @@ | ||
var network = new _network2.default(productId, new _clientInfo2.default(SDK_VERSION), config, transport); | ||
var subscriptions = new _subscriptions2.default(config, network); | ||
var subscriptions = new _subscriptions2.default(config, network, _twilioTransport.TwilsockUnavailableError); | ||
var router = new _router2.default({ config: config, subscriptions: subscriptions, notifications: notifications }); | ||
@@ -389,7 +387,3 @@ | ||
value: function shutdown() { | ||
var _this5 = this; | ||
return this._subscriptions.shutdown().then(function () { | ||
return _this5._twilsock.disconnect(); | ||
}).then(function () {}); | ||
return _promise2.default.all([this._subscriptions.shutdown(), this._twilsock.disconnect()]).then(function () {}); | ||
} | ||
@@ -396,0 +390,0 @@ |
@@ -22,3 +22,3 @@ 'use strict'; | ||
var CDS_URI = 'https://cds.twilio.com'; | ||
var SUBSCRIPTIONS_PATH = '/v3/Subscriptions'; | ||
var SUBSCRIPTIONS_PATH = '/v4/Subscriptions'; | ||
var MAPS_PATH = '/v3/Maps'; | ||
@@ -25,0 +25,0 @@ var LISTS_PATH = '/v3/Lists'; |
@@ -37,2 +37,5 @@ 'use strict'; | ||
/** | ||
* Base class for all Sync entity types | ||
*/ | ||
var Entity = function (_EventEmitter) { | ||
@@ -73,3 +76,3 @@ (0, _inherits3.default)(Entity, _EventEmitter); | ||
value: function _unsubscribe() { | ||
this._deps.router.unsubscribe(this.sid, this); | ||
this._deps.router.unsubscribe(this.sid); | ||
return this; | ||
@@ -79,2 +82,14 @@ } | ||
/** | ||
* Report an error, which is occured during internal processes | ||
*/ | ||
}, { | ||
key: '_reportFailure', | ||
value: function _reportFailure(err) { | ||
this.emit('failure', err); | ||
} | ||
/** | ||
* Closes current entity | ||
* Remove server subscription, local cache, and other related stuff | ||
* @public | ||
@@ -81,0 +96,0 @@ */ |
@@ -35,2 +35,6 @@ 'use strict'; | ||
var _syncerror = require('./syncerror'); | ||
var _syncerror2 = _interopRequireDefault(_syncerror); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -63,2 +67,6 @@ | ||
* Make a GET request by given URI | ||
* @param {string} uri is the target of the HTTP fetch. | ||
* @param {boolean} forceTwilsock instructs (iff true) the transport layer to execute this request | ||
* only when a Twilsock connection is avaliable. A request made at any other time will throw | ||
* the exception of the Twilsock library in use. | ||
* @Returns Promise<Response> Result of successful get request | ||
@@ -71,9 +79,22 @@ */ | ||
value: function get(uri) { | ||
var forceTwilsock = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; | ||
var headers = this._generateHeaders(); | ||
_logger2.default.debug('GET', uri, 'ID:', headers['Twilio-Request-Id']); | ||
return this._transport.get(uri, headers); | ||
return this._transport.get(uri, headers, forceTwilsock).catch(this._mapTransportError); | ||
} | ||
/** | ||
* Make a POST request to the given URI. | ||
* @param {string} uri is the target of the HTTP fetch. | ||
* @param {boolean} forceTwilsock instructs (iff true) the transport layer to execute this request | ||
* only when a Twilsock connection is avaliable. A request made at any other time will throw | ||
* the exception of the Twilsock library in use. | ||
*/ | ||
}, { | ||
key: 'post', | ||
value: function post(uri, body, revision) { | ||
var forceTwilsock = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; | ||
var headers = this._generateHeaders(); | ||
@@ -83,24 +104,33 @@ if (typeof revision !== 'undefined') { | ||
} | ||
_logger2.default.debug('POST', uri, 'ID:', headers['Twilio-Request-Id']); | ||
return this._transport.post(uri, headers, body); | ||
return this._transport.post(uri, headers, body, forceTwilsock).catch(this._mapTransportError); | ||
} | ||
}, { | ||
key: 'put', | ||
value: function put(uri, body, revision) { | ||
var headers = this._generateHeaders(); | ||
if (typeof revision !== 'undefined') { | ||
headers['If-Match'] = revision; | ||
} | ||
_logger2.default.debug('PUT', uri, 'ID:', headers['Twilio-Request-Id']); | ||
return this._transport.put(uri, headers, body); | ||
} | ||
/** | ||
* Make a DELETE request to the given URI. | ||
* @param {string} uri is the target of the HTTP fetch. | ||
* @param {boolean} forceTwilsock instructs (iff true) the transport layer to execute this request | ||
* only when a Twilsock connection is avaliable. A request made at any other time will throw | ||
* the exception of the Twilsock library in use. | ||
*/ | ||
}, { | ||
key: 'delete', | ||
value: function _delete(uri) { | ||
var forceTwilsock = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; | ||
var headers = this._generateHeaders(); | ||
_logger2.default.debug('DELETE', uri, 'ID:', headers['Twilio-Request-Id']); | ||
return this._transport.delete(uri, headers); | ||
return this._transport.delete(uri, headers, forceTwilsock).catch(this._mapTransportError); | ||
} | ||
}, { | ||
key: '_mapTransportError', | ||
value: function _mapTransportError(transportError) { | ||
var body = transportError.body; | ||
if (body && body.status === 429) { | ||
throw new _syncerror2.default(body.message, 'RATE_LIMIT_EXCEEDED'); | ||
} else { | ||
throw transportError; | ||
} | ||
} | ||
}]); | ||
@@ -107,0 +137,0 @@ return Network; |
@@ -7,2 +7,6 @@ 'use strict'; | ||
var _promise = require('babel-runtime/core-js/promise'); | ||
var _promise2 = _interopRequireDefault(_promise); | ||
var _defineProperties = require('babel-runtime/core-js/object/define-properties'); | ||
@@ -24,4 +28,2 @@ | ||
var _utils = require('./utils'); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -32,2 +34,3 @@ | ||
var COREDATA_MAP_NOTIFICATION_TYPE = 'com.twilio.rtd.cds.map'; | ||
var SYNC_NOTIFICATION_TYPE = 'twilio.sync.event'; | ||
@@ -54,2 +57,3 @@ /** | ||
this._notifications.subscribe(COREDATA_MAP_NOTIFICATION_TYPE); | ||
this._notifications.subscribe(SYNC_NOTIFICATION_TYPE); | ||
@@ -74,25 +78,6 @@ this._notifications.on('message', this.onMessage.bind(this)); | ||
value: function onMessage(type, message) { | ||
// As it happens, all message types are intended for the Subscriptions manager. | ||
// | ||
_logger2.default.trace('Notification type:', type, 'content:', message); | ||
var id = void 0; | ||
switch (type) { | ||
case COREDATA_DOCUMENT_NOTIFICATION_TYPE: | ||
id = message.event.document_sid; | ||
break; | ||
case COREDATA_LIST_NOTIFICATION_TYPE: | ||
id = message.event.list_sid; | ||
break; | ||
case COREDATA_MAP_NOTIFICATION_TYPE: | ||
id = message.event.map_sid; | ||
break; | ||
default: | ||
_logger2.default.warn('Unknown message type:', type, 'ignoring'); | ||
} | ||
if (id) { | ||
message.event.type = message.event_type; // Patch message | ||
this._subscriptions.getSubscribers(id).forEach(function (entity) { | ||
entity._update((0, _utils.deepClone)(message.event)); | ||
}); | ||
} | ||
this._subscriptions.acceptMessage(message); | ||
} | ||
@@ -107,9 +92,4 @@ | ||
value: function subscribe(sid, entity) { | ||
return this._subscriptions.add(sid, entity).then(function (isNewSubscription) { | ||
if (isNewSubscription) { | ||
entity._softSync(); | ||
} | ||
}).then(function () { | ||
return entity; | ||
}); | ||
this._subscriptions.add(sid, entity); | ||
return _promise2.default.resolve(entity); | ||
} | ||
@@ -123,6 +103,4 @@ | ||
key: 'unsubscribe', | ||
value: function unsubscribe(sid, entity) { | ||
return this._subscriptions.remove(sid, entity).then(function () { | ||
return entity; | ||
}); | ||
value: function unsubscribe(sid) { | ||
return this._subscriptions.remove(sid); | ||
} | ||
@@ -138,5 +116,3 @@ | ||
value: function onConnected() { | ||
this._subscriptions.forEach(function (id, endpoint) { | ||
endpoint._softSync(); | ||
}); | ||
this._subscriptions.poke(); | ||
} | ||
@@ -143,0 +119,0 @@ }]); |
@@ -7,9 +7,9 @@ 'use strict'; | ||
var _set = require('babel-runtime/core-js/set'); | ||
var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); | ||
var _set2 = _interopRequireDefault(_set); | ||
var _slicedToArray3 = _interopRequireDefault(_slicedToArray2); | ||
var _promise = require('babel-runtime/core-js/promise'); | ||
var _getIterator2 = require('babel-runtime/core-js/get-iterator'); | ||
var _promise2 = _interopRequireDefault(_promise); | ||
var _getIterator3 = _interopRequireDefault(_getIterator2); | ||
@@ -20,2 +20,6 @@ var _map = require('babel-runtime/core-js/map'); | ||
var _extends2 = require('babel-runtime/helpers/extends'); | ||
var _extends3 = _interopRequireDefault(_extends2); | ||
var _defineProperties = require('babel-runtime/core-js/object/define-properties'); | ||
@@ -25,6 +29,2 @@ | ||
var _extends2 = require('babel-runtime/helpers/extends'); | ||
var _extends3 = _interopRequireDefault(_extends2); | ||
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); | ||
@@ -38,10 +38,2 @@ | ||
var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); | ||
var _slicedToArray3 = _interopRequireDefault(_slicedToArray2); | ||
var _getIterator2 = require('babel-runtime/core-js/get-iterator'); | ||
var _getIterator3 = _interopRequireDefault(_getIterator2); | ||
var _backoff = require('backoff'); | ||
@@ -55,62 +47,81 @@ | ||
var _syncerror = require('./syncerror'); | ||
var _syncerror2 = _interopRequireDefault(_syncerror); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
var MAX_SUBSCRIPTION_BATCH_SIZE = 100; | ||
var MAX_BATCH_SIZE = 1000; | ||
function substract(p1, p2, limit) { | ||
var result = []; | ||
var _iteratorNormalCompletion = true; | ||
var _didIteratorError = false; | ||
var _iteratorError = undefined; | ||
/** | ||
* A data container used by the Subscriptions class to track subscribed entities' local | ||
* representations and their state. | ||
*/ | ||
/* eslint-disable key-spacing */ | ||
try { | ||
for (var _iterator = (0, _getIterator3.default)(p1), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | ||
var _step$value = (0, _slicedToArray3.default)(_step.value, 2), | ||
key = _step$value[0], | ||
value = _step$value[1]; | ||
var SubscribedEntity = function () { | ||
function SubscribedEntity(entity) { | ||
var _this = this; | ||
if (!p2.has(key)) { | ||
result.push({ sid: key, type: value.type }); | ||
if (limit && result.length >= limit) { | ||
break; | ||
} | ||
} | ||
} | ||
} catch (err) { | ||
_didIteratorError = true; | ||
_iteratorError = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion && _iterator.return) { | ||
_iterator.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError) { | ||
throw _iteratorError; | ||
} | ||
} | ||
(0, _classCallCheck3.default)(this, SubscribedEntity); | ||
(0, _defineProperties2.default)(this, { | ||
sid: { get: function get() { | ||
return entity.sid; | ||
} }, | ||
type: { get: function get() { | ||
return entity.type; | ||
} }, | ||
lastEventId: { get: function get() { | ||
return entity.lastEventId; | ||
} }, | ||
localObject: { value: entity }, | ||
correlationId: { value: null, writable: true }, | ||
pendingPokeTimer: { value: null, writable: true }, | ||
rejectedWithError: { value: false, writable: true }, | ||
isInTransition: { get: function get() { | ||
return _this.correlationId !== null; | ||
} } | ||
}); | ||
} | ||
return result; | ||
} | ||
(0, _createClass3.default)(SubscribedEntity, [{ | ||
key: 'markAsFailed', | ||
value: function markAsFailed(error) { | ||
this.rejectedWithError = error; | ||
} | ||
}]); | ||
return SubscribedEntity; | ||
}(); | ||
/** | ||
* @class Subscriptions | ||
* @classdesc Subscriptions container for CDS objects | ||
* @classdesc A manager which, in batches of varying size, continuously persists the | ||
* subscription intent of the caller to the Sync backend until it achieves a | ||
* converged state. | ||
*/ | ||
var Subscriptions = function () { | ||
/** | ||
* @constructor | ||
* Prepares a new Subscriptions manager object with zero subscribed or persisted subscriptions. | ||
* | ||
* @param {object} config may include a key 'backoffConfig', wherein any of the parameters | ||
* of Backoff.exponential (from npm 'backoff') are valid and will override the defaults. | ||
* | ||
* @param {Network} must be a viable running Sync Network object, useful for routing requests. | ||
*/ | ||
function Subscriptions(config, network) { | ||
var _this = this; | ||
function Subscriptions(config, network, TwilsockUnvailableErrorType) { | ||
var _this2 = this; | ||
(0, _classCallCheck3.default)(this, Subscriptions); | ||
function createBackoff() { | ||
var defaultBackoffConfig = { | ||
randomisationFactor: 0.2, | ||
initialDelay: 50, | ||
initialDelay: 100, | ||
maxDelay: 2 * 60 * 1000 | ||
@@ -120,4 +131,4 @@ }; | ||
} | ||
(0, _defineProperties2.default)(this, { | ||
_ExceptionOnTwilsockUnavailable: { value: TwilsockUnvailableErrorType, writable: false }, | ||
_config: { value: config }, | ||
@@ -127,26 +138,27 @@ _network: { value: network }, | ||
_types: { value: new _map2.default() }, | ||
// This is always the full set of subscribables (SubscribedEntity instances) intended by | ||
// the client. At any point, whatever the state of these subscriptions on the server, this | ||
// is the intent of the user to which the SDK must converge. | ||
_subscriptions: { value: new _map2.default() }, | ||
// This includes the set of subscribables (SubscribedEntity instances) for whom a request | ||
// has been dispatched (whether or not this particular request ultimately succeeds) to | ||
// establish a live subscription. Entities are removed when the corresponding "cancel" | ||
// request is dispatched. | ||
_persisted: { value: new _map2.default() } | ||
}); | ||
// This block is triggered by #_persist. Every request is executed in a series of (ideally 1) | ||
// backoff 'ready' event, at which point a new subscription set is calculated. | ||
// | ||
this._backoff.on('ready', function () { | ||
var _getAction2 = _this._getAction(), | ||
action = _getAction2.action, | ||
list = _getAction2.list; | ||
var _getSubscriptionUpdat = _this2._getSubscriptionUpdateBatch(), | ||
action = _getSubscriptionUpdat.action, | ||
subscriptionRequests = _getSubscriptionUpdat.subscriptions; | ||
if (action) { | ||
_this._request(action, list.map(function (x) { | ||
return { object_sid: x.sid, object_type: x.type }; | ||
})) // eslint-disable-line camelcase | ||
.then(function () { | ||
_this._backoff.reset(); | ||
_this._backoff.backoff(); | ||
}).catch(function (e) { | ||
_logger2.default.error('Error while persisting subscriptions, retrying', action, e); | ||
_this._backoff.backoff(e); | ||
}); | ||
_this2._applyNewSubscriptionUpdateBatch(action, subscriptionRequests); | ||
} else { | ||
_this._backoff.reset(); | ||
_logger2.default.info('Subscription list persisted'); | ||
_this2._backoff.reset(); | ||
_logger2.default.info('All subscriptions resolved.'); | ||
} | ||
@@ -157,16 +169,52 @@ }); | ||
(0, _createClass3.default)(Subscriptions, [{ | ||
key: '_getAction', | ||
value: function _getAction() { | ||
var _this2 = this; | ||
key: '_getSubscriptionUpdateBatch', | ||
value: function _getSubscriptionUpdateBatch() { | ||
function substract(these, those, ignoreCurrentOp, limit) { | ||
var result = []; | ||
var _iteratorNormalCompletion = true; | ||
var _didIteratorError = false; | ||
var _iteratorError = undefined; | ||
var listToAdd = substract(this._subscriptions, this._persisted, MAX_SUBSCRIPTION_BATCH_SIZE); | ||
try { | ||
for (var _iterator = (0, _getIterator3.default)(these), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | ||
var _step$value = (0, _slicedToArray3.default)(_step.value, 2), | ||
thisKey = _step$value[0], | ||
thisValue = _step$value[1]; | ||
var otherValue = those.get(thisKey); | ||
if (!otherValue && (ignoreCurrentOp || !thisValue.isInTransition) && !thisValue.rejectedWithError) { | ||
result.push(thisValue); | ||
if (limit && result.length >= limit) { | ||
break; | ||
} | ||
} | ||
} | ||
} catch (err) { | ||
_didIteratorError = true; | ||
_iteratorError = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion && _iterator.return) { | ||
_iterator.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError) { | ||
throw _iteratorError; | ||
} | ||
} | ||
} | ||
return result; | ||
} | ||
var listToAdd = substract(this._subscriptions, this._persisted, false, MAX_BATCH_SIZE).map(function (x) { | ||
return new SubscribedEntity(x); | ||
}); | ||
if (listToAdd.length > 0) { | ||
return { action: 'establish', list: listToAdd }; | ||
return { action: 'establish', subscriptions: listToAdd }; | ||
} | ||
var listToRemove = substract(this._persisted, this._subscriptions, MAX_SUBSCRIPTION_BATCH_SIZE).map(function (x) { | ||
return { sid: x.sid, type: _this2._types.get(x.sid) }; | ||
}); | ||
var listToRemove = substract(this._persisted, this._subscriptions, true, MAX_BATCH_SIZE); | ||
if (listToRemove.length > 0) { | ||
return { action: 'cancel', list: listToRemove }; | ||
return { action: 'cancel', subscriptions: listToRemove }; | ||
} | ||
@@ -183,3 +231,142 @@ | ||
} | ||
}, { | ||
key: '_applyNewSubscriptionUpdateBatch', | ||
value: function _applyNewSubscriptionUpdateBatch(action, requests) { | ||
var _this3 = this; | ||
// Keeping in mind that events may begin flowing _before_ we receive the response | ||
requests = this._processLocalActions(action, requests); | ||
var correlationId = new Date().getTime(); | ||
var _iteratorNormalCompletion2 = true; | ||
var _didIteratorError2 = false; | ||
var _iteratorError2 = undefined; | ||
try { | ||
for (var _iterator2 = (0, _getIterator3.default)(requests), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { | ||
var subscribed = _step2.value; | ||
this._recordActionAttemptOn(subscribed, action, correlationId); | ||
} | ||
// Send this batch to the service | ||
} catch (err) { | ||
_didIteratorError2 = true; | ||
_iteratorError2 = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion2 && _iterator2.return) { | ||
_iterator2.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError2) { | ||
throw _iteratorError2; | ||
} | ||
} | ||
} | ||
this._request(action, correlationId, requests.map(function (object) { | ||
return { | ||
object_sid: object.sid, // eslint-disable-line camelcase | ||
object_type: object.type, // eslint-disable-line camelcase | ||
last_event_id: action === 'establish' ? object.lastEventId : undefined // eslint-disable-line no-undefined, camelcase | ||
}; | ||
})).then(function (response) { | ||
if (action === 'establish') { | ||
var _iteratorNormalCompletion3 = true; | ||
var _didIteratorError3 = false; | ||
var _iteratorError3 = undefined; | ||
try { | ||
for (var _iterator3 = (0, _getIterator3.default)(requests), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { | ||
var subscribed = _step3.value; | ||
if (subscribed.correlationId === correlationId) { | ||
_this3._beginReplayTimeout(response.body.estimated_delivery_in_ms, subscribed, action, correlationId); | ||
} | ||
} | ||
} catch (err) { | ||
_didIteratorError3 = true; | ||
_iteratorError3 = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion3 && _iterator3.return) { | ||
_iterator3.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError3) { | ||
throw _iteratorError3; | ||
} | ||
} | ||
} | ||
} | ||
_this3._backoff.reset(); | ||
}).catch(function (e) { | ||
var _iteratorNormalCompletion4 = true; | ||
var _didIteratorError4 = false; | ||
var _iteratorError4 = undefined; | ||
try { | ||
for (var _iterator4 = (0, _getIterator3.default)(requests), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { | ||
var attemptedSubscription = _step4.value; | ||
_this3._recordActionFailureOn(attemptedSubscription, action); | ||
} | ||
} catch (err) { | ||
_didIteratorError4 = true; | ||
_iteratorError4 = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion4 && _iterator4.return) { | ||
_iterator4.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError4) { | ||
throw _iteratorError4; | ||
} | ||
} | ||
} | ||
if (e instanceof _this3._ExceptionOnTwilsockUnavailable) { | ||
_logger2.default.debug('Twilsock connection (required for subscription) not ready (c:' + correlationId + '); waiting\u2026'); | ||
_this3._backoff.reset(); | ||
} else { | ||
_logger2.default.debug('Failed an attempt to ' + action + ' subscriptions (c:' + correlationId + '); retrying', e); | ||
_this3._backoff.backoff(e); | ||
} | ||
}); | ||
} | ||
}, { | ||
key: '_processLocalActions', | ||
value: function _processLocalActions(action, requests) { | ||
if (action === 'cancel') { | ||
return requests.filter(function (request) { | ||
return !request.rejectedWithError; | ||
}); | ||
} | ||
return requests; | ||
} | ||
}, { | ||
key: '_recordActionAttemptOn', | ||
value: function _recordActionAttemptOn(attemptedSubscription, action, correlationId) { | ||
if (action === 'establish') { | ||
this._persisted.set(attemptedSubscription.sid, attemptedSubscription); | ||
attemptedSubscription.correlationId = correlationId; | ||
} else { | ||
// cancel | ||
var persistedSubscription = this._persisted.get(attemptedSubscription.sid); | ||
if (persistedSubscription) { | ||
persistedSubscription.correlationId = null; | ||
} | ||
} | ||
} | ||
}, { | ||
key: '_recordActionFailureOn', | ||
value: function _recordActionFailureOn(attemptedSubscription, action) { | ||
attemptedSubscription.correlationId = null; | ||
if (action === 'establish') { | ||
this._persisted.delete(attemptedSubscription.sid); | ||
} | ||
} | ||
/** | ||
@@ -191,34 +378,45 @@ * @private | ||
key: '_request', | ||
value: function _request(action, requests) { | ||
var _this3 = this; | ||
_logger2.default.debug('Modifying server subscriptions: ', action, requests); | ||
value: function _request(action, correlationId, requests) { | ||
_logger2.default.debug('Attempting \'' + action + '\' request (c:' + correlationId + '):', requests); | ||
/* eslint-disable camelcase */ | ||
var requestBody = { | ||
event_protocol_version: 3, // eslint-disable-line camelcase | ||
event_protocol_version: 3, | ||
action: action, | ||
correlation_id: correlationId, | ||
requests: requests | ||
}; | ||
/* eslint-enable camelcase */ | ||
return this._network.post(this._config.subscriptionsUri, requestBody).then(function (response) { | ||
response.body.results.forEach(function (persisted) { | ||
if (!persisted.subscription) { | ||
_this3._persisted.delete(persisted.object_sid); | ||
_this3._types.delete(persisted.object_sid); | ||
} else { | ||
_this3._persisted.set(persisted.object_sid, persisted.subscription); | ||
var subscription = _this3._subscriptions.get(persisted.object_sid); | ||
if (subscription) { | ||
subscription.resolve(true); | ||
} | ||
return this._network.post(this._config.subscriptionsUri, requestBody, null, true); | ||
} | ||
}, { | ||
key: '_beginReplayTimeout', | ||
value: function _beginReplayTimeout(timeout, subscription, action, failingCorrelationId) { | ||
var _this4 = this; | ||
var isNumeric = !isNaN(parseFloat(timeout)) && isFinite(timeout); | ||
var isValidTimeout = isNumeric && timeout > 0; | ||
if (isValidTimeout) { | ||
subscription.pendingPokeTimer = setTimeout(function () { | ||
if (subscription.correlationId === failingCorrelationId) { | ||
_logger2.default.debug('Attempt to ' + action + ' ' + subscription.sid + ' (c:' + failingCorrelationId + ') timed out without confirmation; trying again.'); | ||
subscription.correlationId = null; | ||
_this4._persisted.delete(subscription.sid); | ||
_this4._persist(); | ||
} | ||
}); | ||
}); | ||
}, timeout); | ||
} | ||
} | ||
/** | ||
* Add subscription | ||
* @param {string} uri URI to the server object | ||
* @param {object} endpoint Endpoint object | ||
* @return Promise<boolean> true if subscription haven't been there before | ||
* Establishes intent to be subscribed to this entity. That subscription will be effected | ||
* asynchronously. | ||
* If subscription to the given sid already exists, it will be overwritten. | ||
* | ||
* @param {string} sid should be a well-formed SID, uniquely identifying a single instance of a Sync entity. | ||
* @param {object} entity should represent the (singular) local representation of this entity. | ||
* Incoming events and modifications to the entity will be directed at the _update() function | ||
* of this provided reference. | ||
* | ||
* @return undefined | ||
*/ | ||
@@ -229,37 +427,24 @@ | ||
value: function add(sid, entity) { | ||
var _this4 = this; | ||
var subscribed = this._subscriptions.has(sid); | ||
var persisted = this._persisted.has(sid); | ||
if (subscribed && persisted) { | ||
var _subscription = this._subscriptions.get(sid); | ||
_subscription.subscribers.add(entity); | ||
return _promise2.default.resolve(false); | ||
} else if (this._subscribed && !persisted) { | ||
var _subscription2 = this._subscriptions.get(sid); | ||
return _subscription2.promise; | ||
_logger2.default.debug('Establishing intent to subscribe to ' + sid); | ||
var existingSubscription = this._subscriptions.get(sid); | ||
if (existingSubscription && existingSubscription.lastEventId === entity.lastEventId) { | ||
// If last event id is the same as before - we're fine | ||
return; | ||
} | ||
var subscription = { type: entity.type, | ||
subscribers: new _set2.default([entity]), | ||
promise: null, | ||
resolve: null, | ||
reject: null | ||
}; | ||
this._subscriptions.set(sid, subscription); | ||
var promiseToSubscribe = new _promise2.default(function (resolve, reject) { | ||
subscription.resolve = resolve; | ||
subscription.reject = reject; | ||
_this4._persist(); | ||
}); | ||
subscription.promise = promiseToSubscribe; | ||
return promiseToSubscribe; | ||
this._persisted.delete(sid); | ||
this._subscriptions.set(sid, entity); | ||
this._persist(); | ||
} | ||
/** | ||
* Remove subscription for the entity | ||
* @param {string} entityUri URI | ||
* @param {object} endpoint Endpoint object | ||
* Establishes the caller's intent to no longer be subscribed to this entity. Following this | ||
* call, no further events shall be routed to the local representation of the entity, even | ||
* though a server-side subscription may take more time to actually terminate. | ||
* | ||
* @param {string} sid should be any well-formed SID, uniquely identifying a Sync entity. | ||
* This call only has meaningful effect if that entity is subscribed at the | ||
* time of call. Otherwise does nothing. | ||
* | ||
* @return undefined | ||
*/ | ||
@@ -269,88 +454,193 @@ | ||
key: 'remove', | ||
value: function remove(id, entity) { | ||
if (!this._subscriptions.has(id)) { | ||
return _promise2.default.resolve(true); | ||
value: function remove(sid) { | ||
_logger2.default.debug('Establishing intent to unsubscribe from ' + sid); | ||
var removed = this._subscriptions.delete(sid); | ||
if (removed) { | ||
this._persist(); | ||
} | ||
} | ||
var subscription = this._subscriptions.get(id); | ||
if (!subscription.subscribers.has(entity)) { | ||
return _promise2.default.resolve(false); | ||
} | ||
/** | ||
* The point of ingestion for remote incoming messages (e.g. new data was written to a map | ||
* to which we are subscribed). | ||
* | ||
* @param {object} message is the full, unaltered body of the incoming notification. | ||
* | ||
* @return undefined | ||
*/ | ||
subscription.subscribers.delete(entity); | ||
if (subscription.subscribers.size > 0) { | ||
return _promise2.default.resolve(false); | ||
}, { | ||
key: 'acceptMessage', | ||
value: function acceptMessage(message) { | ||
_logger2.default.trace('Subscriptions received', message); | ||
switch (message.event_type) { | ||
case 'subscription_established': | ||
this._applySubscriptionEstablishedMessage(message.event, message.correlation_id); | ||
break; | ||
case 'subscription_canceled': | ||
this._applySubscriptionCancelledMessage(message.event, message.correlation_id); | ||
break; | ||
case 'subscription_failed': | ||
this._applySubscriptionFailedMessage(message.event, message.correlation_id); | ||
break; | ||
case (message.event_type.match(/^(?:map|list|document)_/) || {}).input: | ||
{ | ||
var typedSid = function typedSid() { | ||
if (message.event_type.match(/^map_/)) return message.event.map_sid;else if (message.event_type.match(/^list_/)) return message.event.list_sid;else if (message.event_type.match(/^document_/)) return message.event.document_sid;else return undefined; // eslint-disable-line no-undefined | ||
}; | ||
this._applyEventToSubscribedEntity(typedSid(), message); | ||
} | ||
break; | ||
default: | ||
_logger2.default.debug('Dropping unknown message type ' + message.event_type); | ||
break; | ||
} | ||
} | ||
}, { | ||
key: '_applySubscriptionEstablishedMessage', | ||
value: function _applySubscriptionEstablishedMessage(message, correlationId) { | ||
var sid = message.object_sid; | ||
var subscriptionIntent = this._persisted.get(message.object_sid); | ||
if (subscriptionIntent && subscriptionIntent.correlationId === correlationId) { | ||
if (message.replay_status === 'interrupted') { | ||
_logger2.default.debug('Event Replay for subscription to ' + sid + ' (c:' + correlationId + ') interrupted; continuing eagerly.'); | ||
clearTimeout(subscriptionIntent.pendingPokeTimer); | ||
subscriptionIntent.pendingPokeTimer = null; | ||
subscriptionIntent.correlationId = null; | ||
this._persisted.delete(subscriptionIntent.sid); | ||
this._backoff.reset(); | ||
} else if (message.replay_status === 'completed') { | ||
_logger2.default.debug('Event Replay for subscription to ' + sid + ' (c:' + correlationId + ') completed. Subscription is ready.'); | ||
// It's a hack: since server responce doesn't contain a type, but we need it | ||
// we should always keep it locally | ||
this._types.set(id, subscription.type); | ||
clearTimeout(subscriptionIntent.pendingPokeTimer); | ||
subscriptionIntent.pendingPokeTimer = null; | ||
subscriptionIntent.correlationId = null; | ||
this._persisted.set(message.object_sid, subscriptionIntent); | ||
this._subscriptions.delete(id); | ||
this._backoff.reset(); | ||
} | ||
} else { | ||
_logger2.default.debug('Late message for ' + message.object_sid + ' (c:' + correlationId + ') dropped.'); | ||
} | ||
this._persist(); | ||
return _promise2.default.resolve(true); | ||
} | ||
}, { | ||
key: '_applySubscriptionCancelledMessage', | ||
value: function _applySubscriptionCancelledMessage(message, correlationId) { | ||
var persistedSubscription = this._persisted.get(message.object_sid); | ||
if (persistedSubscription && persistedSubscription.correlationId === correlationId) { | ||
clearTimeout(persistedSubscription.pendingPokeTimer); | ||
persistedSubscription.pendingPokeTimer = null; | ||
persistedSubscription.correlationId = null; | ||
/** | ||
* Query subscribers for given URI | ||
* @return {set} | ||
*/ | ||
this._persisted.delete(message.object_sid); | ||
} else { | ||
_logger2.default.debug('Late message for ' + message.object_sid + ' (c:' + correlationId + ') dropped.'); | ||
} | ||
this._persist(); | ||
} | ||
}, { | ||
key: '_applySubscriptionFailedMessage', | ||
value: function _applySubscriptionFailedMessage(message, correlationId) { | ||
var sid = message.object_sid; | ||
var subscriptionIntent = this._subscriptions.get(sid); | ||
var subscription = this._persisted.get(sid); | ||
if (subscriptionIntent && subscription) { | ||
if (subscription.correlationId === correlationId) { | ||
subscription.markAsFailed(message.error); | ||
_logger2.default.error('Failed to subscribe on ' + subscription.sid, message.error); | ||
subscriptionIntent._reportFailure(new _syncerror2.default('Failed to subscribe on service events')); | ||
} | ||
} else if (!subscriptionIntent && subscription) { | ||
this._persisted.delete(sid); | ||
} | ||
this._persist(); | ||
} | ||
}, { | ||
key: 'getSubscribers', | ||
value: function getSubscribers(id) { | ||
var subscription = this._subscriptions.get(id); | ||
return subscription && subscription.subscribers ? subscription.subscribers : new _set2.default(); | ||
key: '_applyEventToSubscribedEntity', | ||
value: function _applyEventToSubscribedEntity(sid, message) { | ||
var subscriptionIntent = sid ? this._subscriptions.get(sid) : null; | ||
if (subscriptionIntent) { | ||
message.event.type = message.event_type; | ||
subscriptionIntent._update(message.event, true); | ||
} else { | ||
_logger2.default.debug('Message dropped for SID \'' + sid + '\', for which there is no subscription.'); | ||
} | ||
} | ||
/** | ||
* Iterates through all subscriptions | ||
* @param {function} hanlder function to call for each subscription | ||
* Prompts a playback of any missed changes made to any subscribed object. This method | ||
* should be invoked whenever the connectivity layer has experienced cross-cutting | ||
* delivery failures that would affect the entire local sync set. Any tangible result | ||
* of this operation will result in calls to the _update() function of subscribed | ||
* Sync entities. | ||
*/ | ||
}, { | ||
key: 'forEach', | ||
value: function forEach(handler) { | ||
this._subscriptions.forEach(function (subscription, id) { | ||
subscription.subscribers.forEach(function (subscriber) { | ||
return handler(id, subscriber); | ||
}); | ||
}); | ||
} | ||
key: 'poke', | ||
value: function poke() { | ||
_logger2.default.info('Triggering event replay for all subscriptions.'); | ||
/** | ||
* Currently just resets retries | ||
* Should remove all subscriptions | ||
*/ | ||
var failedSubscriptions = []; | ||
}, { | ||
key: 'shutdown', | ||
value: function shutdown() { | ||
this._backoff.reset(); | ||
this._subscriptions.clear(); | ||
var _iteratorNormalCompletion5 = true; | ||
var _didIteratorError5 = false; | ||
var _iteratorError5 = undefined; | ||
var listToRemove = []; | ||
var _iteratorNormalCompletion2 = true; | ||
var _didIteratorError2 = false; | ||
var _iteratorError2 = undefined; | ||
try { | ||
for (var _iterator5 = (0, _getIterator3.default)(this._persisted), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { | ||
var _step5$value = (0, _slicedToArray3.default)(_step5.value, 2), | ||
_ = _step5$value[0], | ||
it = _step5$value[1]; | ||
// eslint-disable-line no-unused-vars | ||
clearTimeout(it.pendingPokeTimer); | ||
it.pendingPokeTimer = null; | ||
it.correlationId = null; | ||
if (it.rejectedWithError) { | ||
failedSubscriptions.push(it); | ||
} | ||
} | ||
} catch (err) { | ||
_didIteratorError5 = true; | ||
_iteratorError5 = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion5 && _iterator5.return) { | ||
_iterator5.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError5) { | ||
throw _iteratorError5; | ||
} | ||
} | ||
} | ||
this._persisted.clear(); | ||
var _iteratorNormalCompletion6 = true; | ||
var _didIteratorError6 = false; | ||
var _iteratorError6 = undefined; | ||
try { | ||
for (var _iterator2 = (0, _getIterator3.default)(this._persisted), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { | ||
var _step2$value = (0, _slicedToArray3.default)(_step2.value, 2), | ||
key = _step2$value[0], | ||
value = _step2$value[1]; | ||
for (var _iterator6 = (0, _getIterator3.default)(failedSubscriptions), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) { | ||
var it = _step6.value; | ||
listToRemove.push({ object_sid: key, type: value.type }); // eslint-disable-line camelcase | ||
this._persisted.set(it.sid, it); | ||
} | ||
} catch (err) { | ||
_didIteratorError2 = true; | ||
_iteratorError2 = err; | ||
_didIteratorError6 = true; | ||
_iteratorError6 = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion2 && _iterator2.return) { | ||
_iterator2.return(); | ||
if (!_iteratorNormalCompletion6 && _iterator6.return) { | ||
_iterator6.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError2) { | ||
throw _iteratorError2; | ||
if (_didIteratorError6) { | ||
throw _iteratorError6; | ||
} | ||
@@ -360,7 +650,15 @@ } | ||
if (listToRemove.length > 0) { | ||
return this._request('cancel', listToRemove).catch(function () {}); | ||
} | ||
return _promise2.default.resolve(); | ||
this._persist(); | ||
} | ||
/** | ||
* Stops all communication, clears any subscription intent, and returns. | ||
*/ | ||
}, { | ||
key: 'shutdown', | ||
value: function shutdown() { | ||
this._backoff.reset(); | ||
this._subscriptions.clear(); | ||
} | ||
}]); | ||
@@ -367,0 +665,0 @@ return Subscriptions; |
@@ -369,2 +369,4 @@ 'use strict'; | ||
value: function _onRemoved(locally) { | ||
this._unsubscribe(); | ||
// Should also do some cleanup here | ||
@@ -398,2 +400,7 @@ this.emit('removed', locally); | ||
} | ||
}, { | ||
key: 'lastEventId', | ||
get: function get() { | ||
return this._lastEventId; | ||
} | ||
}]); | ||
@@ -400,0 +407,0 @@ return SyncDocument; |
@@ -1,2 +0,2 @@ | ||
"use strict"; | ||
'use strict'; | ||
@@ -7,19 +7,19 @@ Object.defineProperty(exports, "__esModule", { | ||
var _defineProperties = require("babel-runtime/core-js/object/define-properties"); | ||
var _defineProperties = require('babel-runtime/core-js/object/define-properties'); | ||
var _defineProperties2 = _interopRequireDefault(_defineProperties); | ||
var _getPrototypeOf = require("babel-runtime/core-js/object/get-prototype-of"); | ||
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); | ||
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); | ||
var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck"); | ||
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); | ||
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); | ||
var _possibleConstructorReturn2 = require("babel-runtime/helpers/possibleConstructorReturn"); | ||
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); | ||
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); | ||
var _inherits2 = require("babel-runtime/helpers/inherits"); | ||
var _inherits2 = require('babel-runtime/helpers/inherits'); | ||
@@ -39,3 +39,3 @@ var _inherits3 = _interopRequireDefault(_inherits2); | ||
(0, _defineProperties2.default)(_this, { | ||
name: { value: _this.constructor.name, enumerable: true }, | ||
name: { value: 'SyncError', enumerable: true }, | ||
message: { value: message, enumerable: true }, | ||
@@ -51,2 +51,2 @@ code: { value: code, enumerable: true } | ||
exports.default = SyncError; | ||
module.exports = exports["default"]; | ||
module.exports = exports['default']; |
@@ -443,2 +443,4 @@ 'use strict'; | ||
value: function _onRemoved(locally) { | ||
this._unsubscribe(); | ||
// Should also do some cleanup here | ||
@@ -477,3 +479,8 @@ this.emit('collectionRemoved', locally); | ||
/** | ||
* Handle update, which came from the server | ||
* Handle update event | ||
* | ||
* @param {Object} update | ||
* @param {Boolean} reliable Caller should point if transport reliable or not | ||
* For reliable transport it will advance lastEventId even if gaps in events were found | ||
* | ||
* @private | ||
@@ -484,3 +491,3 @@ */ | ||
key: '_update', | ||
value: function _update(update) { | ||
value: function _update(update, reliableTransport) { | ||
var itemIndex = Number(update.item_index); | ||
@@ -515,4 +522,7 @@ switch (update.type) { | ||
if (this._lastEventId < update.id) { | ||
this._revision = update.list_revision; | ||
this._revision = update.list_revision; | ||
var updateHasNoGap = update.id - this._lastEventId === 1; | ||
var shouldAdvance = this._lastEventId < update.id && (reliableTransport || updateHasNoGap); | ||
if (shouldAdvance) { | ||
this._lastEventId = update.id; | ||
@@ -589,2 +599,7 @@ } | ||
} | ||
}, { | ||
key: 'lastEventId', | ||
get: function get() { | ||
return this._lastEventId; | ||
} | ||
}]); | ||
@@ -591,0 +606,0 @@ return SyncList; |
@@ -572,3 +572,8 @@ 'use strict'; | ||
/** | ||
* Handle update from the server | ||
* Handle update event | ||
* | ||
* @param {Object} update | ||
* @param {Boolean} reliable Caller should point if transport reliable or not | ||
* For reliable transport it will advance lastEventId even if gaps in events were found | ||
* | ||
* @private | ||
@@ -579,3 +584,3 @@ */ | ||
key: '_update', | ||
value: function _update(update) { | ||
value: function _update(update, reliableTransport) { | ||
switch (update.type) { | ||
@@ -609,4 +614,7 @@ case 'map_item_added': | ||
if (this._lastEventId < update.id) { | ||
this._revision = update.map_revision; | ||
this._revision = update.map_revision; | ||
var updateHasNoGap = update.id - this._lastEventId === 1; | ||
var shouldAdvance = this._lastEventId < update.id && (reliableTransport || updateHasNoGap); | ||
if (shouldAdvance) { | ||
this._lastEventId = update.id; | ||
@@ -683,2 +691,4 @@ } | ||
value: function _onRemoved(locally) { | ||
this._unsubscribe(); | ||
// Should also do some cleanup here | ||
@@ -712,2 +722,7 @@ this.emit('collectionRemoved', locally); | ||
} | ||
}, { | ||
key: 'lastEventId', | ||
get: function get() { | ||
return this._lastEventId; | ||
} | ||
}]); | ||
@@ -714,0 +729,0 @@ return SyncMap; |
{ | ||
"name": "twilio-sync", | ||
"version": "0.3.7-dev-build.162", | ||
"version": "0.3.7-dev-build.177", | ||
"description": "Twilio Sync client library", | ||
@@ -16,26 +16,26 @@ "main": "lib/index.js", | ||
"dependencies": { | ||
"babel-runtime": "^6.11.6", | ||
"babel-runtime": "^6.20.0", | ||
"loglevel": "^1.4.1", | ||
"platform": "^1.3.1", | ||
"platform": "^1.3.3", | ||
"rfc6902": "^1.2.2", | ||
"twilio-notifications": "^0.2.0", | ||
"twilio-transport": "^0.0.8", | ||
"twilsock": "^0.2.0", | ||
"twilio-notifications": "^0.2.1", | ||
"twilio-transport": "^0.1.1", | ||
"twilsock": "^0.2.1", | ||
"uuid": "^3.0.1" | ||
}, | ||
"devDependencies": { | ||
"async-test-tools": "^1.0.5", | ||
"babel-cli": "^6.14.0", | ||
"babel-eslint": "^7.0.0", | ||
"async-test-tools": "^1.0.6", | ||
"babel-cli": "^6.18.0", | ||
"babel-eslint": "^7.1.1", | ||
"babel-plugin-add-module-exports": "^0.2.1", | ||
"babel-plugin-transform-object-assign": "^6.8.0", | ||
"babel-plugin-transform-runtime": "^6.15.0", | ||
"babel-preset-es2015": "^6.9.0", | ||
"babel-preset-es2015": "^6.18.0", | ||
"babelify": "^7.3.0", | ||
"backoff": "^2.5.0", | ||
"browserify": "^13.1.0", | ||
"browserify": "^13.3.0", | ||
"chai": "^3.5.0", | ||
"chai-as-promised": "^6.0.0", | ||
"cheerio": "^0.22.0", | ||
"del": "^2.2.1", | ||
"del": "^2.2.2", | ||
"event-to-promise": "^0.7.0", | ||
@@ -47,3 +47,3 @@ "gulp": "^3.9.1", | ||
"gulp-insert": "^0.5.0", | ||
"gulp-istanbul": "^1.0.0", | ||
"gulp-istanbul": "^1.1.1", | ||
"gulp-mocha": "^3.0.1", | ||
@@ -54,7 +54,7 @@ "gulp-rename": "^1.2.2", | ||
"gulp-uglify": "^2.0.0", | ||
"gulp-util": "^3.0.7", | ||
"ink-docstrap": "^1.2.1", | ||
"gulp-util": "^3.0.8", | ||
"ink-docstrap": "^1.3.0", | ||
"isparta": "^4.0.0", | ||
"jsdoc": "^3.4.0", | ||
"jsonwebtoken": "^7.1.6", | ||
"jsdoc": "^3.4.3", | ||
"jsonwebtoken": "^7.2.1", | ||
"karma": "^1.3.0", | ||
@@ -66,3 +66,3 @@ "karma-browserify": "^5.1.0", | ||
"run-sequence": "^1.2.2", | ||
"sinon": "^1.17.5", | ||
"sinon": "^1.17.7", | ||
"sinon-as-promised": "^4.0.2", | ||
@@ -73,4 +73,5 @@ "sinon-chai": "^2.8.0", | ||
"vinyl-buffer": "^1.0.0", | ||
"vinyl-source-stream": "^1.1.0" | ||
"vinyl-source-stream": "^1.1.0", | ||
"watchify": "^3.8.0" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses eval() which is a dangerous function. This prevents the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 2 instances in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
2
1
1
135085
45
28
3506
+ Addedtwilio-transport@0.1.4(transitive)
Updatedbabel-runtime@^6.20.0
Updatedplatform@^1.3.3
Updatedtwilio-notifications@^0.2.1
Updatedtwilio-transport@^0.1.1
Updatedtwilsock@^0.2.1