launchdarkly-js-sdk-common 4.3.2 → 5.0.0-alpha.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/typings.d.ts CHANGED
@@ -103,9 +103,9 @@ declare module 'launchdarkly-js-sdk-common' {
103
103
  * Whether or not to use the REPORT verb to fetch flag settings.
104
104
  *
105
105
  * If this is true, flag settings will be fetched with a REPORT request
106
- * including a JSON entity body with the user object.
106
+ * including a JSON entity body with the context object.
107
107
  *
108
- * Otherwise (by default) a GET request will be issued with the user passed as
108
+ * Otherwise (by default) a GET request will be issued with the context passed as
109
109
  * a base64 URL-encoded path parameter.
110
110
  *
111
111
  * Do not use unless advised by LaunchDarkly.
@@ -150,7 +150,7 @@ declare module 'launchdarkly-js-sdk-common' {
150
150
  sendEvents?: boolean;
151
151
 
152
152
  /**
153
- * Whether all user attributes (except the user key) should be marked as private, and
153
+ * Whether all context attributes (except the context key) should be marked as private, and
154
154
  * not sent to LaunchDarkly in analytics events.
155
155
  *
156
156
  * By default, this is false.
@@ -158,30 +158,11 @@ declare module 'launchdarkly-js-sdk-common' {
158
158
  allAttributesPrivate?: boolean;
159
159
 
160
160
  /**
161
- * The names of user attributes that should be marked as private, and not sent
162
- * to LaunchDarkly in analytics events. You can also specify this on a per-user basis
163
- * with [[LDUser.privateAttributeNames]].
161
+ * The names of context attributes that should be marked as private, and not sent
162
+ * to LaunchDarkly in analytics events. You can also specify this on a per-context basis
163
+ * with [[LDContextMeta.privateAttributes]].
164
164
  */
165
- privateAttributeNames?: Array<string>;
166
-
167
- /**
168
- * Whether to include full user details in every analytics event.
169
- *
170
- * The default is `false`: events will only include the user key, except for one "index" event
171
- * that provides the full details for the user.
172
- */
173
- inlineUsersInEvents?: boolean;
174
-
175
- /**
176
- * This option is deprecated, and setting it has no effect.
177
- *
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.
183
- */
184
- allowFrequentDuplicateEvents?: boolean;
165
+ privateAttributes?: Array<string>;
185
166
 
186
167
  /**
187
168
  * Whether analytics events should be sent only when you call variation (true), or also when you
@@ -255,18 +236,10 @@ declare module 'launchdarkly-js-sdk-common' {
255
236
  */
256
237
  wrapperVersion?: string;
257
238
 
258
- /**
259
- * Whether to disable the automatic sending of an alias event when [[identify]] is
260
- * called with a non-anonymous user when the previous user is anonymous.
261
- *
262
- * The default value is `false`.
263
- */
264
- autoAliasingOptOut?: boolean;
265
-
266
239
  /**
267
240
  * Information about the application where the LaunchDarkly SDK is running.
268
241
  */
269
- application?: {
242
+ application?: {
270
243
  /**
271
244
  * A unique identifier representing the application where the LaunchDarkly SDK is running.
272
245
  *
@@ -294,6 +267,113 @@ declare module 'launchdarkly-js-sdk-common' {
294
267
  inspectors?: LDInspection[];
295
268
  }
296
269
 
270
+ /**
271
+ *
272
+ * TKTK
273
+ *
274
+ * Meta attributes are used to control behavioral aspects of the Context.
275
+ * They cannot be addressed in targetting rules.
276
+ */
277
+ export interface LDContextMeta {
278
+ /**
279
+ * An optional secondary key for a context.
280
+ *
281
+ * TKTK
282
+ *
283
+ * This affects [feature flag targeting](https://docs.launchdarkly.com/home/flags/targeting-users#targeting-rules-based-on-user-attributes)
284
+ * as follows: if you have chosen to bucket context by a specific attribute, the secondary key (if set)
285
+ * is used to further distinguish between contexts which are otherwise identical according to that attribute.
286
+ */
287
+ secondary?: string;
288
+
289
+ /**
290
+ *
291
+ * TKTK
292
+ *
293
+ * Specifies a list of attribute names (either built-in or custom) which should be
294
+ * marked as private, and not sent to LaunchDarkly in analytics events. This is in
295
+ * addition to any private attributes designated in the global configuration
296
+ * with [[LDOptions.privateAttributes]] or [[LDOptions.allAttributesPrivate]].
297
+ */
298
+ privateAttributes?: string[];
299
+ }
300
+
301
+ /**
302
+ * TKTK
303
+ */
304
+ interface LDContextCommon {
305
+ /**
306
+ * If true, the context will _not_ appear on the Contexts page in the LaunchDarkly dashboard.
307
+ */
308
+ anonymous?: boolean;
309
+
310
+ /**
311
+ * A unique string identifying a context.
312
+ */
313
+ key: string;
314
+
315
+ /**
316
+ * The context's name.
317
+ *
318
+ * You can search for contexts on the Contexts page by name.
319
+ */
320
+ name?: string;
321
+
322
+ /**
323
+ *
324
+ * TODO: U2C We will need some uniform description for this.
325
+ *
326
+ * Meta attributes are used to control behavioral aspects of the Context.
327
+ * They cannot be addressed in targetting rules.
328
+ */
329
+ _meta?: LDContextMeta;
330
+
331
+ /**
332
+ * Any additional attributes associated with the context.
333
+ */
334
+ [attribute: string]: any;
335
+ }
336
+
337
+
338
+ /**
339
+ *
340
+ * TTKTK
341
+ *
342
+ * A single-kind context.
343
+ */
344
+ interface LDSingleKindContext extends LDContextCommon {
345
+ /**
346
+ * The kind of the context.
347
+ */
348
+ kind: string;
349
+ }
350
+
351
+ /**
352
+ *
353
+ * TKTK
354
+ *
355
+ * A multi-kind context.
356
+ */
357
+ interface LDMultiKindContext {
358
+ /**
359
+ * The kind of the context.
360
+ */
361
+ kind: "multi",
362
+
363
+ /**
364
+ * The contexts which compose this multi-kind context.
365
+ *
366
+ * These should be of type LDContextCommon. "multi" is to allow
367
+ * for the top level "kind" attribute.
368
+ */
369
+ [kind: string]: "multi" | LDContextCommon;
370
+ }
371
+
372
+ /**
373
+ * A LaunchDarkly context object.
374
+ */
375
+ export type LDContext = LDUser | LDSingleKindContext | LDMultiKindContext;
376
+
297
377
  /**
298
378
  * A LaunchDarkly user object.
299
379
  */
@@ -388,9 +468,9 @@ declare module 'launchdarkly-js-sdk-common' {
388
468
  * The general category of the reason:
389
469
  *
390
470
  * - `'OFF'`: The flag was off and therefore returned its configured off value.
391
- * - `'FALLTHROUGH'`: The flag was on but the user did not match any targets or rules.
392
- * - `'TARGET_MATCH'`: The user key was specifically targeted for this flag.
393
- * - `'RULE_MATCH'`: the user matched one of the flag's rules.
471
+ * - `'FALLTHROUGH'`: The flag was on but the context did not match any targets or rules.
472
+ * - `'TARGET_MATCH'`: The context key was specifically targeted for this flag.
473
+ * - `'RULE_MATCH'`: the context matched one of the flag's rules.
394
474
  * - `'PREREQUISITE_FAILED'`: The flag was considered off because it had at least one
395
475
  * prerequisite flag that either was off or did not return the desired variation.
396
476
  * - `'ERROR'`: The flag could not be evaluated, e.g. because it does not exist or due
@@ -523,39 +603,39 @@ declare module 'launchdarkly-js-sdk-common' {
523
603
  waitForInitialization(): Promise<void>;
524
604
 
525
605
  /**
526
- * Identifies a user to LaunchDarkly.
606
+ * Identifies a context to LaunchDarkly.
527
607
  *
528
- * Unlike the server-side SDKs, the client-side JavaScript SDKs maintain a current user state,
529
- * which is set at initialization time. You only need to call `identify()` if the user has changed
608
+ * Unlike the server-side SDKs, the client-side JavaScript SDKs maintain a current context state,
609
+ * which is set at initialization time. You only need to call `identify()` if the context has changed
530
610
  * since then.
531
611
  *
532
- * Changing the current user also causes all feature flag values to be reloaded. Until that has
533
- * finished, calls to [[variation]] will still return flag values for the previous user. You can
612
+ * Changing the current context also causes all feature flag values to be reloaded. Until that has
613
+ * finished, calls to [[variation]] will still return flag values for the previous context. You can
534
614
  * use a callback or a Promise to determine when the new flag values are available.
535
615
  *
536
- * @param user
537
- * The user properties. Must contain at least the `key` property.
616
+ * @param context
617
+ * The context properties. Must contain at least the `key` property.
538
618
  * @param hash
539
- * The signed user key if you are using [Secure Mode](https://docs.launchdarkly.com/sdk/features/secure-mode#configuring-secure-mode-in-the-javascript-client-side-sdk).
619
+ * The signed context key if you are using [Secure Mode](https://docs.launchdarkly.com/sdk/features/secure-mode#configuring-secure-mode-in-the-javascript-client-side-sdk).
540
620
  * @param onDone
541
- * A function which will be called as soon as the flag values for the new user are available,
621
+ * A function which will be called as soon as the flag values for the new context are available,
542
622
  * with two parameters: an error value (if any), and an [[LDFlagSet]] containing the new values
543
623
  * (which can also be obtained by calling [[variation]]). If the callback is omitted, you will
544
624
  * receive a Promise instead.
545
625
  * @returns
546
626
  * If you provided a callback, then nothing. Otherwise, a Promise which resolve once the flag
547
- * values for the new user are available, providing an [[LDFlagSet]] containing the new values
627
+ * values for the new context are available, providing an [[LDFlagSet]] containing the new values
548
628
  * (which can also be obtained by calling [[variation]]).
549
629
  */
550
- identify(user: LDUser, hash?: string, onDone?: (err: Error | null, flags: LDFlagSet | null) => void): Promise<LDFlagSet>;
630
+ identify(context: LDContext, hash?: string, onDone?: (err: Error | null, flags: LDFlagSet | null) => void): Promise<LDFlagSet>;
551
631
 
552
632
  /**
553
- * Returns the client's current user.
633
+ * Returns the client's current context.
554
634
  *
555
- * This is the user that was most recently passed to [[identify]], or, if [[identify]] has never
556
- * been called, the initial user specified when the client was created.
635
+ * This is the context that was most recently passed to [[identify]], or, if [[identify]] has never
636
+ * been called, the initial context specified when the client was created.
557
637
  */
558
- getUser(): LDUser;
638
+ getContext(): LDContext;
559
639
 
560
640
  /**
561
641
  * Flushes all pending analytics events.
@@ -575,10 +655,10 @@ declare module 'launchdarkly-js-sdk-common' {
575
655
  flush(onDone?: () => void): Promise<void>;
576
656
 
577
657
  /**
578
- * Determines the variation of a feature flag for the current user.
658
+ * Determines the variation of a feature flag for the current context.
579
659
  *
580
660
  * In the client-side JavaScript SDKs, this is always a fast synchronous operation because all of
581
- * the feature flag values for the current user have already been loaded into memory.
661
+ * the feature flag values for the current context have already been loaded into memory.
582
662
  *
583
663
  * @param key
584
664
  * The unique key of the feature flag.
@@ -590,7 +670,7 @@ declare module 'launchdarkly-js-sdk-common' {
590
670
  variation(key: string, defaultValue?: LDFlagValue): LDFlagValue;
591
671
 
592
672
  /**
593
- * Determines the variation of a feature flag for a user, along with information about how it was
673
+ * Determines the variation of a feature flag for a context, along with information about how it was
594
674
  * calculated.
595
675
  *
596
676
  * Note that this will only work if you have set `evaluationExplanations` to true in [[LDOptions]].
@@ -639,7 +719,7 @@ declare module 'launchdarkly-js-sdk-common' {
639
719
  * The callback parameter is an Error object. If you do not listen for "error"
640
720
  * events, then the errors will be logged with `console.log()`.
641
721
  * - `"change"`: The client has received new feature flag data. This can happen either
642
- * because you have switched users with [[identify]], or because the client has a
722
+ * because you have switched contexts with [[identify]], or because the client has a
643
723
  * stream connection and has received a live change to a flag value (see below).
644
724
  * The callback parameter is an [[LDFlagChangeset]].
645
725
  * - `"change:FLAG-KEY"`: The client has received a new value for a specific flag
@@ -694,23 +774,7 @@ declare module 'launchdarkly-js-sdk-common' {
694
774
  track(key: string, data?: any, metricValue?: number): void;
695
775
 
696
776
  /**
697
- * Associates two users for analytics purposes.
698
- *
699
- * This can be helpful in the situation where a person is represented by multiple
700
- * LaunchDarkly users. This may happen, for example, when a person initially logs into
701
- * an application-- the person might be represented by an anonymous user prior to logging
702
- * in and a different user after logging in, as denoted by a different user key.
703
- *
704
- * @param user
705
- * The newly identified user.
706
- * @param previousUser
707
- * The previously identified user.
708
- */
709
- alias(user: LDUser, previousUser: LDUser): void;
710
-
711
- /**
712
- * Returns a map of all available flags to the current user's values. This will send analytics
713
- * events unless [[LDOptions.sendEventsOnlyForVariation]] is true.
777
+ * Returns a map of all available flags to the current context's values.
714
778
  *
715
779
  * @returns
716
780
  * An object in which each key is a feature flag key and each value is the flag value.
@@ -848,7 +912,7 @@ declare module 'launchdarkly-js-sdk-common' {
848
912
  * wrapper SDKs which have different methods of tracking when a flag was accessed. It is not called when a call is made
849
913
  * to allFlags.
850
914
  */
851
- method: (flagKey: string, flagDetail: LDEvaluationDetail, user: LDUser) => void;
915
+ method: (flagKey: string, flagDetail: LDEvaluationDetail, context: LDContext) => void;
852
916
  }
853
917
 
854
918
  /**
@@ -922,7 +986,7 @@ declare module 'launchdarkly-js-sdk-common' {
922
986
  /**
923
987
  * This method will be called when an identify operation completes.
924
988
  */
925
- method: (user: LDUser) => void;
989
+ method: (context: LDContext) => void;
926
990
  }
927
991
 
928
992
  type LDInspection = LDInspectionFlagUsedHandler | LDInspectionFlagDetailsChangedHandler
package/src/UserFilter.js DELETED
@@ -1,75 +0,0 @@
1
- const utils = require('./utils');
2
-
3
- /**
4
- * The UserFilter object transforms user objects into objects suitable to be sent as JSON to
5
- * the server, hiding any private user attributes.
6
- *
7
- * @param {Object} the LaunchDarkly client configuration object
8
- **/
9
- function UserFilter(config) {
10
- const filter = {};
11
- const allAttributesPrivate = config.allAttributesPrivate;
12
- const privateAttributeNames = config.privateAttributeNames || [];
13
- const ignoreAttrs = { key: true, custom: true, anonymous: true };
14
- const allowedTopLevelAttrs = {
15
- key: true,
16
- secondary: true,
17
- ip: true,
18
- country: true,
19
- email: true,
20
- firstName: true,
21
- lastName: true,
22
- avatar: true,
23
- name: true,
24
- anonymous: true,
25
- custom: true,
26
- };
27
-
28
- filter.filterUser = function(user) {
29
- if (!user) {
30
- return null;
31
- }
32
- const userPrivateAttrs = user.privateAttributeNames || [];
33
-
34
- const isPrivateAttr = function(name) {
35
- return (
36
- !ignoreAttrs[name] &&
37
- (allAttributesPrivate || userPrivateAttrs.indexOf(name) !== -1 || privateAttributeNames.indexOf(name) !== -1)
38
- );
39
- };
40
- const filterAttrs = function(props, isAttributeAllowed) {
41
- return Object.keys(props).reduce(
42
- (acc, name) => {
43
- const ret = acc;
44
- if (isAttributeAllowed(name)) {
45
- if (isPrivateAttr(name)) {
46
- // add to hidden list
47
- ret[1][name] = true;
48
- } else {
49
- ret[0][name] = props[name];
50
- }
51
- }
52
- return ret;
53
- },
54
- [{}, {}]
55
- );
56
- };
57
- const result = filterAttrs(user, key => allowedTopLevelAttrs[key]);
58
- const filteredProps = result[0];
59
- let removedAttrs = result[1];
60
- if (user.custom) {
61
- const customResult = filterAttrs(user.custom, () => true);
62
- filteredProps.custom = customResult[0];
63
- removedAttrs = utils.extend({}, removedAttrs, customResult[1]);
64
- }
65
- const removedAttrNames = Object.keys(removedAttrs);
66
- if (removedAttrNames.length) {
67
- removedAttrNames.sort();
68
- filteredProps.privateAttrs = removedAttrNames;
69
- }
70
- return filteredProps;
71
- };
72
- return filter;
73
- }
74
-
75
- module.exports = UserFilter;
@@ -1,56 +0,0 @@
1
- const { v1: uuidv1 } = require('uuid');
2
-
3
- const errors = require('./errors');
4
- const messages = require('./messages');
5
- const utils = require('./utils');
6
-
7
- // Transforms the user object if necessary to make sure it has a valid key.
8
- // 1. If a key is present, but is not a string, change it to a string.
9
- // 2. If no key is present, and "anonymous" is true, use a UUID as a key. This is cached in local
10
- // storage if possible.
11
- // 3. If there is no key (or no user object), return an error.
12
-
13
- const ldUserIdKey = 'ld:$anonUserId';
14
-
15
- function UserValidator(persistentStorage) {
16
- function getCachedUserId() {
17
- return persistentStorage.get(ldUserIdKey);
18
- }
19
-
20
- function setCachedUserId(id) {
21
- return persistentStorage.set(ldUserIdKey, id);
22
- }
23
-
24
- const ret = {};
25
-
26
- // Validates the user, returning a Promise that resolves to the validated user, or rejects if there is an error.
27
- ret.validateUser = user => {
28
- if (!user) {
29
- return Promise.reject(new errors.LDInvalidUserError(messages.userNotSpecified()));
30
- }
31
-
32
- const userOut = utils.clone(user);
33
- if (userOut.key !== null && userOut.key !== undefined) {
34
- userOut.key = userOut.key.toString();
35
- return Promise.resolve(userOut);
36
- }
37
- if (userOut.anonymous) {
38
- return getCachedUserId().then(cachedId => {
39
- if (cachedId) {
40
- userOut.key = cachedId;
41
- return userOut;
42
- } else {
43
- const id = uuidv1();
44
- userOut.key = id;
45
- return setCachedUserId(id).then(() => userOut);
46
- }
47
- });
48
- } else {
49
- return Promise.reject(new errors.LDInvalidUserError(messages.invalidUser()));
50
- }
51
- };
52
-
53
- return ret;
54
- }
55
-
56
- module.exports = UserValidator;
@@ -1,93 +0,0 @@
1
- import UserFilter from '../UserFilter';
2
-
3
- describe('UserFilter', () => {
4
- // users to serialize
5
- const user = {
6
- key: 'abc',
7
- firstName: 'Sue',
8
- custom: { bizzle: 'def', dizzle: 'ghi' },
9
- };
10
-
11
- const userSpecifyingOwnPrivateAttr = {
12
- key: 'abc',
13
- firstName: 'Sue',
14
- custom: { bizzle: 'def', dizzle: 'ghi' },
15
- privateAttributeNames: ['dizzle', 'unused'],
16
- };
17
-
18
- const userWithUnknownTopLevelAttrs = {
19
- key: 'abc',
20
- firstName: 'Sue',
21
- species: 'human',
22
- hatSize: 6,
23
- custom: { bizzle: 'def', dizzle: 'ghi' },
24
- };
25
-
26
- const anonUser = {
27
- key: 'abc',
28
- anonymous: true,
29
- custom: { bizzle: 'def', dizzle: 'ghi' },
30
- };
31
-
32
- // expected results from serializing user
33
- const userWithAllAttrsHidden = {
34
- key: 'abc',
35
- custom: {},
36
- privateAttrs: ['bizzle', 'dizzle', 'firstName'],
37
- };
38
-
39
- const userWithSomeAttrsHidden = {
40
- key: 'abc',
41
- custom: { dizzle: 'ghi' },
42
- privateAttrs: ['bizzle', 'firstName'],
43
- };
44
-
45
- const userWithOwnSpecifiedAttrHidden = {
46
- key: 'abc',
47
- firstName: 'Sue',
48
- custom: { bizzle: 'def' },
49
- privateAttrs: ['dizzle'],
50
- };
51
-
52
- const anonUserWithAllAttrsHidden = {
53
- key: 'abc',
54
- anonymous: true,
55
- custom: {},
56
- privateAttrs: ['bizzle', 'dizzle'],
57
- };
58
-
59
- it('includes all user attributes by default', () => {
60
- const uf = UserFilter({});
61
- expect(uf.filterUser(user)).toEqual(user);
62
- });
63
-
64
- it('hides all except key if allAttributesPrivate is true', () => {
65
- const uf = UserFilter({ allAttributesPrivate: true });
66
- expect(uf.filterUser(user)).toEqual(userWithAllAttrsHidden);
67
- });
68
-
69
- it('hides some attributes if privateAttributeNames is set', () => {
70
- const uf = UserFilter({ privateAttributeNames: ['firstName', 'bizzle'] });
71
- expect(uf.filterUser(user)).toEqual(userWithSomeAttrsHidden);
72
- });
73
-
74
- it('hides attributes specified in per-user privateAttrs', () => {
75
- const uf = UserFilter({});
76
- expect(uf.filterUser(userSpecifyingOwnPrivateAttr)).toEqual(userWithOwnSpecifiedAttrHidden);
77
- });
78
-
79
- it('looks at both per-user privateAttrs and global config', () => {
80
- const uf = UserFilter({ privateAttributeNames: ['firstName', 'bizzle'] });
81
- expect(uf.filterUser(userSpecifyingOwnPrivateAttr)).toEqual(userWithAllAttrsHidden);
82
- });
83
-
84
- it('strips unknown top-level attributes', () => {
85
- const uf = UserFilter({});
86
- expect(uf.filterUser(userWithUnknownTopLevelAttrs)).toEqual(user);
87
- });
88
-
89
- it('leaves the "anonymous" attribute as is', () => {
90
- const uf = UserFilter({ allAttributesPrivate: true });
91
- expect(uf.filterUser(anonUser)).toEqual(anonUserWithAllAttrsHidden);
92
- });
93
- });
@@ -1,57 +0,0 @@
1
- import UserValidator from '../UserValidator';
2
-
3
- describe('UserValidator', () => {
4
- let localStorage;
5
- let logger;
6
- let uv;
7
-
8
- beforeEach(() => {
9
- localStorage = {};
10
- logger = {
11
- warn: jest.fn(),
12
- };
13
- uv = UserValidator(localStorage, logger);
14
- });
15
-
16
- it('rejects null user', async () => {
17
- await expect(uv.validateUser(null)).rejects.toThrow();
18
- });
19
-
20
- it('leaves user with string key unchanged', async () => {
21
- const u = { key: 'someone', name: 'me' };
22
- expect(await uv.validateUser(u)).toEqual(u);
23
- });
24
-
25
- it('stringifies non-string key', async () => {
26
- const u0 = { key: 123, name: 'me' };
27
- const u1 = { key: '123', name: 'me' };
28
- expect(await uv.validateUser(u0)).toEqual(u1);
29
- });
30
-
31
- it('uses cached key for anonymous user', async () => {
32
- const cachedKey = 'thing';
33
- let storageKey;
34
- localStorage.get = async key => {
35
- storageKey = key;
36
- return cachedKey;
37
- };
38
- const u = { anonymous: true };
39
- expect(await uv.validateUser(u)).toEqual({ key: cachedKey, anonymous: true });
40
- expect(storageKey).toEqual('ld:$anonUserId');
41
- });
42
-
43
- it('generates and stores key for anonymous user', async () => {
44
- let storageKey;
45
- let storedValue;
46
- localStorage.get = async () => null;
47
- localStorage.set = async (key, value) => {
48
- storageKey = key;
49
- storedValue = value;
50
- };
51
- const u0 = { anonymous: true };
52
- const u1 = await uv.validateUser(u0);
53
- expect(storedValue).toEqual(expect.anything());
54
- expect(u1).toEqual({ key: storedValue, anonymous: true });
55
- expect(storageKey).toEqual('ld:$anonUserId');
56
- });
57
- });