Skip to content

Commit 21fb18b

Browse files
committed
fix: Fix field visibility. (#530)
This fixes the field visibility on a number of what should have been private or readonly fields. This is technically a breaking change because the implementation types were exposed, but they are not part of the interface. One attribute that is in the interface is the logger, and it has been marked as readonly now, which is also a breaking change. Setting or manipulating any of these fields would have resulted in potentially inconsistent internal state.
1 parent a99048e commit 21fb18b

16 files changed

+506
-189
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { LDOptions } from '@launchdarkly/js-client-sdk-common';
2+
3+
export default interface RNOptions extends LDOptions {
4+
/**
5+
* Some platforms (windows, web, mac, linux) can continue executing code
6+
* in the background.
7+
*
8+
* Defaults to false.
9+
*/
10+
readonly runInBackground?: boolean;
11+
12+
/**
13+
* Enable handling of network availability. When this is true the
14+
* connection state will automatically change when network
15+
* availability changes.
16+
*
17+
* Defaults to true.
18+
*/
19+
readonly automaticNetworkHandling?: boolean;
20+
21+
/**
22+
* Enable handling associated with transitioning between the foreground
23+
* and background.
24+
*
25+
* Defaults to true.
26+
*/
27+
readonly automaticBackgroundHandling?: boolean;
28+
}
Lines changed: 313 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,325 @@
1-
import { AutoEnvAttributes, type LDContext } from '@launchdarkly/js-client-sdk-common';
1+
import { AutoEnvAttributes, LDLogger, Response } from '@launchdarkly/js-client-sdk-common';
22

3+
import createPlatform from './platform';
4+
import PlatformCrypto from './platform/crypto';
5+
import PlatformEncoding from './platform/PlatformEncoding';
6+
import PlatformInfo from './platform/PlatformInfo';
7+
import PlatformStorage from './platform/PlatformStorage';
38
import ReactNativeLDClient from './ReactNativeLDClient';
49

5-
describe('ReactNativeLDClient', () => {
6-
let ldc: ReactNativeLDClient;
10+
function mockResponse(value: string, statusCode: number) {
11+
const response: Response = {
12+
headers: {
13+
get: jest.fn(),
14+
keys: jest.fn(),
15+
values: jest.fn(),
16+
entries: jest.fn(),
17+
has: jest.fn(),
18+
},
19+
status: statusCode,
20+
text: () => Promise.resolve(value),
21+
json: () => Promise.resolve(JSON.parse(value)),
22+
};
23+
return Promise.resolve(response);
24+
}
725

8-
beforeEach(() => {
9-
ldc = new ReactNativeLDClient('mobile-key', AutoEnvAttributes.Enabled, { sendEvents: false });
26+
/**
27+
* Mocks basicPlatform fetch. Returns the fetch jest.Mock object.
28+
* @param remoteJson
29+
* @param statusCode
30+
*/
31+
function mockFetch(value: string, statusCode: number = 200) {
32+
const f = jest.fn();
33+
f.mockResolvedValue(mockResponse(value, statusCode));
34+
return f;
35+
}
36+
37+
jest.mock('./platform', () => ({
38+
__esModule: true,
39+
default: jest.fn((logger: LDLogger) => ({
40+
crypto: new PlatformCrypto(),
41+
info: new PlatformInfo(logger),
42+
requests: {
43+
createEventSource: jest.fn(),
44+
fetch: jest.fn(),
45+
},
46+
encoding: new PlatformEncoding(),
47+
storage: new PlatformStorage(logger),
48+
})),
49+
}));
50+
51+
const createMockEventSource = (streamUri: string = '', options: any = {}) => ({
52+
streamUri,
53+
options,
54+
onclose: jest.fn(),
55+
addEventListener: jest.fn(),
56+
close: jest.fn(),
57+
});
58+
59+
it('uses correct default diagnostic url', () => {
60+
const mockedFetch = jest.fn();
61+
const logger: LDLogger = {
62+
error: jest.fn(),
63+
warn: jest.fn(),
64+
info: jest.fn(),
65+
debug: jest.fn(),
66+
};
67+
(createPlatform as jest.Mock).mockReturnValue({
68+
crypto: new PlatformCrypto(),
69+
info: new PlatformInfo(logger),
70+
requests: {
71+
createEventSource: jest.fn(),
72+
fetch: mockedFetch,
73+
},
74+
encoding: new PlatformEncoding(),
75+
storage: new PlatformStorage(logger),
76+
});
77+
const client = new ReactNativeLDClient('mobile-key', AutoEnvAttributes.Enabled);
78+
79+
expect(mockedFetch).toHaveBeenCalledWith(
80+
'https://events.launchdarkly.com/mobile/events/diagnostic',
81+
expect.anything(),
82+
);
83+
client.close();
84+
});
85+
86+
it('uses correct default analytics event url', async () => {
87+
const mockedFetch = mockFetch('{"flagA": true}', 200);
88+
const logger: LDLogger = {
89+
error: jest.fn(),
90+
warn: jest.fn(),
91+
info: jest.fn(),
92+
debug: jest.fn(),
93+
};
94+
(createPlatform as jest.Mock).mockReturnValue({
95+
crypto: new PlatformCrypto(),
96+
info: new PlatformInfo(logger),
97+
requests: {
98+
createEventSource: createMockEventSource,
99+
fetch: mockedFetch,
100+
},
101+
encoding: new PlatformEncoding(),
102+
storage: new PlatformStorage(logger),
103+
});
104+
const client = new ReactNativeLDClient('mobile-key', AutoEnvAttributes.Enabled, {
105+
diagnosticOptOut: true,
106+
initialConnectionMode: 'polling',
107+
});
108+
await client.identify({ kind: 'user', key: 'bob' });
109+
await client.flush();
110+
111+
expect(mockedFetch).toHaveBeenCalledWith(
112+
'https://events.launchdarkly.com/mobile',
113+
expect.anything(),
114+
);
115+
});
116+
117+
it('uses correct default polling url', async () => {
118+
const mockedFetch = mockFetch('{"flagA": true}', 200);
119+
const logger: LDLogger = {
120+
error: jest.fn(),
121+
warn: jest.fn(),
122+
info: jest.fn(),
123+
debug: jest.fn(),
124+
};
125+
(createPlatform as jest.Mock).mockReturnValue({
126+
crypto: new PlatformCrypto(),
127+
info: new PlatformInfo(logger),
128+
requests: {
129+
createEventSource: jest.fn(),
130+
fetch: mockedFetch,
131+
},
132+
encoding: new PlatformEncoding(),
133+
storage: new PlatformStorage(logger),
134+
});
135+
const client = new ReactNativeLDClient('mobile-key', AutoEnvAttributes.Enabled, {
136+
diagnosticOptOut: true,
137+
sendEvents: false,
138+
initialConnectionMode: 'polling',
139+
automaticBackgroundHandling: false,
140+
});
141+
await client.identify({ kind: 'user', key: 'bob' });
142+
143+
const regex = /https:\/\/clientsdk\.launchdarkly\.com\/msdk\/evalx\/contexts\/.*/;
144+
expect(mockedFetch).toHaveBeenCalledWith(expect.stringMatching(regex), expect.anything());
145+
});
146+
147+
it('uses correct default streaming url', (done) => {
148+
const mockedCreateEventSource = jest.fn();
149+
const logger: LDLogger = {
150+
error: jest.fn(),
151+
warn: jest.fn(),
152+
info: jest.fn(),
153+
debug: jest.fn(),
154+
};
155+
(createPlatform as jest.Mock).mockReturnValue({
156+
crypto: new PlatformCrypto(),
157+
info: new PlatformInfo(logger),
158+
requests: {
159+
createEventSource: mockedCreateEventSource,
160+
fetch: jest.fn(),
161+
},
162+
encoding: new PlatformEncoding(),
163+
storage: new PlatformStorage(logger),
164+
});
165+
const client = new ReactNativeLDClient('mobile-key', AutoEnvAttributes.Enabled, {
166+
diagnosticOptOut: true,
167+
sendEvents: false,
168+
initialConnectionMode: 'streaming',
169+
automaticBackgroundHandling: false,
10170
});
11171

12-
test('constructing a new client', () => {
13-
expect(ldc.highTimeoutThreshold).toEqual(15);
14-
expect(ldc.sdkKey).toEqual('mobile-key');
15-
expect(ldc.config.serviceEndpoints).toEqual({
16-
analyticsEventPath: '/mobile',
17-
diagnosticEventPath: '/mobile/events/diagnostic',
18-
events: 'https://events.launchdarkly.com',
19-
includeAuthorizationHeader: true,
20-
polling: 'https://clientsdk.launchdarkly.com',
21-
streaming: 'https://clientstream.launchdarkly.com',
172+
client
173+
.identify({ kind: 'user', key: 'bob' }, { timeout: 0 })
174+
.then(() => {})
175+
.catch(() => {})
176+
.then(() => {
177+
const regex = /https:\/\/clientstream\.launchdarkly\.com\/meval\/.*/;
178+
expect(mockedCreateEventSource).toHaveBeenCalledWith(
179+
expect.stringMatching(regex),
180+
expect.anything(),
181+
);
182+
done();
22183
});
184+
});
185+
186+
it('includes authorization header for polling', async () => {
187+
const mockedFetch = mockFetch('{"flagA": true}', 200);
188+
const logger: LDLogger = {
189+
error: jest.fn(),
190+
warn: jest.fn(),
191+
info: jest.fn(),
192+
debug: jest.fn(),
193+
};
194+
(createPlatform as jest.Mock).mockReturnValue({
195+
crypto: new PlatformCrypto(),
196+
info: new PlatformInfo(logger),
197+
requests: {
198+
createEventSource: jest.fn(),
199+
fetch: mockedFetch,
200+
},
201+
encoding: new PlatformEncoding(),
202+
storage: new PlatformStorage(logger),
203+
});
204+
const client = new ReactNativeLDClient('mobile-key', AutoEnvAttributes.Enabled, {
205+
diagnosticOptOut: true,
206+
sendEvents: false,
207+
initialConnectionMode: 'polling',
208+
automaticBackgroundHandling: false,
209+
});
210+
await client.identify({ kind: 'user', key: 'bob' });
211+
212+
expect(mockedFetch).toHaveBeenCalledWith(
213+
expect.anything(),
214+
expect.objectContaining({
215+
headers: expect.objectContaining({ authorization: 'mobile-key' }),
216+
}),
217+
);
218+
});
219+
220+
it('includes authorization header for streaming', (done) => {
221+
const mockedCreateEventSource = jest.fn();
222+
const logger: LDLogger = {
223+
error: jest.fn(),
224+
warn: jest.fn(),
225+
info: jest.fn(),
226+
debug: jest.fn(),
227+
};
228+
(createPlatform as jest.Mock).mockReturnValue({
229+
crypto: new PlatformCrypto(),
230+
info: new PlatformInfo(logger),
231+
requests: {
232+
createEventSource: mockedCreateEventSource,
233+
fetch: jest.fn(),
234+
},
235+
encoding: new PlatformEncoding(),
236+
storage: new PlatformStorage(logger),
237+
});
238+
const client = new ReactNativeLDClient('mobile-key', AutoEnvAttributes.Enabled, {
239+
diagnosticOptOut: true,
240+
sendEvents: false,
241+
initialConnectionMode: 'streaming',
242+
automaticBackgroundHandling: false,
243+
});
244+
245+
client
246+
.identify({ kind: 'user', key: 'bob' }, { timeout: 0 })
247+
.then(() => {})
248+
.catch(() => {})
249+
.then(() => {
250+
expect(mockedCreateEventSource).toHaveBeenCalledWith(
251+
expect.anything(),
252+
expect.objectContaining({
253+
headers: expect.objectContaining({ authorization: 'mobile-key' }),
254+
}),
255+
);
256+
done();
257+
});
258+
});
259+
260+
it('includes authorization header for events', async () => {
261+
const mockedFetch = mockFetch('{"flagA": true}', 200);
262+
const logger: LDLogger = {
263+
error: jest.fn(),
264+
warn: jest.fn(),
265+
info: jest.fn(),
266+
debug: jest.fn(),
267+
};
268+
(createPlatform as jest.Mock).mockReturnValue({
269+
crypto: new PlatformCrypto(),
270+
info: new PlatformInfo(logger),
271+
requests: {
272+
createEventSource: jest.fn(),
273+
fetch: mockedFetch,
274+
},
275+
encoding: new PlatformEncoding(),
276+
storage: new PlatformStorage(logger),
277+
});
278+
const client = new ReactNativeLDClient('mobile-key', AutoEnvAttributes.Enabled, {
279+
diagnosticOptOut: true,
280+
initialConnectionMode: 'polling',
23281
});
282+
await client.identify({ kind: 'user', key: 'bob' });
283+
await client.flush();
24284

25-
test('createStreamUriPath', () => {
26-
const context: LDContext = { kind: 'user', key: 'test-user-key-1' };
285+
expect(mockedFetch).toHaveBeenCalledWith(
286+
expect.anything(),
287+
expect.objectContaining({
288+
headers: expect.objectContaining({ authorization: 'mobile-key' }),
289+
}),
290+
);
291+
});
292+
293+
it('identify with too high of a timeout', () => {
294+
const logger: LDLogger = {
295+
error: jest.fn(),
296+
warn: jest.fn(),
297+
info: jest.fn(),
298+
debug: jest.fn(),
299+
};
300+
const client = new ReactNativeLDClient('mobile-key', AutoEnvAttributes.Enabled, {
301+
sendEvents: false,
302+
initialConnectionMode: 'offline',
303+
logger,
304+
});
305+
client.identify({ key: 'potato', kind: 'user' }, { timeout: 16 });
306+
expect(logger.warn).toHaveBeenCalledWith(
307+
'The identify function was called with a timeout greater than 15 seconds. We recommend a timeout of less than 15 seconds.',
308+
);
309+
});
27310

28-
expect(ldc.createStreamUriPath(context)).toEqual(
29-
'/meval/eyJraW5kIjoidXNlciIsImtleSI6InRlc3QtdXNlci1rZXktMSJ9',
30-
);
311+
it('identify timeout equal to threshold', () => {
312+
const logger: LDLogger = {
313+
error: jest.fn(),
314+
warn: jest.fn(),
315+
info: jest.fn(),
316+
debug: jest.fn(),
317+
};
318+
const client = new ReactNativeLDClient('mobile-key', AutoEnvAttributes.Enabled, {
319+
sendEvents: false,
320+
initialConnectionMode: 'offline',
321+
logger,
31322
});
323+
client.identify({ key: 'potato', kind: 'user' }, { timeout: 15 });
324+
expect(logger.warn).not.toHaveBeenCalled();
32325
});

0 commit comments

Comments
 (0)