@launchdarkly/js-client-sdk-common 1.9.0 → 1.9.1-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{DataManager.d.ts → cjs/DataManager.d.ts} +6 -5
- package/dist/cjs/DataManager.d.ts.map +1 -0
- package/dist/{HookRunner.d.ts → cjs/HookRunner.d.ts} +3 -3
- package/dist/{HookRunner.d.ts.map → cjs/HookRunner.d.ts.map} +1 -1
- package/dist/{LDClientImpl.d.ts → cjs/LDClientImpl.d.ts} +19 -17
- package/dist/cjs/LDClientImpl.d.ts.map +1 -0
- package/dist/{LDEmitter.d.ts → cjs/LDEmitter.d.ts} +4 -4
- package/dist/{LDEmitter.d.ts.map → cjs/LDEmitter.d.ts.map} +1 -1
- package/dist/{api → cjs/api}/LDClient.d.ts +1 -1
- package/dist/{api → cjs/api}/LDClient.d.ts.map +1 -1
- package/dist/cjs/api/LDInspection.d.ts +105 -0
- package/dist/cjs/api/LDInspection.d.ts.map +1 -0
- package/dist/{api → cjs/api}/LDOptions.d.ts +12 -2
- package/dist/cjs/api/LDOptions.d.ts.map +1 -0
- package/dist/{configuration → cjs/configuration}/Configuration.d.ts +4 -1
- package/dist/cjs/configuration/Configuration.d.ts.map +1 -0
- package/dist/{configuration → cjs/configuration}/validators.d.ts.map +1 -1
- package/dist/{context → cjs/context}/addAutoEnv.d.ts +2 -2
- package/dist/cjs/context/addAutoEnv.d.ts.map +1 -0
- package/dist/{context → cjs/context}/ensureKey.d.ts +1 -1
- package/dist/{context → cjs/context}/ensureKey.d.ts.map +1 -1
- package/dist/{streaming → cjs/datasource}/DataSourceConfig.d.ts +1 -0
- package/dist/cjs/datasource/DataSourceConfig.d.ts.map +1 -0
- package/dist/{datasource → cjs/datasource}/DataSourceEventHandler.d.ts +4 -4
- package/dist/{datasource → cjs/datasource}/DataSourceEventHandler.d.ts.map +1 -1
- package/dist/{datasource → cjs/datasource}/DataSourceStatusManager.d.ts +7 -7
- package/dist/cjs/datasource/DataSourceStatusManager.d.ts.map +1 -0
- package/dist/cjs/datasource/Requestor.d.ts +26 -0
- package/dist/cjs/datasource/Requestor.d.ts.map +1 -0
- package/dist/{evaluation → cjs/evaluation}/evaluationDetail.d.ts.map +1 -1
- package/dist/{flag-manager → cjs/flag-manager}/FlagManager.d.ts +4 -5
- package/dist/{flag-manager → cjs/flag-manager}/FlagManager.d.ts.map +1 -1
- package/dist/{flag-manager → cjs/flag-manager}/FlagPersistence.d.ts +13 -13
- package/dist/cjs/flag-manager/FlagPersistence.d.ts.map +1 -0
- package/dist/{flag-manager → cjs/flag-manager}/FlagStore.d.ts +1 -1
- package/dist/{flag-manager → cjs/flag-manager}/FlagStore.d.ts.map +1 -1
- package/dist/{flag-manager → cjs/flag-manager}/FlagUpdater.d.ts +6 -5
- package/dist/cjs/flag-manager/FlagUpdater.d.ts.map +1 -0
- package/dist/{index.cjs → cjs/index.cjs} +581 -374
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/{index.d.ts → cjs/index.d.ts} +2 -2
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/inspection/InspectorManager.d.ts +41 -0
- package/dist/cjs/inspection/InspectorManager.d.ts.map +1 -0
- package/dist/cjs/inspection/createSafeInspector.d.ts +8 -0
- package/dist/cjs/inspection/createSafeInspector.d.ts.map +1 -0
- package/dist/cjs/inspection/getInspectorHook.d.ts +4 -0
- package/dist/cjs/inspection/getInspectorHook.d.ts.map +1 -0
- package/dist/cjs/inspection/messages.d.ts +3 -0
- package/dist/cjs/inspection/messages.d.ts.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/{polling → cjs/polling}/PollingProcessor.d.ts.map +1 -1
- package/dist/cjs/streaming/StreamingProcessor.d.ts +35 -0
- package/dist/cjs/streaming/StreamingProcessor.d.ts.map +1 -0
- package/dist/{streaming → cjs/streaming}/index.d.ts +1 -1
- package/dist/{streaming → cjs/streaming}/index.d.ts.map +1 -1
- package/dist/{types → cjs/types}/index.d.ts +1 -0
- package/dist/{types → cjs/types}/index.d.ts.map +1 -1
- package/dist/esm/DataManager.d.ts +65 -0
- package/dist/esm/DataManager.d.ts.map +1 -0
- package/dist/esm/HookRunner.d.ts +12 -0
- package/dist/esm/HookRunner.d.ts.map +1 -0
- package/dist/esm/LDClientImpl.d.ts +84 -0
- package/dist/esm/LDClientImpl.d.ts.map +1 -0
- package/dist/esm/LDEmitter.d.ts +32 -0
- package/dist/esm/LDEmitter.d.ts.map +1 -0
- package/dist/esm/api/ConnectionMode.d.ts +16 -0
- package/dist/esm/api/ConnectionMode.d.ts.map +1 -0
- package/dist/esm/api/LDClient.d.ts +315 -0
- package/dist/esm/api/LDClient.d.ts.map +1 -0
- package/dist/esm/api/LDEvaluationDetail.d.ts +26 -0
- package/dist/esm/api/LDEvaluationDetail.d.ts.map +1 -0
- package/dist/esm/api/LDIdentifyOptions.d.ts +23 -0
- package/dist/esm/api/LDIdentifyOptions.d.ts.map +1 -0
- package/dist/esm/api/LDInspection.d.ts +105 -0
- package/dist/esm/api/LDInspection.d.ts.map +1 -0
- package/dist/esm/api/LDOptions.d.ts +246 -0
- package/dist/esm/api/LDOptions.d.ts.map +1 -0
- package/dist/esm/api/index.d.ts +8 -0
- package/dist/esm/api/index.d.ts.map +1 -0
- package/dist/esm/api/integrations/Hooks.d.ts +133 -0
- package/dist/esm/api/integrations/Hooks.d.ts.map +1 -0
- package/dist/esm/api/integrations/index.d.ts +2 -0
- package/dist/esm/api/integrations/index.d.ts.map +1 -0
- package/dist/esm/configuration/Configuration.d.ts +86 -0
- package/dist/esm/configuration/Configuration.d.ts.map +1 -0
- package/dist/esm/configuration/index.d.ts +3 -0
- package/dist/esm/configuration/index.d.ts.map +1 -0
- package/dist/esm/configuration/validators.d.ts +5 -0
- package/dist/esm/configuration/validators.d.ts.map +1 -0
- package/dist/esm/context/addAutoEnv.d.ts +31 -0
- package/dist/esm/context/addAutoEnv.d.ts.map +1 -0
- package/dist/esm/context/ensureKey.d.ts +10 -0
- package/dist/esm/context/ensureKey.d.ts.map +1 -0
- package/dist/esm/crypto/digest.d.ts +3 -0
- package/dist/esm/crypto/digest.d.ts.map +1 -0
- package/dist/esm/datasource/DataSourceConfig.d.ts +25 -0
- package/dist/esm/datasource/DataSourceConfig.d.ts.map +1 -0
- package/dist/esm/datasource/DataSourceEventHandler.d.ts +16 -0
- package/dist/esm/datasource/DataSourceEventHandler.d.ts.map +1 -0
- package/dist/esm/datasource/DataSourceStatus.d.ts +39 -0
- package/dist/esm/datasource/DataSourceStatus.d.ts.map +1 -0
- package/dist/esm/datasource/DataSourceStatusErrorInfo.d.ts +8 -0
- package/dist/esm/datasource/DataSourceStatusErrorInfo.d.ts.map +1 -0
- package/dist/esm/datasource/DataSourceStatusManager.d.ts +40 -0
- package/dist/esm/datasource/DataSourceStatusManager.d.ts.map +1 -0
- package/dist/esm/datasource/Requestor.d.ts +26 -0
- package/dist/esm/datasource/Requestor.d.ts.map +1 -0
- package/dist/esm/diagnostics/createDiagnosticsInitConfig.d.ts +16 -0
- package/dist/esm/diagnostics/createDiagnosticsInitConfig.d.ts.map +1 -0
- package/dist/esm/diagnostics/createDiagnosticsManager.d.ts +5 -0
- package/dist/esm/diagnostics/createDiagnosticsManager.d.ts.map +1 -0
- package/dist/esm/evaluation/evaluationDetail.d.ts +5 -0
- package/dist/esm/evaluation/evaluationDetail.d.ts.map +1 -0
- package/dist/esm/events/EventFactory.d.ts +2 -0
- package/dist/esm/events/EventFactory.d.ts.map +1 -0
- package/dist/esm/events/createEventProcessor.d.ts +5 -0
- package/dist/esm/events/createEventProcessor.d.ts.map +1 -0
- package/dist/esm/flag-manager/ContextIndex.d.ts +39 -0
- package/dist/esm/flag-manager/ContextIndex.d.ts.map +1 -0
- package/dist/esm/flag-manager/FlagManager.d.ts +81 -0
- package/dist/esm/flag-manager/FlagManager.d.ts.map +1 -0
- package/dist/esm/flag-manager/FlagPersistence.d.ts +43 -0
- package/dist/esm/flag-manager/FlagPersistence.d.ts.map +1 -0
- package/dist/esm/flag-manager/FlagStore.d.ts +29 -0
- package/dist/esm/flag-manager/FlagStore.d.ts.map +1 -0
- package/dist/esm/flag-manager/FlagUpdater.d.ts +40 -0
- package/dist/esm/flag-manager/FlagUpdater.d.ts.map +1 -0
- package/dist/esm/flag-manager/ItemDescriptor.d.ts +10 -0
- package/dist/esm/flag-manager/ItemDescriptor.d.ts.map +1 -0
- package/dist/esm/flag-manager/calculateChangedKeys.d.ts +6 -0
- package/dist/esm/flag-manager/calculateChangedKeys.d.ts.map +1 -0
- package/dist/esm/index.d.ts +20 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/{index.mjs → esm/index.mjs} +582 -376
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/inspection/InspectorManager.d.ts +41 -0
- package/dist/esm/inspection/InspectorManager.d.ts.map +1 -0
- package/dist/esm/inspection/createSafeInspector.d.ts +8 -0
- package/dist/esm/inspection/createSafeInspector.d.ts.map +1 -0
- package/dist/esm/inspection/getInspectorHook.d.ts +4 -0
- package/dist/esm/inspection/getInspectorHook.d.ts.map +1 -0
- package/dist/esm/inspection/messages.d.ts +3 -0
- package/dist/esm/inspection/messages.d.ts.map +1 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/polling/PollingProcessor.d.ts +3 -0
- package/dist/esm/polling/PollingProcessor.d.ts.map +1 -0
- package/dist/esm/storage/getOrGenerateKey.d.ts +11 -0
- package/dist/esm/storage/getOrGenerateKey.d.ts.map +1 -0
- package/dist/esm/storage/namespaceUtils.d.ts +20 -0
- package/dist/esm/storage/namespaceUtils.d.ts.map +1 -0
- package/dist/esm/streaming/StreamingProcessor.d.ts +35 -0
- package/dist/esm/streaming/StreamingProcessor.d.ts.map +1 -0
- package/dist/esm/streaming/index.d.ts +4 -0
- package/dist/esm/streaming/index.d.ts.map +1 -0
- package/dist/esm/types/index.d.ts +21 -0
- package/dist/esm/types/index.d.ts.map +1 -0
- package/package.json +16 -8
- package/dist/DataManager.d.ts.map +0 -1
- package/dist/LDClientImpl.d.ts.map +0 -1
- package/dist/api/LDOptions.d.ts.map +0 -1
- package/dist/configuration/Configuration.d.ts.map +0 -1
- package/dist/context/addAutoEnv.d.ts.map +0 -1
- package/dist/datasource/DataSourceStatusManager.d.ts.map +0 -1
- package/dist/flag-manager/FlagPersistence.d.ts.map +0 -1
- package/dist/flag-manager/FlagUpdater.d.ts.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/polling/Requestor.d.ts +0 -21
- package/dist/polling/Requestor.d.ts.map +0 -1
- package/dist/streaming/DataSourceConfig.d.ts.map +0 -1
- package/dist/streaming/StreamingProcessor.d.ts +0 -33
- package/dist/streaming/StreamingProcessor.d.ts.map +0 -1
- /package/dist/{api → cjs/api}/ConnectionMode.d.ts +0 -0
- /package/dist/{api → cjs/api}/ConnectionMode.d.ts.map +0 -0
- /package/dist/{api → cjs/api}/LDEvaluationDetail.d.ts +0 -0
- /package/dist/{api → cjs/api}/LDEvaluationDetail.d.ts.map +0 -0
- /package/dist/{api → cjs/api}/LDIdentifyOptions.d.ts +0 -0
- /package/dist/{api → cjs/api}/LDIdentifyOptions.d.ts.map +0 -0
- /package/dist/{api → cjs/api}/index.d.ts +0 -0
- /package/dist/{api → cjs/api}/index.d.ts.map +0 -0
- /package/dist/{api → cjs/api}/integrations/Hooks.d.ts +0 -0
- /package/dist/{api → cjs/api}/integrations/Hooks.d.ts.map +0 -0
- /package/dist/{api → cjs/api}/integrations/index.d.ts +0 -0
- /package/dist/{api → cjs/api}/integrations/index.d.ts.map +0 -0
- /package/dist/{configuration → cjs/configuration}/index.d.ts +0 -0
- /package/dist/{configuration → cjs/configuration}/index.d.ts.map +0 -0
- /package/dist/{configuration → cjs/configuration}/validators.d.ts +0 -0
- /package/dist/{crypto → cjs/crypto}/digest.d.ts +0 -0
- /package/dist/{crypto → cjs/crypto}/digest.d.ts.map +0 -0
- /package/dist/{datasource → cjs/datasource}/DataSourceStatus.d.ts +0 -0
- /package/dist/{datasource → cjs/datasource}/DataSourceStatus.d.ts.map +0 -0
- /package/dist/{datasource → cjs/datasource}/DataSourceStatusErrorInfo.d.ts +0 -0
- /package/dist/{datasource → cjs/datasource}/DataSourceStatusErrorInfo.d.ts.map +0 -0
- /package/dist/{diagnostics → cjs/diagnostics}/createDiagnosticsInitConfig.d.ts +0 -0
- /package/dist/{diagnostics → cjs/diagnostics}/createDiagnosticsInitConfig.d.ts.map +0 -0
- /package/dist/{diagnostics → cjs/diagnostics}/createDiagnosticsManager.d.ts +0 -0
- /package/dist/{diagnostics → cjs/diagnostics}/createDiagnosticsManager.d.ts.map +0 -0
- /package/dist/{evaluation → cjs/evaluation}/evaluationDetail.d.ts +0 -0
- /package/dist/{events → cjs/events}/EventFactory.d.ts +0 -0
- /package/dist/{events → cjs/events}/EventFactory.d.ts.map +0 -0
- /package/dist/{events → cjs/events}/createEventProcessor.d.ts +0 -0
- /package/dist/{events → cjs/events}/createEventProcessor.d.ts.map +0 -0
- /package/dist/{flag-manager → cjs/flag-manager}/ContextIndex.d.ts +0 -0
- /package/dist/{flag-manager → cjs/flag-manager}/ContextIndex.d.ts.map +0 -0
- /package/dist/{flag-manager → cjs/flag-manager}/ItemDescriptor.d.ts +0 -0
- /package/dist/{flag-manager → cjs/flag-manager}/ItemDescriptor.d.ts.map +0 -0
- /package/dist/{flag-manager → cjs/flag-manager}/calculateChangedKeys.d.ts +0 -0
- /package/dist/{flag-manager → cjs/flag-manager}/calculateChangedKeys.d.ts.map +0 -0
- /package/dist/{polling → cjs/polling}/PollingProcessor.d.ts +0 -0
- /package/dist/{storage → cjs/storage}/getOrGenerateKey.d.ts +0 -0
- /package/dist/{storage → cjs/storage}/getOrGenerateKey.d.ts.map +0 -0
- /package/dist/{storage → cjs/storage}/namespaceUtils.d.ts +0 -0
- /package/dist/{storage → cjs/storage}/namespaceUtils.d.ts.map +0 -0
|
@@ -32,6 +32,72 @@ exports.DataSourceState = void 0;
|
|
|
32
32
|
// NetworkUnavailable,
|
|
33
33
|
})(exports.DataSourceState || (exports.DataSourceState = {}));
|
|
34
34
|
|
|
35
|
+
// eslint-disable-next-line max-classes-per-file
|
|
36
|
+
function isOk(status) {
|
|
37
|
+
return status >= 200 && status <= 299;
|
|
38
|
+
}
|
|
39
|
+
class LDRequestError extends Error {
|
|
40
|
+
constructor(message, status) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.status = status;
|
|
43
|
+
this.name = 'LaunchDarklyRequestError';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Note: The requestor is implemented independently from polling such that it can be used to
|
|
48
|
+
* make a one-off request.
|
|
49
|
+
*/
|
|
50
|
+
class Requestor {
|
|
51
|
+
constructor(_requests, _uri, _headers, _method, _body) {
|
|
52
|
+
this._requests = _requests;
|
|
53
|
+
this._uri = _uri;
|
|
54
|
+
this._headers = _headers;
|
|
55
|
+
this._method = _method;
|
|
56
|
+
this._body = _body;
|
|
57
|
+
}
|
|
58
|
+
async requestPayload() {
|
|
59
|
+
let status;
|
|
60
|
+
try {
|
|
61
|
+
const res = await this._requests.fetch(this._uri, {
|
|
62
|
+
method: this._method,
|
|
63
|
+
headers: this._headers,
|
|
64
|
+
body: this._body,
|
|
65
|
+
});
|
|
66
|
+
if (isOk(res.status)) {
|
|
67
|
+
return await res.text();
|
|
68
|
+
}
|
|
69
|
+
// Assigning so it can be thrown after the try/catch.
|
|
70
|
+
status = res.status;
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
throw new LDRequestError(err?.message);
|
|
74
|
+
}
|
|
75
|
+
throw new LDRequestError(`Unexpected status code: ${status}`, status);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function makeRequestor(plainContextString, serviceEndpoints, paths, requests, encoding, baseHeaders, baseQueryParams, withReasons, useReport, secureModeHash) {
|
|
79
|
+
let body;
|
|
80
|
+
let method = 'GET';
|
|
81
|
+
const headers = { ...baseHeaders };
|
|
82
|
+
if (useReport) {
|
|
83
|
+
method = 'REPORT';
|
|
84
|
+
headers['content-type'] = 'application/json';
|
|
85
|
+
body = plainContextString; // context is in body for REPORT
|
|
86
|
+
}
|
|
87
|
+
const path = useReport
|
|
88
|
+
? paths.pathReport(encoding, plainContextString)
|
|
89
|
+
: paths.pathGet(encoding, plainContextString);
|
|
90
|
+
const parameters = [...(baseQueryParams ?? [])];
|
|
91
|
+
if (withReasons) {
|
|
92
|
+
parameters.push({ key: 'withReasons', value: 'true' });
|
|
93
|
+
}
|
|
94
|
+
if (secureModeHash) {
|
|
95
|
+
parameters.push({ key: 'h', value: secureModeHash });
|
|
96
|
+
}
|
|
97
|
+
const uri = jsSdkCommon.getPollingUri(serviceEndpoints, path, parameters);
|
|
98
|
+
return new Requestor(requests, uri, headers, method, body);
|
|
99
|
+
}
|
|
100
|
+
|
|
35
101
|
// eslint-disable-next-line max-classes-per-file
|
|
36
102
|
const validators = {
|
|
37
103
|
logger: jsSdkCommon.TypeValidators.Object,
|
|
@@ -56,6 +122,7 @@ const validators = {
|
|
|
56
122
|
wrapperVersion: jsSdkCommon.TypeValidators.String,
|
|
57
123
|
payloadFilterKey: jsSdkCommon.TypeValidators.stringMatchingRegex(/^[a-zA-Z0-9](\w|\.|-)*$/),
|
|
58
124
|
hooks: jsSdkCommon.TypeValidators.createTypeArray('Hook[]', {}),
|
|
125
|
+
inspectors: jsSdkCommon.TypeValidators.createTypeArray('LDInspection', {}),
|
|
59
126
|
};
|
|
60
127
|
|
|
61
128
|
const DEFAULT_POLLING_INTERVAL = 60 * 5;
|
|
@@ -71,8 +138,13 @@ function ensureSafeLogger(logger) {
|
|
|
71
138
|
class ConfigurationImpl {
|
|
72
139
|
constructor(pristineOptions = {}, internalOptions = {}) {
|
|
73
140
|
this.logger = jsSdkCommon.createSafeLogger();
|
|
141
|
+
// Naming conventions is not followed for these lines because the config validation
|
|
142
|
+
// accesses members based on the keys of the options. (sdk-763)
|
|
143
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
74
144
|
this.baseUri = DEFAULT_POLLING;
|
|
145
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
75
146
|
this.eventsUri = jsSdkCommon.ServiceEndpoints.DEFAULT_EVENTS;
|
|
147
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
76
148
|
this.streamUri = DEFAULT_STREAM;
|
|
77
149
|
this.maxCachedContexts = 5;
|
|
78
150
|
this.capacity = 100;
|
|
@@ -89,8 +161,9 @@ class ConfigurationImpl {
|
|
|
89
161
|
this.privateAttributes = [];
|
|
90
162
|
this.pollInterval = DEFAULT_POLLING_INTERVAL;
|
|
91
163
|
this.hooks = [];
|
|
164
|
+
this.inspectors = [];
|
|
92
165
|
this.logger = ensureSafeLogger(pristineOptions.logger);
|
|
93
|
-
const errors = this.
|
|
166
|
+
const errors = this._validateTypesAndNames(pristineOptions);
|
|
94
167
|
errors.forEach((e) => this.logger.warn(e));
|
|
95
168
|
this.serviceEndpoints = new jsSdkCommon.ServiceEndpoints(this.streamUri, this.baseUri, this.eventsUri, internalOptions.analyticsEventPath, internalOptions.diagnosticEventPath, internalOptions.includeAuthorizationHeader, pristineOptions.payloadFilterKey);
|
|
96
169
|
this.useReport = pristineOptions.useReport ?? false;
|
|
@@ -98,7 +171,7 @@ class ConfigurationImpl {
|
|
|
98
171
|
this.userAgentHeaderName = internalOptions.userAgentHeaderName ?? 'user-agent';
|
|
99
172
|
this.trackEventModifier = internalOptions.trackEventModifier ?? ((event) => event);
|
|
100
173
|
}
|
|
101
|
-
|
|
174
|
+
_validateTypesAndNames(pristineOptions) {
|
|
102
175
|
const errors = [];
|
|
103
176
|
Object.entries(pristineOptions).forEach(([k, v]) => {
|
|
104
177
|
const validator = validators[k];
|
|
@@ -406,11 +479,12 @@ function createErrorEvaluationDetail(errorKind, def) {
|
|
|
406
479
|
};
|
|
407
480
|
}
|
|
408
481
|
function createSuccessEvaluationDetail(value, variationIndex, reason) {
|
|
409
|
-
|
|
482
|
+
const res = {
|
|
410
483
|
value,
|
|
411
484
|
variationIndex: variationIndex ?? null,
|
|
412
485
|
reason: reason ?? null,
|
|
413
486
|
};
|
|
487
|
+
return res;
|
|
414
488
|
}
|
|
415
489
|
|
|
416
490
|
const createEventProcessor = (clientSideID, config, platform, baseHeaders, diagnosticsManager) => {
|
|
@@ -508,23 +582,23 @@ class ContextIndex {
|
|
|
508
582
|
* then persists changes after the updater has completed.
|
|
509
583
|
*/
|
|
510
584
|
class FlagPersistence {
|
|
511
|
-
constructor(
|
|
512
|
-
this.
|
|
513
|
-
this.
|
|
514
|
-
this.
|
|
515
|
-
this.
|
|
516
|
-
this.
|
|
517
|
-
this.
|
|
518
|
-
this.
|
|
519
|
-
this.
|
|
585
|
+
constructor(_platform, _environmentNamespace, _maxCachedContexts, _flagStore, _flagUpdater, _logger, _timeStamper = () => Date.now()) {
|
|
586
|
+
this._platform = _platform;
|
|
587
|
+
this._environmentNamespace = _environmentNamespace;
|
|
588
|
+
this._maxCachedContexts = _maxCachedContexts;
|
|
589
|
+
this._flagStore = _flagStore;
|
|
590
|
+
this._flagUpdater = _flagUpdater;
|
|
591
|
+
this._logger = _logger;
|
|
592
|
+
this._timeStamper = _timeStamper;
|
|
593
|
+
this._indexKeyPromise = namespaceForContextIndex(this._environmentNamespace);
|
|
520
594
|
}
|
|
521
595
|
/**
|
|
522
596
|
* Inits flag persistence for the provided context with the provided flags. This will result
|
|
523
597
|
* in the underlying {@link FlagUpdater} switching its active context.
|
|
524
598
|
*/
|
|
525
599
|
async init(context, newFlags) {
|
|
526
|
-
this.
|
|
527
|
-
await this.
|
|
600
|
+
this._flagUpdater.init(context, newFlags);
|
|
601
|
+
await this._storeCache(context);
|
|
528
602
|
}
|
|
529
603
|
/**
|
|
530
604
|
* Upserts a flag into the {@link FlagUpdater} and stores that to persistence if the upsert
|
|
@@ -532,8 +606,8 @@ class FlagPersistence {
|
|
|
532
606
|
* the active context.
|
|
533
607
|
*/
|
|
534
608
|
async upsert(context, key, item) {
|
|
535
|
-
if (this.
|
|
536
|
-
await this.
|
|
609
|
+
if (this._flagUpdater.upsert(context, key, item)) {
|
|
610
|
+
await this._storeCache(context);
|
|
537
611
|
return true;
|
|
538
612
|
}
|
|
539
613
|
return false;
|
|
@@ -543,19 +617,19 @@ class FlagPersistence {
|
|
|
543
617
|
* {@link FlagUpdater} this {@link FlagPersistence} was constructed with.
|
|
544
618
|
*/
|
|
545
619
|
async loadCached(context) {
|
|
546
|
-
const storageKey = await namespaceForContextData(this.
|
|
547
|
-
let flagsJson = await this.
|
|
620
|
+
const storageKey = await namespaceForContextData(this._platform.crypto, this._environmentNamespace, context);
|
|
621
|
+
let flagsJson = await this._platform.storage?.get(storageKey);
|
|
548
622
|
if (flagsJson === null || flagsJson === undefined) {
|
|
549
623
|
// Fallback: in version <10.3.1 flag data was stored under the canonical key, check
|
|
550
624
|
// to see if data is present and migrate the data if present.
|
|
551
|
-
flagsJson = await this.
|
|
625
|
+
flagsJson = await this._platform.storage?.get(context.canonicalKey);
|
|
552
626
|
if (flagsJson === null || flagsJson === undefined) {
|
|
553
627
|
// return false indicating cache did not load if flag json is still absent
|
|
554
628
|
return false;
|
|
555
629
|
}
|
|
556
630
|
// migrate data from version <10.3.1 and cleanup data that was under canonical key
|
|
557
|
-
await this.
|
|
558
|
-
await this.
|
|
631
|
+
await this._platform.storage?.set(storageKey, flagsJson);
|
|
632
|
+
await this._platform.storage?.clear(context.canonicalKey);
|
|
559
633
|
}
|
|
560
634
|
try {
|
|
561
635
|
const flags = JSON.parse(flagsJson);
|
|
@@ -564,43 +638,43 @@ class FlagPersistence {
|
|
|
564
638
|
acc[key] = { version: flag.version, flag };
|
|
565
639
|
return acc;
|
|
566
640
|
}, {});
|
|
567
|
-
this.
|
|
568
|
-
this.
|
|
641
|
+
this._flagUpdater.initCached(context, descriptors);
|
|
642
|
+
this._logger.debug('Loaded cached flag evaluations from persistent storage');
|
|
569
643
|
return true;
|
|
570
644
|
}
|
|
571
645
|
catch (e) {
|
|
572
|
-
this.
|
|
646
|
+
this._logger.warn(`Could not load cached flag evaluations from persistent storage: ${e.message}`);
|
|
573
647
|
return false;
|
|
574
648
|
}
|
|
575
649
|
}
|
|
576
|
-
async
|
|
577
|
-
if (this.
|
|
578
|
-
return this.
|
|
650
|
+
async _loadIndex() {
|
|
651
|
+
if (this._contextIndex !== undefined) {
|
|
652
|
+
return this._contextIndex;
|
|
579
653
|
}
|
|
580
|
-
const json = await this.
|
|
654
|
+
const json = await this._platform.storage?.get(await this._indexKeyPromise);
|
|
581
655
|
if (!json) {
|
|
582
|
-
this.
|
|
583
|
-
return this.
|
|
656
|
+
this._contextIndex = new ContextIndex();
|
|
657
|
+
return this._contextIndex;
|
|
584
658
|
}
|
|
585
659
|
try {
|
|
586
|
-
this.
|
|
587
|
-
this.
|
|
660
|
+
this._contextIndex = ContextIndex.fromJson(json);
|
|
661
|
+
this._logger.debug('Loaded context index from persistent storage');
|
|
588
662
|
}
|
|
589
663
|
catch (e) {
|
|
590
|
-
this.
|
|
591
|
-
this.
|
|
664
|
+
this._logger.warn(`Could not load index from persistent storage: ${e.message}`);
|
|
665
|
+
this._contextIndex = new ContextIndex();
|
|
592
666
|
}
|
|
593
|
-
return this.
|
|
594
|
-
}
|
|
595
|
-
async
|
|
596
|
-
const index = await this.
|
|
597
|
-
const storageKey = await namespaceForContextData(this.
|
|
598
|
-
index.notice(storageKey, this.
|
|
599
|
-
const pruned = index.prune(this.
|
|
600
|
-
await Promise.all(pruned.map(async (it) => this.
|
|
667
|
+
return this._contextIndex;
|
|
668
|
+
}
|
|
669
|
+
async _storeCache(context) {
|
|
670
|
+
const index = await this._loadIndex();
|
|
671
|
+
const storageKey = await namespaceForContextData(this._platform.crypto, this._environmentNamespace, context);
|
|
672
|
+
index.notice(storageKey, this._timeStamper());
|
|
673
|
+
const pruned = index.prune(this._maxCachedContexts);
|
|
674
|
+
await Promise.all(pruned.map(async (it) => this._platform.storage?.clear(it.id)));
|
|
601
675
|
// store index
|
|
602
|
-
await this.
|
|
603
|
-
const allFlags = this.
|
|
676
|
+
await this._platform.storage?.set(await this._indexKeyPromise, index.toJson());
|
|
677
|
+
const allFlags = this._flagStore.getAll();
|
|
604
678
|
// mapping item descriptors to flags
|
|
605
679
|
const flags = Object.entries(allFlags).reduce((acc, [key, descriptor]) => {
|
|
606
680
|
if (descriptor.flag !== null && descriptor.flag !== undefined) {
|
|
@@ -610,7 +684,7 @@ class FlagPersistence {
|
|
|
610
684
|
}, {});
|
|
611
685
|
const jsonAll = JSON.stringify(flags);
|
|
612
686
|
// store flag data
|
|
613
|
-
await this.
|
|
687
|
+
await this._platform.storage?.set(storageKey, jsonAll);
|
|
614
688
|
}
|
|
615
689
|
}
|
|
616
690
|
|
|
@@ -619,25 +693,25 @@ class FlagPersistence {
|
|
|
619
693
|
*/
|
|
620
694
|
class DefaultFlagStore {
|
|
621
695
|
constructor() {
|
|
622
|
-
this.
|
|
696
|
+
this._flags = {};
|
|
623
697
|
}
|
|
624
698
|
init(newFlags) {
|
|
625
|
-
this.
|
|
699
|
+
this._flags = Object.entries(newFlags).reduce((acc, [key, flag]) => {
|
|
626
700
|
acc[key] = flag;
|
|
627
701
|
return acc;
|
|
628
702
|
}, {});
|
|
629
703
|
}
|
|
630
704
|
insertOrUpdate(key, update) {
|
|
631
|
-
this.
|
|
705
|
+
this._flags[key] = update;
|
|
632
706
|
}
|
|
633
707
|
get(key) {
|
|
634
|
-
if (Object.prototype.hasOwnProperty.call(this.
|
|
635
|
-
return this.
|
|
708
|
+
if (Object.prototype.hasOwnProperty.call(this._flags, key)) {
|
|
709
|
+
return this._flags[key];
|
|
636
710
|
}
|
|
637
711
|
return undefined;
|
|
638
712
|
}
|
|
639
713
|
getAll() {
|
|
640
|
-
return this.
|
|
714
|
+
return this._flags;
|
|
641
715
|
}
|
|
642
716
|
}
|
|
643
717
|
|
|
@@ -666,19 +740,19 @@ function calculateChangedKeys(existingObject, newObject) {
|
|
|
666
740
|
*/
|
|
667
741
|
class FlagUpdater {
|
|
668
742
|
constructor(flagStore, logger) {
|
|
669
|
-
this.
|
|
670
|
-
this.
|
|
671
|
-
this.
|
|
743
|
+
this._changeCallbacks = new Array();
|
|
744
|
+
this._flagStore = flagStore;
|
|
745
|
+
this._logger = logger;
|
|
672
746
|
}
|
|
673
747
|
init(context, newFlags) {
|
|
674
|
-
this.
|
|
675
|
-
const oldFlags = this.
|
|
676
|
-
this.
|
|
748
|
+
this._activeContextKey = context.canonicalKey;
|
|
749
|
+
const oldFlags = this._flagStore.getAll();
|
|
750
|
+
this._flagStore.init(newFlags);
|
|
677
751
|
const changed = calculateChangedKeys(oldFlags, newFlags);
|
|
678
752
|
if (changed.length > 0) {
|
|
679
|
-
this.
|
|
753
|
+
this._changeCallbacks.forEach((callback) => {
|
|
680
754
|
try {
|
|
681
|
-
callback(context, changed);
|
|
755
|
+
callback(context, changed, 'init');
|
|
682
756
|
}
|
|
683
757
|
catch (err) {
|
|
684
758
|
/* intentionally empty */
|
|
@@ -687,25 +761,25 @@ class FlagUpdater {
|
|
|
687
761
|
}
|
|
688
762
|
}
|
|
689
763
|
initCached(context, newFlags) {
|
|
690
|
-
if (this.
|
|
764
|
+
if (this._activeContextKey === context.canonicalKey) {
|
|
691
765
|
return;
|
|
692
766
|
}
|
|
693
767
|
this.init(context, newFlags);
|
|
694
768
|
}
|
|
695
769
|
upsert(context, key, item) {
|
|
696
|
-
if (this.
|
|
697
|
-
this.
|
|
770
|
+
if (this._activeContextKey !== context.canonicalKey) {
|
|
771
|
+
this._logger.warn('Received an update for an inactive context.');
|
|
698
772
|
return false;
|
|
699
773
|
}
|
|
700
|
-
const currentValue = this.
|
|
774
|
+
const currentValue = this._flagStore.get(key);
|
|
701
775
|
if (currentValue !== undefined && currentValue.version >= item.version) {
|
|
702
776
|
// this is an out of order update that can be ignored
|
|
703
777
|
return false;
|
|
704
778
|
}
|
|
705
|
-
this.
|
|
706
|
-
this.
|
|
779
|
+
this._flagStore.insertOrUpdate(key, item);
|
|
780
|
+
this._changeCallbacks.forEach((callback) => {
|
|
707
781
|
try {
|
|
708
|
-
callback(context, [key]);
|
|
782
|
+
callback(context, [key], 'patch');
|
|
709
783
|
}
|
|
710
784
|
catch (err) {
|
|
711
785
|
/* intentionally empty */
|
|
@@ -714,12 +788,12 @@ class FlagUpdater {
|
|
|
714
788
|
return true;
|
|
715
789
|
}
|
|
716
790
|
on(callback) {
|
|
717
|
-
this.
|
|
791
|
+
this._changeCallbacks.push(callback);
|
|
718
792
|
}
|
|
719
793
|
off(callback) {
|
|
720
|
-
const index = this.
|
|
794
|
+
const index = this._changeCallbacks.indexOf(callback);
|
|
721
795
|
if (index > -1) {
|
|
722
|
-
this.
|
|
796
|
+
this._changeCallbacks.splice(index, 1);
|
|
723
797
|
}
|
|
724
798
|
}
|
|
725
799
|
}
|
|
@@ -733,40 +807,39 @@ class DefaultFlagManager {
|
|
|
733
807
|
* @param timeStamper exists for testing purposes
|
|
734
808
|
*/
|
|
735
809
|
constructor(platform, sdkKey, maxCachedContexts, logger, timeStamper = () => Date.now()) {
|
|
736
|
-
this.
|
|
737
|
-
this.
|
|
738
|
-
this.
|
|
739
|
-
this.flagPersistencePromise = this.initPersistence(platform, sdkKey, maxCachedContexts, logger, timeStamper);
|
|
810
|
+
this._flagStore = new DefaultFlagStore();
|
|
811
|
+
this._flagUpdater = new FlagUpdater(this._flagStore, logger);
|
|
812
|
+
this._flagPersistencePromise = this._initPersistence(platform, sdkKey, maxCachedContexts, logger, timeStamper);
|
|
740
813
|
}
|
|
741
|
-
async
|
|
814
|
+
async _initPersistence(platform, sdkKey, maxCachedContexts, logger, timeStamper = () => Date.now()) {
|
|
742
815
|
const environmentNamespace = await namespaceForEnvironment(platform.crypto, sdkKey);
|
|
743
|
-
return new FlagPersistence(platform, environmentNamespace, maxCachedContexts, this.
|
|
816
|
+
return new FlagPersistence(platform, environmentNamespace, maxCachedContexts, this._flagStore, this._flagUpdater, logger, timeStamper);
|
|
744
817
|
}
|
|
745
818
|
get(key) {
|
|
746
|
-
return this.
|
|
819
|
+
return this._flagStore.get(key);
|
|
747
820
|
}
|
|
748
821
|
getAll() {
|
|
749
|
-
return this.
|
|
822
|
+
return this._flagStore.getAll();
|
|
750
823
|
}
|
|
751
824
|
setBootstrap(context, newFlags) {
|
|
752
825
|
// Bypasses the persistence as we do not want to put these flags into any cache.
|
|
753
826
|
// Generally speaking persistence likely *SHOULD* be disabled when using bootstrap.
|
|
754
|
-
this.
|
|
827
|
+
this._flagUpdater.init(context, newFlags);
|
|
755
828
|
}
|
|
756
829
|
async init(context, newFlags) {
|
|
757
|
-
return (await this.
|
|
830
|
+
return (await this._flagPersistencePromise).init(context, newFlags);
|
|
758
831
|
}
|
|
759
832
|
async upsert(context, key, item) {
|
|
760
|
-
return (await this.
|
|
833
|
+
return (await this._flagPersistencePromise).upsert(context, key, item);
|
|
761
834
|
}
|
|
762
835
|
async loadCached(context) {
|
|
763
|
-
return (await this.
|
|
836
|
+
return (await this._flagPersistencePromise).loadCached(context);
|
|
764
837
|
}
|
|
765
838
|
on(callback) {
|
|
766
|
-
this.
|
|
839
|
+
this._flagUpdater.on(callback);
|
|
767
840
|
}
|
|
768
841
|
off(callback) {
|
|
769
|
-
this.
|
|
842
|
+
this._flagUpdater.off(callback);
|
|
770
843
|
}
|
|
771
844
|
}
|
|
772
845
|
|
|
@@ -816,39 +889,186 @@ function executeAfterIdentify(logger, hooks, hookContext, updatedData, result) {
|
|
|
816
889
|
}
|
|
817
890
|
}
|
|
818
891
|
class HookRunner {
|
|
819
|
-
constructor(
|
|
820
|
-
this.
|
|
821
|
-
this.
|
|
822
|
-
this.
|
|
892
|
+
constructor(_logger, initialHooks) {
|
|
893
|
+
this._logger = _logger;
|
|
894
|
+
this._hooks = [];
|
|
895
|
+
this._hooks.push(...initialHooks);
|
|
823
896
|
}
|
|
824
897
|
withEvaluation(key, context, defaultValue, method) {
|
|
825
|
-
if (this.
|
|
898
|
+
if (this._hooks.length === 0) {
|
|
826
899
|
return method();
|
|
827
900
|
}
|
|
828
|
-
const hooks = [...this.
|
|
901
|
+
const hooks = [...this._hooks];
|
|
829
902
|
const hookContext = {
|
|
830
903
|
flagKey: key,
|
|
831
904
|
context,
|
|
832
905
|
defaultValue,
|
|
833
906
|
};
|
|
834
|
-
const hookData = executeBeforeEvaluation(this.
|
|
907
|
+
const hookData = executeBeforeEvaluation(this._logger, hooks, hookContext);
|
|
835
908
|
const result = method();
|
|
836
|
-
executeAfterEvaluation(this.
|
|
909
|
+
executeAfterEvaluation(this._logger, hooks, hookContext, hookData, result);
|
|
837
910
|
return result;
|
|
838
911
|
}
|
|
839
912
|
identify(context, timeout) {
|
|
840
|
-
const hooks = [...this.
|
|
913
|
+
const hooks = [...this._hooks];
|
|
841
914
|
const hookContext = {
|
|
842
915
|
context,
|
|
843
916
|
timeout,
|
|
844
917
|
};
|
|
845
|
-
const hookData = executeBeforeIdentify(this.
|
|
918
|
+
const hookData = executeBeforeIdentify(this._logger, hooks, hookContext);
|
|
846
919
|
return (result) => {
|
|
847
|
-
executeAfterIdentify(this.
|
|
920
|
+
executeAfterIdentify(this._logger, hooks, hookContext, hookData, result);
|
|
848
921
|
};
|
|
849
922
|
}
|
|
850
923
|
addHook(hook) {
|
|
851
|
-
this.
|
|
924
|
+
this._hooks.push(hook);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
function getInspectorHook(inspectorManager) {
|
|
929
|
+
return {
|
|
930
|
+
getMetadata() {
|
|
931
|
+
return {
|
|
932
|
+
name: 'LaunchDarkly-Inspector-Adapter',
|
|
933
|
+
};
|
|
934
|
+
},
|
|
935
|
+
afterEvaluation: (hookContext, data, detail) => {
|
|
936
|
+
inspectorManager.onFlagUsed(hookContext.flagKey, detail, hookContext.context);
|
|
937
|
+
return data;
|
|
938
|
+
},
|
|
939
|
+
afterIdentify(hookContext, data, _result) {
|
|
940
|
+
inspectorManager.onIdentityChanged(hookContext.context);
|
|
941
|
+
return data;
|
|
942
|
+
},
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
function invalidInspector(type, name) {
|
|
947
|
+
return `an inspector: "${name}" of an invalid type (${type}) was configured`;
|
|
948
|
+
}
|
|
949
|
+
function inspectorMethodError(type, name) {
|
|
950
|
+
return `an inspector: "${name}" of type: "${type}" generated an exception`;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* Wrap an inspector ensuring that calling its methods are safe.
|
|
955
|
+
* @param inspector Inspector to wrap.
|
|
956
|
+
*/
|
|
957
|
+
function createSafeInspector(inspector, logger) {
|
|
958
|
+
let errorLogged = false;
|
|
959
|
+
const wrapper = {
|
|
960
|
+
method: (...args) => {
|
|
961
|
+
try {
|
|
962
|
+
// We are proxying arguments here to the underlying method. Typescript doesn't care
|
|
963
|
+
// for this as it cannot validate the parameters are correct, but we are also the caller
|
|
964
|
+
// in this case and will dispatch things with the correct arguments. The dispatch to this
|
|
965
|
+
// will itself happen with a type guard.
|
|
966
|
+
// @ts-ignore
|
|
967
|
+
inspector.method(...args);
|
|
968
|
+
}
|
|
969
|
+
catch {
|
|
970
|
+
// If something goes wrong in an inspector we want to log that something
|
|
971
|
+
// went wrong. We don't want to flood the logs, so we only log something
|
|
972
|
+
// the first time that something goes wrong.
|
|
973
|
+
// We do not include the exception in the log, because we do not know what
|
|
974
|
+
// kind of data it may contain.
|
|
975
|
+
if (!errorLogged) {
|
|
976
|
+
errorLogged = true;
|
|
977
|
+
logger.warn(inspectorMethodError(wrapper.type, wrapper.name));
|
|
978
|
+
}
|
|
979
|
+
// Prevent errors.
|
|
980
|
+
}
|
|
981
|
+
},
|
|
982
|
+
type: inspector.type,
|
|
983
|
+
name: inspector.name,
|
|
984
|
+
synchronous: inspector.synchronous,
|
|
985
|
+
};
|
|
986
|
+
return wrapper;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
const FLAG_USED_TYPE = 'flag-used';
|
|
990
|
+
const FLAG_DETAILS_CHANGED_TYPE = 'flag-details-changed';
|
|
991
|
+
const FLAG_DETAIL_CHANGED_TYPE = 'flag-detail-changed';
|
|
992
|
+
const IDENTITY_CHANGED_TYPE = 'client-identity-changed';
|
|
993
|
+
const VALID__TYPES = [
|
|
994
|
+
FLAG_USED_TYPE,
|
|
995
|
+
FLAG_DETAILS_CHANGED_TYPE,
|
|
996
|
+
FLAG_DETAIL_CHANGED_TYPE,
|
|
997
|
+
IDENTITY_CHANGED_TYPE,
|
|
998
|
+
];
|
|
999
|
+
function validateInspector(inspector, logger) {
|
|
1000
|
+
const valid = VALID__TYPES.includes(inspector.type) &&
|
|
1001
|
+
inspector.method &&
|
|
1002
|
+
typeof inspector.method === 'function';
|
|
1003
|
+
if (!valid) {
|
|
1004
|
+
logger.warn(invalidInspector(inspector.type, inspector.name));
|
|
1005
|
+
}
|
|
1006
|
+
return valid;
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Manages dispatching of inspection data to registered inspectors.
|
|
1010
|
+
*/
|
|
1011
|
+
class InspectorManager {
|
|
1012
|
+
constructor(inspectors, logger) {
|
|
1013
|
+
this._safeInspectors = [];
|
|
1014
|
+
const validInspectors = inspectors.filter((inspector) => validateInspector(inspector, logger));
|
|
1015
|
+
this._safeInspectors = validInspectors.map((inspector) => createSafeInspector(inspector, logger));
|
|
1016
|
+
}
|
|
1017
|
+
hasInspectors() {
|
|
1018
|
+
return this._safeInspectors.length !== 0;
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Notify registered inspectors of a flag being used.
|
|
1022
|
+
*
|
|
1023
|
+
* @param flagKey The key for the flag.
|
|
1024
|
+
* @param detail The LDEvaluationDetail for the flag.
|
|
1025
|
+
* @param context The LDContext for the flag.
|
|
1026
|
+
*/
|
|
1027
|
+
onFlagUsed(flagKey, detail, context) {
|
|
1028
|
+
this._safeInspectors.forEach((inspector) => {
|
|
1029
|
+
if (inspector.type === FLAG_USED_TYPE) {
|
|
1030
|
+
inspector.method(flagKey, detail, context);
|
|
1031
|
+
}
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Notify registered inspectors that the flags have been replaced.
|
|
1036
|
+
*
|
|
1037
|
+
* @param flags The current flags as a Record<string, LDEvaluationDetail>.
|
|
1038
|
+
*/
|
|
1039
|
+
onFlagsChanged(flags) {
|
|
1040
|
+
this._safeInspectors.forEach((inspector) => {
|
|
1041
|
+
if (inspector.type === FLAG_DETAILS_CHANGED_TYPE) {
|
|
1042
|
+
inspector.method(flags);
|
|
1043
|
+
}
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
/**
|
|
1047
|
+
* Notify registered inspectors that a flag value has changed.
|
|
1048
|
+
*
|
|
1049
|
+
* @param flagKey The key for the flag that changed.
|
|
1050
|
+
* @param flag An `LDEvaluationDetail` for the flag.
|
|
1051
|
+
*/
|
|
1052
|
+
onFlagChanged(flagKey, flag) {
|
|
1053
|
+
this._safeInspectors.forEach((inspector) => {
|
|
1054
|
+
if (inspector.type === FLAG_DETAIL_CHANGED_TYPE) {
|
|
1055
|
+
inspector.method(flagKey, flag);
|
|
1056
|
+
}
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
/**
|
|
1060
|
+
* Notify the registered inspectors that the context identity has changed.
|
|
1061
|
+
*
|
|
1062
|
+
* The notification itself will be dispatched asynchronously.
|
|
1063
|
+
*
|
|
1064
|
+
* @param context The `LDContext` which is now identified.
|
|
1065
|
+
*/
|
|
1066
|
+
onIdentityChanged(context) {
|
|
1067
|
+
this._safeInspectors.forEach((inspector) => {
|
|
1068
|
+
if (inspector.type === IDENTITY_CHANGED_TYPE) {
|
|
1069
|
+
inspector.method(context);
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
852
1072
|
}
|
|
853
1073
|
}
|
|
854
1074
|
|
|
@@ -859,16 +1079,16 @@ class HookRunner {
|
|
|
859
1079
|
* a system to allow listeners which have counts independent of the primary listener counts.
|
|
860
1080
|
*/
|
|
861
1081
|
class LDEmitter {
|
|
862
|
-
constructor(
|
|
863
|
-
this.
|
|
864
|
-
this.
|
|
1082
|
+
constructor(_logger) {
|
|
1083
|
+
this._logger = _logger;
|
|
1084
|
+
this._listeners = new Map();
|
|
865
1085
|
}
|
|
866
1086
|
on(name, listener) {
|
|
867
|
-
if (!this.
|
|
868
|
-
this.
|
|
1087
|
+
if (!this._listeners.has(name)) {
|
|
1088
|
+
this._listeners.set(name, [listener]);
|
|
869
1089
|
}
|
|
870
1090
|
else {
|
|
871
|
-
this.
|
|
1091
|
+
this._listeners.get(name)?.push(listener);
|
|
872
1092
|
}
|
|
873
1093
|
}
|
|
874
1094
|
/**
|
|
@@ -878,7 +1098,7 @@ class LDEmitter {
|
|
|
878
1098
|
* @param listener Optional. If unspecified, all listeners for the event will be removed.
|
|
879
1099
|
*/
|
|
880
1100
|
off(name, listener) {
|
|
881
|
-
const existingListeners = this.
|
|
1101
|
+
const existingListeners = this._listeners.get(name);
|
|
882
1102
|
if (!existingListeners) {
|
|
883
1103
|
return;
|
|
884
1104
|
}
|
|
@@ -886,33 +1106,33 @@ class LDEmitter {
|
|
|
886
1106
|
// remove from internal cache
|
|
887
1107
|
const updated = existingListeners.filter((fn) => fn !== listener);
|
|
888
1108
|
if (updated.length === 0) {
|
|
889
|
-
this.
|
|
1109
|
+
this._listeners.delete(name);
|
|
890
1110
|
}
|
|
891
1111
|
else {
|
|
892
|
-
this.
|
|
1112
|
+
this._listeners.set(name, updated);
|
|
893
1113
|
}
|
|
894
1114
|
return;
|
|
895
1115
|
}
|
|
896
1116
|
// listener was not specified, so remove them all for that event
|
|
897
|
-
this.
|
|
1117
|
+
this._listeners.delete(name);
|
|
898
1118
|
}
|
|
899
|
-
|
|
1119
|
+
_invokeListener(listener, name, ...detail) {
|
|
900
1120
|
try {
|
|
901
1121
|
listener(...detail);
|
|
902
1122
|
}
|
|
903
1123
|
catch (err) {
|
|
904
|
-
this.
|
|
1124
|
+
this._logger?.error(`Encountered error invoking handler for "${name}", detail: "${err}"`);
|
|
905
1125
|
}
|
|
906
1126
|
}
|
|
907
1127
|
emit(name, ...detail) {
|
|
908
|
-
const listeners = this.
|
|
909
|
-
listeners?.forEach((listener) => this.
|
|
1128
|
+
const listeners = this._listeners.get(name);
|
|
1129
|
+
listeners?.forEach((listener) => this._invokeListener(listener, name, ...detail));
|
|
910
1130
|
}
|
|
911
1131
|
eventNames() {
|
|
912
|
-
return [...this.
|
|
1132
|
+
return [...this._listeners.keys()];
|
|
913
1133
|
}
|
|
914
1134
|
listenerCount(name) {
|
|
915
|
-
return this.
|
|
1135
|
+
return this._listeners.get(name)?.length ?? 0;
|
|
916
1136
|
}
|
|
917
1137
|
}
|
|
918
1138
|
|
|
@@ -925,40 +1145,45 @@ class LDClientImpl {
|
|
|
925
1145
|
this.sdkKey = sdkKey;
|
|
926
1146
|
this.autoEnvAttributes = autoEnvAttributes;
|
|
927
1147
|
this.platform = platform;
|
|
928
|
-
this.
|
|
929
|
-
this.
|
|
930
|
-
this.
|
|
931
|
-
this.
|
|
932
|
-
this.
|
|
1148
|
+
this._identifyTimeout = 5;
|
|
1149
|
+
this._highTimeoutThreshold = 15;
|
|
1150
|
+
this._eventFactoryDefault = new EventFactory(false);
|
|
1151
|
+
this._eventFactoryWithReasons = new EventFactory(true);
|
|
1152
|
+
this._eventSendingEnabled = false;
|
|
933
1153
|
if (!sdkKey) {
|
|
934
1154
|
throw new Error('You must configure the client with a client-side SDK key');
|
|
935
1155
|
}
|
|
936
1156
|
if (!platform.encoding) {
|
|
937
1157
|
throw new Error('Platform must implement Encoding because btoa is required.');
|
|
938
1158
|
}
|
|
939
|
-
this.
|
|
940
|
-
this.logger = this.
|
|
941
|
-
this.
|
|
942
|
-
this.
|
|
943
|
-
this.
|
|
944
|
-
this.
|
|
1159
|
+
this._config = new ConfigurationImpl(options, internalOptions);
|
|
1160
|
+
this.logger = this._config.logger;
|
|
1161
|
+
this._baseHeaders = jsSdkCommon.defaultHeaders(this.sdkKey, this.platform.info, this._config.tags, this._config.serviceEndpoints.includeAuthorizationHeader, this._config.userAgentHeaderName);
|
|
1162
|
+
this._flagManager = new DefaultFlagManager(this.platform, sdkKey, this._config.maxCachedContexts, this._config.logger);
|
|
1163
|
+
this._diagnosticsManager = createDiagnosticsManager(sdkKey, this._config, platform);
|
|
1164
|
+
this._eventProcessor = createEventProcessor(sdkKey, this._config, platform, this._baseHeaders, this._diagnosticsManager);
|
|
945
1165
|
this.emitter = new LDEmitter();
|
|
946
1166
|
this.emitter.on('error', (c, err) => {
|
|
947
1167
|
this.logger.error(`error: ${err}, context: ${JSON.stringify(c)}`);
|
|
948
1168
|
});
|
|
949
|
-
this.
|
|
1169
|
+
this._flagManager.on((context, flagKeys, type) => {
|
|
1170
|
+
this._handleInspectionChanged(flagKeys, type);
|
|
950
1171
|
const ldContext = jsSdkCommon.Context.toLDContext(context);
|
|
951
1172
|
this.emitter.emit('change', ldContext, flagKeys);
|
|
952
1173
|
flagKeys.forEach((it) => {
|
|
953
1174
|
this.emitter.emit(`change:${it}`, ldContext);
|
|
954
1175
|
});
|
|
955
1176
|
});
|
|
956
|
-
this.dataManager = dataManagerFactory(this.
|
|
957
|
-
this.
|
|
1177
|
+
this.dataManager = dataManagerFactory(this._flagManager, this._config, this._baseHeaders, this.emitter, this._diagnosticsManager);
|
|
1178
|
+
this._hookRunner = new HookRunner(this.logger, this._config.hooks);
|
|
1179
|
+
this._inspectorManager = new InspectorManager(this._config.inspectors, this.logger);
|
|
1180
|
+
if (this._inspectorManager.hasInspectors()) {
|
|
1181
|
+
this._hookRunner.addHook(getInspectorHook(this._inspectorManager));
|
|
1182
|
+
}
|
|
958
1183
|
}
|
|
959
1184
|
allFlags() {
|
|
960
1185
|
// extracting all flag values
|
|
961
|
-
const result = Object.entries(this.
|
|
1186
|
+
const result = Object.entries(this._flagManager.getAll()).reduce((acc, [key, descriptor]) => {
|
|
962
1187
|
if (descriptor.flag !== null && descriptor.flag !== undefined && !descriptor.flag.deleted) {
|
|
963
1188
|
acc[key] = descriptor.flag.value;
|
|
964
1189
|
}
|
|
@@ -968,13 +1193,13 @@ class LDClientImpl {
|
|
|
968
1193
|
}
|
|
969
1194
|
async close() {
|
|
970
1195
|
await this.flush();
|
|
971
|
-
this.
|
|
972
|
-
this.
|
|
1196
|
+
this._eventProcessor?.close();
|
|
1197
|
+
this._updateProcessor?.close();
|
|
973
1198
|
this.logger.debug('Closed event processor and data source.');
|
|
974
1199
|
}
|
|
975
1200
|
async flush() {
|
|
976
1201
|
try {
|
|
977
|
-
await this.
|
|
1202
|
+
await this._eventProcessor?.flush();
|
|
978
1203
|
this.logger.debug('Successfully flushed event processor.');
|
|
979
1204
|
}
|
|
980
1205
|
catch (e) {
|
|
@@ -989,12 +1214,12 @@ class LDClientImpl {
|
|
|
989
1214
|
// code. We are returned the unchecked context so that if a consumer identifies with an invalid context
|
|
990
1215
|
// and then calls getContext, they get back the same context they provided, without any assertion about
|
|
991
1216
|
// validity.
|
|
992
|
-
return this.
|
|
1217
|
+
return this._uncheckedContext ? jsSdkCommon.clone(this._uncheckedContext) : undefined;
|
|
993
1218
|
}
|
|
994
1219
|
getInternalContext() {
|
|
995
|
-
return this.
|
|
1220
|
+
return this._checkedContext;
|
|
996
1221
|
}
|
|
997
|
-
|
|
1222
|
+
_createIdentifyPromise(timeout) {
|
|
998
1223
|
let res;
|
|
999
1224
|
let rej;
|
|
1000
1225
|
const slow = new Promise((resolve, reject) => {
|
|
@@ -1027,16 +1252,16 @@ class LDClientImpl {
|
|
|
1027
1252
|
*/
|
|
1028
1253
|
async identify(pristineContext, identifyOptions) {
|
|
1029
1254
|
if (identifyOptions?.timeout) {
|
|
1030
|
-
this.
|
|
1255
|
+
this._identifyTimeout = identifyOptions.timeout;
|
|
1031
1256
|
}
|
|
1032
|
-
if (this.
|
|
1257
|
+
if (this._identifyTimeout > this._highTimeoutThreshold) {
|
|
1033
1258
|
this.logger.warn('The identify function was called with a timeout greater than ' +
|
|
1034
|
-
`${this.
|
|
1035
|
-
`${this.
|
|
1259
|
+
`${this._highTimeoutThreshold} seconds. We recommend a timeout of less than ` +
|
|
1260
|
+
`${this._highTimeoutThreshold} seconds.`);
|
|
1036
1261
|
}
|
|
1037
1262
|
let context = await ensureKey(pristineContext, this.platform);
|
|
1038
1263
|
if (this.autoEnvAttributes === jsSdkCommon.AutoEnvAttributes.Enabled) {
|
|
1039
|
-
context = await addAutoEnv(context, this.platform, this.
|
|
1264
|
+
context = await addAutoEnv(context, this.platform, this._config);
|
|
1040
1265
|
}
|
|
1041
1266
|
const checkedContext = jsSdkCommon.Context.fromLDContext(context);
|
|
1042
1267
|
if (!checkedContext.valid) {
|
|
@@ -1044,12 +1269,12 @@ class LDClientImpl {
|
|
|
1044
1269
|
this.emitter.emit('error', context, error);
|
|
1045
1270
|
return Promise.reject(error);
|
|
1046
1271
|
}
|
|
1047
|
-
this.
|
|
1048
|
-
this.
|
|
1049
|
-
this.
|
|
1050
|
-
const { identifyPromise, identifyResolve, identifyReject } = this.
|
|
1051
|
-
this.logger.debug(`Identifying ${JSON.stringify(this.
|
|
1052
|
-
const afterIdentify = this.
|
|
1272
|
+
this._uncheckedContext = context;
|
|
1273
|
+
this._checkedContext = checkedContext;
|
|
1274
|
+
this._eventProcessor?.sendEvent(this._eventFactoryDefault.identifyEvent(this._checkedContext));
|
|
1275
|
+
const { identifyPromise, identifyResolve, identifyReject } = this._createIdentifyPromise(this._identifyTimeout);
|
|
1276
|
+
this.logger.debug(`Identifying ${JSON.stringify(this._checkedContext)}`);
|
|
1277
|
+
const afterIdentify = this._hookRunner.identify(context, identifyOptions?.timeout);
|
|
1053
1278
|
await this.dataManager.identify(identifyResolve, identifyReject, checkedContext, identifyOptions);
|
|
1054
1279
|
return identifyPromise.then((res) => {
|
|
1055
1280
|
afterIdentify({ status: 'completed' });
|
|
@@ -1066,38 +1291,38 @@ class LDClientImpl {
|
|
|
1066
1291
|
this.emitter.off(eventName, listener);
|
|
1067
1292
|
}
|
|
1068
1293
|
track(key, data, metricValue) {
|
|
1069
|
-
if (!this.
|
|
1070
|
-
this.logger.warn(ClientMessages.
|
|
1294
|
+
if (!this._checkedContext || !this._checkedContext.valid) {
|
|
1295
|
+
this.logger.warn(ClientMessages.MissingContextKeyNoEvent);
|
|
1071
1296
|
return;
|
|
1072
1297
|
}
|
|
1073
1298
|
// 0 is valid, so do not truthy check the metric value
|
|
1074
1299
|
if (metricValue !== undefined && !jsSdkCommon.TypeValidators.Number.is(metricValue)) {
|
|
1075
1300
|
this.logger?.warn(ClientMessages.invalidMetricValue(typeof metricValue));
|
|
1076
1301
|
}
|
|
1077
|
-
this.
|
|
1302
|
+
this._eventProcessor?.sendEvent(this._config.trackEventModifier(this._eventFactoryDefault.customEvent(key, this._checkedContext, data, metricValue)));
|
|
1078
1303
|
}
|
|
1079
|
-
|
|
1080
|
-
if (!this.
|
|
1081
|
-
this.logger.debug(ClientMessages.
|
|
1304
|
+
_variationInternal(flagKey, defaultValue, eventFactory, typeChecker) {
|
|
1305
|
+
if (!this._uncheckedContext) {
|
|
1306
|
+
this.logger.debug(ClientMessages.MissingContextKeyNoEvent);
|
|
1082
1307
|
return createErrorEvaluationDetail(ErrorKinds.UserNotSpecified, defaultValue);
|
|
1083
1308
|
}
|
|
1084
|
-
const evalContext = jsSdkCommon.Context.fromLDContext(this.
|
|
1085
|
-
const foundItem = this.
|
|
1309
|
+
const evalContext = jsSdkCommon.Context.fromLDContext(this._uncheckedContext);
|
|
1310
|
+
const foundItem = this._flagManager.get(flagKey);
|
|
1086
1311
|
if (foundItem === undefined || foundItem.flag.deleted) {
|
|
1087
1312
|
const defVal = defaultValue ?? null;
|
|
1088
1313
|
const error = new jsSdkCommon.LDClientError(`Unknown feature flag "${flagKey}"; returning default value ${defVal}.`);
|
|
1089
|
-
this.emitter.emit('error', this.
|
|
1090
|
-
this.
|
|
1314
|
+
this.emitter.emit('error', this._uncheckedContext, error);
|
|
1315
|
+
this._eventProcessor?.sendEvent(this._eventFactoryDefault.unknownFlagEvent(flagKey, defVal, evalContext));
|
|
1091
1316
|
return createErrorEvaluationDetail(ErrorKinds.FlagNotFound, defaultValue);
|
|
1092
1317
|
}
|
|
1093
|
-
const { reason, value, variation } = foundItem.flag;
|
|
1318
|
+
const { reason, value, variation, prerequisites } = foundItem.flag;
|
|
1094
1319
|
if (typeChecker) {
|
|
1095
1320
|
const [matched, type] = typeChecker(value);
|
|
1096
1321
|
if (!matched) {
|
|
1097
|
-
this.
|
|
1322
|
+
this._eventProcessor?.sendEvent(eventFactory.evalEventClient(flagKey, defaultValue, // track default value on type errors
|
|
1098
1323
|
defaultValue, foundItem.flag, evalContext, reason));
|
|
1099
1324
|
const error = new jsSdkCommon.LDClientError(`Wrong type "${type}" for feature flag "${flagKey}"; returning default value`);
|
|
1100
|
-
this.emitter.emit('error', this.
|
|
1325
|
+
this.emitter.emit('error', this._uncheckedContext, error);
|
|
1101
1326
|
return createErrorEvaluationDetail(ErrorKinds.WrongType, defaultValue);
|
|
1102
1327
|
}
|
|
1103
1328
|
}
|
|
@@ -1106,21 +1331,24 @@ class LDClientImpl {
|
|
|
1106
1331
|
this.logger.debug('Result value is null. Providing default value.');
|
|
1107
1332
|
successDetail.value = defaultValue;
|
|
1108
1333
|
}
|
|
1109
|
-
|
|
1334
|
+
prerequisites?.forEach((prereqKey) => {
|
|
1335
|
+
this._variationInternal(prereqKey, undefined, this._eventFactoryDefault);
|
|
1336
|
+
});
|
|
1337
|
+
this._eventProcessor?.sendEvent(eventFactory.evalEventClient(flagKey, value, defaultValue, foundItem.flag, evalContext, reason));
|
|
1110
1338
|
return successDetail;
|
|
1111
1339
|
}
|
|
1112
1340
|
variation(flagKey, defaultValue) {
|
|
1113
|
-
const { value } = this.
|
|
1341
|
+
const { value } = this._hookRunner.withEvaluation(flagKey, this._uncheckedContext, defaultValue, () => this._variationInternal(flagKey, defaultValue, this._eventFactoryDefault));
|
|
1114
1342
|
return value;
|
|
1115
1343
|
}
|
|
1116
1344
|
variationDetail(flagKey, defaultValue) {
|
|
1117
|
-
return this.
|
|
1345
|
+
return this._hookRunner.withEvaluation(flagKey, this._uncheckedContext, defaultValue, () => this._variationInternal(flagKey, defaultValue, this._eventFactoryWithReasons));
|
|
1118
1346
|
}
|
|
1119
|
-
|
|
1120
|
-
return this.
|
|
1347
|
+
_typedEval(key, defaultValue, eventFactory, typeChecker) {
|
|
1348
|
+
return this._hookRunner.withEvaluation(key, this._uncheckedContext, defaultValue, () => this._variationInternal(key, defaultValue, eventFactory, typeChecker));
|
|
1121
1349
|
}
|
|
1122
1350
|
boolVariation(key, defaultValue) {
|
|
1123
|
-
return this.
|
|
1351
|
+
return this._typedEval(key, defaultValue, this._eventFactoryDefault, (value) => [
|
|
1124
1352
|
jsSdkCommon.TypeValidators.Boolean.is(value),
|
|
1125
1353
|
jsSdkCommon.TypeValidators.Boolean.getType(),
|
|
1126
1354
|
]).value;
|
|
@@ -1129,31 +1357,31 @@ class LDClientImpl {
|
|
|
1129
1357
|
return this.variation(key, defaultValue);
|
|
1130
1358
|
}
|
|
1131
1359
|
numberVariation(key, defaultValue) {
|
|
1132
|
-
return this.
|
|
1360
|
+
return this._typedEval(key, defaultValue, this._eventFactoryDefault, (value) => [
|
|
1133
1361
|
jsSdkCommon.TypeValidators.Number.is(value),
|
|
1134
1362
|
jsSdkCommon.TypeValidators.Number.getType(),
|
|
1135
1363
|
]).value;
|
|
1136
1364
|
}
|
|
1137
1365
|
stringVariation(key, defaultValue) {
|
|
1138
|
-
return this.
|
|
1366
|
+
return this._typedEval(key, defaultValue, this._eventFactoryDefault, (value) => [
|
|
1139
1367
|
jsSdkCommon.TypeValidators.String.is(value),
|
|
1140
1368
|
jsSdkCommon.TypeValidators.String.getType(),
|
|
1141
1369
|
]).value;
|
|
1142
1370
|
}
|
|
1143
1371
|
boolVariationDetail(key, defaultValue) {
|
|
1144
|
-
return this.
|
|
1372
|
+
return this._typedEval(key, defaultValue, this._eventFactoryWithReasons, (value) => [
|
|
1145
1373
|
jsSdkCommon.TypeValidators.Boolean.is(value),
|
|
1146
1374
|
jsSdkCommon.TypeValidators.Boolean.getType(),
|
|
1147
1375
|
]);
|
|
1148
1376
|
}
|
|
1149
1377
|
numberVariationDetail(key, defaultValue) {
|
|
1150
|
-
return this.
|
|
1378
|
+
return this._typedEval(key, defaultValue, this._eventFactoryWithReasons, (value) => [
|
|
1151
1379
|
jsSdkCommon.TypeValidators.Number.is(value),
|
|
1152
1380
|
jsSdkCommon.TypeValidators.Number.getType(),
|
|
1153
1381
|
]);
|
|
1154
1382
|
}
|
|
1155
1383
|
stringVariationDetail(key, defaultValue) {
|
|
1156
|
-
return this.
|
|
1384
|
+
return this._typedEval(key, defaultValue, this._eventFactoryWithReasons, (value) => [
|
|
1157
1385
|
jsSdkCommon.TypeValidators.String.is(value),
|
|
1158
1386
|
jsSdkCommon.TypeValidators.String.getType(),
|
|
1159
1387
|
]);
|
|
@@ -1162,7 +1390,7 @@ class LDClientImpl {
|
|
|
1162
1390
|
return this.variationDetail(key, defaultValue);
|
|
1163
1391
|
}
|
|
1164
1392
|
addHook(hook) {
|
|
1165
|
-
this.
|
|
1393
|
+
this._hookRunner.addHook(hook);
|
|
1166
1394
|
}
|
|
1167
1395
|
/**
|
|
1168
1396
|
* Enable/Disable event sending.
|
|
@@ -1170,13 +1398,13 @@ class LDClientImpl {
|
|
|
1170
1398
|
* @param flush True to flush while disabling. Useful to flush on certain state transitions.
|
|
1171
1399
|
*/
|
|
1172
1400
|
setEventSendingEnabled(enabled, flush) {
|
|
1173
|
-
if (this.
|
|
1401
|
+
if (this._eventSendingEnabled === enabled) {
|
|
1174
1402
|
return;
|
|
1175
1403
|
}
|
|
1176
|
-
this.
|
|
1404
|
+
this._eventSendingEnabled = enabled;
|
|
1177
1405
|
if (enabled) {
|
|
1178
1406
|
this.logger.debug('Starting event processor');
|
|
1179
|
-
this.
|
|
1407
|
+
this._eventProcessor?.start();
|
|
1180
1408
|
}
|
|
1181
1409
|
else if (flush) {
|
|
1182
1410
|
this.logger?.debug('Flushing event processor before disabling.');
|
|
@@ -1184,92 +1412,70 @@ class LDClientImpl {
|
|
|
1184
1412
|
this.flush().then(() => {
|
|
1185
1413
|
// While waiting for the flush event sending could be re-enabled, in which case
|
|
1186
1414
|
// we do not want to close the event processor.
|
|
1187
|
-
if (!this.
|
|
1415
|
+
if (!this._eventSendingEnabled) {
|
|
1188
1416
|
this.logger?.debug('Stopping event processor.');
|
|
1189
|
-
this.
|
|
1417
|
+
this._eventProcessor?.close();
|
|
1190
1418
|
}
|
|
1191
1419
|
});
|
|
1192
1420
|
}
|
|
1193
1421
|
else {
|
|
1194
1422
|
// Just disabled.
|
|
1195
1423
|
this.logger?.debug('Stopping event processor.');
|
|
1196
|
-
this.
|
|
1424
|
+
this._eventProcessor?.close();
|
|
1197
1425
|
}
|
|
1198
1426
|
}
|
|
1199
1427
|
sendEvent(event) {
|
|
1200
|
-
this.
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
function isOk(status) {
|
|
1205
|
-
return status >= 200 && status <= 299;
|
|
1206
|
-
}
|
|
1207
|
-
class LDRequestError extends Error {
|
|
1208
|
-
constructor(message, status) {
|
|
1209
|
-
super(message);
|
|
1210
|
-
this.status = status;
|
|
1211
|
-
this.name = 'LaunchDarklyRequestError';
|
|
1428
|
+
this._eventProcessor?.sendEvent(event);
|
|
1212
1429
|
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
this.method = method;
|
|
1224
|
-
this.body = body;
|
|
1225
|
-
}
|
|
1226
|
-
async requestPayload() {
|
|
1227
|
-
let status;
|
|
1228
|
-
try {
|
|
1229
|
-
const res = await this.requests.fetch(this.uri, {
|
|
1230
|
-
method: this.method,
|
|
1231
|
-
headers: this.headers,
|
|
1232
|
-
body: this.body,
|
|
1233
|
-
});
|
|
1234
|
-
if (isOk(res.status)) {
|
|
1235
|
-
return await res.text();
|
|
1430
|
+
_handleInspectionChanged(flagKeys, type) {
|
|
1431
|
+
if (!this._inspectorManager.hasInspectors()) {
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
const details = {};
|
|
1435
|
+
flagKeys.forEach((flagKey) => {
|
|
1436
|
+
const item = this._flagManager.get(flagKey);
|
|
1437
|
+
if (item?.flag && !item.flag.deleted) {
|
|
1438
|
+
const { reason, value, variation } = item.flag;
|
|
1439
|
+
details[flagKey] = createSuccessEvaluationDetail(value, variation, reason);
|
|
1236
1440
|
}
|
|
1237
|
-
|
|
1238
|
-
|
|
1441
|
+
});
|
|
1442
|
+
if (type === 'init') {
|
|
1443
|
+
this._inspectorManager.onFlagsChanged(details);
|
|
1239
1444
|
}
|
|
1240
|
-
|
|
1241
|
-
|
|
1445
|
+
else if (type === 'patch') {
|
|
1446
|
+
Object.entries(details).forEach(([flagKey, detail]) => {
|
|
1447
|
+
this._inspectorManager.onFlagChanged(flagKey, detail);
|
|
1448
|
+
});
|
|
1242
1449
|
}
|
|
1243
|
-
throw new LDRequestError(`Unexpected status code: ${status}`, status);
|
|
1244
1450
|
}
|
|
1245
1451
|
}
|
|
1246
1452
|
|
|
1247
1453
|
class DataSourceEventHandler {
|
|
1248
|
-
constructor(
|
|
1249
|
-
this.
|
|
1250
|
-
this.
|
|
1251
|
-
this.
|
|
1454
|
+
constructor(_flagManager, _statusManager, _logger) {
|
|
1455
|
+
this._flagManager = _flagManager;
|
|
1456
|
+
this._statusManager = _statusManager;
|
|
1457
|
+
this._logger = _logger;
|
|
1252
1458
|
}
|
|
1253
1459
|
async handlePut(context, flags) {
|
|
1254
|
-
this.
|
|
1460
|
+
this._logger.debug(`Got PUT: ${Object.keys(flags)}`);
|
|
1255
1461
|
// mapping flags to item descriptors
|
|
1256
1462
|
const descriptors = Object.entries(flags).reduce((acc, [key, flag]) => {
|
|
1257
1463
|
acc[key] = { version: flag.version, flag };
|
|
1258
1464
|
return acc;
|
|
1259
1465
|
}, {});
|
|
1260
|
-
await this.
|
|
1261
|
-
this.
|
|
1466
|
+
await this._flagManager.init(context, descriptors);
|
|
1467
|
+
this._statusManager.requestStateUpdate(exports.DataSourceState.Valid);
|
|
1262
1468
|
}
|
|
1263
1469
|
async handlePatch(context, patchFlag) {
|
|
1264
|
-
this.
|
|
1265
|
-
this.
|
|
1470
|
+
this._logger.debug(`Got PATCH ${JSON.stringify(patchFlag, null, 2)}`);
|
|
1471
|
+
this._flagManager.upsert(context, patchFlag.key, {
|
|
1266
1472
|
version: patchFlag.version,
|
|
1267
1473
|
flag: patchFlag,
|
|
1268
1474
|
});
|
|
1269
1475
|
}
|
|
1270
1476
|
async handleDelete(context, deleteFlag) {
|
|
1271
|
-
this.
|
|
1272
|
-
this.
|
|
1477
|
+
this._logger.debug(`Got DELETE ${JSON.stringify(deleteFlag, null, 2)}`);
|
|
1478
|
+
this._flagManager.upsert(context, deleteFlag.key, {
|
|
1273
1479
|
version: deleteFlag.version,
|
|
1274
1480
|
flag: {
|
|
1275
1481
|
...deleteFlag,
|
|
@@ -1284,10 +1490,10 @@ class DataSourceEventHandler {
|
|
|
1284
1490
|
});
|
|
1285
1491
|
}
|
|
1286
1492
|
handleStreamingError(error) {
|
|
1287
|
-
this.
|
|
1493
|
+
this._statusManager.reportError(error.kind, error.message, error.code, error.recoverable);
|
|
1288
1494
|
}
|
|
1289
1495
|
handlePollingError(error) {
|
|
1290
|
-
this.
|
|
1496
|
+
this._statusManager.reportError(error.kind, error.message, error.status, error.recoverable);
|
|
1291
1497
|
}
|
|
1292
1498
|
}
|
|
1293
1499
|
|
|
@@ -1295,17 +1501,17 @@ class DataSourceEventHandler {
|
|
|
1295
1501
|
* Tracks the current data source status and emits updates when the status changes.
|
|
1296
1502
|
*/
|
|
1297
1503
|
class DataSourceStatusManager {
|
|
1298
|
-
constructor(
|
|
1299
|
-
this.
|
|
1300
|
-
this.
|
|
1301
|
-
this.
|
|
1302
|
-
this.
|
|
1504
|
+
constructor(_emitter, timeStamper = () => Date.now()) {
|
|
1505
|
+
this._emitter = _emitter;
|
|
1506
|
+
this._state = exports.DataSourceState.Closed;
|
|
1507
|
+
this._stateSinceMillis = timeStamper();
|
|
1508
|
+
this._timeStamper = timeStamper;
|
|
1303
1509
|
}
|
|
1304
1510
|
get status() {
|
|
1305
1511
|
return {
|
|
1306
|
-
state: this.
|
|
1307
|
-
stateSince: this.
|
|
1308
|
-
lastError: this.
|
|
1512
|
+
state: this._state,
|
|
1513
|
+
stateSince: this._stateSinceMillis,
|
|
1514
|
+
lastError: this._errorInfo,
|
|
1309
1515
|
};
|
|
1310
1516
|
}
|
|
1311
1517
|
/**
|
|
@@ -1314,17 +1520,17 @@ class DataSourceStatusManager {
|
|
|
1314
1520
|
* @param requestedState to track
|
|
1315
1521
|
* @param isError to indicate that the state update is a result of an error occurring.
|
|
1316
1522
|
*/
|
|
1317
|
-
|
|
1318
|
-
const newState = requestedState === exports.DataSourceState.Interrupted && this.
|
|
1523
|
+
_updateState(requestedState, isError = false) {
|
|
1524
|
+
const newState = requestedState === exports.DataSourceState.Interrupted && this._state === exports.DataSourceState.Initializing // don't go to interrupted from initializing (recoverable errors when initializing are not noteworthy)
|
|
1319
1525
|
? exports.DataSourceState.Initializing
|
|
1320
1526
|
: requestedState;
|
|
1321
|
-
const changedState = this.
|
|
1527
|
+
const changedState = this._state !== newState;
|
|
1322
1528
|
if (changedState) {
|
|
1323
|
-
this.
|
|
1324
|
-
this.
|
|
1529
|
+
this._state = newState;
|
|
1530
|
+
this._stateSinceMillis = this._timeStamper();
|
|
1325
1531
|
}
|
|
1326
1532
|
if (changedState || isError) {
|
|
1327
|
-
this.
|
|
1533
|
+
this._emitter.emit('dataSourceStatus', this.status);
|
|
1328
1534
|
}
|
|
1329
1535
|
}
|
|
1330
1536
|
/**
|
|
@@ -1333,7 +1539,7 @@ class DataSourceStatusManager {
|
|
|
1333
1539
|
* @param state that is requested
|
|
1334
1540
|
*/
|
|
1335
1541
|
requestStateUpdate(state) {
|
|
1336
|
-
this.
|
|
1542
|
+
this._updateState(state);
|
|
1337
1543
|
}
|
|
1338
1544
|
/**
|
|
1339
1545
|
* Reports a datasource error to this manager. Since the {@link DataSourceStatus} includes error
|
|
@@ -1350,10 +1556,10 @@ class DataSourceStatusManager {
|
|
|
1350
1556
|
kind,
|
|
1351
1557
|
message,
|
|
1352
1558
|
statusCode,
|
|
1353
|
-
time: this.
|
|
1559
|
+
time: this._timeStamper(),
|
|
1354
1560
|
};
|
|
1355
|
-
this.
|
|
1356
|
-
this.
|
|
1561
|
+
this._errorInfo = errorInfo;
|
|
1562
|
+
this._updateState(recoverable ? exports.DataSourceState.Interrupted : exports.DataSourceState.Closed, true);
|
|
1357
1563
|
}
|
|
1358
1564
|
}
|
|
1359
1565
|
|
|
@@ -1361,54 +1567,34 @@ class DataSourceStatusManager {
|
|
|
1361
1567
|
* @internal
|
|
1362
1568
|
*/
|
|
1363
1569
|
class PollingProcessor {
|
|
1364
|
-
constructor(
|
|
1365
|
-
this.
|
|
1366
|
-
this.
|
|
1367
|
-
this.
|
|
1368
|
-
this.
|
|
1369
|
-
this.
|
|
1370
|
-
this.
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
const parameters = [
|
|
1375
|
-
...(dataSourceConfig.queryParameters ?? []),
|
|
1376
|
-
];
|
|
1377
|
-
if (this.dataSourceConfig.withReasons) {
|
|
1378
|
-
parameters.push({ key: 'withReasons', value: 'true' });
|
|
1379
|
-
}
|
|
1380
|
-
const uri = jsSdkCommon.getPollingUri(dataSourceConfig.serviceEndpoints, path, parameters);
|
|
1381
|
-
this.pollInterval = dataSourceConfig.pollInterval;
|
|
1382
|
-
let method = 'GET';
|
|
1383
|
-
const headers = { ...dataSourceConfig.baseHeaders };
|
|
1384
|
-
let body;
|
|
1385
|
-
if (dataSourceConfig.useReport) {
|
|
1386
|
-
method = 'REPORT';
|
|
1387
|
-
headers['content-type'] = 'application/json';
|
|
1388
|
-
body = plainContextString; // context is in body for REPORT
|
|
1389
|
-
}
|
|
1390
|
-
this.requestor = new Requestor(requests, uri, headers, method, body);
|
|
1391
|
-
}
|
|
1392
|
-
async poll() {
|
|
1393
|
-
if (this.stopped) {
|
|
1570
|
+
constructor(_requestor, _pollIntervalSeconds, _dataHandler, _errorHandler, _logger) {
|
|
1571
|
+
this._requestor = _requestor;
|
|
1572
|
+
this._pollIntervalSeconds = _pollIntervalSeconds;
|
|
1573
|
+
this._dataHandler = _dataHandler;
|
|
1574
|
+
this._errorHandler = _errorHandler;
|
|
1575
|
+
this._logger = _logger;
|
|
1576
|
+
this._stopped = false;
|
|
1577
|
+
}
|
|
1578
|
+
async _poll() {
|
|
1579
|
+
if (this._stopped) {
|
|
1394
1580
|
return;
|
|
1395
1581
|
}
|
|
1396
1582
|
const reportJsonError = (data) => {
|
|
1397
|
-
this.
|
|
1398
|
-
this.
|
|
1399
|
-
this.
|
|
1583
|
+
this._logger?.error('Polling received invalid data');
|
|
1584
|
+
this._logger?.debug(`Invalid JSON follows: ${data}`);
|
|
1585
|
+
this._errorHandler?.(new jsSdkCommon.LDPollingError(jsSdkCommon.DataSourceErrorKind.InvalidData, 'Malformed JSON data in polling response'));
|
|
1400
1586
|
};
|
|
1401
|
-
this.
|
|
1587
|
+
this._logger?.debug('Polling LaunchDarkly for feature flag updates');
|
|
1402
1588
|
const startTime = Date.now();
|
|
1403
1589
|
try {
|
|
1404
|
-
const res = await this.
|
|
1590
|
+
const res = await this._requestor.requestPayload();
|
|
1405
1591
|
try {
|
|
1406
1592
|
const flags = JSON.parse(res);
|
|
1407
1593
|
try {
|
|
1408
|
-
this.
|
|
1594
|
+
this._dataHandler?.(flags);
|
|
1409
1595
|
}
|
|
1410
1596
|
catch (err) {
|
|
1411
|
-
this.
|
|
1597
|
+
this._logger?.error(`Exception from data handler: ${err}`);
|
|
1412
1598
|
}
|
|
1413
1599
|
}
|
|
1414
1600
|
catch {
|
|
@@ -1419,29 +1605,29 @@ class PollingProcessor {
|
|
|
1419
1605
|
const requestError = err;
|
|
1420
1606
|
if (requestError.status !== undefined) {
|
|
1421
1607
|
if (!jsSdkCommon.isHttpRecoverable(requestError.status)) {
|
|
1422
|
-
this.
|
|
1423
|
-
this.
|
|
1608
|
+
this._logger?.error(jsSdkCommon.httpErrorMessage(err, 'polling request'));
|
|
1609
|
+
this._errorHandler?.(new jsSdkCommon.LDPollingError(jsSdkCommon.DataSourceErrorKind.ErrorResponse, requestError.message, requestError.status));
|
|
1424
1610
|
return;
|
|
1425
1611
|
}
|
|
1426
1612
|
}
|
|
1427
|
-
this.
|
|
1613
|
+
this._logger?.error(jsSdkCommon.httpErrorMessage(err, 'polling request', 'will retry'));
|
|
1428
1614
|
}
|
|
1429
1615
|
const elapsed = Date.now() - startTime;
|
|
1430
|
-
const sleepFor = Math.max(this.
|
|
1431
|
-
this.
|
|
1432
|
-
this.
|
|
1433
|
-
this.
|
|
1616
|
+
const sleepFor = Math.max(this._pollIntervalSeconds * 1000 - elapsed, 0);
|
|
1617
|
+
this._logger?.debug('Elapsed: %d ms, sleeping for %d ms', elapsed, sleepFor);
|
|
1618
|
+
this._timeoutHandle = setTimeout(() => {
|
|
1619
|
+
this._poll();
|
|
1434
1620
|
}, sleepFor);
|
|
1435
1621
|
}
|
|
1436
1622
|
start() {
|
|
1437
|
-
this.
|
|
1623
|
+
this._poll();
|
|
1438
1624
|
}
|
|
1439
1625
|
stop() {
|
|
1440
|
-
if (this.
|
|
1441
|
-
clearTimeout(this.
|
|
1442
|
-
this.
|
|
1626
|
+
if (this._timeoutHandle) {
|
|
1627
|
+
clearTimeout(this._timeoutHandle);
|
|
1628
|
+
this._timeoutHandle = undefined;
|
|
1443
1629
|
}
|
|
1444
|
-
this.
|
|
1630
|
+
this._stopped = true;
|
|
1445
1631
|
}
|
|
1446
1632
|
close() {
|
|
1447
1633
|
this.stop();
|
|
@@ -1454,40 +1640,43 @@ const reportJsonError = (type, data, logger, errorHandler) => {
|
|
|
1454
1640
|
errorHandler?.(new jsSdkCommon.LDStreamingError(jsSdkCommon.DataSourceErrorKind.InvalidData, 'Malformed JSON data in event stream'));
|
|
1455
1641
|
};
|
|
1456
1642
|
class StreamingProcessor {
|
|
1457
|
-
constructor(
|
|
1458
|
-
this.
|
|
1459
|
-
this.
|
|
1460
|
-
this.
|
|
1461
|
-
this.
|
|
1462
|
-
this.
|
|
1463
|
-
this.
|
|
1464
|
-
this.
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1643
|
+
constructor(_plainContextString, _dataSourceConfig, _listeners, _requests, encoding, _pollingRequestor, _diagnosticsManager, _errorHandler, _logger) {
|
|
1644
|
+
this._plainContextString = _plainContextString;
|
|
1645
|
+
this._dataSourceConfig = _dataSourceConfig;
|
|
1646
|
+
this._listeners = _listeners;
|
|
1647
|
+
this._requests = _requests;
|
|
1648
|
+
this._pollingRequestor = _pollingRequestor;
|
|
1649
|
+
this._diagnosticsManager = _diagnosticsManager;
|
|
1650
|
+
this._errorHandler = _errorHandler;
|
|
1651
|
+
this._logger = _logger;
|
|
1652
|
+
let path;
|
|
1653
|
+
if (_dataSourceConfig.useReport && !_requests.getEventSourceCapabilities().customMethod) {
|
|
1654
|
+
path = _dataSourceConfig.paths.pathPing(encoding, _plainContextString);
|
|
1655
|
+
}
|
|
1656
|
+
else {
|
|
1657
|
+
path = _dataSourceConfig.useReport
|
|
1658
|
+
? _dataSourceConfig.paths.pathReport(encoding, _plainContextString)
|
|
1659
|
+
: _dataSourceConfig.paths.pathGet(encoding, _plainContextString);
|
|
1660
|
+
}
|
|
1472
1661
|
const parameters = [
|
|
1473
|
-
...(
|
|
1662
|
+
...(_dataSourceConfig.queryParameters ?? []),
|
|
1474
1663
|
];
|
|
1475
|
-
if (this.
|
|
1664
|
+
if (this._dataSourceConfig.withReasons) {
|
|
1476
1665
|
parameters.push({ key: 'withReasons', value: 'true' });
|
|
1477
1666
|
}
|
|
1478
|
-
this.
|
|
1479
|
-
this.
|
|
1480
|
-
this.
|
|
1481
|
-
this.
|
|
1667
|
+
this._requests = _requests;
|
|
1668
|
+
this._headers = { ..._dataSourceConfig.baseHeaders };
|
|
1669
|
+
this._logger = _logger;
|
|
1670
|
+
this._streamUri = jsSdkCommon.getStreamingUri(_dataSourceConfig.serviceEndpoints, path, parameters);
|
|
1482
1671
|
}
|
|
1483
|
-
|
|
1484
|
-
this.
|
|
1672
|
+
_logConnectionStarted() {
|
|
1673
|
+
this._connectionAttemptStartTime = Date.now();
|
|
1485
1674
|
}
|
|
1486
|
-
|
|
1487
|
-
if (this.
|
|
1488
|
-
this.
|
|
1675
|
+
_logConnectionResult(success) {
|
|
1676
|
+
if (this._connectionAttemptStartTime && this._diagnosticsManager) {
|
|
1677
|
+
this._diagnosticsManager.recordStreamInit(this._connectionAttemptStartTime, !success, Date.now() - this._connectionAttemptStartTime);
|
|
1489
1678
|
}
|
|
1490
|
-
this.
|
|
1679
|
+
this._connectionAttemptStartTime = undefined;
|
|
1491
1680
|
}
|
|
1492
1681
|
/**
|
|
1493
1682
|
* This is a wrapper around the passed errorHandler which adds additional
|
|
@@ -1498,75 +1687,101 @@ class StreamingProcessor {
|
|
|
1498
1687
|
*
|
|
1499
1688
|
* @private
|
|
1500
1689
|
*/
|
|
1501
|
-
|
|
1690
|
+
_retryAndHandleError(err) {
|
|
1502
1691
|
if (!jsSdkCommon.shouldRetry(err)) {
|
|
1503
|
-
this.
|
|
1504
|
-
this.
|
|
1505
|
-
this.
|
|
1692
|
+
this._logConnectionResult(false);
|
|
1693
|
+
this._errorHandler?.(new jsSdkCommon.LDStreamingError(jsSdkCommon.DataSourceErrorKind.ErrorResponse, err.message, err.status, false));
|
|
1694
|
+
this._logger?.error(jsSdkCommon.httpErrorMessage(err, 'streaming request'));
|
|
1506
1695
|
return false;
|
|
1507
1696
|
}
|
|
1508
|
-
this.
|
|
1509
|
-
this.
|
|
1510
|
-
this.
|
|
1697
|
+
this._logger?.warn(jsSdkCommon.httpErrorMessage(err, 'streaming request', 'will retry'));
|
|
1698
|
+
this._logConnectionResult(false);
|
|
1699
|
+
this._logConnectionStarted();
|
|
1511
1700
|
return true;
|
|
1512
1701
|
}
|
|
1513
1702
|
start() {
|
|
1514
|
-
this.
|
|
1703
|
+
this._logConnectionStarted();
|
|
1515
1704
|
let methodAndBodyOverrides;
|
|
1516
|
-
if (this.
|
|
1705
|
+
if (this._dataSourceConfig.useReport) {
|
|
1517
1706
|
// REPORT will include a body, so content type is required.
|
|
1518
|
-
this.
|
|
1707
|
+
this._headers['content-type'] = 'application/json';
|
|
1519
1708
|
// orverrides default method with REPORT and adds body.
|
|
1520
|
-
methodAndBodyOverrides = { method: 'REPORT', body: this.
|
|
1709
|
+
methodAndBodyOverrides = { method: 'REPORT', body: this._plainContextString };
|
|
1521
1710
|
}
|
|
1522
1711
|
else {
|
|
1523
1712
|
// no method or body override
|
|
1524
1713
|
methodAndBodyOverrides = {};
|
|
1525
1714
|
}
|
|
1526
1715
|
// TLS is handled by the platform implementation.
|
|
1527
|
-
const eventSource = this.
|
|
1528
|
-
headers: this.
|
|
1716
|
+
const eventSource = this._requests.createEventSource(this._streamUri, {
|
|
1717
|
+
headers: this._headers,
|
|
1529
1718
|
...methodAndBodyOverrides,
|
|
1530
|
-
errorFilter: (error) => this.
|
|
1531
|
-
initialRetryDelayMillis: this.
|
|
1719
|
+
errorFilter: (error) => this._retryAndHandleError(error),
|
|
1720
|
+
initialRetryDelayMillis: this._dataSourceConfig.initialRetryDelayMillis,
|
|
1532
1721
|
readTimeoutMillis: 5 * 60 * 1000,
|
|
1533
1722
|
retryResetIntervalMillis: 60 * 1000,
|
|
1534
1723
|
});
|
|
1535
|
-
this.
|
|
1724
|
+
this._eventSource = eventSource;
|
|
1536
1725
|
eventSource.onclose = () => {
|
|
1537
|
-
this.
|
|
1726
|
+
this._logger?.info('Closed LaunchDarkly stream connection');
|
|
1538
1727
|
};
|
|
1539
1728
|
eventSource.onerror = () => {
|
|
1540
1729
|
// The work is done by `errorFilter`.
|
|
1541
1730
|
};
|
|
1542
1731
|
eventSource.onopen = () => {
|
|
1543
|
-
this.
|
|
1732
|
+
this._logger?.info('Opened LaunchDarkly stream connection');
|
|
1544
1733
|
};
|
|
1545
1734
|
eventSource.onretrying = (e) => {
|
|
1546
|
-
this.
|
|
1735
|
+
this._logger?.info(`Will retry stream connection in ${e.delayMillis} milliseconds`);
|
|
1547
1736
|
};
|
|
1548
|
-
this.
|
|
1737
|
+
this._listeners.forEach(({ deserializeData, processJson }, eventName) => {
|
|
1549
1738
|
eventSource.addEventListener(eventName, (event) => {
|
|
1550
|
-
this.
|
|
1739
|
+
this._logger?.debug(`Received ${eventName} event`);
|
|
1551
1740
|
if (event?.data) {
|
|
1552
|
-
this.
|
|
1741
|
+
this._logConnectionResult(true);
|
|
1553
1742
|
const { data } = event;
|
|
1554
1743
|
const dataJson = deserializeData(data);
|
|
1555
1744
|
if (!dataJson) {
|
|
1556
|
-
reportJsonError(eventName, data, this.
|
|
1745
|
+
reportJsonError(eventName, data, this._logger, this._errorHandler);
|
|
1557
1746
|
return;
|
|
1558
1747
|
}
|
|
1559
1748
|
processJson(dataJson);
|
|
1560
1749
|
}
|
|
1561
1750
|
else {
|
|
1562
|
-
this.
|
|
1751
|
+
this._errorHandler?.(new jsSdkCommon.LDStreamingError(jsSdkCommon.DataSourceErrorKind.InvalidData, 'Unexpected payload from event stream'));
|
|
1563
1752
|
}
|
|
1564
1753
|
});
|
|
1565
1754
|
});
|
|
1755
|
+
// here we set up a listener that will poll when ping is received
|
|
1756
|
+
eventSource.addEventListener('ping', async () => {
|
|
1757
|
+
this._logger?.debug('Got PING, going to poll LaunchDarkly for feature flag updates');
|
|
1758
|
+
try {
|
|
1759
|
+
const res = await this._pollingRequestor.requestPayload();
|
|
1760
|
+
try {
|
|
1761
|
+
const payload = JSON.parse(res);
|
|
1762
|
+
try {
|
|
1763
|
+
// forward the payload on to the PUT listener
|
|
1764
|
+
this._listeners.get('put')?.processJson(payload);
|
|
1765
|
+
}
|
|
1766
|
+
catch (err) {
|
|
1767
|
+
this._logger?.error(`Exception from data handler: ${err}`);
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
catch {
|
|
1771
|
+
this._logger?.error('Polling after ping received invalid data');
|
|
1772
|
+
this._logger?.debug(`Invalid JSON follows: ${res}`);
|
|
1773
|
+
this._errorHandler?.(new jsSdkCommon.LDPollingError(jsSdkCommon.DataSourceErrorKind.InvalidData, 'Malformed JSON data in ping polling response'));
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
catch (err) {
|
|
1777
|
+
const requestError = err;
|
|
1778
|
+
this._errorHandler?.(new jsSdkCommon.LDPollingError(jsSdkCommon.DataSourceErrorKind.ErrorResponse, requestError.message, requestError.status));
|
|
1779
|
+
}
|
|
1780
|
+
});
|
|
1566
1781
|
}
|
|
1567
1782
|
stop() {
|
|
1568
|
-
this.
|
|
1569
|
-
this.
|
|
1783
|
+
this._eventSource?.close();
|
|
1784
|
+
this._eventSource = undefined;
|
|
1570
1785
|
}
|
|
1571
1786
|
close() {
|
|
1572
1787
|
this.stop();
|
|
@@ -1586,35 +1801,26 @@ class BaseDataManager {
|
|
|
1586
1801
|
this.diagnosticsManager = diagnosticsManager;
|
|
1587
1802
|
this.logger = config.logger;
|
|
1588
1803
|
this.dataSourceStatusManager = new DataSourceStatusManager(emitter);
|
|
1589
|
-
this.
|
|
1804
|
+
this._dataSourceEventHandler = new DataSourceEventHandler(flagManager, this.dataSourceStatusManager, this.config.logger);
|
|
1590
1805
|
}
|
|
1591
1806
|
/**
|
|
1592
1807
|
* Set additional connection parameters for requests polling/streaming.
|
|
1593
1808
|
*/
|
|
1594
1809
|
setConnectionParams(connectionParams) {
|
|
1595
|
-
this.
|
|
1810
|
+
this._connectionParams = connectionParams;
|
|
1596
1811
|
}
|
|
1597
|
-
createPollingProcessor(context, checkedContext, identifyResolve, identifyReject) {
|
|
1598
|
-
const processor = new PollingProcessor(
|
|
1599
|
-
|
|
1600
|
-
serviceEndpoints: this.config.serviceEndpoints,
|
|
1601
|
-
paths: this.getPollingPaths(),
|
|
1602
|
-
baseHeaders: this.baseHeaders,
|
|
1603
|
-
pollInterval: this.config.pollInterval,
|
|
1604
|
-
withReasons: this.config.withReasons,
|
|
1605
|
-
useReport: this.config.useReport,
|
|
1606
|
-
queryParameters: this.connectionParams?.queryParameters,
|
|
1607
|
-
}, this.platform.requests, this.platform.encoding, async (flags) => {
|
|
1608
|
-
await this.dataSourceEventHandler.handlePut(checkedContext, flags);
|
|
1812
|
+
createPollingProcessor(context, checkedContext, requestor, identifyResolve, identifyReject) {
|
|
1813
|
+
const processor = new PollingProcessor(requestor, this.config.pollInterval, async (flags) => {
|
|
1814
|
+
await this._dataSourceEventHandler.handlePut(checkedContext, flags);
|
|
1609
1815
|
identifyResolve?.();
|
|
1610
1816
|
}, (err) => {
|
|
1611
1817
|
this.emitter.emit('error', context, err);
|
|
1612
|
-
this.
|
|
1818
|
+
this._dataSourceEventHandler.handlePollingError(err);
|
|
1613
1819
|
identifyReject?.(err);
|
|
1614
|
-
});
|
|
1615
|
-
this.updateProcessor = this.
|
|
1820
|
+
}, this.logger);
|
|
1821
|
+
this.updateProcessor = this._decorateProcessorWithStatusReporting(processor, this.dataSourceStatusManager);
|
|
1616
1822
|
}
|
|
1617
|
-
createStreamingProcessor(context, checkedContext, identifyResolve, identifyReject) {
|
|
1823
|
+
createStreamingProcessor(context, checkedContext, pollingRequestor, identifyResolve, identifyReject) {
|
|
1618
1824
|
const processor = new StreamingProcessor(JSON.stringify(context), {
|
|
1619
1825
|
credential: this.credential,
|
|
1620
1826
|
serviceEndpoints: this.config.serviceEndpoints,
|
|
@@ -1623,38 +1829,38 @@ class BaseDataManager {
|
|
|
1623
1829
|
initialRetryDelayMillis: this.config.streamInitialReconnectDelay * 1000,
|
|
1624
1830
|
withReasons: this.config.withReasons,
|
|
1625
1831
|
useReport: this.config.useReport,
|
|
1626
|
-
queryParameters: this.
|
|
1627
|
-
}, this.createStreamListeners(checkedContext, identifyResolve), this.platform.requests, this.platform.encoding, this.diagnosticsManager, (e) => {
|
|
1832
|
+
queryParameters: this._connectionParams?.queryParameters,
|
|
1833
|
+
}, this.createStreamListeners(checkedContext, identifyResolve), this.platform.requests, this.platform.encoding, pollingRequestor, this.diagnosticsManager, (e) => {
|
|
1628
1834
|
this.emitter.emit('error', context, e);
|
|
1629
|
-
this.
|
|
1835
|
+
this._dataSourceEventHandler.handleStreamingError(e);
|
|
1630
1836
|
identifyReject?.(e);
|
|
1631
|
-
});
|
|
1632
|
-
this.updateProcessor = this.
|
|
1837
|
+
}, this.logger);
|
|
1838
|
+
this.updateProcessor = this._decorateProcessorWithStatusReporting(processor, this.dataSourceStatusManager);
|
|
1633
1839
|
}
|
|
1634
1840
|
createStreamListeners(context, identifyResolve) {
|
|
1635
1841
|
const listeners = new Map();
|
|
1636
1842
|
listeners.set('put', {
|
|
1637
1843
|
deserializeData: JSON.parse,
|
|
1638
1844
|
processJson: async (flags) => {
|
|
1639
|
-
await this.
|
|
1845
|
+
await this._dataSourceEventHandler.handlePut(context, flags);
|
|
1640
1846
|
identifyResolve?.();
|
|
1641
1847
|
},
|
|
1642
1848
|
});
|
|
1643
1849
|
listeners.set('patch', {
|
|
1644
1850
|
deserializeData: JSON.parse,
|
|
1645
1851
|
processJson: async (patchFlag) => {
|
|
1646
|
-
this.
|
|
1852
|
+
this._dataSourceEventHandler.handlePatch(context, patchFlag);
|
|
1647
1853
|
},
|
|
1648
1854
|
});
|
|
1649
1855
|
listeners.set('delete', {
|
|
1650
1856
|
deserializeData: JSON.parse,
|
|
1651
1857
|
processJson: async (deleteFlag) => {
|
|
1652
|
-
this.
|
|
1858
|
+
this._dataSourceEventHandler.handleDelete(context, deleteFlag);
|
|
1653
1859
|
},
|
|
1654
1860
|
});
|
|
1655
1861
|
return listeners;
|
|
1656
1862
|
}
|
|
1657
|
-
|
|
1863
|
+
_decorateProcessorWithStatusReporting(processor, statusManager) {
|
|
1658
1864
|
return {
|
|
1659
1865
|
start: () => {
|
|
1660
1866
|
// update status before starting processor to ensure potential errors are reported after initializing
|
|
@@ -1677,6 +1883,7 @@ exports.platform = jsSdkCommon__namespace;
|
|
|
1677
1883
|
exports.BaseDataManager = BaseDataManager;
|
|
1678
1884
|
exports.LDClientImpl = LDClientImpl;
|
|
1679
1885
|
exports.Requestor = Requestor;
|
|
1886
|
+
exports.makeRequestor = makeRequestor;
|
|
1680
1887
|
Object.keys(jsSdkCommon).forEach(function (k) {
|
|
1681
1888
|
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
1682
1889
|
enumerable: true,
|