launchdarkly-js-sdk-common 5.0.3 → 5.1.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 CHANGED
@@ -2,6 +2,10 @@
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
+ ## [5.0.3] - 2023-03-21
6
+ ### Changed:
7
+ - Update `LDContext` to allow for key to be optional. This is used when making an anonymous context with a generated key.
8
+
5
9
  ## [5.0.2] - 2023-02-15
6
10
  ### Changed:
7
11
  - Removed usage of optional chaining (`?.`) to improve compatibility with projects which are using older transpilation tooling.
package/CODEOWNERS ADDED
@@ -0,0 +1,2 @@
1
+ # Repository Maintainers
2
+ * @launchdarkly/team-sdk
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## LaunchDarkly overview
6
6
 
7
- [LaunchDarkly](https://www.launchdarkly.com) is a feature management platform that serves over 100 billion feature flags daily to help teams build better software, faster. [Get started](https://docs.launchdarkly.com/home/getting-started) using LaunchDarkly today!
7
+ [LaunchDarkly](https://www.launchdarkly.com) is a feature management platform that serves trillions of feature flags daily to help teams build better software, faster. [Get started](https://docs.launchdarkly.com/home/getting-started) using LaunchDarkly today!
8
8
 
9
9
  [![Twitter Follow](https://img.shields.io/twitter/follow/launchdarkly.svg?style=social&label=Follow&maxAge=2592000)](https://twitter.com/intent/follow?screen_name=launchdarkly)
10
10
 
@@ -27,7 +27,7 @@ We encourage pull requests and other contributions from the community. Check out
27
27
  * Gradually roll out a feature to an increasing percentage of users, and track the effect that the feature has on key metrics (for instance, how likely is a user to complete a purchase if they have feature A versus feature B?).
28
28
  * Turn off a feature that you realize is causing performance problems in production, without needing to re-deploy, or even restart the application with a changed configuration file.
29
29
  * Grant access to certain features based on user attributes, like payment plan (eg: users on the ‘gold’ plan get access to more features than users in the ‘silver’ plan). Disable parts of your application to facilitate maintenance, without taking everything offline.
30
- * LaunchDarkly provides feature flag SDKs for a wide variety of languages and technologies. Check out [our documentation](https://docs.launchdarkly.com/sdk) for a complete list.
30
+ * LaunchDarkly provides feature flag SDKs for a wide variety of languages and technologies. Read [our documentation](https://docs.launchdarkly.com/sdk) for a complete list.
31
31
  * Explore LaunchDarkly
32
32
  * [launchdarkly.com](https://www.launchdarkly.com/ "LaunchDarkly Main Website") for more information
33
33
  * [docs.launchdarkly.com](https://docs.launchdarkly.com/ "LaunchDarkly Documentation") for our documentation and SDK reference guides
package/SECURITY.md ADDED
@@ -0,0 +1,5 @@
1
+ # Reporting and Fixing Security Issues
2
+
3
+ Please report all security issues to the LaunchDarkly security team by submitting a bug bounty report to our [HackerOne program](https://hackerone.com/launchdarkly?type=team). LaunchDarkly will triage and address all valid security issues following the response targets defined in our program policy. Valid security issues may be eligible for a bounty.
4
+
5
+ Please do not open issues or pull requests for security issues. This makes the problem immediately visible to everyone, including potentially malicious actors.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "launchdarkly-js-sdk-common",
3
- "version": "5.0.3",
3
+ "version": "5.1.0",
4
4
  "description": "LaunchDarkly SDK for JavaScript - common code",
5
5
  "author": "LaunchDarkly <team@launchdarkly.com>",
6
6
  "license": "Apache-2.0",
@@ -28,6 +28,7 @@
28
28
  "@babel/runtime": "7.6.3",
29
29
  "@rollup/plugin-replace": "^2.2.0",
30
30
  "@types/jest": "^27.4.1",
31
+ "@types/node": "12.12.6",
31
32
  "babel-eslint": "^10.1.0",
32
33
  "babel-jest": "^25.1.0",
33
34
  "cross-env": "^5.1.4",
@@ -16,23 +16,27 @@ function ContextFilter(config) {
16
16
  * @param {Object} context
17
17
  * @returns {string[]} A list of the attributes to filter.
18
18
  */
19
- const getAttributesToFilter = context =>
20
- (allAttributesPrivate
19
+ const getAttributesToFilter = (context, redactAnonymous) =>
20
+ (allAttributesPrivate || (redactAnonymous && context.anonymous)
21
21
  ? Object.keys(context)
22
22
  : [...privateAttributes, ...((context._meta && context._meta.privateAttributes) || [])]
23
23
  ).filter(attr => !protectedAttributes.some(protectedAttr => AttributeReference.compare(attr, protectedAttr)));
24
24
 
25
25
  /**
26
26
  * @param {Object} context
27
+ * @param {boolean} redactAnonymous
27
28
  * @returns {Object} A copy of the context with private attributes removed,
28
29
  * and the redactedAttributes meta populated.
29
30
  */
30
- const filterSingleKind = context => {
31
+ const filterSingleKind = (context, redactAnonymous) => {
31
32
  if (typeof context !== 'object' || context === null || Array.isArray(context)) {
32
33
  return undefined;
33
34
  }
34
35
 
35
- const { cloned, excluded } = AttributeReference.cloneExcluding(context, getAttributesToFilter(context));
36
+ const { cloned, excluded } = AttributeReference.cloneExcluding(
37
+ context,
38
+ getAttributesToFilter(context, redactAnonymous)
39
+ );
36
40
  cloned.key = String(cloned.key);
37
41
  if (excluded.length) {
38
42
  if (!cloned._meta) {
@@ -57,10 +61,11 @@ function ContextFilter(config) {
57
61
 
58
62
  /**
59
63
  * @param {Object} context
64
+ * @param {boolean} redactAnonymous
60
65
  * @returns {Object} A copy of the context with the private attributes removed,
61
66
  * and the redactedAttributes meta populated for each sub-context.
62
67
  */
63
- const filterMultiKind = context => {
68
+ const filterMultiKind = (context, redactAnonymous) => {
64
69
  const filtered = {
65
70
  kind: context.kind,
66
71
  };
@@ -68,7 +73,7 @@ function ContextFilter(config) {
68
73
 
69
74
  for (const contextKey of contextKeys) {
70
75
  if (contextKey !== 'kind') {
71
- const filteredContext = filterSingleKind(context[contextKey]);
76
+ const filteredContext = filterSingleKind(context[contextKey], redactAnonymous);
72
77
  if (filteredContext) {
73
78
  filtered[contextKey] = filteredContext;
74
79
  }
@@ -121,13 +126,13 @@ function ContextFilter(config) {
121
126
  return filtered;
122
127
  };
123
128
 
124
- filter.filter = context => {
129
+ filter.filter = (context, redactAnonymous = false) => {
125
130
  if (context.kind === undefined || context.kind === null) {
126
- return filterSingleKind(legacyToSingleKind(context));
131
+ return filterSingleKind(legacyToSingleKind(context), redactAnonymous);
127
132
  } else if (context.kind === 'multi') {
128
- return filterMultiKind(context);
133
+ return filterMultiKind(context, redactAnonymous);
129
134
  } else {
130
- return filterSingleKind(context);
135
+ return filterSingleKind(context, redactAnonymous);
131
136
  }
132
137
  };
133
138
 
@@ -50,6 +50,9 @@ function EventProcessor(
50
50
  if (e.kind === 'identify') {
51
51
  // identify events always have an inline context
52
52
  ret.context = contextFilter.filter(e.context);
53
+ } else if (e.kind === 'feature') {
54
+ // feature events always have an inline context
55
+ ret.context = contextFilter.filter(e.context, true);
53
56
  } else {
54
57
  ret.contextKeys = getContextKeysFromEvent(e);
55
58
  delete ret['context'];
@@ -136,8 +139,7 @@ function EventProcessor(
136
139
  }
137
140
  queue = [];
138
141
  logger.debug(messages.debugPostingEvents(eventsToSend.length));
139
- return eventSender.sendEvents(eventsToSend, mainEventsUrl).then(responses => {
140
- const responseInfo = responses && responses[0];
142
+ return eventSender.sendEvents(eventsToSend, mainEventsUrl).then(responseInfo => {
141
143
  if (responseInfo) {
142
144
  if (responseInfo.serverTime) {
143
145
  lastKnownPastTime = responseInfo.serverTime;
@@ -3,12 +3,8 @@ const utils = require('./utils');
3
3
  const { v1: uuidv1 } = require('uuid');
4
4
  const { getLDHeaders, transformHeaders } = require('./headers');
5
5
 
6
- const MAX_URL_LENGTH = 2000;
7
-
8
6
  function EventSender(platform, environmentId, options) {
9
- const imageUrlPath = '/a/' + environmentId + '.gif';
10
7
  const baseHeaders = utils.extend({ 'Content-Type': 'application/json' }, getLDHeaders(platform, options));
11
- const httpFallbackPing = platform.httpFallbackPing; // this will be set for us if we're in the browser SDK
12
8
  const sender = {};
13
9
 
14
10
  function getResponseInfo(result) {
@@ -23,7 +19,11 @@ function EventSender(platform, environmentId, options) {
23
19
  return ret;
24
20
  }
25
21
 
26
- sender.sendChunk = (events, url, isDiagnostic, usePost) => {
22
+ sender.sendEvents = (events, url, isDiagnostic) => {
23
+ if (!platform.httpRequest) {
24
+ return Promise.resolve();
25
+ }
26
+
27
27
  const jsonBody = JSON.stringify(events);
28
28
  const payloadId = isDiagnostic ? null : uuidv1();
29
29
 
@@ -55,31 +55,7 @@ function EventSender(platform, environmentId, options) {
55
55
  });
56
56
  }
57
57
 
58
- if (usePost) {
59
- return doPostRequest(true).catch(() => {});
60
- } else {
61
- httpFallbackPing && httpFallbackPing(url + imageUrlPath + '?d=' + utils.base64URLEncode(jsonBody));
62
- return Promise.resolve(); // we don't wait for this request to complete, it's just a one-way ping
63
- }
64
- };
65
-
66
- sender.sendEvents = function(events, url, isDiagnostic) {
67
- if (!platform.httpRequest) {
68
- return Promise.resolve();
69
- }
70
- const canPost = platform.httpAllowsPost();
71
- let chunks;
72
- if (canPost) {
73
- // no need to break up events into chunks if we can send a POST
74
- chunks = [events];
75
- } else {
76
- chunks = utils.chunkEventsForUrl(MAX_URL_LENGTH - url.length, events);
77
- }
78
- const results = [];
79
- for (let i = 0; i < chunks.length; i++) {
80
- results.push(sender.sendChunk(chunks[i], url, isDiagnostic, canPost));
81
- }
82
- return Promise.all(results);
58
+ return doPostRequest(true).catch(() => {});
83
59
  };
84
60
 
85
61
  return sender;
@@ -283,6 +283,11 @@ describe('when handling single kind contexts', () => {
283
283
  expect(uf.filter(anonymousContext)).toEqual(contextWithAllAttrsHidden);
284
284
  });
285
285
 
286
+ it('all attributes are redacted when anonymous', () => {
287
+ const uf = ContextFilter({});
288
+ expect(uf.filter(anonymousContext, true)).toEqual(contextWithAllAttrsHidden);
289
+ });
290
+
286
291
  it('converts non-boolean anonymous to boolean.', () => {
287
292
  const uf = ContextFilter({});
288
293
  expect(uf.filter({ kind: 'user', key: 'user', anonymous: 'string' })).toEqual({
@@ -330,6 +335,7 @@ describe('when handling mult-kind contexts', () => {
330
335
  user: {
331
336
  key: 'abc',
332
337
  name: 'alphabet',
338
+ anonymous: true,
333
339
  letters: ['a', 'b', 'c'],
334
340
  order: 3,
335
341
  object: {
@@ -342,6 +348,25 @@ describe('when handling mult-kind contexts', () => {
342
348
  },
343
349
  };
344
350
 
351
+ const orgAndUserContextWithAnonymousRedaction = {
352
+ kind: 'multi',
353
+ organization: {
354
+ key: 'LD',
355
+ rocks: true,
356
+ name: 'name',
357
+ department: {
358
+ name: 'sdk',
359
+ },
360
+ },
361
+ user: {
362
+ key: 'abc',
363
+ anonymous: true,
364
+ _meta: {
365
+ redactedAttributes: ['/letters', '/name', '/object', '/order'],
366
+ },
367
+ },
368
+ };
369
+
345
370
  const orgAndUserContextAllPrivate = {
346
371
  kind: 'multi',
347
372
  organization: {
@@ -352,6 +377,7 @@ describe('when handling mult-kind contexts', () => {
352
377
  },
353
378
  user: {
354
379
  key: 'abc',
380
+ anonymous: true,
355
381
  _meta: {
356
382
  redactedAttributes: ['/letters', '/name', '/object', '/order'],
357
383
  },
@@ -373,6 +399,7 @@ describe('when handling mult-kind contexts', () => {
373
399
  user: {
374
400
  key: 'abc',
375
401
  order: 3,
402
+ anonymous: true,
376
403
  object: {
377
404
  a: 'a',
378
405
  },
@@ -395,6 +422,7 @@ describe('when handling mult-kind contexts', () => {
395
422
  user: {
396
423
  key: 'abc',
397
424
  name: 'alphabet',
425
+ anonymous: true,
398
426
  order: 3,
399
427
  object: {
400
428
  a: 'a',
@@ -415,6 +443,11 @@ describe('when handling mult-kind contexts', () => {
415
443
  expect(uf.filter(orgAndUserContext)).toEqual(orgAndUserContextAllPrivate);
416
444
  });
417
445
 
446
+ it('it should remove attributes from all anonymous contexts', () => {
447
+ const uf = ContextFilter({});
448
+ expect(uf.filter(orgAndUserContext, true)).toEqual(orgAndUserContextWithAnonymousRedaction);
449
+ });
450
+
418
451
  it('it should apply private attributes from the context to the context.', () => {
419
452
  const uf = ContextFilter({});
420
453
  expect(uf.filter(orgAndUserContext)).toEqual(orgAndUserContextIncludedPrivate);
@@ -75,7 +75,7 @@ describe.each([
75
75
  expect(e.value).toEqual(source.value);
76
76
  expect(e.default).toEqual(source.default);
77
77
  expect(e.reason).toEqual(source.reason);
78
- checkUserInline(e, source, inlineUser);
78
+ expect(e.context).toEqual(inlineUser);
79
79
  }
80
80
 
81
81
  function checkCustomEvent(e, source) {
@@ -136,7 +136,7 @@ describe.each([
136
136
  expect(mockEventSender.calls.length()).toEqual(1);
137
137
  const output = (await mockEventSender.calls.take()).events;
138
138
  expect(output.length).toEqual(2);
139
- checkFeatureEvent(output[0], event, false);
139
+ checkFeatureEvent(output[0], event, false, eventContext);
140
140
  checkSummaryEvent(output[1]);
141
141
  });
142
142
  });
@@ -159,7 +159,7 @@ describe.each([
159
159
  expect(mockEventSender.calls.length()).toEqual(1);
160
160
  const output = (await mockEventSender.calls.take()).events;
161
161
  expect(output.length).toEqual(2);
162
- checkFeatureEvent(output[0], event, false);
162
+ checkFeatureEvent(output[0], event, false, eventContext);
163
163
  checkSummaryEvent(output[1]);
164
164
  });
165
165
  });
@@ -236,7 +236,7 @@ describe.each([
236
236
  expect(mockEventSender.calls.length()).toEqual(1);
237
237
  const output = (await mockEventSender.calls.take()).events;
238
238
  expect(output.length).toEqual(3);
239
- checkFeatureEvent(output[0], e, false);
239
+ checkFeatureEvent(output[0], e, false, { ...context, kind: context.kind || 'user' });
240
240
  checkFeatureEvent(output[1], e, true, { ...context, kind: context.kind || 'user' });
241
241
  checkSummaryEvent(output[2]);
242
242
  });
@@ -1,8 +1,6 @@
1
1
  import EventSender from '../EventSender';
2
2
  import * as utils from '../utils';
3
3
 
4
- import * as base64 from 'base64-js';
5
-
6
4
  import { respond, networkError } from './mockHttp';
7
5
  import * as stubPlatform from './stubPlatform';
8
6
 
@@ -20,74 +18,6 @@ describe('EventSender', () => {
20
18
  platform = stubPlatform.defaults();
21
19
  });
22
20
 
23
- function fakeImageCreator() {
24
- const ret = function(url) {
25
- ret.urls.push(url);
26
- };
27
- ret.urls = [];
28
- return ret;
29
- }
30
-
31
- function base64URLDecode(str) {
32
- let s = str;
33
- while (s.length % 4 !== 0) {
34
- s = s + '=';
35
- }
36
- s = s.replace(/_/g, '/').replace(/-/g, '+');
37
- const decodedBytes = base64.toByteArray(s);
38
- const decodedStr = String.fromCharCode.apply(String, decodedBytes);
39
- return decodeURIComponent(escape(decodedStr));
40
- }
41
-
42
- function decodeOutputFromUrl(url, baseUrl) {
43
- const prefix = baseUrl + '/a/' + envId + '.gif?d=';
44
- if (!url.startsWith(prefix)) {
45
- throw 'URL "' + url + '" did not have expected prefix "' + prefix + '"';
46
- }
47
- return JSON.parse(base64URLDecode(url.substring(prefix.length)));
48
- }
49
-
50
- describe('using image endpoint when CORS is not available', () => {
51
- it('should encode events in a single chunk if they fit', async () => {
52
- const server = platform.testing.http.newServer();
53
- const imageCreator = fakeImageCreator();
54
- const platformWithoutCors = { ...platform, httpAllowsPost: () => false, httpFallbackPing: imageCreator };
55
- const sender = EventSender(platformWithoutCors, envId);
56
- const event1 = { kind: 'identify', key: 'userKey1' };
57
- const event2 = { kind: 'identify', key: 'userKey2' };
58
- const events = [event1, event2];
59
-
60
- await sender.sendEvents(events, server.url);
61
-
62
- const urls = imageCreator.urls;
63
- expect(urls.length).toEqual(1);
64
- expect(decodeOutputFromUrl(urls[0], server.url)).toEqual(events);
65
-
66
- expect(server.requests.length()).toEqual(0);
67
- });
68
-
69
- it('should send events in multiple chunks if necessary', async () => {
70
- const server = platform.testing.http.newServer();
71
- const imageCreator = fakeImageCreator();
72
- const platformWithoutCors = { ...platform, httpAllowsPost: () => false, httpFallbackPing: imageCreator };
73
- const sender = EventSender(platformWithoutCors, envId);
74
- const events = [];
75
- for (let i = 0; i < 80; i++) {
76
- events.push({ kind: 'identify', key: 'thisIsALongUserKey' + i });
77
- }
78
-
79
- await sender.sendEvents(events, server.url);
80
-
81
- const urls = imageCreator.urls;
82
- expect(urls.length).toEqual(3);
83
- expect(decodeOutputFromUrl(urls[0], server.url)).toEqual(events.slice(0, 31));
84
- expect(decodeOutputFromUrl(urls[1], server.url)).toEqual(events.slice(31, 61));
85
- expect(decodeOutputFromUrl(urls[2], server.url)).toEqual(events.slice(61, 80));
86
-
87
- expect(server.requests.length()).toEqual(0);
88
- });
89
- });
90
-
91
21
  describe('using POST when CORS is available', () => {
92
22
  it('should send all events in request body', async () => {
93
23
  const server = platform.testing.http.newServer();
@@ -239,6 +169,20 @@ describe('EventSender', () => {
239
169
  });
240
170
  });
241
171
 
172
+ describe('verify sendEvents response format', () => {
173
+ it('includes date header', async () => {
174
+ const options = { sendLDHeaders: true };
175
+ const server = platform.testing.http.newServer();
176
+ server.byDefault(respond(202, { date: 'Wed, 21 Oct 2015 07:28:00 GMT' }, '{}'));
177
+
178
+ const sender = EventSender(platform, envId, options);
179
+ const event = { kind: 'identify', key: 'userKey' };
180
+ const responseInfo = await sender.sendEvents([event], server.url);
181
+
182
+ expect(responseInfo.serverTime).toEqual(1445412480000);
183
+ });
184
+ });
185
+
242
186
  describe('When HTTP requests are not available at all', () => {
243
187
  it('should silently discard events', async () => {
244
188
  const server = platform.testing.http.newServer();
@@ -13,8 +13,6 @@ import { MockHttpState } from './mockHttp';
13
13
  // httpRequest?: (method, url, headers, body, sync) => requestProperties
14
14
  // requestProperties.promise: Promise // resolves to { status, header: (name) => value, body } or rejects for a network error
15
15
  // requestProperties.cancel?: () => void // provided if it's possible to cancel requests in this implementation
16
- // httpAllowsPost: boolean // true if we can do cross-origin POST requests
17
- // httpFallbackPing?: (url) => {} // method for doing an HTTP GET without awaiting the result (i.e. browser image mechanism)
18
16
  // getCurrentUrl: () => string // returns null if we're not in a browser
19
17
  // isDoNotTrack: () => boolean
20
18
  // localStorage: {
@@ -45,8 +43,6 @@ export function defaults() {
45
43
  httpRequest: mockHttpState.doRequest,
46
44
  diagnosticSdkData: { name: 'stub-sdk' },
47
45
  diagnosticPlatformData: { name: 'stub-platform' },
48
- httpAllowsPost: () => true,
49
- httpAllowsSync: () => true,
50
46
  getCurrentUrl: () => currentUrl,
51
47
  isDoNotTrack: () => doNotTrack,
52
48
  eventSourceFactory: (url, options) => {
@@ -62,7 +62,7 @@ export function MockEventSender() {
62
62
  calls,
63
63
  sendEvents: (events, url) => {
64
64
  calls.add({ events, url });
65
- return Promise.resolve([{ serverTime, status }]);
65
+ return Promise.resolve({ serverTime, status });
66
66
  },
67
67
  setServerTime: time => {
68
68
  serverTime = time;
@@ -1,4 +1,4 @@
1
- import { appendUrlPath, base64URLEncode, chunkEventsForUrl, getLDUserAgentString, wrapPromiseCallback } from '../utils';
1
+ import { appendUrlPath, getLDUserAgentString, wrapPromiseCallback } from '../utils';
2
2
 
3
3
  import * as stubPlatform from './stubPlatform';
4
4
 
@@ -63,15 +63,4 @@ describe('utils', () => {
63
63
  expect(ua).toEqual('stubClient/7.8.9');
64
64
  });
65
65
  });
66
-
67
- describe('chunkEventsForUrl', () => {
68
- it('should properly chunk the list of events', () => {
69
- const context = { key: 'foo', kind: 'user' };
70
- const event = { kind: 'identify', key: context.key };
71
- const eventLength = base64URLEncode(JSON.stringify(event)).length;
72
- const events = [event, event, event, event, event];
73
- const chunks = chunkEventsForUrl(eventLength * 2, events);
74
- expect(chunks).toEqual([[event, event], [event, event], [event]]);
75
- });
76
- });
77
66
  });
package/src/utils.js CHANGED
@@ -112,46 +112,6 @@ function transformVersionedValuesToValues(flagsState) {
112
112
  return ret;
113
113
  }
114
114
 
115
- /**
116
- * Returns an array of event groups each of which can be safely URL-encoded
117
- * without hitting the safe maximum URL length of certain browsers.
118
- *
119
- * @param {number} maxLength maximum URL length targeted
120
- * @param {Array[Object}]} events queue of events to divide
121
- * @returns Array[Array[Object]]
122
- */
123
- function chunkEventsForUrl(maxLength, events) {
124
- const allEvents = events.slice(0);
125
- const allChunks = [];
126
- let remainingSpace = maxLength;
127
- let chunk;
128
-
129
- while (allEvents.length > 0) {
130
- chunk = [];
131
-
132
- while (remainingSpace > 0) {
133
- const event = allEvents.shift();
134
- if (!event) {
135
- break;
136
- }
137
- remainingSpace = remainingSpace - base64URLEncode(JSON.stringify(event)).length;
138
- // If we are over the max size, put this one back on the queue
139
- // to try in the next round, unless this event alone is larger
140
- // than the limit, in which case, screw it, and try it anyway.
141
- if (remainingSpace < 0 && chunk.length > 0) {
142
- allEvents.unshift(event);
143
- } else {
144
- chunk.push(event);
145
- }
146
- }
147
-
148
- remainingSpace = maxLength;
149
- allChunks.push(chunk);
150
- }
151
-
152
- return allChunks;
153
- }
154
-
155
115
  function getLDUserAgentString(platform) {
156
116
  const version = platform.version || '?';
157
117
  return platform.userAgent + '/' + version;
@@ -188,7 +148,6 @@ module.exports = {
188
148
  appendUrlPath,
189
149
  base64URLEncode,
190
150
  btoa,
191
- chunkEventsForUrl,
192
151
  clone,
193
152
  deepEquals,
194
153
  extend,
package/typings.d.ts CHANGED
@@ -848,14 +848,14 @@ declare module 'launchdarkly-js-sdk-common' {
848
848
  off(key: string, callback: (...args: any[]) => void, context?: any): void;
849
849
 
850
850
  /**
851
- * Track page events to use in goals or A/B tests.
851
+ * Track page events to use in metrics (goals) or Experimentation.
852
852
  *
853
853
  * LaunchDarkly automatically tracks pageviews and clicks that are specified in the
854
- * Goals section of their dashboard. This can be used to track custom goals or other
855
- * events that do not currently have goals.
854
+ * Metrics section of their dashboard. This can be used to track custom metrics or other
855
+ * events that do not currently have metrics.
856
856
  *
857
857
  * @param key
858
- * The name of the event, which may correspond to a goal in A/B tests.
858
+ * The name of the event, which may correspond to a metric in experiments.
859
859
  * @param data
860
860
  * Additional information to associate with the event.
861
861
  * @param metricValue