Skip to content

Commit c1490e2

Browse files
committed
feat: Add configuration validation for ReactNative specific configuration. (#532)
1 parent 21fb18b commit c1490e2

File tree

4 files changed

+166
-8
lines changed

4 files changed

+166
-8
lines changed

packages/sdk/react-native/src/RNOptions.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { LDOptions } from '@launchdarkly/js-client-sdk-common';
22

3-
export default interface RNOptions extends LDOptions {
3+
export interface RNSpecificOptions {
44
/**
55
* Some platforms (windows, web, mac, linux) can continue executing code
66
* in the background.
@@ -26,3 +26,5 @@ export default interface RNOptions extends LDOptions {
2626
*/
2727
readonly automaticBackgroundHandling?: boolean;
2828
}
29+
30+
export default interface RNOptions extends LDOptions, RNSpecificOptions {}

packages/sdk/react-native/src/ReactNativeLDClient.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
type LDContext,
1010
} from '@launchdarkly/js-client-sdk-common';
1111

12+
import validateOptions, { filterToBaseOptions } from './options';
1213
import createPlatform from './platform';
1314
import { ConnectionDestination, ConnectionManager } from './platform/ConnectionManager';
1415
import LDOptions from './RNOptions';
@@ -59,7 +60,7 @@ export default class ReactNativeLDClient extends LDClientImpl {
5960
sdkKey,
6061
autoEnvAttributes,
6162
createPlatform(logger),
62-
{ ...options, logger },
63+
{ ...filterToBaseOptions(options), logger },
6364
internalOptions,
6465
);
6566

@@ -77,17 +78,15 @@ export default class ReactNativeLDClient extends LDClientImpl {
7778
},
7879
};
7980

81+
const validatedRnOptions = validateOptions(options, logger);
8082
const initialConnectionMode = options.initialConnectionMode ?? 'streaming';
8183
this.connectionManager = new ConnectionManager(
8284
logger,
8385
{
8486
initialConnectionMode,
85-
// TODO: Add the ability to configure connection management.
86-
// This is not yet added as the RN SDK needs package specific configuration added.
87-
// TODO: Add RN config option validation beyond base options.
88-
automaticNetworkHandling: options.automaticNetworkHandling ?? true,
89-
automaticBackgroundHandling: options.automaticBackgroundHandling ?? true,
90-
runInBackground: options.runInBackground ?? true,
87+
automaticNetworkHandling: validatedRnOptions.automaticNetworkHandling,
88+
automaticBackgroundHandling: validatedRnOptions.automaticBackgroundHandling,
89+
runInBackground: validatedRnOptions.runInBackground,
9190
},
9291
destination,
9392
new RNStateDetector(),
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { LDLogger } from '@launchdarkly/js-client-sdk-common';
2+
3+
import validateOptions, { filterToBaseOptions } from './options';
4+
5+
it('logs no warnings when all configuration is valid', () => {
6+
const logger: LDLogger = {
7+
debug: jest.fn(),
8+
info: jest.fn(),
9+
warn: jest.fn(),
10+
error: jest.fn(),
11+
};
12+
13+
validateOptions(
14+
{
15+
runInBackground: true,
16+
automaticBackgroundHandling: true,
17+
automaticNetworkHandling: true,
18+
},
19+
logger,
20+
);
21+
22+
expect(logger.debug).not.toHaveBeenCalled();
23+
expect(logger.info).not.toHaveBeenCalled();
24+
expect(logger.warn).not.toHaveBeenCalled();
25+
expect(logger.error).not.toHaveBeenCalled();
26+
});
27+
28+
it('warns for invalid configuration', () => {
29+
const logger: LDLogger = {
30+
debug: jest.fn(),
31+
info: jest.fn(),
32+
warn: jest.fn(),
33+
error: jest.fn(),
34+
};
35+
36+
validateOptions(
37+
{
38+
// @ts-ignore
39+
runInBackground: 'toast',
40+
// @ts-ignore
41+
automaticBackgroundHandling: 42,
42+
// @ts-ignore
43+
automaticNetworkHandling: {},
44+
},
45+
logger,
46+
);
47+
48+
expect(logger.warn).toHaveBeenCalledTimes(3);
49+
expect(logger.warn).toHaveBeenCalledWith(
50+
'Config option "runInBackground" should be of type boolean, got string, using default value',
51+
);
52+
expect(logger.warn).toHaveBeenCalledWith(
53+
'Config option "automaticBackgroundHandling" should be of type boolean, got number, using default value',
54+
);
55+
expect(logger.warn).toHaveBeenCalledWith(
56+
'Config option "automaticNetworkHandling" should be of type boolean, got object, using default value',
57+
);
58+
});
59+
60+
it('applies default options', () => {
61+
const logger: LDLogger = {
62+
debug: jest.fn(),
63+
info: jest.fn(),
64+
warn: jest.fn(),
65+
error: jest.fn(),
66+
};
67+
const opts = validateOptions({}, logger);
68+
69+
expect(opts.runInBackground).toBe(false);
70+
expect(opts.automaticBackgroundHandling).toBe(true);
71+
expect(opts.automaticNetworkHandling).toBe(true);
72+
73+
expect(logger.debug).not.toHaveBeenCalled();
74+
expect(logger.info).not.toHaveBeenCalled();
75+
expect(logger.warn).not.toHaveBeenCalled();
76+
expect(logger.error).not.toHaveBeenCalled();
77+
});
78+
79+
it('filters to base options', () => {
80+
const logger: LDLogger = {
81+
debug: jest.fn(),
82+
info: jest.fn(),
83+
warn: jest.fn(),
84+
error: jest.fn(),
85+
};
86+
const opts = {
87+
debug: false,
88+
runInBackground: true,
89+
automaticBackgroundHandling: true,
90+
automaticNetworkHandling: true,
91+
};
92+
93+
const baseOpts = filterToBaseOptions(opts);
94+
expect(baseOpts.debug).toBe(false);
95+
expect(Object.keys(baseOpts).length).toEqual(1);
96+
97+
expect(logger.debug).not.toHaveBeenCalled();
98+
expect(logger.info).not.toHaveBeenCalled();
99+
expect(logger.warn).not.toHaveBeenCalled();
100+
expect(logger.error).not.toHaveBeenCalled();
101+
});
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import {
2+
LDLogger,
3+
LDOptions,
4+
OptionMessages,
5+
TypeValidator,
6+
TypeValidators,
7+
} from '@launchdarkly/js-client-sdk-common';
8+
9+
import RNOptions from './RNOptions';
10+
11+
export interface ValidatedOptions {
12+
runInBackground: boolean;
13+
automaticNetworkHandling: boolean;
14+
automaticBackgroundHandling: boolean;
15+
}
16+
17+
const optDefaults = {
18+
runInBackground: false,
19+
automaticNetworkHandling: true,
20+
automaticBackgroundHandling: true,
21+
};
22+
23+
const validators: { [Property in keyof RNOptions]: TypeValidator | undefined } = {
24+
runInBackground: TypeValidators.Boolean,
25+
automaticNetworkHandling: TypeValidators.Boolean,
26+
automaticBackgroundHandling: TypeValidators.Boolean,
27+
};
28+
29+
export function filterToBaseOptions(opts: RNOptions): LDOptions {
30+
const baseOptions: LDOptions = { ...opts };
31+
32+
// Remove any RN specific configuration keys so we don't get warnings from
33+
// the base implementation for unknown configuration.
34+
Object.keys(optDefaults).forEach((key) => {
35+
delete (baseOptions as any)[key];
36+
});
37+
return baseOptions;
38+
}
39+
40+
export default function validateOptions(opts: RNOptions, logger: LDLogger): ValidatedOptions {
41+
const output: ValidatedOptions = { ...optDefaults };
42+
43+
Object.entries(validators).forEach((entry) => {
44+
const [key, validator] = entry as [keyof RNOptions, TypeValidator];
45+
const value = opts[key];
46+
if (value !== undefined) {
47+
if (validator.is(value)) {
48+
output[key as keyof ValidatedOptions] = value as any;
49+
} else {
50+
logger.warn(OptionMessages.wrongOptionType(key, validator.getType(), typeof value));
51+
}
52+
}
53+
});
54+
55+
return output;
56+
}

0 commit comments

Comments
 (0)