UNPKG

@launchdarkly/js-server-sdk-common

Version:
361 lines 19.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.defaultValues = exports.DEFAULT_STREAM_RECONNECT_DELAY = exports.DEFAULT_POLL_INTERVAL = void 0; const js_sdk_common_1 = require("@launchdarkly/js-sdk-common"); const LDDataSystemOptions_1 = require("../api/options/LDDataSystemOptions"); const InMemoryFeatureStore_1 = require("../store/InMemoryFeatureStore"); const TransactionalFeatureStore_1 = require("../store/TransactionalFeatureStore"); // Once things are internal to the implementation of the SDK we can depend on // types. Calls to the SDK could contain anything without any regard to typing. // So, data we take from external sources must be normalized into something // that can be trusted. /** * These perform cursory validations. Complex objects are implemented with classes * and these should allow for conditional construction. */ const validations = { baseUri: js_sdk_common_1.TypeValidators.String, streamUri: js_sdk_common_1.TypeValidators.String, eventsUri: js_sdk_common_1.TypeValidators.String, timeout: js_sdk_common_1.TypeValidators.numberWithMin(1), capacity: js_sdk_common_1.TypeValidators.Number, logger: js_sdk_common_1.TypeValidators.Object, featureStore: js_sdk_common_1.TypeValidators.ObjectOrFactory, dataSystem: js_sdk_common_1.TypeValidators.Object, bigSegments: js_sdk_common_1.TypeValidators.Object, updateProcessor: js_sdk_common_1.TypeValidators.ObjectOrFactory, flushInterval: js_sdk_common_1.TypeValidators.Number, pollInterval: js_sdk_common_1.TypeValidators.numberWithMin(30), proxyOptions: js_sdk_common_1.TypeValidators.Object, offline: js_sdk_common_1.TypeValidators.Boolean, stream: js_sdk_common_1.TypeValidators.Boolean, streamInitialReconnectDelay: js_sdk_common_1.TypeValidators.Number, useLdd: js_sdk_common_1.TypeValidators.Boolean, sendEvents: js_sdk_common_1.TypeValidators.Boolean, allAttributesPrivate: js_sdk_common_1.TypeValidators.Boolean, privateAttributes: js_sdk_common_1.TypeValidators.StringArray, contextKeysCapacity: js_sdk_common_1.TypeValidators.Number, contextKeysFlushInterval: js_sdk_common_1.TypeValidators.Number, tlsParams: js_sdk_common_1.TypeValidators.Object, diagnosticOptOut: js_sdk_common_1.TypeValidators.Boolean, diagnosticRecordingInterval: js_sdk_common_1.TypeValidators.numberWithMin(60), wrapperName: js_sdk_common_1.TypeValidators.String, wrapperVersion: js_sdk_common_1.TypeValidators.String, application: js_sdk_common_1.TypeValidators.Object, payloadFilterKey: js_sdk_common_1.TypeValidators.stringMatchingRegex(/^[a-zA-Z0-9](\w|\.|-)*$/), hooks: js_sdk_common_1.TypeValidators.createTypeArray('Hook[]', {}), enableEventCompression: js_sdk_common_1.TypeValidators.Boolean, dataSourceOptionsType: js_sdk_common_1.TypeValidators.String, }; exports.DEFAULT_POLL_INTERVAL = 30; exports.DEFAULT_STREAM_RECONNECT_DELAY = 1; const defaultStandardDataSourceOptions = { dataSourceOptionsType: 'standard', streamInitialReconnectDelay: exports.DEFAULT_STREAM_RECONNECT_DELAY, pollInterval: exports.DEFAULT_POLL_INTERVAL, }; const defaultStreamingDataSourceOptions = { dataSourceOptionsType: 'streamingOnly', streamInitialReconnectDelay: exports.DEFAULT_STREAM_RECONNECT_DELAY, }; const defaultPollingDataSourceOptions = { dataSourceOptionsType: 'pollingOnly', pollInterval: exports.DEFAULT_POLL_INTERVAL, }; const defaultDataSystemOptions = { dataSource: defaultStandardDataSourceOptions, }; /** * @internal */ exports.defaultValues = { baseUri: 'https://sdk.launchdarkly.com', streamUri: 'https://stream.launchdarkly.com', eventsUri: js_sdk_common_1.ServiceEndpoints.DEFAULT_EVENTS, stream: true, streamInitialReconnectDelay: exports.DEFAULT_STREAM_RECONNECT_DELAY, sendEvents: true, timeout: 10, capacity: 10000, flushInterval: 5, pollInterval: exports.DEFAULT_POLL_INTERVAL, offline: false, useLdd: false, allAttributesPrivate: false, privateAttributes: [], contextKeysCapacity: 1000, contextKeysFlushInterval: 300, diagnosticOptOut: false, diagnosticRecordingInterval: 900, featureStore: () => new InMemoryFeatureStore_1.default(), enableEventCompression: false, dataSystem: defaultDataSystemOptions, }; function validateTypesAndNames(options, defaults) { const errors = []; const validatedOptions = Object.assign({}, defaults); Object.keys(options).forEach((optionName) => { var _a; // We need to tell typescript it doesn't actually know what options are. // If we don't then it complains we are doing crazy things with it. const optionValue = options[optionName]; const validator = validations[optionName]; if (validator) { if (!validator.is(optionValue)) { if (validator.getType() === 'boolean') { errors.push(js_sdk_common_1.OptionMessages.wrongOptionTypeBoolean(optionName, typeof optionValue)); validatedOptions[optionName] = !!optionValue; } else if (validator instanceof js_sdk_common_1.NumberWithMinimum && js_sdk_common_1.TypeValidators.Number.is(optionValue)) { const { min } = validator; errors.push(js_sdk_common_1.OptionMessages.optionBelowMinimum(optionName, optionValue, min)); validatedOptions[optionName] = min; } else { errors.push(js_sdk_common_1.OptionMessages.wrongOptionType(optionName, validator.getType(), typeof optionValue)); validatedOptions[optionName] = exports.defaultValues[optionName]; } } else { validatedOptions[optionName] = optionValue; } } else { (_a = options.logger) === null || _a === void 0 ? void 0 : _a.warn(js_sdk_common_1.OptionMessages.unknownOption(optionName)); } }); return { errors, validatedOptions }; } function validateEndpoints(options, validatedOptions) { var _a, _b, _c; const { baseUri, streamUri, eventsUri } = options; const streamingEndpointSpecified = streamUri !== undefined && streamUri !== null; const pollingEndpointSpecified = baseUri !== undefined && baseUri !== null; const eventEndpointSpecified = eventsUri !== undefined && eventsUri !== null; if (streamingEndpointSpecified === pollingEndpointSpecified && streamingEndpointSpecified === eventEndpointSpecified) { // Either everything is default, or everything is set. return; } if (!streamingEndpointSpecified && validatedOptions.stream) { (_a = validatedOptions.logger) === null || _a === void 0 ? void 0 : _a.warn(js_sdk_common_1.OptionMessages.partialEndpoint('streamUri')); } if (!pollingEndpointSpecified) { (_b = validatedOptions.logger) === null || _b === void 0 ? void 0 : _b.warn(js_sdk_common_1.OptionMessages.partialEndpoint('baseUri')); } if (!eventEndpointSpecified && validatedOptions.sendEvents) { (_c = validatedOptions.logger) === null || _c === void 0 ? void 0 : _c.warn(js_sdk_common_1.OptionMessages.partialEndpoint('eventsUri')); } } // Validators for fields inside dataSystem.fdv1Fallback. baseUri is an arbitrary string // (the polling endpoint URI) and pollInterval reuses the same minimum as the top-level // pollInterval option. const fdv1FallbackValidations = { baseUri: js_sdk_common_1.TypeValidators.String, pollInterval: js_sdk_common_1.TypeValidators.numberWithMin(30), }; function validateFDv1FallbackOptions(options) { const errors = []; const validatedOptions = {}; Object.keys(options).forEach((optionName) => { const optionValue = options[optionName]; const validator = fdv1FallbackValidations[optionName]; if (!validator) { // Unknown field on the fdv1Fallback object: drop it and warn so misconfigurations // surface during development. errors.push(js_sdk_common_1.OptionMessages.unknownOption(`dataSystem.fdv1Fallback.${optionName}`)); return; } if (!validator.is(optionValue)) { if (validator instanceof js_sdk_common_1.NumberWithMinimum && js_sdk_common_1.TypeValidators.Number.is(optionValue)) { const { min } = validator; errors.push(js_sdk_common_1.OptionMessages.optionBelowMinimum(`dataSystem.fdv1Fallback.${optionName}`, optionValue, min)); validatedOptions[optionName] = min; } else { errors.push(js_sdk_common_1.OptionMessages.wrongOptionType(`dataSystem.fdv1Fallback.${optionName}`, validator.getType(), typeof optionValue)); // Drop the invalid value so the SDK falls back to defaults derived from the // top-level configuration. } } else { validatedOptions[optionName] = optionValue; } }); return { errors, validatedOptions: validatedOptions }; } // validateTypesAndNames shares its `validations` table with the top-level options, so baseUri // passes type checking here too -- but standard/streamingOnly/pollingOnly never read it off // dataSource; each already gets its endpoint from Configuration.serviceEndpoints. Reject it // explicitly, mirroring how validateFDv1FallbackOptions rejects fields outside its own table. function rejectDataSourceBaseUri(dataSource, validatedDataSource) { if (!Object.prototype.hasOwnProperty.call(dataSource, 'baseUri')) { return []; } delete validatedDataSource.baseUri; return [js_sdk_common_1.OptionMessages.unknownOption('dataSystem.dataSource.baseUri')]; } function validateDataSystemOptions(options) { const allErrors = []; const validatedOptions = Object.assign({}, options); if (options.persistentStore && !js_sdk_common_1.TypeValidators.ObjectOrFactory.is(options.persistentStore)) { validatedOptions.persistentStore = undefined; // default is to not use this allErrors.push(js_sdk_common_1.OptionMessages.wrongOptionType('persistentStore', 'LDFeatureStore', typeof options.persistentStore)); } // fdv1Fallback may be: undefined (use defaults), null (opt out so the data source // terminates on the directive), or an FDv1FallbackConfiguration object. Validate the // fields of the object form; preserve null and undefined. if (options.fdv1Fallback !== undefined && options.fdv1Fallback !== null) { if (js_sdk_common_1.TypeValidators.Object.is(options.fdv1Fallback)) { const { errors: fbErrors, validatedOptions: fbValidated } = validateFDv1FallbackOptions(options.fdv1Fallback); validatedOptions.fdv1Fallback = fbValidated; allErrors.push(...fbErrors); } else { // Anything else (string, number, etc) is a misconfiguration. Drop it so the SDK // falls back to default FDv1 fallback behavior. validatedOptions.fdv1Fallback = undefined; allErrors.push(js_sdk_common_1.OptionMessages.wrongOptionType('dataSystem.fdv1Fallback', 'FDv1FallbackConfiguration', typeof options.fdv1Fallback)); } } if (options.dataSource) { let errors; let validatedDataSourceOptions; if ((0, LDDataSystemOptions_1.isStandardOptions)(options.dataSource)) { ({ errors, validatedOptions: validatedDataSourceOptions } = validateTypesAndNames(options.dataSource, defaultStandardDataSourceOptions)); errors.push(...rejectDataSourceBaseUri(options.dataSource, validatedDataSourceOptions)); } else if ((0, LDDataSystemOptions_1.isStreamingOnlyOptions)(options.dataSource)) { ({ errors, validatedOptions: validatedDataSourceOptions } = validateTypesAndNames(options.dataSource, defaultStreamingDataSourceOptions)); errors.push(...rejectDataSourceBaseUri(options.dataSource, validatedDataSourceOptions)); } else if ((0, LDDataSystemOptions_1.isPollingOnlyOptions)(options.dataSource)) { ({ errors, validatedOptions: validatedDataSourceOptions } = validateTypesAndNames(options.dataSource, defaultPollingDataSourceOptions)); errors.push(...rejectDataSourceBaseUri(options.dataSource, validatedDataSourceOptions)); } else if ((0, LDDataSystemOptions_1.isCustomOptions)(options.dataSource)) { validatedDataSourceOptions = options.dataSource; errors = []; } else { // provided datasource options don't fit any expected form, drop them and use defaults validatedDataSourceOptions = defaultStandardDataSourceOptions; errors = [ js_sdk_common_1.OptionMessages.wrongOptionType('dataSource', 'DataSourceOptions', typeof options.dataSource), ]; } validatedOptions.dataSource = validatedDataSourceOptions; allErrors.push(...errors); } else { // use default datasource options if no datasource was specified validatedOptions.dataSource = defaultStandardDataSourceOptions; } return { errors: allErrors, validatedOptions }; } /** * Configuration options for the LDClient. * * @internal */ class Configuration { constructor(options = {}, internalOptions = {}) { var _a; // The default will handle undefined, but not null. // Because we can be called from JS we need to be extra defensive. // eslint-disable-next-line no-param-reassign options = options || {}; // If there isn't a valid logger from the platform, then logs would go nowhere. this.logger = options.logger; const { errors, validatedOptions: topLevelResult } = validateTypesAndNames(options, exports.defaultValues); const validatedOptions = topLevelResult; errors.forEach((error) => { var _a; (_a = this.logger) === null || _a === void 0 ? void 0 : _a.warn(error); }); validateEndpoints(options, validatedOptions); if (options.dataSystem) { // validate the data system options, this will also apply reasonable defaults const { errors: dsErrors, validatedOptions: dsResult } = validateDataSystemOptions(options.dataSystem); const validatedDSOptions = dsResult; this.dataSystem = { dataSource: validatedDSOptions.dataSource, useLdd: validatedDSOptions.useLdd, fdv1Fallback: validatedDSOptions.fdv1Fallback, featureStoreFactory: (clientContext) => { const { persistentStore } = validatedDSOptions; let store; if (persistentStore === undefined) { // the persistent store provided was either undefined or invalid, default to memory store store = new InMemoryFeatureStore_1.default(); } else if (js_sdk_common_1.TypeValidators.Function.is(persistentStore)) { store = persistentStore(clientContext); } else { store = persistentStore; } // FDv2 always calls applyChanges, so the returned store must support it. A // store that already implements LDTransactionalFeatureStore is used as-is to // preserve its native transactional path. if (js_sdk_common_1.TypeValidators.Function.is(store.applyChanges)) { return store; } return new TransactionalFeatureStore_1.default(store); }, }; dsErrors.forEach((error) => { var _a; (_a = this.logger) === null || _a === void 0 ? void 0 : _a.warn(error); }); } this.serviceEndpoints = new js_sdk_common_1.ServiceEndpoints(validatedOptions.streamUri, validatedOptions.baseUri, validatedOptions.eventsUri, internalOptions.analyticsEventPath, internalOptions.diagnosticEventPath, internalOptions.includeAuthorizationHeader, validatedOptions.payloadFilterKey); this.eventsCapacity = validatedOptions.capacity; this.timeout = validatedOptions.timeout; this.bigSegments = validatedOptions.bigSegments; this.flushInterval = validatedOptions.flushInterval; this.pollInterval = validatedOptions.pollInterval; this.proxyOptions = validatedOptions.proxyOptions; this.offline = validatedOptions.offline; this.stream = validatedOptions.stream; this.streamInitialReconnectDelay = validatedOptions.streamInitialReconnectDelay; this.useLdd = validatedOptions.useLdd; this.sendEvents = validatedOptions.sendEvents; this.allAttributesPrivate = validatedOptions.allAttributesPrivate; this.privateAttributes = validatedOptions.privateAttributes; this.contextKeysCapacity = validatedOptions.contextKeysCapacity; this.contextKeysFlushInterval = validatedOptions.contextKeysFlushInterval; this.tlsParams = validatedOptions.tlsParams; this.diagnosticOptOut = validatedOptions.diagnosticOptOut; this.wrapperName = validatedOptions.wrapperName; this.payloadFilterKey = validatedOptions.payloadFilterKey; this.wrapperVersion = validatedOptions.wrapperVersion; this.tags = new js_sdk_common_1.ApplicationTags(validatedOptions); this.diagnosticRecordingInterval = validatedOptions.diagnosticRecordingInterval; if (js_sdk_common_1.TypeValidators.Function.is(validatedOptions.updateProcessor)) { // @ts-ignore this.updateProcessorFactory = validatedOptions.updateProcessor; } else { // The processor is already created, just have the method return it. // @ts-ignore this.updateProcessorFactory = () => validatedOptions.updateProcessor; } if (js_sdk_common_1.TypeValidators.Function.is(validatedOptions.featureStore)) { // @ts-ignore this.featureStoreFactory = validatedOptions.featureStore; } else { // The store is already created, just have the method return it. // @ts-ignore this.featureStoreFactory = () => validatedOptions.featureStore; } this.hooks = validatedOptions.hooks; this.enableEventCompression = validatedOptions.enableEventCompression; this.getImplementationHooks = (_a = internalOptions.getImplementationHooks) !== null && _a !== void 0 ? _a : (() => []); this.applicationInfo = validatedOptions.application; } } exports.default = Configuration; //# sourceMappingURL=Configuration.js.map