UNPKG

@launchdarkly/js-server-sdk-common

Version:
197 lines 11.1 kB
"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) { const adaptor = js_sdk_common_1.internal.FDv1PayloadAdaptor(payloadProcessor); adaptor.useSelector('FDv1Fallback').processFullTransfer(data); } /** * @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, fallbackToFDv1) => { var _a, _b, _c, _d, _e, _f; if (this._stopped) { return; } const elapsed = Date.now() - startTime; const sleepFor = Math.max(this._pollInterval * 1000 - elapsed, 0); // Helper to emit the terminal FDv1 fallback signal. Callers must `return` after // invoking this; no further poll is scheduled. const emitFallback = () => { var _a; const fallbackErr = err instanceof js_sdk_common_1.LDFlagDeliveryFallbackError ? err : new js_sdk_common_1.LDFlagDeliveryFallbackError(js_sdk_common_1.DataSourceErrorKind.ErrorResponse, err ? (0, js_sdk_common_1.httpErrorMessage)(err, 'polling request', 'falling back to FDv1') : `Response header indicates to fallback to FDv1`, err === null || err === void 0 ? void 0 : err.status); (_a = this._logger) === null || _a === void 0 ? void 0 : _a.warn(fallbackErr.message); statusCallback(js_sdk_common_1.subsystem.DataSourceState.Closed, fallbackErr); }; (_a = this._logger) === null || _a === void 0 ? void 0 : _a.debug('Elapsed: %d ms, sleeping for %d ms', elapsed, sleepFor); if (err) { // The fallback directive can ride along on either an error or a successful response. // Honor it before any other error handling so we always switch to FDv1 when asked. if (fallbackToFDv1 || err instanceof js_sdk_common_1.LDFlagDeliveryFallbackError) { emitFallback(); return; } const { status } = err; if (status && !(0, js_sdk_common_1.isHttpRecoverable)(status)) { const message = (0, js_sdk_common_1.httpErrorMessage)(err, 'polling request'); (_b = this._logger) === null || _b === void 0 ? void 0 : _b.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)); (_c = this._logger) === null || _c === void 0 ? void 0 : _c.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) => { // If a per-event error fires while the fallback directive is in flight, route // it through the LDFlagDeliveryFallbackError path so CompositeDataSource still // engages FDv1. Otherwise the directive would be lost: the composite's status // handler would treat the LDPollingError as an ordinary failure and fall // through to the next FDv2 source. if (fallbackToFDv1) { emitFallback(); return; } statusCallback(js_sdk_common_1.subsystem.DataSourceState.Interrupted, new js_sdk_common_1.LDPollingError(errorKind, message)); }, this._logger); // When the fallback directive rides along on a 200 response with a valid payload, // the directive must be applied atomically with the data callback. CompositeData- // Source disables its callback handler as soon as basis-during-init arrives or an // error status is reported, so a separate status callback emitted afterwards // would be silently dropped. Instead, attach a fallbackToFDv1 marker to the data // object: CompositeDataSource will swap its synchronizer list to FDv1 before // resolving the switchToSync transition. payloadProcessor.addPayloadListener((payload) => { var _a; const data = { initMetadata, payload, }; if (fallbackToFDv1) { data.fallbackToFDv1 = true; (_a = this._logger) === null || _a === void 0 ? void 0 : _a.warn(`Response header indicates to fallback to FDv1`); } dataCallback(payload.type === 'full', data); }); (_d = this._logger) === null || _d === void 0 ? void 0 : _d.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); } // The fallback directive (if any) was attached to the data callback above so // CompositeDataSource has already taken over the transition. Skip the Valid // status report and the next-poll scheduling in that case. if (fallbackToFDv1) { return; } statusCallback(js_sdk_common_1.subsystem.DataSourceState.Valid); } catch (_g) { // We could not parse this JSON. Report the problem and fallthrough to // start another poll. (_e = this._logger) === null || _e === void 0 ? void 0 : _e.error('Response contained invalid data'); (_f = this._logger) === null || _f === void 0 ? void 0 : _f.debug(`${err} - Body follows: ${body}`); // The fallback directive must be honored even when the body could not be parsed. // Emit it BEFORE the generic Interrupted/LDPollingError status callback below -- // CompositeDataSource disables its callback handler on the first error status, // so the LDFlagDeliveryFallbackError must be emitted first or the directive is // silently dropped and the SDK retries FDv2 sources. if (fallbackToFDv1) { emitFallback(); return; } 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')); } } else if (fallbackToFDv1) { // 200/304 with no body but a fallback directive: emit the directive directly. emitFallback(); return; } // 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