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/package.json +3 -1
- package/src/AnonymousContextProcessor.js +95 -0
- package/src/ContextFilter.js +147 -0
- package/src/EventProcessor.js +30 -13
- package/src/EventSender.js +1 -1
- package/src/Identity.js +10 -11
- package/src/PersistentFlagStore.js +3 -3
- package/src/Requestor.js +4 -4
- package/src/Stream.js +5 -5
- package/src/__tests__/ContextFilter-test.js +470 -0
- package/src/__tests__/EventProcessor-test.js +50 -121
- package/src/__tests__/LDClient-events-test.js +9 -152
- package/src/__tests__/LDClient-inspectors-test.js +1 -1
- package/src/__tests__/LDClient-test.js +18 -15
- package/src/__tests__/TransientContextProcessor-test.js +115 -0
- package/src/__tests__/attributeReference-test.js +400 -0
- package/src/__tests__/configuration-test.js +20 -21
- package/src/__tests__/context-test.js +93 -0
- package/src/__tests__/diagnosticEvents-test.js +0 -4
- package/src/__tests__/utils-test.js +3 -9
- package/src/attributeReference.js +143 -0
- package/src/configuration.js +4 -8
- package/src/context.js +96 -0
- package/src/diagnosticEvents.js +0 -2
- package/src/index.js +76 -89
- package/src/messages.js +8 -8
- package/src/utils.js +18 -15
- package/test-types.ts +3 -7
- package/typings.d.ts +140 -76
- package/src/UserFilter.js +0 -75
- package/src/UserValidator.js +0 -56
- package/src/__tests__/UserFilter-test.js +0 -93
- package/src/__tests__/UserValidator-test.js +0 -57
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
|
|
106
|
+
* including a JSON entity body with the context object.
|
|
107
107
|
*
|
|
108
|
-
* Otherwise (by default) a GET request will be issued with the
|
|
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
|
|
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
|
|
162
|
-
* to LaunchDarkly in analytics events. You can also specify this on a per-
|
|
163
|
-
* with [[
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
392
|
-
* - `'TARGET_MATCH'`: The
|
|
393
|
-
* - `'RULE_MATCH'`: the
|
|
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
|
|
606
|
+
* Identifies a context to LaunchDarkly.
|
|
527
607
|
*
|
|
528
|
-
* Unlike the server-side SDKs, the client-side JavaScript SDKs maintain a current
|
|
529
|
-
* which is set at initialization time. You only need to call `identify()` if the
|
|
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
|
|
533
|
-
* finished, calls to [[variation]] will still return flag values for the previous
|
|
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
|
|
537
|
-
* The
|
|
616
|
+
* @param context
|
|
617
|
+
* The context properties. Must contain at least the `key` property.
|
|
538
618
|
* @param hash
|
|
539
|
-
* The signed
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
633
|
+
* Returns the client's current context.
|
|
554
634
|
*
|
|
555
|
-
* This is the
|
|
556
|
-
* been called, the initial
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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,
|
|
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: (
|
|
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;
|
package/src/UserValidator.js
DELETED
|
@@ -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
|
-
});
|