launchdarkly-js-sdk-common 4.1.1 → 4.3.0
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/CHANGELOG.md +14 -0
- package/package.json +1 -1
- package/src/InspectorManager.js +120 -0
- package/src/SafeInspector.js +34 -0
- package/src/__tests__/InspectorManager-test.js +186 -0
- package/src/__tests__/LDClient-inspectors-test.js +127 -0
- package/src/__tests__/SafeInspector-test.js +61 -0
- package/src/__tests__/configuration-test.js +20 -21
- package/src/__tests__/diagnosticEvents-test.js +0 -2
- package/src/configuration.js +5 -3
- package/src/diagnosticEvents.js +0 -1
- package/src/index.js +45 -14
- package/src/messages.js +6 -0
- package/typings.d.ts +112 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the `launchdarkly-js-sdk-common` package will be documented in this file. Changes that affect the dependent SDKs such as `launchdarkly-js-client-sdk` should also be logged in those projects, in the next release that uses the updated version of this package. This project adheres to [Semantic Versioning](http://semver.org).
|
|
4
4
|
|
|
5
|
+
## [4.2.0] - 2022-10-03
|
|
6
|
+
### Removed:
|
|
7
|
+
- Removed `seenRequests` cache. This cache was used to de-duplicate events, but it has been supplanted with summary events.
|
|
8
|
+
|
|
9
|
+
### Deprecated:
|
|
10
|
+
- The `allowFrequentDuplicateEvents` configuration has been deprecated because it controlled the behavior of the `seenRequests` cache.
|
|
11
|
+
|
|
12
|
+
## [4.1.1] - 2022-06-07
|
|
13
|
+
### Changed:
|
|
14
|
+
- Enforce a 64 character limit for `application.id` and `application.version` configuration options.
|
|
15
|
+
|
|
16
|
+
### Fixed:
|
|
17
|
+
- Do not include deleted flags in `allFlags`.
|
|
18
|
+
|
|
5
19
|
## [4.1.0] - 2022-04-21
|
|
6
20
|
### Added:
|
|
7
21
|
- `LDOptionsBase.application`, for configuration of application metadata that may be used in LaunchDarkly analytics or other product features. This does not affect feature flag evaluations.
|
package/package.json
CHANGED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
const { messages } = require('.');
|
|
2
|
+
const SafeInspector = require('./SafeInspector');
|
|
3
|
+
const { onNextTick } = require('./utils');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The types of supported inspectors.
|
|
7
|
+
*/
|
|
8
|
+
const InspectorTypes = {
|
|
9
|
+
flagUsed: 'flag-used',
|
|
10
|
+
flagDetailsChanged: 'flag-details-changed',
|
|
11
|
+
flagDetailChanged: 'flag-detail-changed',
|
|
12
|
+
clientIdentityChanged: 'client-identity-changed',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
Object.freeze(InspectorTypes);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Manages dispatching of inspection data to registered inspectors.
|
|
19
|
+
*/
|
|
20
|
+
function InspectorManager(inspectors, logger) {
|
|
21
|
+
const manager = {};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Collection of inspectors keyed by type.
|
|
25
|
+
* @type {{[type: string]: object[]}}
|
|
26
|
+
*/
|
|
27
|
+
const inspectorsByType = {
|
|
28
|
+
[InspectorTypes.flagUsed]: [],
|
|
29
|
+
[InspectorTypes.flagDetailsChanged]: [],
|
|
30
|
+
[InspectorTypes.flagDetailChanged]: [],
|
|
31
|
+
[InspectorTypes.clientIdentityChanged]: [],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const safeInspectors = inspectors?.map(inspector => SafeInspector(inspector, logger));
|
|
35
|
+
|
|
36
|
+
safeInspectors.forEach(safeInspector => {
|
|
37
|
+
// Only add inspectors of supported types.
|
|
38
|
+
if (Object.prototype.hasOwnProperty.call(inspectorsByType, safeInspector.type)) {
|
|
39
|
+
inspectorsByType[safeInspector.type].push(safeInspector);
|
|
40
|
+
} else {
|
|
41
|
+
logger.warn(messages.invalidInspector(safeInspector.type, safeInspector.name));
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check if there is an inspector of a specific type registered.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} type The type of the inspector to check.
|
|
49
|
+
* @returns True if there are any inspectors of that type registered.
|
|
50
|
+
*/
|
|
51
|
+
manager.hasListeners = type => inspectorsByType[type]?.length;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Notify registered inspectors of a flag being used.
|
|
55
|
+
*
|
|
56
|
+
* The notification itself will be dispatched asynchronously.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} flagKey The key for the flag.
|
|
59
|
+
* @param {Object} detail The LDEvaluationDetail for the flag.
|
|
60
|
+
* @param {Object} user The LDUser for the flag.
|
|
61
|
+
*/
|
|
62
|
+
manager.onFlagUsed = (flagKey, detail, user) => {
|
|
63
|
+
if (inspectorsByType[InspectorTypes.flagUsed].length) {
|
|
64
|
+
onNextTick(() => {
|
|
65
|
+
inspectorsByType[InspectorTypes.flagUsed].forEach(inspector => inspector.method(flagKey, detail, user));
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Notify registered inspectors that the flags have been replaced.
|
|
72
|
+
*
|
|
73
|
+
* The notification itself will be dispatched asynchronously.
|
|
74
|
+
*
|
|
75
|
+
* @param {Record<string, Object>} flags The current flags as a Record<string, LDEvaluationDetail>.
|
|
76
|
+
*/
|
|
77
|
+
manager.onFlags = flags => {
|
|
78
|
+
if (inspectorsByType[InspectorTypes.flagDetailsChanged].length) {
|
|
79
|
+
onNextTick(() => {
|
|
80
|
+
inspectorsByType[InspectorTypes.flagDetailsChanged].forEach(inspector => inspector.method(flags));
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Notify registered inspectors that a flag value has changed.
|
|
87
|
+
*
|
|
88
|
+
* The notification itself will be dispatched asynchronously.
|
|
89
|
+
*
|
|
90
|
+
* @param {string} flagKey The key for the flag that changed.
|
|
91
|
+
* @param {Object} flag An `LDEvaluationDetail` for the flag.
|
|
92
|
+
*/
|
|
93
|
+
manager.onFlagChanged = (flagKey, flag) => {
|
|
94
|
+
if (inspectorsByType[InspectorTypes.flagDetailChanged].length) {
|
|
95
|
+
onNextTick(() => {
|
|
96
|
+
console.log('what?');
|
|
97
|
+
inspectorsByType[InspectorTypes.flagDetailChanged].forEach(inspector => inspector.method(flagKey, flag));
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Notify the registered inspectors that the user identity has changed.
|
|
104
|
+
*
|
|
105
|
+
* The notification itself will be dispatched asynchronously.
|
|
106
|
+
*
|
|
107
|
+
* @param {Object} user The `LDUser` which is now identified.
|
|
108
|
+
*/
|
|
109
|
+
manager.onIdentityChanged = user => {
|
|
110
|
+
if (inspectorsByType[InspectorTypes.clientIdentityChanged].length) {
|
|
111
|
+
onNextTick(() => {
|
|
112
|
+
inspectorsByType[InspectorTypes.clientIdentityChanged].forEach(inspector => inspector.method(user));
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return manager;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
module.exports = { InspectorTypes, InspectorManager };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const { messages } = require('.');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wrap an inspector ensuring that calling its methods are safe.
|
|
5
|
+
* @param {object} inspector Inspector to wrap.
|
|
6
|
+
*/
|
|
7
|
+
function SafeInspector(inspector, logger) {
|
|
8
|
+
let errorLogged = false;
|
|
9
|
+
const wrapper = {
|
|
10
|
+
type: inspector.type,
|
|
11
|
+
name: inspector.name,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
wrapper.method = (...args) => {
|
|
15
|
+
try {
|
|
16
|
+
inspector.method(...args);
|
|
17
|
+
} catch {
|
|
18
|
+
// If something goes wrong in an inspector we want to log that something
|
|
19
|
+
// went wrong. We don't want to flood the logs, so we only log something
|
|
20
|
+
// the first time that something goes wrong.
|
|
21
|
+
// We do not include the exception in the log, because we do not know what
|
|
22
|
+
// kind of data it may contain.
|
|
23
|
+
if (!errorLogged) {
|
|
24
|
+
errorLogged = true;
|
|
25
|
+
logger.warn(messages.inspectorMethodError(wrapper.type, wrapper.name));
|
|
26
|
+
}
|
|
27
|
+
// Prevent errors.
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return wrapper;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = SafeInspector;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
const { AsyncQueue } = require('launchdarkly-js-test-helpers');
|
|
2
|
+
const { InspectorTypes, InspectorManager } = require('../InspectorManager');
|
|
3
|
+
const stubPlatform = require('./stubPlatform');
|
|
4
|
+
|
|
5
|
+
describe('given an inspector manager with no registered inspectors', () => {
|
|
6
|
+
const platform = stubPlatform.defaults();
|
|
7
|
+
const manager = InspectorManager([], platform.testing.logger);
|
|
8
|
+
|
|
9
|
+
it('does not cause errors', () => {
|
|
10
|
+
manager.onIdentityChanged({ key: 'key' });
|
|
11
|
+
manager.onFlagUsed(
|
|
12
|
+
'flag-key',
|
|
13
|
+
{
|
|
14
|
+
value: null,
|
|
15
|
+
},
|
|
16
|
+
{ key: 'key' }
|
|
17
|
+
);
|
|
18
|
+
manager.onFlags({});
|
|
19
|
+
manager.onFlagChanged('flag-key', { value: null });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('does not report any registered listeners', () => {
|
|
23
|
+
expect(manager.hasListeners(InspectorTypes.clientIdentityChanged)).toBeFalsy();
|
|
24
|
+
expect(manager.hasListeners(InspectorTypes.flagDetailChanged)).toBeFalsy();
|
|
25
|
+
expect(manager.hasListeners(InspectorTypes.flagDetailsChanged)).toBeFalsy();
|
|
26
|
+
expect(manager.hasListeners(InspectorTypes.flagUsed)).toBeFalsy();
|
|
27
|
+
expect(manager.hasListeners('potato')).toBeFalsy();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('given an inspector with callbacks of every type', () => {
|
|
32
|
+
/**
|
|
33
|
+
* @type {AsyncQueue}
|
|
34
|
+
*/
|
|
35
|
+
const eventQueue = new AsyncQueue();
|
|
36
|
+
const platform = stubPlatform.defaults();
|
|
37
|
+
const manager = InspectorManager(
|
|
38
|
+
[
|
|
39
|
+
{
|
|
40
|
+
type: 'flag-used',
|
|
41
|
+
name: 'my-flag-used-inspector',
|
|
42
|
+
method: (flagKey, flagDetail, user) => {
|
|
43
|
+
eventQueue.add({ type: 'flag-used', flagKey, flagDetail, user });
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
// 'flag-used registered twice.
|
|
47
|
+
{
|
|
48
|
+
type: 'flag-used',
|
|
49
|
+
name: 'my-other-flag-used-inspector',
|
|
50
|
+
method: (flagKey, flagDetail, user) => {
|
|
51
|
+
eventQueue.add({ type: 'flag-used', flagKey, flagDetail, user });
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
type: 'flag-details-changed',
|
|
56
|
+
name: 'my-flag-details-inspector',
|
|
57
|
+
method: details => {
|
|
58
|
+
eventQueue.add({
|
|
59
|
+
type: 'flag-details-changed',
|
|
60
|
+
details,
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
type: 'flag-detail-changed',
|
|
66
|
+
name: 'my-flag-detail-inspector',
|
|
67
|
+
method: (flagKey, flagDetail) => {
|
|
68
|
+
eventQueue.add({
|
|
69
|
+
type: 'flag-detail-changed',
|
|
70
|
+
flagKey,
|
|
71
|
+
flagDetail,
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
type: 'client-identity-changed',
|
|
77
|
+
name: 'my-identity-inspector',
|
|
78
|
+
method: user => {
|
|
79
|
+
eventQueue.add({
|
|
80
|
+
type: 'client-identity-changed',
|
|
81
|
+
user,
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
// Invalid inspector shouldn't have an effect.
|
|
86
|
+
{
|
|
87
|
+
type: 'potato',
|
|
88
|
+
name: 'my-potato-inspector',
|
|
89
|
+
method: () => {},
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
platform.testing.logger
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
afterEach(() => {
|
|
96
|
+
expect(eventQueue.length()).toEqual(0);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
afterAll(() => {
|
|
100
|
+
eventQueue.close();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('logged that there was a bad inspector', () => {
|
|
104
|
+
expect(platform.testing.logger.output.warn).toEqual([
|
|
105
|
+
'an inspector: "my-potato-inspector" of an invalid type (potato) was configured',
|
|
106
|
+
]);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('reports any registered listeners', () => {
|
|
110
|
+
expect(manager.hasListeners(InspectorTypes.clientIdentityChanged)).toBeTruthy();
|
|
111
|
+
expect(manager.hasListeners(InspectorTypes.flagDetailChanged)).toBeTruthy();
|
|
112
|
+
expect(manager.hasListeners(InspectorTypes.flagDetailsChanged)).toBeTruthy();
|
|
113
|
+
expect(manager.hasListeners(InspectorTypes.flagUsed)).toBeTruthy();
|
|
114
|
+
expect(manager.hasListeners('potato')).toBeFalsy();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('executes `onFlagUsed` handlers', async () => {
|
|
118
|
+
manager.onFlagUsed(
|
|
119
|
+
'flag-key',
|
|
120
|
+
{
|
|
121
|
+
value: 'test',
|
|
122
|
+
variationIndex: 1,
|
|
123
|
+
reason: {
|
|
124
|
+
kind: 'OFF',
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{ key: 'test-key' }
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const expectedEvent = {
|
|
131
|
+
type: 'flag-used',
|
|
132
|
+
flagKey: 'flag-key',
|
|
133
|
+
flagDetail: {
|
|
134
|
+
value: 'test',
|
|
135
|
+
variationIndex: 1,
|
|
136
|
+
reason: {
|
|
137
|
+
kind: 'OFF',
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
user: { key: 'test-key' },
|
|
141
|
+
};
|
|
142
|
+
const event1 = await eventQueue.take();
|
|
143
|
+
expect(event1).toMatchObject(expectedEvent);
|
|
144
|
+
|
|
145
|
+
// There are two handlers, so there should be another event.
|
|
146
|
+
const event2 = await eventQueue.take();
|
|
147
|
+
expect(event2).toMatchObject(expectedEvent);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('executes `onFlags` handler', async () => {
|
|
151
|
+
manager.onFlags({
|
|
152
|
+
example: { value: 'a-value' },
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const event = await eventQueue.take();
|
|
156
|
+
expect(event).toMatchObject({
|
|
157
|
+
type: 'flag-details-changed',
|
|
158
|
+
details: {
|
|
159
|
+
example: { value: 'a-value' },
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('executes `onFlagChanged` handler', async () => {
|
|
165
|
+
manager.onFlagChanged('the-flag', { value: 'a-value' });
|
|
166
|
+
|
|
167
|
+
const event = await eventQueue.take();
|
|
168
|
+
expect(event).toMatchObject({
|
|
169
|
+
type: 'flag-detail-changed',
|
|
170
|
+
flagKey: 'the-flag',
|
|
171
|
+
flagDetail: {
|
|
172
|
+
value: 'a-value',
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('executes `onIdentityChanged` handler', async () => {
|
|
178
|
+
manager.onIdentityChanged({ key: 'the-key' });
|
|
179
|
+
|
|
180
|
+
const event = await eventQueue.take();
|
|
181
|
+
expect(event).toMatchObject({
|
|
182
|
+
type: 'client-identity-changed',
|
|
183
|
+
user: { key: 'the-key' },
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
});
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
const { AsyncQueue } = require('launchdarkly-js-test-helpers');
|
|
2
|
+
const { respondJson } = require('./mockHttp');
|
|
3
|
+
const stubPlatform = require('./stubPlatform');
|
|
4
|
+
|
|
5
|
+
const envName = 'UNKNOWN_ENVIRONMENT_ID';
|
|
6
|
+
const user = { key: 'user' };
|
|
7
|
+
|
|
8
|
+
describe('given a streaming client with registered inspectors', () => {
|
|
9
|
+
const eventQueue = new AsyncQueue();
|
|
10
|
+
|
|
11
|
+
const inspectors = [
|
|
12
|
+
{
|
|
13
|
+
type: 'flag-used',
|
|
14
|
+
method: (flagKey, flagDetail, user) => {
|
|
15
|
+
eventQueue.add({ type: 'flag-used', flagKey, flagDetail, user });
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
// 'flag-used registered twice.
|
|
19
|
+
{
|
|
20
|
+
type: 'flag-used',
|
|
21
|
+
method: (flagKey, flagDetail, user) => {
|
|
22
|
+
eventQueue.add({ type: 'flag-used', flagKey, flagDetail, user });
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
type: 'flag-details-changed',
|
|
27
|
+
method: details => {
|
|
28
|
+
eventQueue.add({
|
|
29
|
+
type: 'flag-details-changed',
|
|
30
|
+
details,
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
type: 'flag-detail-changed',
|
|
36
|
+
method: (flagKey, flagDetail) => {
|
|
37
|
+
eventQueue.add({
|
|
38
|
+
type: 'flag-detail-changed',
|
|
39
|
+
flagKey,
|
|
40
|
+
flagDetail,
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
type: 'client-identity-changed',
|
|
46
|
+
method: user => {
|
|
47
|
+
eventQueue.add({
|
|
48
|
+
type: 'client-identity-changed',
|
|
49
|
+
user,
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
let client;
|
|
56
|
+
let platform;
|
|
57
|
+
|
|
58
|
+
beforeEach(async () => {
|
|
59
|
+
platform = stubPlatform.defaults();
|
|
60
|
+
const server = platform.testing.http.newServer();
|
|
61
|
+
server.byDefault(respondJson({}));
|
|
62
|
+
const config = { streaming: true, baseUrl: server.url, inspectors };
|
|
63
|
+
client = platform.testing.makeClient(envName, user, config);
|
|
64
|
+
await client.waitUntilReady();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
expect(eventQueue.length()).toEqual(0);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
afterAll(() => {
|
|
72
|
+
eventQueue.close();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
afterEach(async () => {
|
|
76
|
+
await client.close();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('has an initial identify event and flag payload', async () => {
|
|
80
|
+
// These events cover the initial identify and a polling response.
|
|
81
|
+
const ident = await eventQueue.take();
|
|
82
|
+
expect(ident).toMatchObject({
|
|
83
|
+
type: 'client-identity-changed',
|
|
84
|
+
user,
|
|
85
|
+
});
|
|
86
|
+
const flagsEvent = await eventQueue.take();
|
|
87
|
+
expect(flagsEvent).toMatchObject({
|
|
88
|
+
type: 'flag-details-changed',
|
|
89
|
+
details: {},
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('emits an event for the stream put replacing all flags', async () => {
|
|
94
|
+
// Take initial events.
|
|
95
|
+
eventQueue.take();
|
|
96
|
+
eventQueue.take();
|
|
97
|
+
|
|
98
|
+
const stream = await platform.testing.eventSourcesCreated.take();
|
|
99
|
+
stream.eventSource.mockEmit('put', {
|
|
100
|
+
data: '{"flagKey":{"value":true,"version":1}}',
|
|
101
|
+
});
|
|
102
|
+
const updateEvent = await eventQueue.take();
|
|
103
|
+
expect(updateEvent).toMatchObject({
|
|
104
|
+
type: 'flag-details-changed',
|
|
105
|
+
details: {
|
|
106
|
+
flagKey: { value: true },
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('emits an event for a stream patch changing a flag', async () => {
|
|
112
|
+
// Take initial events.
|
|
113
|
+
eventQueue.take();
|
|
114
|
+
eventQueue.take();
|
|
115
|
+
|
|
116
|
+
const stream = await platform.testing.eventSourcesCreated.take();
|
|
117
|
+
stream.eventSource.mockEmit('patch', {
|
|
118
|
+
data: '{"key": "flagKey", "value":false,"version":2}',
|
|
119
|
+
});
|
|
120
|
+
const updateEvent = await eventQueue.take();
|
|
121
|
+
expect(updateEvent).toMatchObject({
|
|
122
|
+
type: 'flag-detail-changed',
|
|
123
|
+
flagKey: 'flagKey',
|
|
124
|
+
flagDetail: { value: false },
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const SafeInspector = require('../SafeInspector');
|
|
2
|
+
const stubPlatform = require('./stubPlatform');
|
|
3
|
+
|
|
4
|
+
describe('given a safe inspector', () => {
|
|
5
|
+
const platform = stubPlatform.defaults();
|
|
6
|
+
const mockInspector = {
|
|
7
|
+
type: 'the-inspector-type',
|
|
8
|
+
name: 'the-inspector-name',
|
|
9
|
+
method: () => {
|
|
10
|
+
throw new Error('evil inspector');
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
const safeInspector = SafeInspector(mockInspector, platform.testing.logger);
|
|
14
|
+
|
|
15
|
+
it('has the correct type', () => {
|
|
16
|
+
expect(safeInspector.type).toEqual('the-inspector-type');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('does not allow exceptions to propagate', () => {
|
|
20
|
+
safeInspector.method();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('only logs one error', () => {
|
|
24
|
+
safeInspector.method();
|
|
25
|
+
safeInspector.method();
|
|
26
|
+
expect(platform.testing.logger.output.warn).toEqual([
|
|
27
|
+
'an inspector: "the-inspector-name" of type: "the-inspector-type" generated an exception',
|
|
28
|
+
]);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Type and name are required by the schema, but it should operate fine if they are not specified.
|
|
33
|
+
describe('given a safe inspector with no name or type', () => {
|
|
34
|
+
const platform = stubPlatform.defaults();
|
|
35
|
+
const mockInspector = {
|
|
36
|
+
method: () => {
|
|
37
|
+
throw new Error('evil inspector');
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
const safeInspector = SafeInspector(mockInspector, platform.testing.logger);
|
|
41
|
+
|
|
42
|
+
it('has undefined type', () => {
|
|
43
|
+
expect(safeInspector.type).toBeUndefined();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('has undefined name', () => {
|
|
47
|
+
expect(safeInspector.name).toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('does not allow exceptions to propagate', () => {
|
|
51
|
+
safeInspector.method();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('only logs one error', () => {
|
|
55
|
+
safeInspector.method();
|
|
56
|
+
safeInspector.method();
|
|
57
|
+
expect(platform.testing.logger.output.warn).toEqual([
|
|
58
|
+
'an inspector: "undefined" of type: "undefined" generated an exception',
|
|
59
|
+
]);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -44,26 +44,24 @@ describe('configuration', () => {
|
|
|
44
44
|
await listener.expectNoErrors();
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// });
|
|
66
|
-
// }
|
|
47
|
+
function checkDeprecated(oldName, newName, value) {
|
|
48
|
+
const desc = newName
|
|
49
|
+
? 'allows "' + oldName + '" as a deprecated equivalent to "' + newName + '"'
|
|
50
|
+
: 'warns that "' + oldName + '" is deprecated';
|
|
51
|
+
it(desc, async () => {
|
|
52
|
+
const listener = errorListener();
|
|
53
|
+
const config0 = {};
|
|
54
|
+
config0[oldName] = value;
|
|
55
|
+
const config1 = configuration.validate(config0, listener.emitter, null, listener.logger);
|
|
56
|
+
if (newName) {
|
|
57
|
+
expect(config1[newName]).toBe(value);
|
|
58
|
+
expect(config1[oldName]).toBeUndefined();
|
|
59
|
+
} else {
|
|
60
|
+
expect(config1[oldName]).toEqual(value);
|
|
61
|
+
}
|
|
62
|
+
await listener.expectWarningOnly(messages.deprecated(oldName, newName));
|
|
63
|
+
});
|
|
64
|
+
}
|
|
67
65
|
|
|
68
66
|
function checkBooleanProperty(name) {
|
|
69
67
|
it('enforces boolean type and default for "' + name + '"', async () => {
|
|
@@ -103,13 +101,14 @@ describe('configuration', () => {
|
|
|
103
101
|
checkBooleanProperty('allAttributesPrivate');
|
|
104
102
|
checkBooleanProperty('sendLDHeaders');
|
|
105
103
|
checkBooleanProperty('inlineUsersInEvents');
|
|
106
|
-
checkBooleanProperty('allowFrequentDuplicateEvents');
|
|
107
104
|
checkBooleanProperty('sendEventsOnlyForVariation');
|
|
108
105
|
checkBooleanProperty('useReport');
|
|
109
106
|
checkBooleanProperty('evaluationReasons');
|
|
110
107
|
checkBooleanProperty('diagnosticOptOut');
|
|
111
108
|
checkBooleanProperty('streaming');
|
|
112
109
|
|
|
110
|
+
checkDeprecated('allowFrequentDuplicateEvents', undefined, true);
|
|
111
|
+
|
|
113
112
|
function checkNumericProperty(name, validValue) {
|
|
114
113
|
it('enforces numeric type and default for "' + name + '"', async () => {
|
|
115
114
|
await expectDefault(name);
|
|
@@ -101,7 +101,6 @@ describe('DiagnosticsManager', () => {
|
|
|
101
101
|
};
|
|
102
102
|
const defaultConfigInEvent = {
|
|
103
103
|
allAttributesPrivate: false,
|
|
104
|
-
allowFrequentDuplicateEvents: false,
|
|
105
104
|
autoAliasingOptOut: false,
|
|
106
105
|
bootstrapMode: false,
|
|
107
106
|
customBaseURI: false,
|
|
@@ -189,7 +188,6 @@ describe('DiagnosticsManager', () => {
|
|
|
189
188
|
it('sends init event on start() with custom config', async () => {
|
|
190
189
|
const configAndResultValues = [
|
|
191
190
|
[{ allAttributesPrivate: true }, { allAttributesPrivate: true }],
|
|
192
|
-
[{ allowFrequentDuplicateEvents: true }, { allowFrequentDuplicateEvents: true }],
|
|
193
191
|
[{ bootstrap: {} }, { bootstrapMode: true }],
|
|
194
192
|
[{ baseUrl: 'http://other' }, { customBaseURI: true }],
|
|
195
193
|
[{ eventsUrl: 'http://other' }, { customEventsURI: true }],
|
package/src/configuration.js
CHANGED
|
@@ -39,6 +39,7 @@ const baseOptionDefs = {
|
|
|
39
39
|
stateProvider: { type: 'object' }, // not a public option, used internally
|
|
40
40
|
autoAliasingOptOut: { default: false },
|
|
41
41
|
application: { validator: applicationConfigValidator },
|
|
42
|
+
inspectors: { default: [] },
|
|
42
43
|
};
|
|
43
44
|
|
|
44
45
|
/**
|
|
@@ -78,9 +79,10 @@ function validate(options, emitter, extraOptionDefs, logger) {
|
|
|
78
79
|
const optionDefs = utils.extend({ logger: { default: logger } }, baseOptionDefs, extraOptionDefs);
|
|
79
80
|
|
|
80
81
|
const deprecatedOptions = {
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
82
|
+
// The property name is the deprecated name, and the property value is the preferred name if
|
|
83
|
+
// any, or null/undefined if there is no replacement. This should be removed, along with
|
|
84
|
+
// the option, in the next major version.
|
|
85
|
+
allowFrequentDuplicateEvents: undefined,
|
|
84
86
|
};
|
|
85
87
|
|
|
86
88
|
function checkDeprecatedOptions(config) {
|
package/src/diagnosticEvents.js
CHANGED
|
@@ -201,7 +201,6 @@ function DiagnosticsManager(
|
|
|
201
201
|
usingSecureMode: !!config.hash,
|
|
202
202
|
bootstrapMode: !!config.bootstrap,
|
|
203
203
|
fetchGoalsDisabled: !config.fetchGoals,
|
|
204
|
-
allowFrequentDuplicateEvents: !!config.allowFrequentDuplicateEvents,
|
|
205
204
|
sendEventsOnlyForVariation: !!config.sendEventsOnlyForVariation,
|
|
206
205
|
autoAliasingOptOut: !!config.autoAliasingOptOut,
|
|
207
206
|
};
|
package/src/index.js
CHANGED
|
@@ -14,6 +14,7 @@ const { commonBasicLogger } = require('./loggers');
|
|
|
14
14
|
const utils = require('./utils');
|
|
15
15
|
const errors = require('./errors');
|
|
16
16
|
const messages = require('./messages');
|
|
17
|
+
const { InspectorTypes, InspectorManager } = require('./InspectorManager');
|
|
17
18
|
|
|
18
19
|
const changeEvent = 'change';
|
|
19
20
|
const internalChangeEvent = 'internal-change';
|
|
@@ -32,6 +33,7 @@ function initialize(env, user, specifiedOptions, platform, extraOptionDefs) {
|
|
|
32
33
|
const emitter = EventEmitter(logger);
|
|
33
34
|
const initializationStateTracker = InitializationStateTracker(emitter);
|
|
34
35
|
const options = configuration.validate(specifiedOptions, emitter, extraOptionDefs, logger);
|
|
36
|
+
const inspectorManager = InspectorManager(options.inspectors, logger);
|
|
35
37
|
const sendEvents = options.sendEvents;
|
|
36
38
|
let environment = env;
|
|
37
39
|
let hash = options.hash;
|
|
@@ -63,7 +65,6 @@ function initialize(env, user, specifiedOptions, platform, extraOptionDefs) {
|
|
|
63
65
|
|
|
64
66
|
const requestor = Requestor(platform, options, environment);
|
|
65
67
|
|
|
66
|
-
const seenRequests = {};
|
|
67
68
|
let flags = {};
|
|
68
69
|
let useLocalStorage;
|
|
69
70
|
let streamActive;
|
|
@@ -155,11 +156,38 @@ function initialize(env, user, specifiedOptions, platform, extraOptionDefs) {
|
|
|
155
156
|
}
|
|
156
157
|
}
|
|
157
158
|
|
|
159
|
+
function notifyInspectionIdentityChanged() {
|
|
160
|
+
if (inspectorManager.hasListeners(InspectorTypes.clientIdentityChanged)) {
|
|
161
|
+
inspectorManager.onIdentityChanged(ident.getUser());
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function notifyInspectionFlagChanged(data, newFlag) {
|
|
166
|
+
if (inspectorManager.hasListeners(InspectorTypes.flagDetailChanged)) {
|
|
167
|
+
inspectorManager.onFlagChanged(data.key, getFlagDetail(newFlag));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function notifyInspectionFlagsChanged() {
|
|
172
|
+
if (inspectorManager.hasListeners(InspectorTypes.flagDetailsChanged)) {
|
|
173
|
+
inspectorManager.onFlags(
|
|
174
|
+
Object.entries(flags)
|
|
175
|
+
.map(([key, value]) => ({ key, detail: getFlagDetail(value) }))
|
|
176
|
+
.reduce((acc, cur) => {
|
|
177
|
+
// eslint-disable-next-line no-param-reassign
|
|
178
|
+
acc[cur.key] = cur.detail;
|
|
179
|
+
return acc;
|
|
180
|
+
}, {})
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
158
185
|
function onIdentifyChange(user, previousUser) {
|
|
159
186
|
sendIdentifyEvent(user);
|
|
160
187
|
if (!options.autoAliasingOptOut && previousUser && previousUser.anonymous && user && !user.anonymous) {
|
|
161
188
|
alias(user, previousUser);
|
|
162
189
|
}
|
|
190
|
+
notifyInspectionIdentityChanged();
|
|
163
191
|
}
|
|
164
192
|
|
|
165
193
|
function sendIdentifyEvent(user) {
|
|
@@ -181,15 +209,6 @@ function initialize(env, user, specifiedOptions, platform, extraOptionDefs) {
|
|
|
181
209
|
const user = ident.getUser();
|
|
182
210
|
const now = new Date();
|
|
183
211
|
const value = detail ? detail.value : null;
|
|
184
|
-
if (!options.allowFrequentDuplicateEvents) {
|
|
185
|
-
const cacheKey = JSON.stringify(value) + (user && user.key ? user.key : '') + key; // see below
|
|
186
|
-
const cached = seenRequests[cacheKey];
|
|
187
|
-
// cache TTL is five minutes
|
|
188
|
-
if (cached && now - cached < 300000) {
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
seenRequests[cacheKey] = now;
|
|
192
|
-
}
|
|
193
212
|
|
|
194
213
|
const event = {
|
|
195
214
|
kind: 'feature',
|
|
@@ -267,14 +286,14 @@ function initialize(env, user, specifiedOptions, platform, extraOptionDefs) {
|
|
|
267
286
|
}
|
|
268
287
|
|
|
269
288
|
function variation(key, defaultValue) {
|
|
270
|
-
return variationDetailInternal(key, defaultValue, true, false).value;
|
|
289
|
+
return variationDetailInternal(key, defaultValue, true, false, false).value;
|
|
271
290
|
}
|
|
272
291
|
|
|
273
292
|
function variationDetail(key, defaultValue) {
|
|
274
|
-
return variationDetailInternal(key, defaultValue, true, true);
|
|
293
|
+
return variationDetailInternal(key, defaultValue, true, true, false);
|
|
275
294
|
}
|
|
276
295
|
|
|
277
|
-
function variationDetailInternal(key, defaultValue, sendEvent, includeReasonInEvent) {
|
|
296
|
+
function variationDetailInternal(key, defaultValue, sendEvent, includeReasonInEvent, isAllFlags) {
|
|
278
297
|
let detail;
|
|
279
298
|
|
|
280
299
|
if (flags && utils.objectHasOwnProperty(flags, key) && flags[key] && !flags[key].deleted) {
|
|
@@ -291,6 +310,11 @@ function initialize(env, user, specifiedOptions, platform, extraOptionDefs) {
|
|
|
291
310
|
sendFlagEvent(key, detail, defaultValue, includeReasonInEvent);
|
|
292
311
|
}
|
|
293
312
|
|
|
313
|
+
// For the all flags case `onFlags` will be called instead.
|
|
314
|
+
if (!isAllFlags) {
|
|
315
|
+
notifyInspectionIdentityChanged();
|
|
316
|
+
}
|
|
317
|
+
|
|
294
318
|
return detail;
|
|
295
319
|
}
|
|
296
320
|
|
|
@@ -314,7 +338,7 @@ function initialize(env, user, specifiedOptions, platform, extraOptionDefs) {
|
|
|
314
338
|
|
|
315
339
|
for (const key in flags) {
|
|
316
340
|
if (utils.objectHasOwnProperty(flags, key) && !flags[key].deleted) {
|
|
317
|
-
results[key] = variationDetailInternal(key, null, !options.sendEventsOnlyForVariation).value;
|
|
341
|
+
results[key] = variationDetailInternal(key, null, !options.sendEventsOnlyForVariation, false, true).value;
|
|
318
342
|
}
|
|
319
343
|
}
|
|
320
344
|
|
|
@@ -443,6 +467,7 @@ function initialize(env, user, specifiedOptions, platform, extraOptionDefs) {
|
|
|
443
467
|
mods[data.key] = { current: newDetail };
|
|
444
468
|
}
|
|
445
469
|
handleFlagChanges(mods); // don't wait for this Promise to be resolved
|
|
470
|
+
notifyInspectionFlagChanged(data, newFlag);
|
|
446
471
|
} else {
|
|
447
472
|
logger.debug(messages.debugStreamPatchIgnored(data.key));
|
|
448
473
|
}
|
|
@@ -459,6 +484,7 @@ function initialize(env, user, specifiedOptions, platform, extraOptionDefs) {
|
|
|
459
484
|
mods[data.key] = { previous: flags[data.key].value };
|
|
460
485
|
}
|
|
461
486
|
flags[data.key] = { version: data.version, deleted: true };
|
|
487
|
+
notifyInspectionFlagChanged(data, flags[data.key]);
|
|
462
488
|
handleFlagChanges(mods); // don't wait for this Promise to be resolved
|
|
463
489
|
} else {
|
|
464
490
|
logger.debug(messages.debugStreamDeleteIgnored(data.key));
|
|
@@ -500,6 +526,9 @@ function initialize(env, user, specifiedOptions, platform, extraOptionDefs) {
|
|
|
500
526
|
}
|
|
501
527
|
|
|
502
528
|
flags = { ...newFlags };
|
|
529
|
+
|
|
530
|
+
notifyInspectionFlagsChanged();
|
|
531
|
+
|
|
503
532
|
return handleFlagChanges(changes).catch(() => {}); // swallow any exceptions from this Promise
|
|
504
533
|
}
|
|
505
534
|
|
|
@@ -673,6 +702,8 @@ function initialize(env, user, specifiedOptions, platform, extraOptionDefs) {
|
|
|
673
702
|
.fetchFlagSettings(ident.getUser(), hash)
|
|
674
703
|
.then(requestedFlags => {
|
|
675
704
|
flags = requestedFlags || {};
|
|
705
|
+
|
|
706
|
+
notifyInspectionFlagsChanged();
|
|
676
707
|
// Note, we don't need to call updateSettings here because local storage and change events are not relevant
|
|
677
708
|
signalSuccessfulInit();
|
|
678
709
|
})
|
package/src/messages.js
CHANGED
|
@@ -180,6 +180,10 @@ const debugPostingDiagnosticEvent = function(event) {
|
|
|
180
180
|
return 'sending diagnostic event (' + event.kind + ')';
|
|
181
181
|
};
|
|
182
182
|
|
|
183
|
+
const invalidInspector = (type, name) => `an inspector: "${name}" of an invalid type (${type}) was configured`;
|
|
184
|
+
|
|
185
|
+
const inspectorMethodError = (type, name) => `an inspector: "${name}" of type: "${type}" generated an exception`;
|
|
186
|
+
|
|
183
187
|
const invalidTagValue = name => `Config option "${name}" must only contain letters, numbers, ., _ or -.`;
|
|
184
188
|
|
|
185
189
|
const tagValueTooLong = name => `Value of "${name}" was longer than 64 characters and was discarded.`;
|
|
@@ -208,8 +212,10 @@ module.exports = {
|
|
|
208
212
|
httpErrorMessage,
|
|
209
213
|
httpUnavailable,
|
|
210
214
|
identifyDisabled,
|
|
215
|
+
inspectorMethodError,
|
|
211
216
|
invalidContentType,
|
|
212
217
|
invalidData,
|
|
218
|
+
invalidInspector,
|
|
213
219
|
invalidKey,
|
|
214
220
|
invalidTagValue,
|
|
215
221
|
invalidUser,
|
package/typings.d.ts
CHANGED
|
@@ -173,10 +173,13 @@ declare module 'launchdarkly-js-sdk-common' {
|
|
|
173
173
|
inlineUsersInEvents?: boolean;
|
|
174
174
|
|
|
175
175
|
/**
|
|
176
|
-
*
|
|
177
|
-
* evaluated with the same value within the last five minutes.
|
|
176
|
+
* This option is deprecated, and setting it has no effect.
|
|
178
177
|
*
|
|
179
|
-
*
|
|
178
|
+
* The behavior is now to allow frequent duplicate events.
|
|
179
|
+
*
|
|
180
|
+
* This is not a problem because most events will be summarized, and
|
|
181
|
+
* events which are not summarized are important to the operation of features such as
|
|
182
|
+
* experimentation.
|
|
180
183
|
*/
|
|
181
184
|
allowFrequentDuplicateEvents?: boolean;
|
|
182
185
|
|
|
@@ -284,6 +287,11 @@ declare module 'launchdarkly-js-sdk-common' {
|
|
|
284
287
|
*/
|
|
285
288
|
version?: string;
|
|
286
289
|
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Inspectors can be used for collecting information for monitoring, analytics, and debugging.
|
|
293
|
+
*/
|
|
294
|
+
inspectors?: LDInspection[];
|
|
287
295
|
}
|
|
288
296
|
|
|
289
297
|
/**
|
|
@@ -818,4 +826,105 @@ declare module 'launchdarkly-js-sdk-common' {
|
|
|
818
826
|
* You can also specify `'none'` instead to disable all logging.
|
|
819
827
|
*/
|
|
820
828
|
export type LDLogLevel = 'debug' | 'info' | 'warn' | 'error' | 'none';
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* Callback interface for collecting information about the SDK at runtime.
|
|
832
|
+
*
|
|
833
|
+
* This interface is used to collect information about flag usage.
|
|
834
|
+
*
|
|
835
|
+
* This interface should not be used by the application to access flags for the purpose of controlling application
|
|
836
|
+
* flow. It is intended for monitoring, analytics, or debugging purposes.
|
|
837
|
+
*/
|
|
838
|
+
interface LDInspectionFlagUsedHandler {
|
|
839
|
+
type: 'flag-used',
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Name of the inspector. Will be used for logging issues with the inspector.
|
|
843
|
+
*/
|
|
844
|
+
name: string,
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* This method is called when a flag is accessed via a variation method, or it can be called based on actions in
|
|
848
|
+
* wrapper SDKs which have different methods of tracking when a flag was accessed. It is not called when a call is made
|
|
849
|
+
* to allFlags.
|
|
850
|
+
*/
|
|
851
|
+
method: (flagKey: string, flagDetail: LDEvaluationDetail, user: LDUser) => void;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* Callback interface for collecting information about the SDK at runtime.
|
|
856
|
+
*
|
|
857
|
+
* This interface is used to collect information about flag data. In order to understand the
|
|
858
|
+
* current flag state it should be combined with {@link LDInspectionFlagValueChangedHandler}.
|
|
859
|
+
* This interface will get the initial flag information, and
|
|
860
|
+
* {@link LDInspectionFlagValueChangedHandler} will provide changes to individual flags.
|
|
861
|
+
*
|
|
862
|
+
* This interface should not be used by the application to access flags for the purpose of controlling application
|
|
863
|
+
* flow. It is intended for monitoring, analytics, or debugging purposes.
|
|
864
|
+
*/
|
|
865
|
+
interface LDInspectionFlagDetailsChangedHandler {
|
|
866
|
+
type: 'flag-details-changed',
|
|
867
|
+
|
|
868
|
+
/**
|
|
869
|
+
* Name of the inspector. Will be used for logging issues with the inspector.
|
|
870
|
+
*/
|
|
871
|
+
name: string,
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* This method is called when the flags in the store are replaced with new flags. It will contain all flags
|
|
875
|
+
* regardless of if they have been evaluated.
|
|
876
|
+
*/
|
|
877
|
+
method: (details: Record<string, LDEvaluationDetail>) => void;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Callback interface for collecting information about the SDK at runtime.
|
|
883
|
+
*
|
|
884
|
+
* This interface is used to collect changes to flag data, but does not provide the initial
|
|
885
|
+
* data. It can be combined with {@link LDInspectionFlagValuesChangedHandler} to track the
|
|
886
|
+
* entire flag state.
|
|
887
|
+
*
|
|
888
|
+
* This interface should not be used by the application to access flags for the purpose of controlling application
|
|
889
|
+
* flow. It is intended for monitoring, analytics, or debugging purposes.
|
|
890
|
+
*/
|
|
891
|
+
interface LDInspectionFlagDetailChangedHandler {
|
|
892
|
+
type: 'flag-detail-changed',
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* Name of the inspector. Will be used for logging issues with the inspector.
|
|
896
|
+
*/
|
|
897
|
+
name: string,
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* This method is called when a flag is updated. It will not be called
|
|
901
|
+
* when all flags are updated.
|
|
902
|
+
*/
|
|
903
|
+
method: (flagKey: string, detail: LDEvaluationDetail) => void;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* Callback interface for collecting information about the SDK at runtime.
|
|
908
|
+
*
|
|
909
|
+
* This interface is used to track current identity state of the SDK.
|
|
910
|
+
*
|
|
911
|
+
* This interface should not be used by the application to access flags for the purpose of controlling application
|
|
912
|
+
* flow. It is intended for monitoring, analytics, or debugging purposes.
|
|
913
|
+
*/
|
|
914
|
+
interface LDInspectionIdentifyHandler {
|
|
915
|
+
type: 'client-identity-changed',
|
|
916
|
+
|
|
917
|
+
/**
|
|
918
|
+
* Name of the inspector. Will be used for logging issues with the inspector.
|
|
919
|
+
*/
|
|
920
|
+
name: string,
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* This method will be called when an identify operation completes.
|
|
924
|
+
*/
|
|
925
|
+
method: (user: LDUser) => void;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
type LDInspection = LDInspectionFlagUsedHandler | LDInspectionFlagDetailsChangedHandler
|
|
929
|
+
| LDInspectionFlagDetailChangedHandler | LDInspectionIdentifyHandler;
|
|
821
930
|
}
|