launchdarkly-js-sdk-common 4.0.0 → 4.0.1
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/.circleci/config.yml +22 -0
- package/.eslintignore +4 -0
- package/.eslintrc.yaml +104 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/.github/pull_request_template.md +21 -0
- package/.ldrelease/config.yml +24 -0
- package/.prettierignore +1 -0
- package/.prettierrc +5 -0
- package/CHANGELOG.md +4 -0
- package/CONTRIBUTING.md +45 -0
- package/babel.config.js +18 -0
- package/docs/typedoc.js +11 -0
- package/jest.config.js +15 -0
- package/package.json +3 -29
- package/scripts/better-audit.sh +76 -0
- package/src/EventEmitter.js +60 -0
- package/src/EventProcessor.js +175 -0
- package/src/EventSender.js +87 -0
- package/src/EventSummarizer.js +84 -0
- package/src/Identity.js +26 -0
- package/src/InitializationState.js +83 -0
- package/src/PersistentFlagStore.js +50 -0
- package/src/PersistentStorage.js +81 -0
- package/src/Requestor.js +111 -0
- package/src/Stream.js +154 -0
- package/src/UserFilter.js +75 -0
- package/src/UserValidator.js +56 -0
- package/src/__tests__/.eslintrc.yaml +7 -0
- package/src/__tests__/EventProcessor-test.js +559 -0
- package/src/__tests__/EventSender-test.js +252 -0
- package/src/__tests__/EventSource-mock.js +61 -0
- package/src/__tests__/EventSummarizer-test.js +103 -0
- package/src/__tests__/LDClient-events-test.js +757 -0
- package/src/__tests__/LDClient-localstorage-test.js +179 -0
- package/src/__tests__/LDClient-streaming-test.js +683 -0
- package/src/__tests__/LDClient-test.js +761 -0
- package/src/__tests__/PersistentFlagStore-test.js +111 -0
- package/src/__tests__/Requestor-test.js +362 -0
- package/src/__tests__/Stream-test.js +299 -0
- package/src/__tests__/UserFilter-test.js +93 -0
- package/src/__tests__/UserValidator-test.js +57 -0
- package/src/__tests__/configuration-test.js +217 -0
- package/src/__tests__/diagnosticEvents-test.js +449 -0
- package/src/__tests__/loggers-test.js +149 -0
- package/src/__tests__/mockHttp.js +122 -0
- package/src/__tests__/promiseCoalescer-test.js +128 -0
- package/src/__tests__/stubPlatform.js +148 -0
- package/src/__tests__/testUtils.js +77 -0
- package/src/__tests__/utils-test.js +148 -0
- package/src/configuration.js +151 -0
- package/src/diagnosticEvents.js +269 -0
- package/src/errors.js +37 -0
- package/src/index.js +772 -0
- package/src/jest.setup.js +1 -0
- package/src/loggers.js +93 -0
- package/src/messages.js +217 -0
- package/src/promiseCoalescer.js +52 -0
- package/src/utils.js +214 -0
- package/test-types.ts +96 -0
- package/tsconfig.json +13 -0
- package/dist/ldclient-common.cjs.js +0 -2
- package/dist/ldclient-common.cjs.js.map +0 -1
- package/dist/ldclient-common.es.js +0 -2
- package/dist/ldclient-common.es.js.map +0 -1
- package/dist/ldclient-common.min.js +0 -2
- package/dist/ldclient-common.min.js.map +0 -1
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
const errors = require('./errors');
|
|
2
|
+
const { validateLogger } = require('./loggers');
|
|
3
|
+
const messages = require('./messages');
|
|
4
|
+
const utils = require('./utils');
|
|
5
|
+
|
|
6
|
+
// baseOptionDefs should contain an entry for each supported configuration option in the common package.
|
|
7
|
+
// Each entry can have three properties:
|
|
8
|
+
// - "default": the default value if any
|
|
9
|
+
// - "type": a type constraint used if the type can't be inferred from the default value). The allowable
|
|
10
|
+
// values are "boolean", "string", "number", "array", "object", "function", or several of these OR'd
|
|
11
|
+
// together with "|" ("function|object").
|
|
12
|
+
// - "minimum": minimum value if any for numeric properties
|
|
13
|
+
//
|
|
14
|
+
// The extraOptionDefs parameter to validate() uses the same format.
|
|
15
|
+
const baseOptionDefs = {
|
|
16
|
+
baseUrl: { default: 'https://app.launchdarkly.com' },
|
|
17
|
+
streamUrl: { default: 'https://clientstream.launchdarkly.com' },
|
|
18
|
+
eventsUrl: { default: 'https://events.launchdarkly.com' },
|
|
19
|
+
sendEvents: { default: true },
|
|
20
|
+
streaming: { type: 'boolean' }, // default for this is undefined, which is different from false
|
|
21
|
+
sendLDHeaders: { default: true },
|
|
22
|
+
requestHeaderTransform: { type: 'function' },
|
|
23
|
+
inlineUsersInEvents: { default: false },
|
|
24
|
+
allowFrequentDuplicateEvents: { default: false },
|
|
25
|
+
sendEventsOnlyForVariation: { default: false },
|
|
26
|
+
useReport: { default: false },
|
|
27
|
+
evaluationReasons: { default: false },
|
|
28
|
+
eventCapacity: { default: 100, minimum: 1 },
|
|
29
|
+
flushInterval: { default: 2000, minimum: 2000 },
|
|
30
|
+
samplingInterval: { default: 0, minimum: 0 },
|
|
31
|
+
streamReconnectDelay: { default: 1000, minimum: 0 },
|
|
32
|
+
allAttributesPrivate: { default: false },
|
|
33
|
+
privateAttributeNames: { default: [] },
|
|
34
|
+
bootstrap: { type: 'string|object' },
|
|
35
|
+
diagnosticRecordingInterval: { default: 900000, minimum: 2000 },
|
|
36
|
+
diagnosticOptOut: { default: false },
|
|
37
|
+
wrapperName: { type: 'string' },
|
|
38
|
+
wrapperVersion: { type: 'string' },
|
|
39
|
+
stateProvider: { type: 'object' }, // not a public option, used internally
|
|
40
|
+
autoAliasingOptOut: { default: false },
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function validate(options, emitter, extraOptionDefs, logger) {
|
|
44
|
+
const optionDefs = utils.extend({ logger: { default: logger } }, baseOptionDefs, extraOptionDefs);
|
|
45
|
+
|
|
46
|
+
const deprecatedOptions = {
|
|
47
|
+
// As of the latest major version, there are no deprecated options. Next time we deprecate
|
|
48
|
+
// something, add an item here where the property name is the deprecated name, and the
|
|
49
|
+
// property value is the preferred name if any, or null/undefined if there is no replacement.
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function checkDeprecatedOptions(config) {
|
|
53
|
+
const opts = config;
|
|
54
|
+
Object.keys(deprecatedOptions).forEach(oldName => {
|
|
55
|
+
if (opts[oldName] !== undefined) {
|
|
56
|
+
const newName = deprecatedOptions[oldName];
|
|
57
|
+
logger && logger.warn(messages.deprecated(oldName, newName));
|
|
58
|
+
if (newName) {
|
|
59
|
+
if (opts[newName] === undefined) {
|
|
60
|
+
opts[newName] = opts[oldName];
|
|
61
|
+
}
|
|
62
|
+
delete opts[oldName];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function applyDefaults(config) {
|
|
69
|
+
// This works differently from utils.extend() in that it *will not* override a default value
|
|
70
|
+
// if the provided value is explicitly set to null. This provides backward compatibility
|
|
71
|
+
// since in the past we only used the provided values if they were truthy.
|
|
72
|
+
const ret = utils.extend({}, config);
|
|
73
|
+
Object.keys(optionDefs).forEach(name => {
|
|
74
|
+
if (ret[name] === undefined || ret[name] === null) {
|
|
75
|
+
ret[name] = optionDefs[name] && optionDefs[name].default;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
return ret;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function validateTypesAndNames(config) {
|
|
82
|
+
const ret = utils.extend({}, config);
|
|
83
|
+
const typeDescForValue = value => {
|
|
84
|
+
if (value === null) {
|
|
85
|
+
return 'any';
|
|
86
|
+
}
|
|
87
|
+
if (value === undefined) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
if (Array.isArray(value)) {
|
|
91
|
+
return 'array';
|
|
92
|
+
}
|
|
93
|
+
const t = typeof value;
|
|
94
|
+
if (t === 'boolean' || t === 'string' || t === 'number' || t === 'function') {
|
|
95
|
+
return t;
|
|
96
|
+
}
|
|
97
|
+
return 'object';
|
|
98
|
+
};
|
|
99
|
+
Object.keys(config).forEach(name => {
|
|
100
|
+
const value = config[name];
|
|
101
|
+
if (value !== null && value !== undefined) {
|
|
102
|
+
const optionDef = optionDefs[name];
|
|
103
|
+
if (optionDef === undefined) {
|
|
104
|
+
reportArgumentError(messages.unknownOption(name));
|
|
105
|
+
} else {
|
|
106
|
+
const expectedType = optionDef.type || typeDescForValue(optionDef.default);
|
|
107
|
+
if (expectedType !== 'any') {
|
|
108
|
+
const allowedTypes = expectedType.split('|');
|
|
109
|
+
const actualType = typeDescForValue(value);
|
|
110
|
+
if (allowedTypes.indexOf(actualType) < 0) {
|
|
111
|
+
if (expectedType === 'boolean') {
|
|
112
|
+
ret[name] = !!value;
|
|
113
|
+
reportArgumentError(messages.wrongOptionTypeBoolean(name, actualType));
|
|
114
|
+
} else {
|
|
115
|
+
reportArgumentError(messages.wrongOptionType(name, expectedType, actualType));
|
|
116
|
+
ret[name] = optionDef.default;
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
if (actualType === 'number' && optionDef.minimum !== undefined && value < optionDef.minimum) {
|
|
120
|
+
reportArgumentError(messages.optionBelowMinimum(name, value, optionDef.minimum));
|
|
121
|
+
ret[name] = optionDef.minimum;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
return ret;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function reportArgumentError(message) {
|
|
132
|
+
utils.onNextTick(() => {
|
|
133
|
+
emitter && emitter.maybeReportError(new errors.LDInvalidArgumentError(message));
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let config = utils.extend({}, options || {});
|
|
138
|
+
|
|
139
|
+
checkDeprecatedOptions(config);
|
|
140
|
+
|
|
141
|
+
config = applyDefaults(config);
|
|
142
|
+
config = validateTypesAndNames(config);
|
|
143
|
+
validateLogger(config.logger);
|
|
144
|
+
|
|
145
|
+
return config;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = {
|
|
149
|
+
baseOptionDefs,
|
|
150
|
+
validate,
|
|
151
|
+
};
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
const { v1: uuidv1 } = require('uuid');
|
|
2
|
+
// Note that in the diagnostic events spec, these IDs are to be generated with UUID v4. However,
|
|
3
|
+
// in JS we were already using v1 for unique user keys, so to avoid bringing in two packages we
|
|
4
|
+
// will use v1 here as well.
|
|
5
|
+
|
|
6
|
+
const { baseOptionDefs } = require('./configuration');
|
|
7
|
+
const messages = require('./messages');
|
|
8
|
+
|
|
9
|
+
function DiagnosticId(sdkKey) {
|
|
10
|
+
const ret = {
|
|
11
|
+
diagnosticId: uuidv1(),
|
|
12
|
+
};
|
|
13
|
+
if (sdkKey) {
|
|
14
|
+
ret.sdkKeySuffix = sdkKey.length > 6 ? sdkKey.substring(sdkKey.length - 6) : sdkKey;
|
|
15
|
+
}
|
|
16
|
+
return ret;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// A stateful object holding statistics that will go into diagnostic events.
|
|
20
|
+
|
|
21
|
+
function DiagnosticsAccumulator(startTime) {
|
|
22
|
+
let dataSinceDate, droppedEvents, eventsInLastBatch, streamInits;
|
|
23
|
+
|
|
24
|
+
function reset(time) {
|
|
25
|
+
dataSinceDate = time;
|
|
26
|
+
droppedEvents = 0;
|
|
27
|
+
eventsInLastBatch = 0;
|
|
28
|
+
streamInits = [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
reset(startTime);
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
getProps: () => ({
|
|
35
|
+
dataSinceDate,
|
|
36
|
+
droppedEvents,
|
|
37
|
+
eventsInLastBatch,
|
|
38
|
+
streamInits,
|
|
39
|
+
// omit deduplicatedUsers for the JS SDKs because they don't deduplicate users
|
|
40
|
+
}),
|
|
41
|
+
setProps: props => {
|
|
42
|
+
dataSinceDate = props.dataSinceDate;
|
|
43
|
+
droppedEvents = props.droppedEvents || 0;
|
|
44
|
+
eventsInLastBatch = props.eventsInLastBatch || 0;
|
|
45
|
+
streamInits = props.streamInits || [];
|
|
46
|
+
},
|
|
47
|
+
incrementDroppedEvents: () => {
|
|
48
|
+
droppedEvents++;
|
|
49
|
+
},
|
|
50
|
+
setEventsInLastBatch: n => {
|
|
51
|
+
eventsInLastBatch = n;
|
|
52
|
+
},
|
|
53
|
+
recordStreamInit: (timestamp, failed, durationMillis) => {
|
|
54
|
+
const info = { timestamp, failed, durationMillis };
|
|
55
|
+
streamInits.push(info);
|
|
56
|
+
},
|
|
57
|
+
reset,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// An object that maintains information that will go into diagnostic events, and knows how to format
|
|
62
|
+
// those events. It is instantiated by the SDK client, and shared with the event processor.
|
|
63
|
+
//
|
|
64
|
+
// The JS-based SDKs have two modes for diagnostic events. By default, the behavior is basically the
|
|
65
|
+
// same as the server-side SDKs: a "diagnostic-init" event is sent on startup, and then "diagnostic"
|
|
66
|
+
// events with operating statistics are sent periodically. However, in a browser environment this is
|
|
67
|
+
// undesirable because the page may be reloaded frequently. In that case, setting the property
|
|
68
|
+
// "platform.diagnosticUseCombinedEvent" to true enables an alternate mode in which a combination of
|
|
69
|
+
// both kinds of event is sent at intervals, relative to the last time this was done (if any) which
|
|
70
|
+
// is cached in local storage.
|
|
71
|
+
|
|
72
|
+
function DiagnosticsManager(
|
|
73
|
+
platform,
|
|
74
|
+
persistentStorage,
|
|
75
|
+
accumulator,
|
|
76
|
+
eventSender,
|
|
77
|
+
environmentId,
|
|
78
|
+
config,
|
|
79
|
+
diagnosticId
|
|
80
|
+
) {
|
|
81
|
+
const combinedMode = !!platform.diagnosticUseCombinedEvent;
|
|
82
|
+
const localStorageKey = 'ld:' + environmentId + ':$diagnostics';
|
|
83
|
+
const diagnosticEventsUrl = config.eventsUrl + '/events/diagnostic/' + environmentId;
|
|
84
|
+
const periodicInterval = config.diagnosticRecordingInterval;
|
|
85
|
+
const acc = accumulator;
|
|
86
|
+
const initialEventSamplingInterval = 4; // used only in combined mode - see start()
|
|
87
|
+
let streamingEnabled = !!config.streaming;
|
|
88
|
+
let eventSentTime;
|
|
89
|
+
let periodicTimer;
|
|
90
|
+
const manager = {};
|
|
91
|
+
|
|
92
|
+
function makeInitProperties() {
|
|
93
|
+
return {
|
|
94
|
+
sdk: makeSdkData(),
|
|
95
|
+
configuration: makeConfigData(),
|
|
96
|
+
platform: platform.diagnosticPlatformData,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Send a diagnostic event and do not wait for completion.
|
|
101
|
+
function sendDiagnosticEvent(event) {
|
|
102
|
+
config.logger && config.logger.debug(messages.debugPostingDiagnosticEvent(event));
|
|
103
|
+
eventSender
|
|
104
|
+
.sendEvents(event, diagnosticEventsUrl, true)
|
|
105
|
+
.then(() => undefined)
|
|
106
|
+
.catch(() => undefined);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function loadProperties(callback) {
|
|
110
|
+
if (!persistentStorage.isEnabled()) {
|
|
111
|
+
return callback(false); // false indicates that local storage is not available
|
|
112
|
+
}
|
|
113
|
+
persistentStorage
|
|
114
|
+
.get(localStorageKey)
|
|
115
|
+
.then(data => {
|
|
116
|
+
if (data) {
|
|
117
|
+
try {
|
|
118
|
+
const props = JSON.parse(data);
|
|
119
|
+
acc.setProps(props);
|
|
120
|
+
eventSentTime = props.dataSinceDate;
|
|
121
|
+
} catch (e) {
|
|
122
|
+
// disregard malformed cached data
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
callback(true);
|
|
126
|
+
})
|
|
127
|
+
.catch(() => {
|
|
128
|
+
callback(false);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function saveProperties() {
|
|
133
|
+
if (persistentStorage.isEnabled()) {
|
|
134
|
+
const props = { ...acc.getProps() };
|
|
135
|
+
persistentStorage.set(localStorageKey, JSON.stringify(props));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Creates the initial event that is sent by the event processor when the SDK starts up. This will not
|
|
140
|
+
// be repeated during the lifetime of the SDK client. In combined mode, we don't send this.
|
|
141
|
+
function createInitEvent() {
|
|
142
|
+
return {
|
|
143
|
+
kind: 'diagnostic-init',
|
|
144
|
+
id: diagnosticId,
|
|
145
|
+
creationDate: acc.getProps().dataSinceDate,
|
|
146
|
+
...makeInitProperties(),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Creates a periodic event containing time-dependent stats, and resets the state of the manager with
|
|
151
|
+
// regard to those stats. In combined mode (browser SDK) this also contains the configuration data.
|
|
152
|
+
function createPeriodicEventAndReset() {
|
|
153
|
+
const currentTime = new Date().getTime();
|
|
154
|
+
let ret = {
|
|
155
|
+
kind: combinedMode ? 'diagnostic-combined' : 'diagnostic',
|
|
156
|
+
id: diagnosticId,
|
|
157
|
+
creationDate: currentTime,
|
|
158
|
+
...acc.getProps(),
|
|
159
|
+
};
|
|
160
|
+
if (combinedMode) {
|
|
161
|
+
ret = { ...ret, ...makeInitProperties() };
|
|
162
|
+
}
|
|
163
|
+
acc.reset(currentTime);
|
|
164
|
+
return ret;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function sendPeriodicEvent() {
|
|
168
|
+
sendDiagnosticEvent(createPeriodicEventAndReset());
|
|
169
|
+
periodicTimer = setTimeout(sendPeriodicEvent, periodicInterval);
|
|
170
|
+
eventSentTime = new Date().getTime();
|
|
171
|
+
if (combinedMode) {
|
|
172
|
+
saveProperties();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function makeSdkData() {
|
|
177
|
+
const sdkData = { ...platform.diagnosticSdkData };
|
|
178
|
+
if (config.wrapperName) {
|
|
179
|
+
sdkData.wrapperName = config.wrapperName;
|
|
180
|
+
}
|
|
181
|
+
if (config.wrapperVersion) {
|
|
182
|
+
sdkData.wrapperVersion = config.wrapperVersion;
|
|
183
|
+
}
|
|
184
|
+
return sdkData;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function makeConfigData() {
|
|
188
|
+
const configData = {
|
|
189
|
+
customBaseURI: config.baseUrl !== baseOptionDefs.baseUrl.default,
|
|
190
|
+
customStreamURI: config.streamUrl !== baseOptionDefs.streamUrl.default,
|
|
191
|
+
customEventsURI: config.eventsUrl !== baseOptionDefs.eventsUrl.default,
|
|
192
|
+
eventsCapacity: config.eventCapacity,
|
|
193
|
+
eventsFlushIntervalMillis: config.flushInterval,
|
|
194
|
+
reconnectTimeMillis: config.streamReconnectDelay,
|
|
195
|
+
streamingDisabled: !streamingEnabled,
|
|
196
|
+
allAttributesPrivate: !!config.allAttributesPrivate,
|
|
197
|
+
inlineUsersInEvents: !!config.inlineUsersInEvents,
|
|
198
|
+
diagnosticRecordingIntervalMillis: config.diagnosticRecordingInterval,
|
|
199
|
+
// The following extra properties are only provided by client-side JS SDKs:
|
|
200
|
+
usingSecureMode: !!config.hash,
|
|
201
|
+
bootstrapMode: !!config.bootstrap,
|
|
202
|
+
fetchGoalsDisabled: !config.fetchGoals,
|
|
203
|
+
allowFrequentDuplicateEvents: !!config.allowFrequentDuplicateEvents,
|
|
204
|
+
sendEventsOnlyForVariation: !!config.sendEventsOnlyForVariation,
|
|
205
|
+
autoAliasingOptOut: !!config.autoAliasingOptOut,
|
|
206
|
+
};
|
|
207
|
+
// Client-side JS SDKs do not have the following properties which other SDKs have:
|
|
208
|
+
// connectTimeoutMillis
|
|
209
|
+
// pollingIntervalMillis
|
|
210
|
+
// samplingInterval
|
|
211
|
+
// socketTimeoutMillis
|
|
212
|
+
// startWaitMillis
|
|
213
|
+
// userKeysCapacity
|
|
214
|
+
// userKeysFlushIntervalMillis
|
|
215
|
+
// usingProxy
|
|
216
|
+
// usingProxyAuthenticator
|
|
217
|
+
// usingRelayDaemon
|
|
218
|
+
|
|
219
|
+
return configData;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Called when the SDK is starting up. Either send an init event immediately, or, in the alternate
|
|
223
|
+
// mode, check for cached local storage properties and send an event only if we haven't done so
|
|
224
|
+
// recently.
|
|
225
|
+
manager.start = () => {
|
|
226
|
+
if (combinedMode) {
|
|
227
|
+
loadProperties(localStorageAvailable => {
|
|
228
|
+
if (localStorageAvailable) {
|
|
229
|
+
const nextEventTime = (eventSentTime || 0) + periodicInterval;
|
|
230
|
+
const timeNow = new Date().getTime();
|
|
231
|
+
if (timeNow >= nextEventTime) {
|
|
232
|
+
sendPeriodicEvent();
|
|
233
|
+
} else {
|
|
234
|
+
periodicTimer = setTimeout(sendPeriodicEvent, nextEventTime - timeNow);
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
// We don't have the ability to cache anything in local storage, so we don't know if we
|
|
238
|
+
// recently sent an event before this page load, but we would still prefer not to send one
|
|
239
|
+
// on *every* page load. So, as a rough heuristic, we'll decide semi-randomly.
|
|
240
|
+
if (Math.floor(Math.random() * initialEventSamplingInterval) === 0) {
|
|
241
|
+
sendPeriodicEvent();
|
|
242
|
+
} else {
|
|
243
|
+
periodicTimer = setTimeout(sendPeriodicEvent, periodicInterval);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
} else {
|
|
248
|
+
sendDiagnosticEvent(createInitEvent());
|
|
249
|
+
periodicTimer = setTimeout(sendPeriodicEvent, periodicInterval);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
manager.stop = () => {
|
|
254
|
+
periodicTimer && clearTimeout(periodicTimer);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// Called when streaming mode is turned on or off dynamically.
|
|
258
|
+
manager.setStreaming = enabled => {
|
|
259
|
+
streamingEnabled = enabled;
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
return manager;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
module.exports = {
|
|
266
|
+
DiagnosticId,
|
|
267
|
+
DiagnosticsAccumulator,
|
|
268
|
+
DiagnosticsManager,
|
|
269
|
+
};
|
package/src/errors.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
function createCustomError(name) {
|
|
2
|
+
function CustomError(message, code) {
|
|
3
|
+
Error.captureStackTrace && Error.captureStackTrace(this, this.constructor);
|
|
4
|
+
this.message = message;
|
|
5
|
+
this.code = code;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
CustomError.prototype = new Error();
|
|
9
|
+
CustomError.prototype.name = name;
|
|
10
|
+
CustomError.prototype.constructor = CustomError;
|
|
11
|
+
|
|
12
|
+
return CustomError;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const LDUnexpectedResponseError = createCustomError('LaunchDarklyUnexpectedResponseError');
|
|
16
|
+
const LDInvalidEnvironmentIdError = createCustomError('LaunchDarklyInvalidEnvironmentIdError');
|
|
17
|
+
const LDInvalidUserError = createCustomError('LaunchDarklyInvalidUserError');
|
|
18
|
+
const LDInvalidEventKeyError = createCustomError('LaunchDarklyInvalidEventKeyError');
|
|
19
|
+
const LDInvalidArgumentError = createCustomError('LaunchDarklyInvalidArgumentError');
|
|
20
|
+
const LDFlagFetchError = createCustomError('LaunchDarklyFlagFetchError');
|
|
21
|
+
|
|
22
|
+
function isHttpErrorRecoverable(status) {
|
|
23
|
+
if (status >= 400 && status < 500) {
|
|
24
|
+
return status === 400 || status === 408 || status === 429;
|
|
25
|
+
}
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
LDUnexpectedResponseError,
|
|
31
|
+
LDInvalidEnvironmentIdError,
|
|
32
|
+
LDInvalidUserError,
|
|
33
|
+
LDInvalidEventKeyError,
|
|
34
|
+
LDInvalidArgumentError,
|
|
35
|
+
LDFlagFetchError,
|
|
36
|
+
isHttpErrorRecoverable,
|
|
37
|
+
};
|