box-node-sdk
Version:
Official SDK for Box Plaform APIs
223 lines • 10.2 kB
JavaScript
"use strict";
/**
* @fileoverview Enterprise event stream backed by the enterprise events API
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------
var stream_1 = require("stream");
// ------------------------------------------------------------------------------
// Private
// ------------------------------------------------------------------------------
var DEFAULT_OPTIONS = Object.freeze({
pollingInterval: 60, // seconds
chunkSize: 500,
streamType: 'admin_logs',
});
// ------------------------------------------------------------------------------
// Public
// ------------------------------------------------------------------------------
/**
* Stream of Box enterprise events.
*
* By default, the stream starts from the current time.
* Pass 'startDate' to start from a specific time.
* Pass 'streamPosition' to start from a previous stream position, or '0' for all available past events (~1 year).
* Once the stream catches up to the current time, it will begin polling every 'pollingInterval' seconds.
* If 'pollingInterval' = 0, then the stream will end when it catches up to the current time (no polling).
*
* @param {BoxClient} client - The client to use to get events
* @param {Object} [options] - Options
* @param {string} [options.streamPosition] - The stream position to start from (pass '0' for all past events)
* @param {string} [options.startDate] - The date to start from
* @param {string} [options.endDate] - The date to end at
* @param {EventType[]} [options.eventTypeFilter] - Array of event types to return
* @param {int} [options.pollingInterval=60] - Polling interval (in seconds). Pass 0 for no polling.
* @param {int} [options.chunkSize=500] - Number of events to fetch per call (max = 500)
* @constructor
* @extends Readable
*/
var EnterpriseEventStream = /** @class */ (function (_super) {
__extends(EnterpriseEventStream, _super);
function EnterpriseEventStream(client, options) {
var _this = _super.call(this, {
objectMode: true,
}) || this;
/**
* @var {BoxClient} - The client for making API calls
* @private
*/
_this._client = client;
/**
* @var {Object} - Options
* @private
*/
_this._options = Object.assign({}, DEFAULT_OPTIONS, options);
// Handle the case where the caller passes streamPosition = 0 instead of streamPosition = '0'.
if (_this._options.streamType === 'admin_logs' &&
!_this._options.startDate &&
!_this._options.streamPosition &&
_this._options.streamPosition !== 0) {
// If neither startDate nor streamPosition is specified, start from the current time.
_this._options.startDate = new Date()
.toISOString()
.replace(/\.000Z$/, '-00:00');
}
/**
* @var {?string} - The current stream position
* @private
*/
_this._streamPosition = _this._options.streamPosition;
return _this;
}
/**
* @returns {?number} - Returns null if no events have been fetched from Box yet.
*/
EnterpriseEventStream.prototype.getStreamPosition = function () {
return this._streamPosition;
};
/**
* Get the stream state.
*
* @returns {Object} - The stream state
*/
EnterpriseEventStream.prototype.getStreamState = function () {
// We need to return both streamPosition and startDate, since streamPosition will be null until
// the first set of events is returned from Box.
return {
streamPosition: this._streamPosition,
startDate: this._options.startDate,
endDate: this._options.endDate,
eventTypeFilter: this._options.eventTypeFilter,
};
};
/**
* Set the stream state.
*
* @param {Object} state - The stream state
* @returns {void}
*/
EnterpriseEventStream.prototype.setStreamState = function (state) {
// We need to set both streamPosition and startDate, since streamPosition will be null until
// the first set of events is returned from Box.
this._streamPosition = state.streamPosition;
this._options.startDate = state.startDate;
this._options.endDate = state.endDate;
this._options.eventTypeFilter = state.eventTypeFilter;
};
/**
* Fetch the next chunk of events
*
* If there are no events, poll until events are available.
* If an error occurs, emit the error but continuing polling as usual.
* @param {Function} callback - Passed the array of events
* @returns {void}
* @private
*/
EnterpriseEventStream.prototype.fetchEvents = function (callback) {
var self = this, params = {
stream_type: this._options.streamType,
};
// Use the current stream position.
// Handle the case where the caller passes streamPosition === 0 instead of streamPosition === '0'.
if (this._streamPosition || this._streamPosition === 0) {
params.stream_position = this._streamPosition;
}
if (this._options.streamType === 'admin_logs' && this._options.startDate) {
params.created_after = this._options.startDate;
}
if (this._options.streamType === 'admin_logs' && this._options.endDate) {
params.created_before = this._options.endDate;
}
if (this._options.eventTypeFilter) {
params.event_type = this._options.eventTypeFilter.join(',');
}
if (this._options.chunkSize) {
params.limit = this._options.chunkSize;
}
this._client.events.get(params, function (err /* FIXME */, result /* FIXME */) {
if (err) {
self.emit('error', err);
// If there was a "permanent" error, we would call the callback with it here.
// But it's not clear which errors are truly permanent?
// If Box is down or returning errors for an extended period, we still want to resume when it recovers.
// So, continue polling at the regular frequency.
// Don't use a shorter retry interval (to avoid DDOSing Box).
}
if (err || !result || !result.entries || result.entries.length === 0) {
if (!self._options.pollingInterval) {
// If polling is disabled, end the stream.
callback();
return;
}
// There were no events returned (or an error occurred), so schedule another poll.
var delay = self._options.pollingInterval * 1000;
// Stream readers can use this to flush buffered events to a downstream system.
self.emit('wait', delay);
setTimeout(function () {
self.fetchEvents(callback);
}, delay);
return;
}
// Only update the stream position if there were events returned.
// The API currently returns next_stream_position = 0 if there are no events (may be a bug?).
// But we don't want to start over at the beginning in that case, so ignore it.
self._streamPosition = result.next_stream_position;
// Notify the reader of the new stream position.
// Stream readers can respond to the 'newStreamState' event to persist the stream state.
self.emit('newStreamState', self.getStreamState());
callback(null, result.entries);
});
};
/**
* Implementation of the stream-internal read function. This is called
* by the stream whenever it needs more data, and will not be called again
* until data is pushed into the stream.
* @returns {void}
* @private
*/
EnterpriseEventStream.prototype._read = function () {
// Fetch the next chunk of events.
var self = this;
// This will poll forever until events are available.
this.fetchEvents(function (err /* FIXME */, events /* FIXME */) {
if (err || !events || events.length === 0) {
// Close the stream if there was a "permanent" failure or we reached the end of the events.
self.push(null);
return;
}
// Pause the stream to avoid race conditions while pushing in the new events.
// Without this, _read() would be called again from inside each push(),
// resulting in multiple parallel calls to fetchEvents().
// See https://github.com/nodejs/node/issues/3203
var wasPaused = self.isPaused();
self.pause();
// Push all of the events into the stream.
events.forEach(function (event /* FIXME */) {
self.push(event);
});
if (!wasPaused) {
// This will deliver the events and trigger the next call to _read() once they have been consumed.
self.resume();
}
});
};
return EnterpriseEventStream;
}(stream_1.Readable));
module.exports = EnterpriseEventStream;
//# sourceMappingURL=enterprise-event-stream.js.map