@flagship.io/react-sdk 2.2.0-alpha.8 → 2.2.0-alpha.9

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.
@@ -0,0 +1,229 @@
1
+ // eslint-disable-next-line no-use-before-define
2
+ import React, { useState, useEffect, createContext, useRef } from 'react';
3
+ import { Flagship, FlagshipStatus } from '@flagship.io/js-sdk';
4
+ import { getModificationsFromCampaigns, logError } from './utils';
5
+ const initStat = {
6
+ status: {
7
+ isLoading: true,
8
+ isSdkReady: false
9
+ }
10
+ };
11
+ export const FlagshipContext = /*#__PURE__*/createContext({
12
+ state: { ...initStat
13
+ }
14
+ });
15
+ export const FlagshipProvider = ({
16
+ children,
17
+ fetchNow,
18
+ envId,
19
+ apiKey,
20
+ decisionMode,
21
+ timeout,
22
+ logLevel,
23
+ statusChangedCallback,
24
+ logManager,
25
+ pollingInterval,
26
+ visitorData,
27
+ onInitStart,
28
+ onInitDone,
29
+ onBucketingSuccess,
30
+ onBucketingFail,
31
+ loadingComponent,
32
+ onBucketingUpdated,
33
+ onUpdate,
34
+ enableClientCache,
35
+ initialBucketing,
36
+ initialCampaigns,
37
+ initialModifications,
38
+ synchronizeOnBucketingUpdated
39
+ }) => {
40
+ let modifications = new Map();
41
+
42
+ if (initialModifications) {
43
+ initialModifications.forEach(modification => {
44
+ modifications.set(modification.key, modification);
45
+ });
46
+ } else if (initialCampaigns) {
47
+ modifications = getModificationsFromCampaigns(initialCampaigns);
48
+ }
49
+
50
+ const [state, setState] = useState({ ...initStat,
51
+ modifications
52
+ });
53
+ const [lastModified, setLastModified] = useState();
54
+ const stateRef = useRef();
55
+ stateRef.current = state;
56
+ useEffect(() => {
57
+ if (synchronizeOnBucketingUpdated) {
58
+ var _state$visitor;
59
+
60
+ (_state$visitor = state.visitor) === null || _state$visitor === void 0 ? void 0 : _state$visitor.synchronizeModifications();
61
+ }
62
+ }, [lastModified]);
63
+ useEffect(() => {
64
+ updateVisitor();
65
+ }, [visitorData]);
66
+ useEffect(() => {
67
+ initSdk();
68
+ }, [envId, apiKey, decisionMode]);
69
+
70
+ const updateVisitor = () => {
71
+ if (!state.visitor) {
72
+ return;
73
+ }
74
+
75
+ if (visitorData.context) {
76
+ state.visitor.clearContext();
77
+ state.visitor.updateContext(visitorData.context);
78
+ }
79
+
80
+ if (typeof visitorData.hasConsented === 'boolean') {
81
+ state.visitor.setConsent(visitorData.hasConsented);
82
+ }
83
+
84
+ if (state.visitor.anonymousId && !visitorData.isAuthenticated) {
85
+ state.visitor.unauthenticate();
86
+ } else if (!state.visitor.anonymousId && visitorData.isAuthenticated) {
87
+ state.visitor.authenticate(visitorData.id);
88
+ }
89
+
90
+ if (visitorData.id) {
91
+ state.visitor.visitorId = visitorData.id;
92
+ }
93
+
94
+ state.visitor.synchronizeModifications();
95
+ };
96
+
97
+ function initializeState(param) {
98
+ const newStatus = {
99
+ isSdkReady: param.isSdkReady,
100
+ isLoading: param.isLoading,
101
+ isVisitorDefined: !!param.fsVisitor,
102
+ lastRefresh: new Date().toISOString()
103
+ };
104
+ setState(currentState => {
105
+ if (!currentState.status.firstInitSuccess) {
106
+ newStatus.firstInitSuccess = new Date().toISOString();
107
+ }
108
+
109
+ return { ...currentState,
110
+ visitor: param.fsVisitor,
111
+ modifications: param.fsVisitor.modifications,
112
+ config: Flagship.getConfig(),
113
+ status: { ...currentState.status,
114
+ ...newStatus
115
+ }
116
+ };
117
+ });
118
+ return newStatus;
119
+ } // eslint-disable-next-line @typescript-eslint/no-explicit-any
120
+
121
+
122
+ const onVisitorReady = (fsVisitor, error) => {
123
+ if (error) {
124
+ logError(Flagship.getConfig(), error.message || error, 'onReady');
125
+ return;
126
+ }
127
+
128
+ const newStatus = initializeState({
129
+ fsVisitor,
130
+ isSdkReady: true,
131
+ isLoading: false
132
+ });
133
+
134
+ if (onUpdate) {
135
+ onUpdate({
136
+ fsModifications: fsVisitor.modifications,
137
+ config: Flagship.getConfig(),
138
+ status: newStatus
139
+ });
140
+ }
141
+ };
142
+
143
+ const statusChanged = status => {
144
+ if (statusChangedCallback) {
145
+ statusChangedCallback(status);
146
+ }
147
+
148
+ if (status === FlagshipStatus.STARTING && onInitStart) {
149
+ onInitStart();
150
+ } else if (status === FlagshipStatus.READY_PANIC_ON || status === FlagshipStatus.READY) {
151
+ var _stateRef$current;
152
+
153
+ if (onInitDone) {
154
+ onInitDone();
155
+ }
156
+
157
+ if ((_stateRef$current = stateRef.current) !== null && _stateRef$current !== void 0 && _stateRef$current.visitor) {
158
+ var _stateRef$current2;
159
+
160
+ (_stateRef$current2 = stateRef.current) === null || _stateRef$current2 === void 0 ? void 0 : _stateRef$current2.visitor.synchronizeModifications();
161
+ } else {
162
+ const fsVisitor = Flagship.newVisitor({
163
+ visitorId: visitorData.id,
164
+ context: visitorData.context,
165
+ isAuthenticated: visitorData.isAuthenticated,
166
+ hasConsented: visitorData.hasConsented,
167
+ initialCampaigns,
168
+ initialModifications
169
+ });
170
+ fsVisitor === null || fsVisitor === void 0 ? void 0 : fsVisitor.on('ready', error => {
171
+ onVisitorReady(fsVisitor, error);
172
+ });
173
+
174
+ if (!fetchNow && fsVisitor) {
175
+ initializeState({
176
+ fsVisitor,
177
+ isSdkReady: true,
178
+ isLoading: false
179
+ });
180
+ }
181
+ }
182
+ }
183
+ };
184
+
185
+ const onBucketingLastModified = lastUpdate => {
186
+ if (onBucketingUpdated) {
187
+ onBucketingUpdated(lastUpdate);
188
+ }
189
+
190
+ setLastModified(lastUpdate);
191
+ };
192
+
193
+ const initSdk = () => {
194
+ Flagship.start(envId, apiKey, {
195
+ decisionMode,
196
+ fetchNow,
197
+ timeout,
198
+ logLevel,
199
+ statusChangedCallback: statusChanged,
200
+ logManager,
201
+ pollingInterval,
202
+ onBucketingFail,
203
+ onBucketingSuccess,
204
+ enableClientCache,
205
+ onBucketingUpdated: onBucketingLastModified,
206
+ initialBucketing
207
+ });
208
+ };
209
+
210
+ const handleDisplay = () => {
211
+ const isFirstInit = !state.visitor;
212
+
213
+ if (state.status.isLoading && loadingComponent && isFirstInit) {
214
+ return /*#__PURE__*/React.createElement(React.Fragment, null, loadingComponent);
215
+ }
216
+
217
+ return /*#__PURE__*/React.createElement(React.Fragment, null, children);
218
+ };
219
+
220
+ return /*#__PURE__*/React.createElement(FlagshipContext.Provider, {
221
+ value: {
222
+ state,
223
+ setState
224
+ }
225
+ }, handleDisplay());
226
+ };
227
+ FlagshipProvider.defaultProps = {
228
+ nodeEnv: 'production'
229
+ };
@@ -0,0 +1,363 @@
1
+ import { useContext } from 'react';
2
+ import { FlagshipContext } from './FlagshipContext';
3
+ import { logError, logWarn } from './utils';
4
+
5
+ const checkType = (value, defaultValue) => typeof value === 'object' && typeof defaultValue === 'object' && Array.isArray(value) === Array.isArray(defaultValue) || typeof value === typeof defaultValue;
6
+
7
+ const fsModificationsSync = args => {
8
+ const {
9
+ visitor,
10
+ params,
11
+ activateAll,
12
+ state,
13
+ functionName,
14
+ config
15
+ } = args;
16
+
17
+ if (visitor) {
18
+ return visitor.getModificationsSync(params, activateAll);
19
+ }
20
+
21
+ const check = !state.status.isSdkReady && !!state.modifications && state.modifications.size > 0;
22
+ const flags = {};
23
+
24
+ if (check) {
25
+ params.forEach(item => {
26
+ var _state$modifications;
27
+
28
+ const modification = (_state$modifications = state.modifications) === null || _state$modifications === void 0 ? void 0 : _state$modifications.get(item.key);
29
+
30
+ if (modification && checkType(modification === null || modification === void 0 ? void 0 : modification.value, item.defaultValue)) {
31
+ flags[item.key] = modification.value;
32
+ } else {
33
+ flags[item.key] = item.defaultValue;
34
+ }
35
+ });
36
+ return flags;
37
+ }
38
+
39
+ logWarn(config, noVisitorDefault, functionName);
40
+ params.forEach(item => {
41
+ flags[item.key] = item.defaultValue;
42
+ });
43
+ return flags;
44
+ };
45
+ /**
46
+ * Retrieve a modification value by its key. If no modification match the given key or if the stored value type and default value type do not match, default value will be returned.
47
+ */
48
+
49
+
50
+ export const useFsModifications = (params, activateAll) => {
51
+ const {
52
+ state
53
+ } = useContext(FlagshipContext);
54
+ const {
55
+ visitor,
56
+ config
57
+ } = state;
58
+ const functionName = 'useFsModifications';
59
+ return fsModificationsSync({
60
+ functionName,
61
+ state,
62
+ visitor,
63
+ config,
64
+ params,
65
+ activateAll
66
+ });
67
+ };
68
+ /**
69
+ * Retrieve a modification value by its key. If no modification match the given key or if the stored value type and default value type do not match, default value will be returned.
70
+ */
71
+
72
+ export const useFsModification = params => {
73
+ var _state$modifications2;
74
+
75
+ const {
76
+ state
77
+ } = useContext(FlagshipContext);
78
+ const {
79
+ visitor,
80
+ config
81
+ } = state;
82
+ const functionName = 'useFsModifications';
83
+
84
+ if (visitor) {
85
+ return visitor.getModificationSync(params);
86
+ }
87
+
88
+ const modification = (_state$modifications2 = state.modifications) === null || _state$modifications2 === void 0 ? void 0 : _state$modifications2.get(params.key);
89
+
90
+ if (!state.status.isSdkReady && modification && checkType(modification === null || modification === void 0 ? void 0 : modification.value, params.defaultValue)) {
91
+ return modification.value;
92
+ }
93
+
94
+ logWarn(config, noVisitorDefault, functionName);
95
+ return params.defaultValue;
96
+ };
97
+
98
+ const fsModificationInfoSync = args => {
99
+ var _state$modifications3;
100
+
101
+ const {
102
+ key,
103
+ visitor,
104
+ state
105
+ } = args;
106
+
107
+ if (visitor) {
108
+ return visitor.getModificationInfoSync(key);
109
+ }
110
+
111
+ const modification = (_state$modifications3 = state.modifications) === null || _state$modifications3 === void 0 ? void 0 : _state$modifications3.get(key);
112
+
113
+ if (!state.status.isSdkReady && modification) {
114
+ return modification;
115
+ }
116
+
117
+ return null;
118
+ };
119
+ /**
120
+ * Get the campaign modification information value matching the given key.
121
+ * @param {string} key key which identify the modification.
122
+ */
123
+
124
+
125
+ export const useFsModificationInfo = key => {
126
+ const {
127
+ state
128
+ } = useContext(FlagshipContext);
129
+ const {
130
+ visitor
131
+ } = state;
132
+ return fsModificationInfoSync({
133
+ key,
134
+ state,
135
+ visitor
136
+ });
137
+ };
138
+
139
+ const fsSynchronizeModifications = async (functionName, visitor, config) => {
140
+ try {
141
+ if (!visitor) {
142
+ reportNoVisitor(config, functionName);
143
+ return;
144
+ }
145
+
146
+ await visitor.synchronizeModifications(); // eslint-disable-next-line @typescript-eslint/no-explicit-any
147
+ } catch (error) {
148
+ logError(config, error.message || error, functionName);
149
+ }
150
+ };
151
+ /**
152
+ * This function calls the decision api and update all the campaigns modifications from the server according to the visitor context.
153
+ */
154
+
155
+
156
+ export const useFsSynchronizeModifications = async () => {
157
+ const {
158
+ state
159
+ } = useContext(FlagshipContext);
160
+ const {
161
+ visitor,
162
+ config
163
+ } = state;
164
+ const functionName = 'useFsSynchronizeModifications';
165
+ await fsSynchronizeModifications(functionName, visitor, config);
166
+ };
167
+
168
+ const fsActivate = async (params, functionName, visitor, config) => {
169
+ try {
170
+ if (!visitor) {
171
+ logWarn(config, noVisitorMessage, functionName);
172
+ return;
173
+ }
174
+
175
+ await visitor.activateModifications(params); // eslint-disable-next-line @typescript-eslint/no-explicit-any
176
+ } catch (error) {
177
+ logWarn(config, error.message || error, functionName);
178
+ }
179
+ };
180
+ /**
181
+ * Report this user has seen this modification. Report this user has seen these modifications.
182
+ * @param params
183
+ * @returns
184
+ */
185
+
186
+
187
+ export const useFsActivate = async params => {
188
+ const {
189
+ state
190
+ } = useContext(FlagshipContext);
191
+ const {
192
+ visitor,
193
+ config
194
+ } = state;
195
+ const functionName = 'useFsModifications';
196
+ await fsActivate(params, functionName, visitor, config);
197
+ };
198
+ export const useFlagship = () => {
199
+ const {
200
+ state
201
+ } = useContext(FlagshipContext);
202
+ const {
203
+ visitor,
204
+ config
205
+ } = state;
206
+
207
+ const fsUpdateContext = context => {
208
+ const functionName = 'updateContext';
209
+
210
+ if (!visitor) {
211
+ logError(config, noVisitorMessage, functionName);
212
+ return;
213
+ }
214
+
215
+ visitor.clearContext();
216
+ visitor.updateContext(context);
217
+ };
218
+
219
+ const fsClearContext = () => {
220
+ const functionName = 'cleanContext';
221
+
222
+ if (!visitor) {
223
+ logError(config, noVisitorMessage, functionName);
224
+ return;
225
+ }
226
+
227
+ visitor.clearContext();
228
+ };
229
+
230
+ const fsAuthenticate = visitorId => {
231
+ const functionName = 'authenticate';
232
+
233
+ if (!visitor) {
234
+ logError(config, noVisitorMessage, functionName);
235
+ return;
236
+ }
237
+
238
+ visitor.authenticate(visitorId);
239
+ };
240
+
241
+ const fsUnauthenticate = () => {
242
+ const functionName = 'unauthenticate';
243
+
244
+ if (!visitor) {
245
+ logError(config, noVisitorMessage, functionName);
246
+ return;
247
+ }
248
+
249
+ visitor.unauthenticate();
250
+ };
251
+ /**
252
+ * Send a Hit to Flagship servers for reporting.
253
+ */
254
+
255
+
256
+ const fsSendHit = hit => {
257
+ const functionName = 'sendHit';
258
+
259
+ if (!visitor) {
260
+ logError(config, noVisitorMessage, functionName);
261
+ return Promise.resolve();
262
+ }
263
+
264
+ return visitor.sendHit(hit);
265
+ };
266
+ /**
267
+ * Send a Hit to Flagship servers for reporting.
268
+ */
269
+
270
+
271
+ const fsSendHits = hit => {
272
+ const functionName = 'sendHits';
273
+
274
+ if (!visitor) {
275
+ logError(config, noVisitorMessage, functionName);
276
+ return Promise.resolve();
277
+ }
278
+
279
+ return visitor.sendHits(hit);
280
+ };
281
+
282
+ let modifications = visitor === null || visitor === void 0 ? void 0 : visitor.getModificationsArray();
283
+
284
+ if (!state.status.isSdkReady && state.modifications) {
285
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
286
+ modifications = Array.from(state.modifications, ([_key, item]) => item);
287
+ }
288
+
289
+ const activateModification = async params => {
290
+ const functionName = 'activateModification';
291
+ await fsActivate(params, functionName, visitor, config);
292
+ };
293
+
294
+ const synchronizeModifications = async () => {
295
+ const functionName = 'synchronizeModifications';
296
+ await fsSynchronizeModifications(functionName, visitor, config);
297
+ };
298
+
299
+ const getModificationsSync = (params, activateAll) => {
300
+ const functionName = 'getModificationsSync';
301
+ return fsModificationsSync({
302
+ functionName,
303
+ state,
304
+ visitor,
305
+ config,
306
+ params,
307
+ activateAll
308
+ });
309
+ };
310
+
311
+ const getModifications = async (params, activateAll) => {
312
+ const functionName = 'getModifications';
313
+ return fsModificationsSync({
314
+ functionName,
315
+ state,
316
+ visitor,
317
+ config,
318
+ params,
319
+ activateAll
320
+ });
321
+ };
322
+
323
+ const getModificationInfoSync = key => {
324
+ return fsModificationInfoSync({
325
+ key,
326
+ state,
327
+ visitor
328
+ });
329
+ };
330
+
331
+ const getModificationInfo = async key => {
332
+ return fsModificationInfoSync({
333
+ key,
334
+ state,
335
+ visitor
336
+ });
337
+ };
338
+
339
+ return {
340
+ updateContext: fsUpdateContext,
341
+ clearContext: fsClearContext,
342
+ authenticate: fsAuthenticate,
343
+ unauthenticate: fsUnauthenticate,
344
+ status: state.status,
345
+ activateModification,
346
+ synchronizeModifications,
347
+ getModificationsSync,
348
+ getModifications,
349
+ modifications: modifications || [],
350
+ getModificationInfo,
351
+ getModificationInfoSync,
352
+ hit: {
353
+ send: fsSendHit,
354
+ sendMultiple: fsSendHits
355
+ }
356
+ };
357
+ };
358
+ export const noVisitorMessage = 'sdk not correctly initialized... Make sure fsVisitor is ready.';
359
+ export const noVisitorDefault = 'fsVisitor not initialized, returns default value';
360
+
361
+ const reportNoVisitor = (config, tag) => {
362
+ logError(config, noVisitorMessage, tag);
363
+ };
@@ -0,0 +1,3 @@
1
+ export * from './FlagshipContext';
2
+ export { DecisionMode } from '@flagship.io/js-sdk';
3
+ export * from './FlagshipHooks';
@@ -0,0 +1,46 @@
1
+ import { LogLevel } from '@flagship.io/js-sdk';
2
+ export function logError(config, message, tag) {
3
+ if (!config || !config.logManager || typeof config.logManager.error !== 'function' || !config.logLevel || config.logLevel < LogLevel.ERROR) {
4
+ return;
5
+ }
6
+
7
+ config.logManager.error(message, tag);
8
+ }
9
+ export function logInfo(config, message, tag) {
10
+ if (!config || !config.logManager || typeof config.logManager.info !== 'function' || !config.logLevel || config.logLevel < LogLevel.INFO) {
11
+ return;
12
+ }
13
+
14
+ config.logManager.info(message, tag);
15
+ }
16
+ export function logWarn(config, message, tag) {
17
+ if (!config || !config.logManager || typeof config.logManager.warning !== 'function' || !config.logLevel || config.logLevel < LogLevel.WARNING) {
18
+ return;
19
+ }
20
+
21
+ config.logManager.warning(message, tag);
22
+ }
23
+ export const getModificationsFromCampaigns = campaigns => {
24
+ const modifications = new Map();
25
+
26
+ if (!campaigns || !Array.isArray(campaigns)) {
27
+ return modifications;
28
+ }
29
+
30
+ campaigns.forEach(campaign => {
31
+ const object = campaign.variation.modifications.value;
32
+
33
+ for (const key in object) {
34
+ const value = object[key];
35
+ modifications.set(key, {
36
+ key,
37
+ campaignId: campaign.id,
38
+ variationGroupId: campaign.variationGroupId,
39
+ variationId: campaign.variation.id,
40
+ isReference: campaign.variation.reference,
41
+ value
42
+ });
43
+ }
44
+ });
45
+ return modifications;
46
+ };