@frontegg/entitlements-javascript-commons 1.1.0-alpha.1 → 1.1.0-alpha.3
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/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/plans/index.d.ts +2 -0
- package/dist/plans/index.js +18 -0
- package/dist/plans/index.js.map +1 -0
- package/dist/plans/plan.evaluator.d.ts +2 -0
- package/dist/plans/plan.evaluator.js +21 -0
- package/dist/plans/plan.evaluator.js.map +1 -0
- package/dist/plans/types.d.ts +8 -0
- package/dist/plans/types.js +3 -0
- package/dist/plans/types.js.map +1 -0
- package/dist/user-entitlements/index.d.ts +3 -3
- package/dist/user-entitlements/index.js +3 -3
- package/dist/user-entitlements/index.js.map +1 -1
- package/dist/user-entitlements/is-entitled-to-feature/evaluators/direct-entitlement.evaluator.d.ts +2 -0
- package/dist/user-entitlements/is-entitled-to-feature/evaluators/direct-entitlement.evaluator.js +20 -0
- package/dist/user-entitlements/is-entitled-to-feature/evaluators/direct-entitlement.evaluator.js.map +1 -0
- package/dist/user-entitlements/is-entitled-to-feature/evaluators/feature-flag.evaluator.d.ts +2 -0
- package/dist/user-entitlements/is-entitled-to-feature/evaluators/feature-flag.evaluator.js +23 -0
- package/dist/user-entitlements/is-entitled-to-feature/evaluators/feature-flag.evaluator.js.map +1 -0
- package/dist/user-entitlements/is-entitled-to-feature/evaluators/index.d.ts +2 -0
- package/dist/user-entitlements/is-entitled-to-feature/evaluators/index.js +11 -0
- package/dist/user-entitlements/is-entitled-to-feature/evaluators/index.js.map +1 -0
- package/dist/user-entitlements/is-entitled-to-feature/evaluators/plan-targeting-rules.evaluator.d.ts +2 -0
- package/dist/user-entitlements/is-entitled-to-feature/evaluators/plan-targeting-rules.evaluator.js +29 -0
- package/dist/user-entitlements/is-entitled-to-feature/evaluators/plan-targeting-rules.evaluator.js.map +1 -0
- package/dist/user-entitlements/is-entitled-to-feature/evaluators/types.d.ts +2 -0
- package/dist/user-entitlements/is-entitled-to-feature/evaluators/types.js +3 -0
- package/dist/user-entitlements/is-entitled-to-feature/evaluators/types.js.map +1 -0
- package/dist/user-entitlements/is-entitled-to-feature/index.d.ts +1 -0
- package/dist/user-entitlements/is-entitled-to-feature/index.js +18 -0
- package/dist/user-entitlements/is-entitled-to-feature/index.js.map +1 -0
- package/dist/user-entitlements/{is-entitled.evaluator.d.ts → is-entitled-to-feature/is-entitled-to-feature.evaluator.d.ts} +1 -2
- package/dist/user-entitlements/is-entitled-to-feature/is-entitled-to-feature.evaluator.js +17 -0
- package/dist/user-entitlements/is-entitled-to-feature/is-entitled-to-feature.evaluator.js.map +1 -0
- package/dist/user-entitlements/is-entitled-to-permission/index.d.ts +1 -0
- package/dist/user-entitlements/is-entitled-to-permission/index.js +18 -0
- package/dist/user-entitlements/is-entitled-to-permission/index.js.map +1 -0
- package/dist/user-entitlements/is-entitled-to-permission/is-entitled-to-permission.evaluator.d.ts +2 -0
- package/dist/user-entitlements/is-entitled-to-permission/is-entitled-to-permission.evaluator.js +30 -0
- package/dist/user-entitlements/is-entitled-to-permission/is-entitled-to-permission.evaluator.js.map +1 -0
- package/dist/user-entitlements/types.d.ts +7 -2
- package/dist/user-entitlements/types.js.map +1 -1
- package/dist/user-entitlements/{attributes.utils.d.ts → utils/attributes.utils.d.ts} +1 -1
- package/dist/user-entitlements/utils/attributes.utils.js.map +1 -0
- package/dist/user-entitlements/utils/entitlement-results.utils.d.ts +3 -0
- package/dist/user-entitlements/utils/entitlement-results.utils.js +25 -0
- package/dist/user-entitlements/utils/entitlement-results.utils.js.map +1 -0
- package/dist/user-entitlements/utils/flatten.utils.js.map +1 -0
- package/dist/user-entitlements/utils/index.d.ts +3 -0
- package/dist/user-entitlements/utils/index.js +20 -0
- package/dist/user-entitlements/utils/index.js.map +1 -0
- package/dist/user-entitlements/{permissions.utils.d.ts → utils/permissions.utils.d.ts} +1 -1
- package/dist/user-entitlements/utils/permissions.utils.js.map +1 -0
- package/docs/CHANGELOG.md +14 -0
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/user-entitlements/index.ts +3 -3
- package/src/user-entitlements/is-entitled-to-feature/evaluators/direct-entitlement.evaluator.ts +28 -0
- package/src/user-entitlements/is-entitled-to-feature/evaluators/feature-flag.evaluator.ts +24 -0
- package/src/user-entitlements/is-entitled-to-feature/evaluators/index.ts +8 -0
- package/src/user-entitlements/is-entitled-to-feature/evaluators/plan-targeting-rules.evaluator.ts +30 -0
- package/src/user-entitlements/is-entitled-to-feature/evaluators/tests/direct-entitlements.evaluator.spec.ts +81 -0
- package/src/user-entitlements/is-entitled-to-feature/evaluators/tests/feature-flag.evaluator.spec.ts +57 -0
- package/src/user-entitlements/is-entitled-to-feature/evaluators/tests/plan-targeting-rules.evaluator.spec.ts +70 -0
- package/src/user-entitlements/is-entitled-to-feature/evaluators/types.ts +7 -0
- package/src/user-entitlements/is-entitled-to-feature/index.ts +1 -0
- package/src/user-entitlements/is-entitled-to-feature/is-entitled-to-feature.evaluator.ts +18 -0
- package/src/user-entitlements/is-entitled-to-feature/tests/is-entitled-to-feature.evaluator.spec.ts +68 -0
- package/src/user-entitlements/is-entitled-to-permission/index.ts +1 -0
- package/src/user-entitlements/is-entitled-to-permission/is-entitled-to-permission.evaluator.ts +38 -0
- package/src/user-entitlements/is-entitled-to-permission/tests/is-entitled-to-permission.evaluator.spec.ts +79 -0
- package/src/user-entitlements/types.ts +11 -4
- package/src/user-entitlements/{attributes.utils.ts → utils/attributes.utils.ts} +1 -1
- package/src/user-entitlements/utils/entitlement-results.utils.ts +22 -0
- package/src/user-entitlements/utils/index.ts +3 -0
- package/src/user-entitlements/{permissions.utils.ts → utils/permissions.utils.ts} +1 -1
- package/src/user-entitlements/{tests → utils/tests}/attributes.utils.spec.ts +1 -1
- package/src/user-entitlements/utils/tests/entitlement-results.utils.spec.ts +46 -0
- package/src/user-entitlements/{tests → utils/tests}/permissions.utils.spec.ts +1 -1
- package/dist/user-entitlements/attributes.utils.js.map +0 -1
- package/dist/user-entitlements/flatten.utils.js.map +0 -1
- package/dist/user-entitlements/is-entitled.evaluator.js +0 -59
- package/dist/user-entitlements/is-entitled.evaluator.js.map +0 -1
- package/dist/user-entitlements/permissions.utils.js.map +0 -1
- package/src/user-entitlements/is-entitled.evaluator.ts +0 -82
- package/src/user-entitlements/tests/is-entitled.evaluator.spec.ts +0 -298
- /package/dist/user-entitlements/{attributes.utils.js → utils/attributes.utils.js} +0 -0
- /package/dist/user-entitlements/{flatten.utils.d.ts → utils/flatten.utils.d.ts} +0 -0
- /package/dist/user-entitlements/{flatten.utils.js → utils/flatten.utils.js} +0 -0
- /package/dist/user-entitlements/{permissions.utils.js → utils/permissions.utils.js} +0 -0
- /package/src/user-entitlements/{flatten.utils.ts → utils/flatten.utils.ts} +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Attributes, EntitlementResult, NotEntitledJustification, UserEntitlementsContext } from '../../types';
|
|
2
|
+
import { evaluateFeatureFlag } from '../../../feature-flags';
|
|
3
|
+
import { prepareAttributes } from '../../utils/attributes.utils';
|
|
4
|
+
import { TreatmentEnum } from '../../../rules';
|
|
5
|
+
|
|
6
|
+
export function featureFlagEvaluator(
|
|
7
|
+
featureKey: string,
|
|
8
|
+
userEntitlementsContext: UserEntitlementsContext,
|
|
9
|
+
attributes: Attributes = {},
|
|
10
|
+
): EntitlementResult {
|
|
11
|
+
const feature = userEntitlementsContext.features[featureKey];
|
|
12
|
+
if (feature && feature.featureFlag) {
|
|
13
|
+
const preparedAttributes = prepareAttributes(attributes);
|
|
14
|
+
const { treatment } = evaluateFeatureFlag(feature.featureFlag, preparedAttributes);
|
|
15
|
+
if (treatment === TreatmentEnum.True) {
|
|
16
|
+
return { isEntitled: true };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
isEntitled: false,
|
|
22
|
+
justification: NotEntitledJustification.MISSING_FEATURE,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { directEntitlementEvalutor } from './direct-entitlement.evaluator';
|
|
2
|
+
import { featureFlagEvaluator } from './feature-flag.evaluator';
|
|
3
|
+
import { planTargetingRulesEvalutor } from './plan-targeting-rules.evaluator';
|
|
4
|
+
import { IsEntitledEvaluator } from './types';
|
|
5
|
+
|
|
6
|
+
export function getIsEntitledEvaluators(): IsEntitledEvaluator[] {
|
|
7
|
+
return [directEntitlementEvalutor, featureFlagEvaluator, planTargetingRulesEvalutor];
|
|
8
|
+
}
|
package/src/user-entitlements/is-entitled-to-feature/evaluators/plan-targeting-rules.evaluator.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Attributes, EntitlementResult, NotEntitledJustification, UserEntitlementsContext } from '../../types';
|
|
2
|
+
import { prepareAttributes } from '../../utils/attributes.utils';
|
|
3
|
+
import { TreatmentEnum } from '../../../rules';
|
|
4
|
+
import { evaluatePlan } from '../../../plans';
|
|
5
|
+
|
|
6
|
+
export function planTargetingRulesEvalutor(
|
|
7
|
+
featureKey: string,
|
|
8
|
+
userEntitlementsContext: UserEntitlementsContext,
|
|
9
|
+
attributes: Attributes = {},
|
|
10
|
+
): EntitlementResult {
|
|
11
|
+
const feature = userEntitlementsContext.features[featureKey];
|
|
12
|
+
if (feature && feature.planIds && feature.planIds.length > 0) {
|
|
13
|
+
const preparedAttributes = prepareAttributes(attributes);
|
|
14
|
+
const plans = userEntitlementsContext.plans;
|
|
15
|
+
for (const planId of feature.planIds) {
|
|
16
|
+
const plan = plans[planId];
|
|
17
|
+
if (plan) {
|
|
18
|
+
const { treatment } = evaluatePlan(plan, preparedAttributes);
|
|
19
|
+
if (treatment === TreatmentEnum.True) {
|
|
20
|
+
return { isEntitled: true };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
isEntitled: false,
|
|
28
|
+
justification: NotEntitledJustification.MISSING_FEATURE,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { NO_EXPIRATION_TIME, NotEntitledJustification } from '../../../types';
|
|
2
|
+
import { directEntitlementEvalutor } from '../direct-entitlement.evaluator';
|
|
3
|
+
|
|
4
|
+
describe('directEntitlementEvalutor', () => {
|
|
5
|
+
describe('entitled', () => {
|
|
6
|
+
test('feature is entitled with no expiration date', async () => {
|
|
7
|
+
const result = directEntitlementEvalutor('feature-key', {
|
|
8
|
+
features: {
|
|
9
|
+
'feature-key': {
|
|
10
|
+
expireTime: NO_EXPIRATION_TIME,
|
|
11
|
+
linkedPermissions: [],
|
|
12
|
+
planIds: [],
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
permissions: {},
|
|
16
|
+
plans: {},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
expect(result).toEqual({ isEntitled: true });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('feature is entitled with a valid expiration date', async () => {
|
|
23
|
+
const result = directEntitlementEvalutor('feature-key', {
|
|
24
|
+
features: {
|
|
25
|
+
'feature-key': {
|
|
26
|
+
expireTime: Date.now() + 3600,
|
|
27
|
+
linkedPermissions: [],
|
|
28
|
+
planIds: [],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
permissions: {},
|
|
32
|
+
plans: {},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(result).toEqual({ isEntitled: true });
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe('not-entitled', () => {
|
|
39
|
+
test('feature is not entitled', async () => {
|
|
40
|
+
const result = directEntitlementEvalutor('feature-key', {
|
|
41
|
+
features: {
|
|
42
|
+
'feature-key': {
|
|
43
|
+
expireTime: null,
|
|
44
|
+
linkedPermissions: [],
|
|
45
|
+
planIds: [],
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
permissions: {},
|
|
49
|
+
plans: {},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE });
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('feature is entitled with an expired expiration date', async () => {
|
|
56
|
+
const result = directEntitlementEvalutor('feature-key', {
|
|
57
|
+
features: {
|
|
58
|
+
'feature-key': {
|
|
59
|
+
expireTime: Date.now() - 3600,
|
|
60
|
+
linkedPermissions: [],
|
|
61
|
+
planIds: [],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
permissions: {},
|
|
65
|
+
plans: {},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.BUNDLE_EXPIRED });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('feature does not exist', async () => {
|
|
72
|
+
const result = directEntitlementEvalutor('feature-key', {
|
|
73
|
+
features: {},
|
|
74
|
+
permissions: {},
|
|
75
|
+
plans: {},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE });
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
package/src/user-entitlements/is-entitled-to-feature/evaluators/tests/feature-flag.evaluator.spec.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as AttributesUtils from '../../../utils/attributes.utils';
|
|
2
|
+
import * as FeatureFlagEvaluator from '../../../../feature-flags/feature-flag.evaluator';
|
|
3
|
+
import { TreatmentEnum } from '../../../../rules';
|
|
4
|
+
import { featureFlagEvaluator } from '../feature-flag.evaluator';
|
|
5
|
+
import { NotEntitledJustification } from '../../../types';
|
|
6
|
+
describe('featureFlagEvaluator', () => {
|
|
7
|
+
beforeAll(async () => {
|
|
8
|
+
jest.spyOn(AttributesUtils, 'prepareAttributes').mockReturnValue({ testAttribute: 'test-value' });
|
|
9
|
+
});
|
|
10
|
+
describe('entitled', () => {
|
|
11
|
+
test('feature flag evaluation return truthy', async () => {
|
|
12
|
+
jest.spyOn(FeatureFlagEvaluator, 'evaluateFeatureFlag').mockReturnValue({ treatment: TreatmentEnum.True });
|
|
13
|
+
const result = featureFlagEvaluator('feature-key', {
|
|
14
|
+
features: {
|
|
15
|
+
'feature-key': {
|
|
16
|
+
expireTime: null,
|
|
17
|
+
linkedPermissions: [],
|
|
18
|
+
planIds: [],
|
|
19
|
+
featureFlag: { defaultTreatment: TreatmentEnum.True, on: true, offTreatment: TreatmentEnum.False },
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
permissions: {},
|
|
23
|
+
plans: {},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
expect(result).toEqual({ isEntitled: true });
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe('not-entitled', () => {
|
|
30
|
+
test('feature flag evaluation return falsy', async () => {
|
|
31
|
+
jest.spyOn(FeatureFlagEvaluator, 'evaluateFeatureFlag').mockReturnValue({ treatment: TreatmentEnum.False });
|
|
32
|
+
const result = featureFlagEvaluator('feature-key', {
|
|
33
|
+
features: {
|
|
34
|
+
'feature-key': {
|
|
35
|
+
expireTime: null,
|
|
36
|
+
linkedPermissions: [],
|
|
37
|
+
planIds: [],
|
|
38
|
+
featureFlag: { defaultTreatment: TreatmentEnum.False, on: true, offTreatment: TreatmentEnum.False },
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
permissions: {},
|
|
42
|
+
plans: {},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE });
|
|
46
|
+
});
|
|
47
|
+
test('feataure does not exist', async () => {
|
|
48
|
+
const result = featureFlagEvaluator('feature-key', {
|
|
49
|
+
features: {},
|
|
50
|
+
permissions: {},
|
|
51
|
+
plans: {},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE });
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as PlanEvaluator from '../../../../plans/plan.evaluator';
|
|
2
|
+
import { TreatmentEnum } from '../../../../rules';
|
|
3
|
+
import { NotEntitledJustification } from '../../../types';
|
|
4
|
+
import { planTargetingRulesEvalutor } from '../plan-targeting-rules.evaluator';
|
|
5
|
+
describe('planTargetingRulesEvalutor', () => {
|
|
6
|
+
describe('entitled', () => {
|
|
7
|
+
test('some of the plans targeting rules return truthy', async () => {
|
|
8
|
+
jest.spyOn(PlanEvaluator, 'evaluatePlan').mockReturnValueOnce({ treatment: TreatmentEnum.True });
|
|
9
|
+
jest.spyOn(PlanEvaluator, 'evaluatePlan').mockReturnValueOnce({ treatment: TreatmentEnum.False });
|
|
10
|
+
|
|
11
|
+
const result = planTargetingRulesEvalutor('feature-key', {
|
|
12
|
+
features: {
|
|
13
|
+
'feature-key': {
|
|
14
|
+
expireTime: null,
|
|
15
|
+
linkedPermissions: [],
|
|
16
|
+
planIds: ['plan-1', 'plan-2'],
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
permissions: {},
|
|
20
|
+
plans: {
|
|
21
|
+
'plan-1': {
|
|
22
|
+
defaultTreatment: TreatmentEnum.True,
|
|
23
|
+
},
|
|
24
|
+
'plan-2': {
|
|
25
|
+
defaultTreatment: TreatmentEnum.False,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
expect(result).toEqual({ isEntitled: true });
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
describe('not-entitled', () => {
|
|
34
|
+
test('all of the plans targeting rules return falsy', async () => {
|
|
35
|
+
jest.spyOn(PlanEvaluator, 'evaluatePlan').mockReturnValueOnce({ treatment: TreatmentEnum.False });
|
|
36
|
+
jest.spyOn(PlanEvaluator, 'evaluatePlan').mockReturnValueOnce({ treatment: TreatmentEnum.False });
|
|
37
|
+
|
|
38
|
+
const result = planTargetingRulesEvalutor('feature-key', {
|
|
39
|
+
features: {
|
|
40
|
+
'feature-key': {
|
|
41
|
+
expireTime: null,
|
|
42
|
+
linkedPermissions: [],
|
|
43
|
+
planIds: ['plan-1', 'plan-2'],
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
permissions: {},
|
|
47
|
+
plans: {
|
|
48
|
+
'plan-1': {
|
|
49
|
+
defaultTreatment: TreatmentEnum.False,
|
|
50
|
+
},
|
|
51
|
+
'plan-2': {
|
|
52
|
+
defaultTreatment: TreatmentEnum.False,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('feature does not exist', async () => {
|
|
61
|
+
const result = planTargetingRulesEvalutor('feature-key', {
|
|
62
|
+
features: {},
|
|
63
|
+
permissions: {},
|
|
64
|
+
plans: {},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE });
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './is-entitled-to-feature.evaluator';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { EntitlementResult, NotEntitledJustification, UserEntitlementsContext, Attributes } from '../types';
|
|
2
|
+
import { getResult, shouldContinue } from '../utils';
|
|
3
|
+
import { getIsEntitledEvaluators } from './evaluators';
|
|
4
|
+
export function evaluateIsEntitledToFeature(
|
|
5
|
+
featureKey: string,
|
|
6
|
+
userEntitlementsContext: UserEntitlementsContext,
|
|
7
|
+
attributes: Attributes = {},
|
|
8
|
+
): EntitlementResult {
|
|
9
|
+
const entitlementResults: EntitlementResult[] = [];
|
|
10
|
+
for (const evaluator of getIsEntitledEvaluators()) {
|
|
11
|
+
entitlementResults.push(evaluator(featureKey, userEntitlementsContext, attributes));
|
|
12
|
+
if (!shouldContinue(entitlementResults)) {
|
|
13
|
+
break;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return getResult(entitlementResults);
|
|
18
|
+
}
|
package/src/user-entitlements/is-entitled-to-feature/tests/is-entitled-to-feature.evaluator.spec.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as IsEntitledEvaluators from '../evaluators';
|
|
2
|
+
import { evaluateIsEntitledToFeature } from '../is-entitled-to-feature.evaluator';
|
|
3
|
+
import { EntitlementResult, NotEntitledJustification, NO_EXPIRATION_TIME, UserEntitlementsContext } from '../../types';
|
|
4
|
+
|
|
5
|
+
describe('evaluateIsEntitledToFeature', () => {
|
|
6
|
+
const mockTruthyEvaluator = (): EntitlementResult => ({ isEntitled: true });
|
|
7
|
+
const mockFalsyMissingFeatureEvaluator = (): EntitlementResult => ({
|
|
8
|
+
isEntitled: false,
|
|
9
|
+
justification: NotEntitledJustification.MISSING_PERMISSION,
|
|
10
|
+
});
|
|
11
|
+
const mockFalsyExpiredEvaluator = (): EntitlementResult => ({
|
|
12
|
+
isEntitled: false,
|
|
13
|
+
justification: NotEntitledJustification.BUNDLE_EXPIRED,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('entitled', () => {
|
|
17
|
+
test('given all evaluators return truthy, feature is entitled', async () => {
|
|
18
|
+
jest
|
|
19
|
+
.spyOn(IsEntitledEvaluators, 'getIsEntitledEvaluators')
|
|
20
|
+
.mockReturnValue([mockTruthyEvaluator, mockTruthyEvaluator]);
|
|
21
|
+
const result = evaluateIsEntitledToFeature('feauture-key', {
|
|
22
|
+
features: {},
|
|
23
|
+
permissions: {},
|
|
24
|
+
plans: {},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
expect(result).toEqual({ isEntitled: true });
|
|
28
|
+
});
|
|
29
|
+
test('given some evaluators return truthy, feature is entitled', async () => {
|
|
30
|
+
jest
|
|
31
|
+
.spyOn(IsEntitledEvaluators, 'getIsEntitledEvaluators')
|
|
32
|
+
.mockReturnValue([mockTruthyEvaluator, mockFalsyMissingFeatureEvaluator]);
|
|
33
|
+
const result = evaluateIsEntitledToFeature('feauture-key', {
|
|
34
|
+
features: {},
|
|
35
|
+
permissions: {},
|
|
36
|
+
plans: {},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
expect(result).toEqual({ isEntitled: true });
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe('not-entitled', () => {
|
|
43
|
+
test('given all evaluators return falsy with missing feature, feature is not entitled, justified with missing feature', async () => {
|
|
44
|
+
jest
|
|
45
|
+
.spyOn(IsEntitledEvaluators, 'getIsEntitledEvaluators')
|
|
46
|
+
.mockReturnValue([mockFalsyMissingFeatureEvaluator, mockFalsyMissingFeatureEvaluator]);
|
|
47
|
+
const result = evaluateIsEntitledToFeature('feauture-key', {
|
|
48
|
+
features: {},
|
|
49
|
+
permissions: {},
|
|
50
|
+
plans: {},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE });
|
|
54
|
+
});
|
|
55
|
+
test('given all evaluators return falsy, some with missing feature, some with bundle expired, feature is not entitled, justified with bundle expired', async () => {
|
|
56
|
+
jest
|
|
57
|
+
.spyOn(IsEntitledEvaluators, 'getIsEntitledEvaluators')
|
|
58
|
+
.mockReturnValue([mockFalsyMissingFeatureEvaluator, mockFalsyExpiredEvaluator]);
|
|
59
|
+
const result = evaluateIsEntitledToFeature('feauture-key', {
|
|
60
|
+
features: {},
|
|
61
|
+
permissions: {},
|
|
62
|
+
plans: {},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.BUNDLE_EXPIRED });
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './is-entitled-to-permission.evaluator';
|
package/src/user-entitlements/is-entitled-to-permission/is-entitled-to-permission.evaluator.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { evaluateIsEntitledToFeature } from '../is-entitled-to-feature/is-entitled-to-feature.evaluator';
|
|
2
|
+
import { UserEntitlementsContext, Attributes, EntitlementResult, NotEntitledJustification } from '../types';
|
|
3
|
+
import { getResult, shouldContinue } from '../utils';
|
|
4
|
+
import { checkPermission } from '../utils/permissions.utils';
|
|
5
|
+
|
|
6
|
+
export function evaluateIsEntitledToPermissions(
|
|
7
|
+
permissionKey: string,
|
|
8
|
+
userEntitlementsContext: UserEntitlementsContext,
|
|
9
|
+
attributes?: Attributes,
|
|
10
|
+
): EntitlementResult {
|
|
11
|
+
const hasPermission = checkPermission(userEntitlementsContext.permissions, permissionKey);
|
|
12
|
+
if (!hasPermission) {
|
|
13
|
+
return { isEntitled: false, justification: NotEntitledJustification.MISSING_PERMISSION };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const linkedFeatures = getLinkedFeatures(permissionKey, userEntitlementsContext);
|
|
17
|
+
|
|
18
|
+
if (!linkedFeatures.length) {
|
|
19
|
+
return { isEntitled: true };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const entitlementResults: EntitlementResult[] = [];
|
|
23
|
+
for (const featureKey of linkedFeatures) {
|
|
24
|
+
entitlementResults.push(evaluateIsEntitledToFeature(featureKey, userEntitlementsContext, attributes));
|
|
25
|
+
|
|
26
|
+
if (!shouldContinue(entitlementResults)) {
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return getResult(entitlementResults);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getLinkedFeatures(permissionKey: string, userEntitlementsContext: UserEntitlementsContext): string[] {
|
|
35
|
+
return Object.keys(userEntitlementsContext.features).filter((featureKey) =>
|
|
36
|
+
userEntitlementsContext.features[featureKey].linkedPermissions.includes(permissionKey),
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { evaluateIsEntitledToPermissions } from '../is-entitled-to-permission.evaluator';
|
|
2
|
+
import { NO_EXPIRATION_TIME, NotEntitledJustification, UserEntitlementsContext } from '../../types';
|
|
3
|
+
import * as IsEntitledToFeatureEvaluator from '../../is-entitled-to-feature/is-entitled-to-feature.evaluator';
|
|
4
|
+
describe('evaluateIsEntitledToPermission', () => {
|
|
5
|
+
describe('entitled', () => {
|
|
6
|
+
test('permission granted, no linked feature/s to it', async () => {
|
|
7
|
+
const userEntitlementContext: UserEntitlementsContext = {
|
|
8
|
+
features: {},
|
|
9
|
+
plans: {},
|
|
10
|
+
|
|
11
|
+
permissions: { 'test.permission': true },
|
|
12
|
+
};
|
|
13
|
+
const result = evaluateIsEntitledToPermissions('test.permission', userEntitlementContext, {});
|
|
14
|
+
|
|
15
|
+
expect(result).toEqual({ isEntitled: true });
|
|
16
|
+
});
|
|
17
|
+
test('permission granted with linked feature/s, feature is entitled', async () => {
|
|
18
|
+
jest.spyOn(IsEntitledToFeatureEvaluator, 'evaluateIsEntitledToFeature').mockReturnValue({ isEntitled: true });
|
|
19
|
+
|
|
20
|
+
const userEntitlementContext: UserEntitlementsContext = {
|
|
21
|
+
features: {
|
|
22
|
+
'test-feature': { planIds: [], expireTime: NO_EXPIRATION_TIME, linkedPermissions: ['test.permission'] },
|
|
23
|
+
},
|
|
24
|
+
plans: {},
|
|
25
|
+
|
|
26
|
+
permissions: { 'test.permission': true },
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const result = evaluateIsEntitledToPermissions('test.permission', userEntitlementContext, {});
|
|
30
|
+
|
|
31
|
+
expect(result).toEqual({ isEntitled: true });
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('not entitled', () => {
|
|
36
|
+
test('permission not granted', async () => {
|
|
37
|
+
const userEntitlementContext: UserEntitlementsContext = {
|
|
38
|
+
features: {},
|
|
39
|
+
plans: {},
|
|
40
|
+
permissions: {},
|
|
41
|
+
};
|
|
42
|
+
const result = evaluateIsEntitledToPermissions('test.permission', userEntitlementContext, {});
|
|
43
|
+
|
|
44
|
+
expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.MISSING_PERMISSION });
|
|
45
|
+
});
|
|
46
|
+
test('permission granted with linked feature/s, no feature is entiteld', async () => {
|
|
47
|
+
jest
|
|
48
|
+
.spyOn(IsEntitledToFeatureEvaluator, 'evaluateIsEntitledToFeature')
|
|
49
|
+
.mockReturnValue({ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE });
|
|
50
|
+
|
|
51
|
+
const userEntitlementContext: UserEntitlementsContext = {
|
|
52
|
+
features: { 'test-feature': { planIds: [], expireTime: null, linkedPermissions: ['test.permission'] } },
|
|
53
|
+
plans: {},
|
|
54
|
+
permissions: { 'test.permission': true },
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const result = evaluateIsEntitledToPermissions('test.permission', userEntitlementContext, {});
|
|
58
|
+
|
|
59
|
+
expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE });
|
|
60
|
+
});
|
|
61
|
+
test('permission granted with linked feature/s, no feature is entiteld with expired bundle', async () => {
|
|
62
|
+
jest
|
|
63
|
+
.spyOn(IsEntitledToFeatureEvaluator, 'evaluateIsEntitledToFeature')
|
|
64
|
+
.mockReturnValue({ isEntitled: false, justification: NotEntitledJustification.BUNDLE_EXPIRED });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const userEntitlementContext: UserEntitlementsContext = {
|
|
68
|
+
features: {
|
|
69
|
+
'test-feature': { planIds: [], expireTime: Date.now() - 3600, linkedPermissions: ['test.permission'] },
|
|
70
|
+
},
|
|
71
|
+
plans: {},
|
|
72
|
+
permissions: { 'test.permission': true },
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const result = evaluateIsEntitledToPermissions('test.permission', userEntitlementContext, {});
|
|
76
|
+
|
|
77
|
+
expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.BUNDLE_EXPIRED });
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
import { FeatureFlag } from '../feature-flags/types';
|
|
2
|
+
import { Plan } from '../plans';
|
|
2
3
|
export type UserEntitlementsContext = {
|
|
3
4
|
features: Record<
|
|
4
5
|
string,
|
|
5
6
|
{
|
|
7
|
+
planIds: string[];
|
|
6
8
|
expireTime: number | null;
|
|
7
9
|
linkedPermissions: string[];
|
|
8
10
|
featureFlag?: FeatureFlag;
|
|
9
11
|
}
|
|
10
12
|
>;
|
|
13
|
+
plans: Record<string, Plan>;
|
|
11
14
|
permissions: Permissions;
|
|
12
15
|
};
|
|
13
16
|
|
|
14
|
-
export type EntitlementResult =
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
17
|
+
export type EntitlementResult =
|
|
18
|
+
| {
|
|
19
|
+
isEntitled: true;
|
|
20
|
+
}
|
|
21
|
+
| {
|
|
22
|
+
isEntitled: false;
|
|
23
|
+
justification: NotEntitledJustification;
|
|
24
|
+
};
|
|
18
25
|
|
|
19
26
|
export enum NotEntitledJustification {
|
|
20
27
|
MISSING_FEATURE = 'MISSING_FEATURE',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Attributes, JwtAttributes, FronteggAttributes } from '
|
|
1
|
+
import { Attributes, JwtAttributes, FronteggAttributes } from '../types';
|
|
2
2
|
import { flatten } from './flatten.utils';
|
|
3
3
|
/**
|
|
4
4
|
* Merges both `custom` and `jwt` records, map Frontegg attributes and modifies record keys with corrisponding prefixes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { EntitlementResult, NotEntitledJustification } from '../types';
|
|
2
|
+
export function getResult(entitlementResults: EntitlementResult[]): EntitlementResult {
|
|
3
|
+
let hasExpired = false;
|
|
4
|
+
for (const entitlementResult of entitlementResults) {
|
|
5
|
+
if (entitlementResult.isEntitled) {
|
|
6
|
+
return entitlementResult;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (entitlementResult.justification === NotEntitledJustification.BUNDLE_EXPIRED) {
|
|
10
|
+
hasExpired = true;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
isEntitled: false,
|
|
16
|
+
justification: hasExpired ? NotEntitledJustification.BUNDLE_EXPIRED : NotEntitledJustification.MISSING_FEATURE,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function shouldContinue(entitlementResults: EntitlementResult[]): boolean {
|
|
21
|
+
return entitlementResults.every(({ isEntitled }) => !isEntitled);
|
|
22
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Permissions } from '
|
|
1
|
+
import { Permissions } from '../types';
|
|
2
2
|
export function checkPermission(permissions: Permissions, requiredPermission: string): boolean {
|
|
3
3
|
return Object.keys(permissions).some((permissionKey) =>
|
|
4
4
|
createPermissionCheckRegex(permissionKey).test(requiredPermission),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as AttributesUtils from '../attributes.utils';
|
|
2
|
-
import { Attributes, FronteggAttributes, JwtAttributes } from '
|
|
2
|
+
import { Attributes, FronteggAttributes, JwtAttributes } from '../../types';
|
|
3
3
|
describe('prepareAttributes', () => {
|
|
4
4
|
test('given custom & jwt attributes, expected is merged & flatten attributes record', () => {
|
|
5
5
|
const attributes: Attributes = {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { EntitlementResult, NotEntitledJustification } from '../../types';
|
|
2
|
+
import { getResult, shouldContinue } from '../entitlement-results.utils';
|
|
3
|
+
describe('getResult', () => {
|
|
4
|
+
describe('entitled', () => {
|
|
5
|
+
test('given some of the results are truthy, exected is entitled', async () => {
|
|
6
|
+
const entitlementsResults: EntitlementResult[] = [
|
|
7
|
+
{ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE },
|
|
8
|
+
{ isEntitled: true },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
const result = getResult(entitlementsResults);
|
|
12
|
+
|
|
13
|
+
expect(result).toEqual({ isEntitled: true });
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
describe('not-entitled', () => {
|
|
17
|
+
test('given all of results are falsy with missing feature, expected is not entitled with missing feature justification', async () => {
|
|
18
|
+
const entitlementsResults: EntitlementResult[] = [
|
|
19
|
+
{ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE },
|
|
20
|
+
{ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const result = getResult(entitlementsResults);
|
|
24
|
+
|
|
25
|
+
expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE });
|
|
26
|
+
});
|
|
27
|
+
test('given all of results are falsy with some expired, expected is not entitled with expired justification', async () => {
|
|
28
|
+
const entitlementsResults: EntitlementResult[] = [
|
|
29
|
+
{ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE },
|
|
30
|
+
{ isEntitled: false, justification: NotEntitledJustification.BUNDLE_EXPIRED },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const result = getResult(entitlementsResults);
|
|
34
|
+
|
|
35
|
+
expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.BUNDLE_EXPIRED });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('given empty results, expected is not entitled with missing feature justification', async () => {
|
|
39
|
+
const entitlementsResults: EntitlementResult[] = [];
|
|
40
|
+
|
|
41
|
+
const result = getResult(entitlementsResults);
|
|
42
|
+
|
|
43
|
+
expect(result).toEqual({ isEntitled: false, justification: NotEntitledJustification.MISSING_FEATURE });
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"attributes.utils.js","sourceRoot":"","sources":["../../src/user-entitlements/attributes.utils.ts"],"names":[],"mappings":";;;AACA,mDAA0C;AAC1C;;;;;;GAMG;AACH,SAAgB,iBAAiB,CAC/B,aAAyB,EAAE,EAC3B,8BAAqF;IAErF,MAAM,EAAE,MAAM,GAAG,EAAE,EAAE,GAAG,GAAG,EAAE,EAAE,GAAG,UAAU,CAAC;IAC7C,MAAM,iBAAiB,GAAG,IAAA,uBAAO,EAA+B,GAAG,CAAC,CAAC;IACrE,MAAM,kBAAkB,GAAG,8BAA8B;QACvD,CAAC,CAAC,8BAA8B,CAAC,GAAG,CAAC;QACrC,CAAC,CAAC,+BAA+B,CAAC,GAAG,CAAC,CAAC;IACzC,MAAM,wBAAwB,GAAG,WAAW,CAAC;IAC7C,MAAM,mBAAmB,GAAG,MAAM,CAAC;IAEnC,OAAO;QACL,GAAG,MAAM;QACT,GAAG,0BAA0B,CAAC,kBAAkB,EAAE,wBAAwB,CAAC;QAC3E,GAAG,0BAA0B,CAAC,iBAAiB,EAAE,mBAAmB,CAAC;KACtE,CAAC;AACJ,CAAC;AAjBD,8CAiBC;AAED,SAAgB,+BAA+B,CAAC,GAAkB;IAChE,OAAO;QACL,KAAK,EAAE,GAAG,CAAC,KAAe;QAC1B,aAAa,EAAE,GAAG,CAAC,cAAyB;QAC5C,QAAQ,EAAE,GAAG,CAAC,QAAkB;QAChC,MAAM,EAAE,GAAG,CAAC,EAAY;KACzB,CAAC;AACJ,CAAC;AAPD,0EAOC;AAED,SAAgB,0BAA0B,CAAC,MAA+B,EAAE,MAAc;IACxF,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,cAAc,EAAE,UAAU,EAAE,EAAE;QAC/D,cAAc,CAAC,GAAG,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;QAC9D,OAAO,cAAc,CAAC;IACxB,CAAC,EAAE,EAAE,CAAC,CAAC;AACT,CAAC;AALD,gEAKC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"flatten.utils.js","sourceRoot":"","sources":["../../src/user-entitlements/flatten.utils.ts"],"names":[],"mappings":";;;AAQA,SAAgB,OAAO,CAAmB,MAAe,EAAE,IAAqB;IAC9E,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;IAElB,MAAM,SAAS,GAAG,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,SAAS,KAAI,GAAG,CAAC;IACzC,MAAM,QAAQ,GAAG,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,QAAQ,CAAC;IAChC,MAAM,YAAY,GAAG,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,YAAY,KAAI,WAAW,CAAC;IACvD,MAAM,MAAM,GAAG,EAAE,CAAC;IAElB,SAAS,IAAI,CAAC,MAAM,EAAE,IAAK,EAAE,YAAa;QACxC,YAAY,GAAG,YAAY,IAAI,CAAC,CAAC;QACjC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,UAAU,GAAG;YACvC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;YAC1B,MAAM,OAAO,GAAG,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,IAAI,KAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACnD,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACnD,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;YACjC,MAAM,QAAQ,GAAG,IAAI,KAAK,iBAAiB,IAAI,IAAI,KAAK,gBAAgB,CAAC;YAEzE,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,SAAS,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;YAC/E,IACE,CAAC,OAAO;gBACR,CAAC,QAAQ;gBACT,QAAQ;gBACR,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM;gBACzB,CAAC,CAAC,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,QAAQ,CAAA,IAAI,CAAC,QAAQ,IAAI,YAAY,GAAG,QAAQ,CAAC,CAAC,EAC1D;gBACA,OAAO,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,GAAG,CAAC,CAAC,CAAC;aAC9C;YAED,MAAM,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC;QACzB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,CAAC;IAEb,OAAO,MAAiB,CAAC;AAC3B,CAAC;AAnCD,0BAmCC;AAED,SAAS,QAAQ,CAAC,GAAG;IACnB,OAAO,GAAG,IAAI,GAAG,CAAC,WAAW,IAAI,OAAO,GAAG,CAAC,WAAW,CAAC,QAAQ,KAAK,UAAU,IAAI,GAAG,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AACnH,CAAC;AAED,SAAS,WAAW,CAAC,GAAG;IACtB,OAAO,GAAG,CAAC;AACb,CAAC"}
|