-
Notifications
You must be signed in to change notification settings - Fork 38
Expand file tree
/
Copy pathLDReactClient.tsx
More file actions
130 lines (123 loc) · 4.68 KB
/
LDReactClient.tsx
File metadata and controls
130 lines (123 loc) · 4.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import {
createClient as createBaseClient,
LDContext,
LDContextStrict,
LDIdentifyOptions,
LDIdentifyResult,
LDOptions,
LDStartOptions,
LDWaitForInitializationResult,
} from '@launchdarkly/js-client-sdk';
import { createNoopClient } from './createNoopClient';
import { InitializedState, LDReactClient } from './LDClient';
import { LDReactClientOptions } from './LDOptions';
/**
* Creates a new instance of the LaunchDarkly client for React.
*
* @remarks
* **NOTE:** We recommend using the convenience factory function {@link createLDReactProvider}
* instead of this function to create your client instance if you can.
*
* This factory function is provided to allow the caller to have more control over their client instance.
* When using this function, the caller is responsible for:
* - calling `client.start()` before or after mounting.
* - subscribing to client lifecycle events.
*
* Refer to {@link createLDReactProviderWithClient} for the default behavior.
*
* @example
* ```tsx
* import { createClient } from '@launchdarkly/react-sdk';
* const client = createClient(clientSideID, context, options);
*
* await client.start();
* ```
*
* @param clientSideID launchdarkly client side id @see https://launchdarkly.com/docs/sdk/concepts/client-side-server-side#client-side-id
* @param context launchdarkly context @see https://launchdarkly.com/docs/sdk/concepts/context
* @param options options for the client @see {@link LDReactClientOptions}
* @returns the new client instance @see {@link LDReactClient}
*/
export function createClient(
clientSideID: string,
context: LDContext,
options: LDReactClientOptions = {},
): LDReactClient {
// This should not happen during runtime, but some frameworks such as Next.js supports
// static rendering which will attempt to render client code during build time. In these cases,
// we will need to use the noop client to avoid errors.
if (typeof window === 'undefined') {
return createNoopClient();
}
const { useCamelCaseFlagKeys: shouldUseCamelCaseFlagKeys = true, ...ldOptions } = options;
const baseClientOptions: LDOptions = {
...ldOptions,
wrapperName: ldOptions?.wrapperName ?? 'react-client-sdk',
wrapperVersion: ldOptions?.wrapperVersion ?? '0.2.1', // x-release-please-version
};
const baseClient = createBaseClient(clientSideID, context, baseClientOptions);
let initializationState: InitializedState = 'initializing';
let hasBootstrap = false;
let startCalled = false;
let startNotified = false;
const subscribers = new Set<(context: LDContextStrict) => void>();
const initStatusSubscribers = new Set<(result: LDWaitForInitializationResult) => void>();
let lastInitResult: LDWaitForInitializationResult | undefined;
function notifyContextSubscribers() {
const newContext = baseClient.getContext();
if (newContext) {
subscribers.forEach((cb) => cb(newContext));
}
}
return {
...baseClient,
start: (startOptions?: LDStartOptions) => {
// The base client start method is idempotent, so we can just return
// the result if it has already been called.
if (startCalled) {
return baseClient.start(startOptions);
}
startCalled = true;
if (startOptions?.bootstrap) {
hasBootstrap = true;
}
return baseClient.start(startOptions).then((result: LDWaitForInitializationResult) => {
initializationState = result.status;
lastInitResult = result;
if (!startNotified) {
startNotified = true;
notifyContextSubscribers();
}
initStatusSubscribers.forEach((cb) => cb(result));
return result;
});
},
identify: (ldContext: LDContext, identifyOptions?: LDIdentifyOptions) =>
baseClient.identify(ldContext, identifyOptions).then((result: LDIdentifyResult) => {
if (result.status === 'completed') {
notifyContextSubscribers();
}
return result;
}),
getInitializationState: () => initializationState,
getInitializationError: () =>
lastInitResult?.status === 'failed' ? lastInitResult.error : undefined,
onContextChange: (callback: (ldContext: LDContextStrict) => void) => {
subscribers.add(callback);
return () => {
subscribers.delete(callback);
};
},
onInitializationStatusChange: (callback: (result: LDWaitForInitializationResult) => void) => {
if (lastInitResult) {
callback(lastInitResult);
}
initStatusSubscribers.add(callback);
return () => {
initStatusSubscribers.delete(callback);
};
},
isReady: () => initializationState !== 'initializing' || hasBootstrap,
shouldUseCamelCaseFlagKeys: () => shouldUseCamelCaseFlagKeys,
};
}