@flagship.io/react-sdk 5.1.1 → 5.2.0
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/edge/setupTests.js +1 -0
- package/dist/edge/src/FSFlag.js +59 -0
- package/dist/edge/src/FlagshipContext.js +8 -0
- package/dist/edge/src/FlagshipHooks.js +152 -0
- package/dist/edge/src/FlagshipProvider.js +159 -0
- package/dist/edge/src/FlagshipProviderSSR.js +50 -0
- package/dist/edge/src/constants.js +4 -0
- package/dist/edge/src/deps.js +1 -0
- package/dist/edge/src/index.js +7 -0
- package/dist/edge/src/internalType.js +4 -0
- package/dist/edge/src/sdkVersion.js +2 -0
- package/dist/edge/src/type.js +1 -0
- package/dist/edge/src/utils.js +154 -0
- package/dist/{index.node.js → index.node.cjs} +184 -60
- package/dist/index.node.cjs.map +1 -0
- package/dist/src/FSFlag.d.ts +2 -2
- package/dist/src/FSFlag.js +3 -3
- package/dist/src/FlagshipContext.d.ts +1 -1
- package/dist/src/FlagshipHooks.d.ts +2 -2
- package/dist/src/FlagshipHooks.js +5 -5
- package/dist/src/FlagshipProvider.d.ts +2 -2
- package/dist/src/FlagshipProvider.js +24 -21
- package/dist/src/FlagshipProviderSSR.d.ts +10 -0
- package/dist/src/FlagshipProviderSSR.js +50 -0
- package/dist/src/deps.d.ts +1 -0
- package/dist/src/deps.js +1 -0
- package/dist/src/index.d.ts +6 -5
- package/dist/src/index.js +6 -5
- package/dist/src/internalType.d.ts +9 -0
- package/dist/src/sdkVersion.d.ts +1 -1
- package/dist/src/sdkVersion.js +1 -1
- package/dist/src/type.d.ts +2 -2
- package/dist/src/utils.d.ts +2 -2
- package/dist/src/utils.js +1 -1
- package/package.json +25 -11
- package/dist/index.node.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom/jest-globals';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Flagship, FSFlagMetadata, FSFlagStatus } from './deps.js';
|
|
2
|
+
import { GET_FLAG_CAST_ERROR, noVisitorMessage } from './constants.js';
|
|
3
|
+
import { hasSameType, logInfo, logWarn, sprintf } from './utils.js';
|
|
4
|
+
export class FSFlag {
|
|
5
|
+
key;
|
|
6
|
+
flag;
|
|
7
|
+
constructor(key, state) {
|
|
8
|
+
const flagsData = state.flags;
|
|
9
|
+
if (!state.hasVisitorData) {
|
|
10
|
+
logWarn(Flagship.getConfig(), noVisitorMessage, 'GetFlag');
|
|
11
|
+
}
|
|
12
|
+
this.key = key;
|
|
13
|
+
this.flag = flagsData?.get(key);
|
|
14
|
+
}
|
|
15
|
+
getValue(defaultValue) {
|
|
16
|
+
if (!this.flag) {
|
|
17
|
+
return defaultValue;
|
|
18
|
+
}
|
|
19
|
+
if (this.flag.value === null || this.flag.value === undefined) {
|
|
20
|
+
return defaultValue;
|
|
21
|
+
}
|
|
22
|
+
if (defaultValue !== null && defaultValue !== undefined && !hasSameType(this.flag.value, defaultValue)) {
|
|
23
|
+
logInfo(Flagship.getConfig(), sprintf(GET_FLAG_CAST_ERROR, this.key), 'getValue');
|
|
24
|
+
return defaultValue;
|
|
25
|
+
}
|
|
26
|
+
return this.flag.value;
|
|
27
|
+
}
|
|
28
|
+
exists() {
|
|
29
|
+
if (!this.flag) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return !!(this.flag.campaignId && this.flag.variationId && this.flag.variationGroupId);
|
|
33
|
+
}
|
|
34
|
+
async visitorExposed() {
|
|
35
|
+
// do nothing
|
|
36
|
+
}
|
|
37
|
+
get metadata() {
|
|
38
|
+
if (!this.flag) {
|
|
39
|
+
return FSFlagMetadata.Empty();
|
|
40
|
+
}
|
|
41
|
+
return new FSFlagMetadata({
|
|
42
|
+
campaignId: this.flag.campaignId,
|
|
43
|
+
campaignName: this.flag.campaignName,
|
|
44
|
+
variationGroupId: this.flag.variationGroupId,
|
|
45
|
+
variationGroupName: this.flag.variationGroupName,
|
|
46
|
+
variationId: this.flag.variationId,
|
|
47
|
+
variationName: this.flag.variationName,
|
|
48
|
+
isReference: !!this.flag.isReference,
|
|
49
|
+
campaignType: this.flag.campaignType,
|
|
50
|
+
slug: this.flag.slug
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
get status() {
|
|
54
|
+
if (!this.exists()) {
|
|
55
|
+
return FSFlagStatus.NOT_FOUND;
|
|
56
|
+
}
|
|
57
|
+
return FSFlagStatus.FETCHED;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useCallback, useContext, useMemo } from 'react';
|
|
3
|
+
import { Flagship, FSFlagCollection } from './deps.js';
|
|
4
|
+
import { noVisitorMessage } from './constants.js';
|
|
5
|
+
import { FlagshipContext } from './FlagshipContext.js';
|
|
6
|
+
import { FSFlag } from './FSFlag.js';
|
|
7
|
+
import { deepClone, hasContextChanged, logError, logWarn } from './utils.js';
|
|
8
|
+
/**
|
|
9
|
+
* This hook returns a flag object by its key. If no flag match the given key an empty flag will be returned.
|
|
10
|
+
* @param key
|
|
11
|
+
* @param defaultValue
|
|
12
|
+
* @returns
|
|
13
|
+
*/
|
|
14
|
+
export const useFsFlag = (key) => {
|
|
15
|
+
const { state } = useContext(FlagshipContext);
|
|
16
|
+
const { visitor } = state;
|
|
17
|
+
if (!visitor) {
|
|
18
|
+
return new FSFlag(key, state);
|
|
19
|
+
}
|
|
20
|
+
return visitor.getFlag(key);
|
|
21
|
+
};
|
|
22
|
+
const handleContextChange = (param) => {
|
|
23
|
+
const { updateFunction, functionName, visitor, config } = param;
|
|
24
|
+
if (!visitor) {
|
|
25
|
+
logError(config, noVisitorMessage, functionName);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const originalContextClone = deepClone(visitor.context);
|
|
29
|
+
updateFunction();
|
|
30
|
+
const updatedContext = visitor.context;
|
|
31
|
+
if (hasContextChanged(originalContextClone, updatedContext)) {
|
|
32
|
+
visitor.fetchFlags();
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
export const useFlagship = () => {
|
|
36
|
+
const { state } = useContext(FlagshipContext);
|
|
37
|
+
const { visitor, config } = state;
|
|
38
|
+
const fsUpdateContext = useCallback((context) => {
|
|
39
|
+
handleContextChange({
|
|
40
|
+
config,
|
|
41
|
+
visitor,
|
|
42
|
+
updateFunction: () => visitor?.updateContext(context),
|
|
43
|
+
functionName: 'updateContext'
|
|
44
|
+
});
|
|
45
|
+
}, [visitor]);
|
|
46
|
+
const fsClearContext = useCallback(() => {
|
|
47
|
+
handleContextChange({
|
|
48
|
+
config,
|
|
49
|
+
visitor,
|
|
50
|
+
updateFunction: () => visitor?.clearContext(),
|
|
51
|
+
functionName: 'cleanContext'
|
|
52
|
+
});
|
|
53
|
+
}, [visitor]);
|
|
54
|
+
const fsAuthenticate = useCallback((visitorId) => {
|
|
55
|
+
const functionName = 'authenticate';
|
|
56
|
+
if (!visitor) {
|
|
57
|
+
logError(config, noVisitorMessage, functionName);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const originalVisitorId = visitor.visitorId;
|
|
61
|
+
visitor.authenticate(visitorId);
|
|
62
|
+
if (originalVisitorId !== visitorId) {
|
|
63
|
+
visitor.fetchFlags();
|
|
64
|
+
}
|
|
65
|
+
}, [visitor]);
|
|
66
|
+
const fsUnauthenticate = useCallback(() => {
|
|
67
|
+
const functionName = 'unauthenticate';
|
|
68
|
+
if (!visitor) {
|
|
69
|
+
logError(config, noVisitorMessage, functionName);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const originalVisitorId = visitor.visitorId;
|
|
73
|
+
visitor.unauthenticate();
|
|
74
|
+
if (originalVisitorId !== visitor.visitorId) {
|
|
75
|
+
visitor.fetchFlags();
|
|
76
|
+
}
|
|
77
|
+
}, [visitor]);
|
|
78
|
+
const fsSendHit = useCallback((hit) => {
|
|
79
|
+
const functionName = 'sendHit';
|
|
80
|
+
if (!visitor) {
|
|
81
|
+
logError(config, noVisitorMessage, functionName);
|
|
82
|
+
return Promise.resolve();
|
|
83
|
+
}
|
|
84
|
+
if (Array.isArray(hit)) {
|
|
85
|
+
return visitor.sendHits(hit);
|
|
86
|
+
}
|
|
87
|
+
return visitor.sendHit(hit);
|
|
88
|
+
}, [visitor]);
|
|
89
|
+
const getFlag = useCallback((key) => {
|
|
90
|
+
if (!visitor) {
|
|
91
|
+
return new FSFlag(key, state);
|
|
92
|
+
}
|
|
93
|
+
return visitor.getFlag(key);
|
|
94
|
+
}, [visitor]);
|
|
95
|
+
const fetchFlags = useCallback(async () => {
|
|
96
|
+
if (!visitor) {
|
|
97
|
+
logWarn(config, noVisitorMessage, 'fetchFlags');
|
|
98
|
+
return Promise.resolve();
|
|
99
|
+
}
|
|
100
|
+
return visitor.fetchFlags();
|
|
101
|
+
}, [visitor]);
|
|
102
|
+
const setConsent = useCallback((hasConsented) => {
|
|
103
|
+
if (!visitor) {
|
|
104
|
+
logWarn(config, noVisitorMessage, 'setConsent');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
visitor.setConsent(hasConsented);
|
|
108
|
+
}, [visitor]);
|
|
109
|
+
const close = useCallback(() => {
|
|
110
|
+
return Flagship.close();
|
|
111
|
+
}, []);
|
|
112
|
+
const getFlags = useCallback(() => {
|
|
113
|
+
if (!visitor) {
|
|
114
|
+
const flags = new Map();
|
|
115
|
+
state.flags?.forEach((flag, key) => {
|
|
116
|
+
flags.set(key, new FSFlag(key, state));
|
|
117
|
+
});
|
|
118
|
+
return new FSFlagCollection({ flags });
|
|
119
|
+
}
|
|
120
|
+
return visitor.getFlags();
|
|
121
|
+
}, [visitor]);
|
|
122
|
+
const collectEAIEventsAsync = useCallback(async (...args) => {
|
|
123
|
+
if (!visitor) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
return visitor.collectEAIEventsAsync(...args);
|
|
127
|
+
}, [visitor]);
|
|
128
|
+
return useMemo(() => ({
|
|
129
|
+
visitorId: visitor?.visitorId,
|
|
130
|
+
anonymousId: visitor?.anonymousId,
|
|
131
|
+
context: { ...visitor?.context },
|
|
132
|
+
hasConsented: visitor?.hasConsented,
|
|
133
|
+
sdkStatus: Flagship.getStatus(),
|
|
134
|
+
flagsStatus: visitor?.flagsStatus,
|
|
135
|
+
setConsent,
|
|
136
|
+
updateContext: fsUpdateContext,
|
|
137
|
+
clearContext: fsClearContext,
|
|
138
|
+
authenticate: fsAuthenticate,
|
|
139
|
+
unauthenticate: fsUnauthenticate,
|
|
140
|
+
sendHits: fsSendHit,
|
|
141
|
+
getFlag,
|
|
142
|
+
fetchFlags,
|
|
143
|
+
close,
|
|
144
|
+
getFlags,
|
|
145
|
+
collectEAIEventsAsync
|
|
146
|
+
}), [visitor, setConsent,
|
|
147
|
+
fsUpdateContext,
|
|
148
|
+
fsClearContext, fsAuthenticate, fsUnauthenticate,
|
|
149
|
+
fsSendHit, getFlag, fetchFlags, close,
|
|
150
|
+
getFlags,
|
|
151
|
+
collectEAIEventsAsync]);
|
|
152
|
+
};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useRef, useEffect } from "react";
|
|
4
|
+
import { Flagship, DecisionMode, FSSdkStatus } from "./deps.js";
|
|
5
|
+
import { FlagshipContext, initStat } from "./FlagshipContext.js";
|
|
6
|
+
import { INTERNAL_EVENTS } from "./internalType.js";
|
|
7
|
+
import { version as SDK_VERSION } from "./sdkVersion.js";
|
|
8
|
+
import { useNonInitialEffect, logError, extractFlagsMap } from "./utils.js";
|
|
9
|
+
export function FlagshipProvider({ children, envId, apiKey, decisionMode = DecisionMode.DECISION_API, visitorData, loadingComponent, onSdkStatusChanged, onBucketingUpdated, initialCampaigns, initialFlagsData, fetchFlagsOnBucketingUpdated, hitDeduplicationTime = 2, fetchNow = true, language = 1, sdkVersion = SDK_VERSION, onFlagsStatusChanged, shouldSaveInstance, ...props }) {
|
|
10
|
+
const flags = extractFlagsMap(initialFlagsData, initialCampaigns);
|
|
11
|
+
const [state, setState] = useState({
|
|
12
|
+
...initStat,
|
|
13
|
+
flags,
|
|
14
|
+
hasVisitorData: !!visitorData,
|
|
15
|
+
});
|
|
16
|
+
const [lastModified, setLastModified] = useState();
|
|
17
|
+
const stateRef = useRef();
|
|
18
|
+
stateRef.current = state;
|
|
19
|
+
const visitorDataRef = useRef(visitorData);
|
|
20
|
+
// #region functions
|
|
21
|
+
const onBucketingLastModified = (lastUpdate) => {
|
|
22
|
+
if (onBucketingUpdated) {
|
|
23
|
+
onBucketingUpdated(lastUpdate);
|
|
24
|
+
}
|
|
25
|
+
setLastModified(lastUpdate);
|
|
26
|
+
};
|
|
27
|
+
const statusChanged = (status) => {
|
|
28
|
+
if (onSdkStatusChanged) {
|
|
29
|
+
onSdkStatusChanged(status);
|
|
30
|
+
}
|
|
31
|
+
switch (status) {
|
|
32
|
+
case FSSdkStatus.SDK_PANIC:
|
|
33
|
+
case FSSdkStatus.SDK_INITIALIZED:
|
|
34
|
+
createVisitor();
|
|
35
|
+
break;
|
|
36
|
+
case FSSdkStatus.SDK_NOT_INITIALIZED:
|
|
37
|
+
setState((prev) => ({
|
|
38
|
+
...prev,
|
|
39
|
+
config: Flagship.getConfig(),
|
|
40
|
+
isInitializing: false,
|
|
41
|
+
}));
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const initSdk = () => {
|
|
46
|
+
Flagship.start(envId, apiKey, {
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
48
|
+
decisionMode: decisionMode,
|
|
49
|
+
fetchNow,
|
|
50
|
+
onSdkStatusChanged: statusChanged,
|
|
51
|
+
onBucketingUpdated: onBucketingLastModified,
|
|
52
|
+
hitDeduplicationTime,
|
|
53
|
+
language,
|
|
54
|
+
sdkVersion,
|
|
55
|
+
...props,
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
function initializeState(param) {
|
|
59
|
+
setState((currentState) => ({
|
|
60
|
+
...currentState,
|
|
61
|
+
visitor: param.fsVisitor,
|
|
62
|
+
config: Flagship.getConfig(),
|
|
63
|
+
isInitializing: false,
|
|
64
|
+
hasVisitorData: !!visitorData,
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
68
|
+
const onVisitorReady = (fsVisitor, error) => {
|
|
69
|
+
if (error) {
|
|
70
|
+
logError(Flagship.getConfig(), error.message || error, "onReady");
|
|
71
|
+
}
|
|
72
|
+
initializeState({ fsVisitor });
|
|
73
|
+
};
|
|
74
|
+
const createVisitor = () => {
|
|
75
|
+
if (!visitorDataRef.current) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const fsVisitor = Flagship.newVisitor({
|
|
79
|
+
visitorId: visitorDataRef.current.id,
|
|
80
|
+
context: visitorDataRef.current.context,
|
|
81
|
+
isAuthenticated: visitorDataRef.current.isAuthenticated,
|
|
82
|
+
hasConsented: visitorDataRef.current.hasConsented,
|
|
83
|
+
initialCampaigns,
|
|
84
|
+
initialFlagsData,
|
|
85
|
+
onFlagsStatusChanged,
|
|
86
|
+
shouldSaveInstance,
|
|
87
|
+
});
|
|
88
|
+
fsVisitor?.on("ready", (error) => {
|
|
89
|
+
onVisitorReady(fsVisitor, error);
|
|
90
|
+
});
|
|
91
|
+
if (!fetchNow) {
|
|
92
|
+
initializeState({ fsVisitor });
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
function updateVisitor() {
|
|
96
|
+
if (!visitorDataRef.current ||
|
|
97
|
+
Flagship.getStatus() !== FSSdkStatus.SDK_INITIALIZED) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (!state.visitor ||
|
|
101
|
+
(state.visitor.visitorId !== visitorDataRef.current.id &&
|
|
102
|
+
(!visitorDataRef.current.isAuthenticated ||
|
|
103
|
+
(visitorDataRef.current.isAuthenticated &&
|
|
104
|
+
state.visitor.anonymousId)))) {
|
|
105
|
+
state.visitor?.cleanup();
|
|
106
|
+
createVisitor();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (visitorDataRef.current.hasConsented !== state.visitor.hasConsented) {
|
|
110
|
+
state.visitor.setConsent(visitorDataRef.current.hasConsented ?? true);
|
|
111
|
+
}
|
|
112
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
113
|
+
state.visitor.updateContext(visitorDataRef.current.context);
|
|
114
|
+
if (!state.visitor.anonymousId && visitorDataRef.current.isAuthenticated) {
|
|
115
|
+
state.visitor.authenticate(visitorDataRef.current.id);
|
|
116
|
+
}
|
|
117
|
+
if (state.visitor.anonymousId && !visitorDataRef.current.isAuthenticated) {
|
|
118
|
+
state.visitor.unauthenticate();
|
|
119
|
+
}
|
|
120
|
+
state.visitor.fetchFlags();
|
|
121
|
+
}
|
|
122
|
+
// #endregion
|
|
123
|
+
useNonInitialEffect(() => {
|
|
124
|
+
if (fetchFlagsOnBucketingUpdated) {
|
|
125
|
+
state.visitor?.fetchFlags();
|
|
126
|
+
}
|
|
127
|
+
}, [lastModified]);
|
|
128
|
+
useNonInitialEffect(() => {
|
|
129
|
+
visitorDataRef.current = visitorData;
|
|
130
|
+
updateVisitor();
|
|
131
|
+
}, [JSON.stringify(visitorData)]);
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
initSdk();
|
|
134
|
+
}, [envId, apiKey, decisionMode]);
|
|
135
|
+
const handleDisplay = () => {
|
|
136
|
+
const isFirstInit = !state.visitor;
|
|
137
|
+
if (state.isInitializing && loadingComponent && isFirstInit && fetchNow) {
|
|
138
|
+
return _jsx(_Fragment, { children: loadingComponent });
|
|
139
|
+
}
|
|
140
|
+
return _jsx(_Fragment, { children: children });
|
|
141
|
+
};
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
window?.addEventListener?.(INTERNAL_EVENTS.FsTriggerRendering, onVariationsForced);
|
|
144
|
+
return () => window?.removeEventListener?.(INTERNAL_EVENTS.FsTriggerRendering, onVariationsForced);
|
|
145
|
+
}, [state.config?.isQAModeEnabled]);
|
|
146
|
+
const onVariationsForced = (e) => {
|
|
147
|
+
const { detail } = e;
|
|
148
|
+
if (detail.forcedReFetchFlags) {
|
|
149
|
+
stateRef.current?.visitor?.fetchFlags();
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
setState((state) => ({
|
|
153
|
+
...state,
|
|
154
|
+
toggleForcedVariations: !state.toggleForcedVariations,
|
|
155
|
+
}));
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
return (_jsx(FlagshipContext.Provider, { value: { state, setState }, children: handleDisplay() }));
|
|
159
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Flagship, DecisionMode, FSSdkStatus } from "./deps.js";
|
|
3
|
+
import { FlagshipProvider } from "./FlagshipProvider.js";
|
|
4
|
+
import { version as SDK_VERSION } from "./sdkVersion.js";
|
|
5
|
+
async function initFlagship({ envId, apiKey, config, }) {
|
|
6
|
+
if (Flagship.getStatus() !== FSSdkStatus.SDK_NOT_INITIALIZED) {
|
|
7
|
+
return Flagship;
|
|
8
|
+
}
|
|
9
|
+
await Flagship.start(envId, apiKey, {
|
|
10
|
+
...config,
|
|
11
|
+
fetchNow: false,
|
|
12
|
+
sdkVersion: SDK_VERSION,
|
|
13
|
+
});
|
|
14
|
+
return Flagship;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* FlagshipProviderSSR is a server-side rendering component for Flagship.
|
|
18
|
+
* It initializes the Flagship SDK and provides the necessary context for rendering.
|
|
19
|
+
* This component is designed to be used in server-side environments where you need to fetch and provide
|
|
20
|
+
* Flagship data before rendering the React application.
|
|
21
|
+
* @returns
|
|
22
|
+
*/
|
|
23
|
+
export async function FlagshipProviderSSR({ children, envId, apiKey, decisionMode = DecisionMode.DECISION_API, visitorData, loadingComponent, onSdkStatusChanged, onBucketingUpdated, fetchFlagsOnBucketingUpdated, hitDeduplicationTime = 2, fetchNow = false, onFlagsStatusChanged, shouldSaveInstance, ...props }) {
|
|
24
|
+
const language = 1;
|
|
25
|
+
const fs = await initFlagship({
|
|
26
|
+
envId,
|
|
27
|
+
apiKey,
|
|
28
|
+
config: {
|
|
29
|
+
...props,
|
|
30
|
+
decisionMode: decisionMode,
|
|
31
|
+
initialBucketing: props.initialBucketing,
|
|
32
|
+
hitDeduplicationTime,
|
|
33
|
+
language,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
let initialFlagsData = props.initialFlagsData;
|
|
37
|
+
if (!props.initialFlagsData && !props.initialCampaigns) {
|
|
38
|
+
const visitor = fs.newVisitor({
|
|
39
|
+
visitorId: visitorData?.id,
|
|
40
|
+
context: visitorData?.context,
|
|
41
|
+
isAuthenticated: visitorData?.isAuthenticated,
|
|
42
|
+
hasConsented: visitorData?.hasConsented,
|
|
43
|
+
onFlagsStatusChanged,
|
|
44
|
+
shouldSaveInstance,
|
|
45
|
+
});
|
|
46
|
+
await visitor.fetchFlags();
|
|
47
|
+
initialFlagsData = visitor.getFlags().toJSON();
|
|
48
|
+
}
|
|
49
|
+
return (_jsx(FlagshipProvider, { envId: envId, apiKey: apiKey, decisionMode: decisionMode, visitorData: visitorData, loadingComponent: loadingComponent, onSdkStatusChanged: onSdkStatusChanged, onBucketingUpdated: onBucketingUpdated, initialFlagsData: initialFlagsData, fetchFlagsOnBucketingUpdated: fetchFlagsOnBucketingUpdated, hitDeduplicationTime: hitDeduplicationTime, fetchNow: fetchNow, language: language, sdkVersion: SDK_VERSION, onFlagsStatusChanged: onFlagsStatusChanged, shouldSaveInstance: shouldSaveInstance, ...props, children: children }));
|
|
50
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export const noVisitorMessage = 'flagship Visitor not initialized.';
|
|
2
|
+
export const noVisitorDefault = 'fsVisitor not initialized, returns default value';
|
|
3
|
+
export const GET_FLAG_CAST_ERROR = 'Flag for key {0} has a different type. Default value is returned.';
|
|
4
|
+
export const GET_METADATA_CAST_ERROR = 'Flag for key {0} has a different type with defaultValue, an empty metadata object is returned';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '@flagship.io/js-sdk/dist/edge.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { FlagshipProvider } from "./FlagshipProvider.js";
|
|
2
|
+
export * from "./deps.js";
|
|
3
|
+
export * from "./FlagshipHooks.js";
|
|
4
|
+
export * from "./type.js";
|
|
5
|
+
export { FlagshipProvider } from "./FlagshipProvider.js";
|
|
6
|
+
export { FlagshipProviderSSR } from "./FlagshipProviderSSR.js";
|
|
7
|
+
export default FlagshipProvider;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
|
+
import { Flagship, LogLevel } from './deps.js';
|
|
4
|
+
export function logError(config, message, tag) {
|
|
5
|
+
if (!config || !config.logLevel || config.logLevel < LogLevel.ERROR) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
if (typeof config.onLog === 'function') {
|
|
9
|
+
config.onLog(LogLevel.ERROR, tag, message);
|
|
10
|
+
}
|
|
11
|
+
if (config.logManager && typeof config.logManager.error === 'function') {
|
|
12
|
+
config.logManager.error(message, tag);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function logInfo(config, message, tag) {
|
|
16
|
+
if (!config || !config.logLevel || config.logLevel < LogLevel.INFO) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (typeof config.onLog === 'function') {
|
|
20
|
+
config.onLog(LogLevel.INFO, tag, message);
|
|
21
|
+
}
|
|
22
|
+
if (config.logManager && typeof config.logManager.info === 'function') {
|
|
23
|
+
config.logManager.info(message, tag);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function logWarn(config, message, tag) {
|
|
27
|
+
if (!config || !config.logLevel || config.logLevel < LogLevel.WARNING) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (typeof config.onLog === 'function') {
|
|
31
|
+
config.onLog(LogLevel.WARNING, tag, message);
|
|
32
|
+
}
|
|
33
|
+
if (config.logManager && typeof config.logManager.warning === 'function') {
|
|
34
|
+
config.logManager.warning(message, tag);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export const getFlagsFromCampaigns = (campaigns) => {
|
|
38
|
+
const flags = new Map();
|
|
39
|
+
if (!campaigns || !Array.isArray(campaigns)) {
|
|
40
|
+
return flags;
|
|
41
|
+
}
|
|
42
|
+
campaigns.forEach((campaign) => {
|
|
43
|
+
const object = campaign.variation.modifications.value;
|
|
44
|
+
for (const key in object) {
|
|
45
|
+
const value = object[key];
|
|
46
|
+
flags.set(key, {
|
|
47
|
+
key,
|
|
48
|
+
campaignId: campaign.id,
|
|
49
|
+
campaignName: campaign.name || '',
|
|
50
|
+
variationGroupId: campaign.variationGroupId,
|
|
51
|
+
variationGroupName: campaign.variationGroupName || '',
|
|
52
|
+
variationId: campaign.variation.id,
|
|
53
|
+
variationName: campaign.variation.name || '',
|
|
54
|
+
isReference: campaign.variation.reference,
|
|
55
|
+
slug: campaign.slug || '',
|
|
56
|
+
value
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
return flags;
|
|
61
|
+
};
|
|
62
|
+
export function uuidV4() {
|
|
63
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (char) {
|
|
64
|
+
const rand = (Math.random() * 16) | 0;
|
|
65
|
+
const value = char === 'x' ? rand : (rand & 0x3) | 0x8;
|
|
66
|
+
return value.toString(16);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
export function useNonInitialEffect(effect, deps) {
|
|
70
|
+
const initialRender = useRef(true);
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (initialRender.current) {
|
|
73
|
+
initialRender.current = false;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (typeof effect === 'function') {
|
|
77
|
+
return effect();
|
|
78
|
+
}
|
|
79
|
+
}, deps);
|
|
80
|
+
}
|
|
81
|
+
export function hasSameType(flagValue, defaultValue) {
|
|
82
|
+
if (typeof flagValue !== typeof defaultValue) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
if (typeof flagValue === 'object' && typeof defaultValue === 'object' &&
|
|
86
|
+
Array.isArray(flagValue) !== Array.isArray(defaultValue)) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
92
|
+
export function sprintf(format, ...value) {
|
|
93
|
+
let formatted = format;
|
|
94
|
+
for (let i = 0; i < value.length; i++) {
|
|
95
|
+
const element = value[i];
|
|
96
|
+
formatted = formatted.replace(new RegExp(`\\{${i}\\}`, 'g'), element);
|
|
97
|
+
}
|
|
98
|
+
return formatted;
|
|
99
|
+
}
|
|
100
|
+
export function hexToValue(hex, config) {
|
|
101
|
+
if (typeof hex !== 'string') {
|
|
102
|
+
logError(config, `Invalid hex string: ${hex}`, 'hexToValue');
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
let jsonString = '';
|
|
106
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
107
|
+
const hexChar = hex.slice(i, i + 2);
|
|
108
|
+
const charCode = parseInt(hexChar, 16);
|
|
109
|
+
if (isNaN(charCode)) {
|
|
110
|
+
logError(config, `Invalid hex character: ${hexChar}`, 'hexToValue');
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
jsonString += String.fromCharCode(charCode);
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
const value = JSON.parse(jsonString);
|
|
117
|
+
return value;
|
|
118
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
logError(config, `Error while parsing JSON: ${error?.message}`, 'hexToValue');
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export function extractFlagsMap(initialFlagsData, initialCampaigns) {
|
|
126
|
+
let flags = new Map();
|
|
127
|
+
if (Array.isArray(initialFlagsData)) {
|
|
128
|
+
initialFlagsData.forEach((flag) => {
|
|
129
|
+
flags.set(flag.key, {
|
|
130
|
+
key: flag.key,
|
|
131
|
+
campaignId: flag.campaignId,
|
|
132
|
+
campaignName: flag.campaignName,
|
|
133
|
+
variationGroupId: flag.variationGroupId,
|
|
134
|
+
variationGroupName: flag.variationGroupName,
|
|
135
|
+
variationId: flag.variationId,
|
|
136
|
+
variationName: flag.variationName,
|
|
137
|
+
isReference: flag.isReference,
|
|
138
|
+
campaignType: flag.campaignType,
|
|
139
|
+
slug: flag.slug,
|
|
140
|
+
value: hexToValue(flag.hex, Flagship.getConfig())?.v
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
else if (initialCampaigns) {
|
|
145
|
+
flags = getFlagsFromCampaigns(initialCampaigns);
|
|
146
|
+
}
|
|
147
|
+
return flags;
|
|
148
|
+
}
|
|
149
|
+
export function deepClone(obj) {
|
|
150
|
+
return JSON.parse(JSON.stringify(obj));
|
|
151
|
+
}
|
|
152
|
+
export function hasContextChanged(original, updated) {
|
|
153
|
+
return JSON.stringify(original) !== JSON.stringify(updated);
|
|
154
|
+
}
|