launchdarkly-js-sdk-common 4.1.0 → 4.2.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.
@@ -5,7 +5,7 @@ repo:
5
5
  private: js-sdk-common-private
6
6
 
7
7
  branches:
8
- - name: master
8
+ - name: main
9
9
  description: 4.x
10
10
  - name: 3.x
11
11
 
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.1.1] - 2022-06-07
6
+ ### Changed:
7
+ - Enforce a 64 character limit for `application.id` and `application.version` configuration options.
8
+
9
+ ### Fixed:
10
+ - Do not include deleted flags in `allFlags`.
11
+
12
+ ## [4.1.0] - 2022-04-21
13
+ ### Added:
14
+ - `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.
15
+
16
+ ### Fixed:
17
+ - The `baseUrl`, `streamUrl`, and `eventsUrl` properties now work properly regardless of whether the URL string has a trailing slash. Previously, a trailing slash would cause request URL paths to have double slashes.
18
+
5
19
  ## [4.0.3] - 2022-02-16
6
20
  ### Fixed:
7
21
  - If the SDK receives invalid JSON data from a streaming connection (possibly as a result of the connection being cut off), it now uses its regular error-handling logic: the error is emitted as an `error` event or, if there are no `error` event listeners, it is logged. Previously, it would be thrown as an unhandled exception.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "launchdarkly-js-sdk-common",
3
- "version": "4.1.0",
3
+ "version": "4.2.0",
4
4
  "description": "LaunchDarkly SDK for JavaScript - common code",
5
5
  "author": "LaunchDarkly <team@launchdarkly.com>",
6
6
  "license": "Apache-2.0",
@@ -44,26 +44,24 @@ describe('configuration', () => {
44
44
  await listener.expectNoErrors();
45
45
  }
46
46
 
47
- // As of the latest major version, there are no deprecated options. This logic can be restored
48
- // the next time we deprecate something.
49
- // function checkDeprecated(oldName, newName, value) {
50
- // const desc = newName
51
- // ? 'allows "' + oldName + '" as a deprecated equivalent to "' + newName + '"'
52
- // : 'warns that "' + oldName + '" is deprecated';
53
- // it(desc, async () => {
54
- // const listener = errorListener();
55
- // const config0 = {};
56
- // config0[oldName] = value;
57
- // const config1 = configuration.validate(config0, listener.emitter, null, listener.logger);
58
- // if (newName) {
59
- // expect(config1[newName]).toBe(value);
60
- // expect(config1[oldName]).toBeUndefined();
61
- // } else {
62
- // expect(config1[oldName]).toEqual(value);
63
- // }
64
- // await listener.expectWarningOnly(messages.deprecated(oldName, newName));
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);
@@ -230,6 +229,13 @@ describe('configuration', () => {
230
229
  await listener.expectWarningOnly(messages.invalidTagValue('application.id'));
231
230
  });
232
231
 
232
+ it('logs a warning when a tag value is too long', async () => {
233
+ const listener = errorListener();
234
+ const configIn = { application: { id: 'a'.repeat(65), version: 'b'.repeat(64) } };
235
+ expect(configuration.validate(configIn, listener.emitter, null, listener.logger).application.id).toBeUndefined();
236
+ await listener.expectWarningOnly(messages.tagValueTooLong('application.id'));
237
+ });
238
+
233
239
  it('handles a valid application version', async () => {
234
240
  const listener = errorListener();
235
241
  const configIn = { application: { version: 'test-version' } };
@@ -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 }],
@@ -48,24 +48,28 @@ const allowedTagCharacters = /^(\w|\.|-)+$/;
48
48
 
49
49
  /**
50
50
  * Verify that a value meets the requirements for a tag value.
51
- * @param {Object} config
52
51
  * @param {string} tagValue
52
+ * @param {Object} logger
53
53
  */
54
- function validateTagValue(name, config, tagValue, logger) {
54
+ function validateTagValue(name, tagValue, logger) {
55
55
  if (typeof tagValue !== 'string' || !tagValue.match(allowedTagCharacters)) {
56
56
  logger.warn(messages.invalidTagValue(name));
57
57
  return undefined;
58
58
  }
59
+ if (tagValue.length > 64) {
60
+ logger.warn(messages.tagValueTooLong(name));
61
+ return undefined;
62
+ }
59
63
  return tagValue;
60
64
  }
61
65
 
62
- function applicationConfigValidator(name, config, value, logger) {
66
+ function applicationConfigValidator(name, value, logger) {
63
67
  const validated = {};
64
68
  if (value.id) {
65
- validated.id = validateTagValue(`${name}.id`, config, value.id, logger);
69
+ validated.id = validateTagValue(`${name}.id`, value.id, logger);
66
70
  }
67
71
  if (value.version) {
68
- validated.version = validateTagValue(`${name}.version`, config, value.version, logger);
72
+ validated.version = validateTagValue(`${name}.version`, value.version, logger);
69
73
  }
70
74
  return validated;
71
75
  }
@@ -74,9 +78,10 @@ function validate(options, emitter, extraOptionDefs, logger) {
74
78
  const optionDefs = utils.extend({ logger: { default: logger } }, baseOptionDefs, extraOptionDefs);
75
79
 
76
80
  const deprecatedOptions = {
77
- // As of the latest major version, there are no deprecated options. Next time we deprecate
78
- // something, add an item here where the property name is the deprecated name, and the
79
- // property value is the preferred name if any, or null/undefined if there is no replacement.
81
+ // The property name is the deprecated name, and the property value is the preferred name if
82
+ // any, or null/undefined if there is no replacement. This should be removed, along with
83
+ // the option, in the next major version.
84
+ allowFrequentDuplicateEvents: undefined,
80
85
  };
81
86
 
82
87
  function checkDeprecatedOptions(config) {
@@ -136,7 +141,7 @@ function validate(options, emitter, extraOptionDefs, logger) {
136
141
  const expectedType = optionDef.type || typeDescForValue(optionDef.default);
137
142
  const validator = optionDef.validator;
138
143
  if (validator) {
139
- const validated = validator(name, config, config[name], logger);
144
+ const validated = validator(name, config[name], logger);
140
145
  if (validated !== undefined) {
141
146
  ret[name] = validated;
142
147
  } else {
@@ -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
@@ -63,7 +63,6 @@ function initialize(env, user, specifiedOptions, platform, extraOptionDefs) {
63
63
 
64
64
  const requestor = Requestor(platform, options, environment);
65
65
 
66
- const seenRequests = {};
67
66
  let flags = {};
68
67
  let useLocalStorage;
69
68
  let streamActive;
@@ -181,15 +180,6 @@ function initialize(env, user, specifiedOptions, platform, extraOptionDefs) {
181
180
  const user = ident.getUser();
182
181
  const now = new Date();
183
182
  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
183
 
194
184
  const event = {
195
185
  kind: 'feature',
@@ -313,7 +303,7 @@ function initialize(env, user, specifiedOptions, platform, extraOptionDefs) {
313
303
  }
314
304
 
315
305
  for (const key in flags) {
316
- if (utils.objectHasOwnProperty(flags, key)) {
306
+ if (utils.objectHasOwnProperty(flags, key) && !flags[key].deleted) {
317
307
  results[key] = variationDetailInternal(key, null, !options.sendEventsOnlyForVariation).value;
318
308
  }
319
309
  }
package/src/messages.js CHANGED
@@ -182,6 +182,8 @@ const debugPostingDiagnosticEvent = function(event) {
182
182
 
183
183
  const invalidTagValue = name => `Config option "${name}" must only contain letters, numbers, ., _ or -.`;
184
184
 
185
+ const tagValueTooLong = name => `Value of "${name}" was longer than 64 characters and was discarded.`;
186
+
185
187
  module.exports = {
186
188
  bootstrapInvalid,
187
189
  bootstrapOldFormat,
@@ -217,6 +219,7 @@ module.exports = {
217
219
  streamClosing,
218
220
  streamConnecting,
219
221
  streamError,
222
+ tagValueTooLong,
220
223
  unknownCustomEventKey,
221
224
  unknownOption,
222
225
  userNotSpecified,
package/typings.d.ts CHANGED
@@ -173,10 +173,13 @@ declare module 'launchdarkly-js-sdk-common' {
173
173
  inlineUsersInEvents?: boolean;
174
174
 
175
175
  /**
176
- * Whether or not to send an analytics event for a flag evaluation even if the same flag was
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
- * By default, this is false (duplicate events within five minutes will be dropped).
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
 
@@ -701,7 +704,8 @@ declare module 'launchdarkly-js-sdk-common' {
701
704
  alias(user: LDUser, previousUser: LDUser): void;
702
705
 
703
706
  /**
704
- * Returns a map of all available flags to the current user's values.
707
+ * Returns a map of all available flags to the current user's values. This will send analytics
708
+ * events unless [[LDOptions.sendEventsOnlyForVariation]] is true.
705
709
  *
706
710
  * @returns
707
711
  * An object in which each key is a feature flag key and each value is the flag value.