@launchdarkly/js-server-sdk-common
Version:
LaunchDarkly Server SDK for JavaScript - common code
188 lines • 8.63 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const js_sdk_common_1 = require("@launchdarkly/js-sdk-common");
const serialization_1 = require("../store/serialization");
function selectorAsQueryParams(selector) {
if (!selector) {
return [];
}
return [
{
key: 'basis',
value: selector,
},
];
}
// helper function to transform FDv1 response data into events the PayloadProcessor can parse
function processFDv1FlagsAndSegments(payloadProcessor, data) {
payloadProcessor.processEvents([
{
event: `server-intent`,
data: {
payloads: [
{
id: `FDv1Fallback`,
target: 1,
intentCode: `xfer-full`,
},
],
},
},
]);
Object.entries((data === null || data === void 0 ? void 0 : data.flags) || []).forEach(([key, flag]) => {
payloadProcessor.processEvents([
{
event: `put-object`,
data: {
kind: 'flag',
key,
version: flag.version,
object: flag,
},
},
]);
});
Object.entries((data === null || data === void 0 ? void 0 : data.segments) || []).forEach(([key, segment]) => {
payloadProcessor.processEvents([
{
event: `put-object`,
data: {
kind: 'segment',
key,
version: segment.version,
object: segment,
},
},
]);
});
payloadProcessor.processEvents([
{
event: `payload-transferred`,
data: {
state: `FDv1Fallback`,
version: 1,
},
},
]);
}
/**
* @internal
*/
class PollingProcessorFDv2 {
/**
* @param _requestor to fetch flags
* @param _pollInterval in seconds controlling how frequently polling request is made
* @param _logger for logging
* @param _processResponseAsFDv1 defaults to false, but if set to true, this data source will process
* the response body as FDv1 and convert it into a FDv2 payload.
*/
constructor(_requestor, _pollInterval = 30, _logger, _processResponseAsFDv1 = false) {
this._requestor = _requestor;
this._pollInterval = _pollInterval;
this._logger = _logger;
this._processResponseAsFDv1 = _processResponseAsFDv1;
this._stopped = false;
}
_poll(dataCallback, statusCallback, selectorGetter) {
var _a;
if (this._stopped) {
return;
}
const startTime = Date.now();
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.debug('Polling LaunchDarkly for feature flag updates');
this._requestor.requestAllData((err, body, headers) => {
var _a, _b, _c, _d, _e, _f, _g;
if (this._stopped) {
return;
}
const elapsed = Date.now() - startTime;
const sleepFor = Math.max(this._pollInterval * 1000 - elapsed, 0);
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.debug('Elapsed: %d ms, sleeping for %d ms', elapsed, sleepFor);
if (err) {
const { status } = err;
// this is a short term error and will be removed once FDv2 adoption is sufficient.
if (err instanceof js_sdk_common_1.LDFlagDeliveryFallbackError) {
(_b = this._logger) === null || _b === void 0 ? void 0 : _b.error(err.message);
statusCallback(js_sdk_common_1.subsystem.DataSourceState.Closed, err);
// It is not recoverable, return and do not trigger another poll.
return;
}
if (status && !(0, js_sdk_common_1.isHttpRecoverable)(status)) {
const message = (0, js_sdk_common_1.httpErrorMessage)(err, 'polling request');
(_c = this._logger) === null || _c === void 0 ? void 0 : _c.error(message);
statusCallback(js_sdk_common_1.subsystem.DataSourceState.Closed, new js_sdk_common_1.LDPollingError(js_sdk_common_1.DataSourceErrorKind.ErrorResponse, message, status, false));
// It is not recoverable, return and do not trigger another poll.
return;
}
const message = (0, js_sdk_common_1.httpErrorMessage)(err, 'polling request', 'will retry');
statusCallback(js_sdk_common_1.subsystem.DataSourceState.Interrupted, new js_sdk_common_1.LDPollingError(js_sdk_common_1.DataSourceErrorKind.ErrorResponse, message, status));
(_d = this._logger) === null || _d === void 0 ? void 0 : _d.warn(message);
// schedule poll
this._timeoutHandle = setTimeout(() => {
this._poll(dataCallback, statusCallback, selectorGetter);
}, sleepFor);
return;
}
const initMetadata = js_sdk_common_1.internal.initMetadataFromHeaders(headers);
if (body) {
try {
const payloadProcessor = new js_sdk_common_1.internal.PayloadProcessor({
flag: (flag) => {
(0, serialization_1.processFlag)(flag);
return flag;
},
segment: (segment) => {
(0, serialization_1.processSegment)(segment);
return segment;
},
}, (errorKind, message) => {
statusCallback(js_sdk_common_1.subsystem.DataSourceState.Interrupted, new js_sdk_common_1.LDPollingError(errorKind, message));
}, this._logger);
payloadProcessor.addPayloadListener((payload) => {
dataCallback(payload.basis, { initMetadata, payload });
});
(_e = this._logger) === null || _e === void 0 ? void 0 : _e.debug(`Got body: ${body}`);
if (!this._processResponseAsFDv1) {
// FDv2 case
const parsed = JSON.parse(body);
payloadProcessor.processEvents(parsed.events);
}
else {
// FDv1 case
const parsed = JSON.parse(body);
processFDv1FlagsAndSegments(payloadProcessor, parsed);
}
statusCallback(js_sdk_common_1.subsystem.DataSourceState.Valid);
}
catch (_h) {
// We could not parse this JSON. Report the problem and fallthrough to
// start another poll.
(_f = this._logger) === null || _f === void 0 ? void 0 : _f.error('Response contained invalid data');
(_g = this._logger) === null || _g === void 0 ? void 0 : _g.debug(`${err} - Body follows: ${body}`);
statusCallback(js_sdk_common_1.subsystem.DataSourceState.Interrupted, new js_sdk_common_1.LDPollingError(js_sdk_common_1.DataSourceErrorKind.InvalidData, 'Malformed data in polling response'));
}
}
// schedule poll
this._timeoutHandle = setTimeout(() => {
this._poll(dataCallback, statusCallback, selectorGetter);
}, sleepFor);
}, selectorAsQueryParams(selectorGetter === null || selectorGetter === void 0 ? void 0 : selectorGetter()));
}
start(dataCallback, statusCallback, selectorGetter) {
this._statusCallback = statusCallback; // hold reference for usage in stop()
statusCallback(js_sdk_common_1.subsystem.DataSourceState.Initializing);
this._poll(dataCallback, statusCallback, selectorGetter);
}
stop() {
var _a;
if (this._timeoutHandle) {
clearTimeout(this._timeoutHandle);
this._timeoutHandle = undefined;
}
(_a = this._statusCallback) === null || _a === void 0 ? void 0 : _a.call(this, js_sdk_common_1.subsystem.DataSourceState.Closed);
this._stopped = true;
this._statusCallback = undefined;
}
}
exports.default = PollingProcessorFDv2;
//# sourceMappingURL=PollingProcessorFDv2.js.map