@featureflare/react 0.0.5-beta.186 → 0.0.5
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/README.md +103 -57
- package/dist/index.cjs +3 -244
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -39
- package/dist/index.d.ts +2 -39
- package/dist/index.js +8 -247
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -12,104 +12,150 @@ pnpm add @featureflare/react
|
|
|
12
12
|
yarn add @featureflare/react
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
**Peer Dependencies:**
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
- `react >= 18`
|
|
18
|
+
- `react-dom >= 18`
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
import { FeatureFlareProvider, resolveFeatureFlareBrowserConfig, useFlags } from '@featureflare/react';
|
|
20
|
+
## Usage
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
useFlags({
|
|
24
|
-
user: { id: 'user-123', email: 'dev@company.com' },
|
|
25
|
-
defaultValue: false,
|
|
26
|
-
refreshIntervalMs: 10000,
|
|
27
|
-
pauseWhenHidden: true,
|
|
28
|
-
});
|
|
22
|
+
### Setup Provider
|
|
29
23
|
|
|
30
|
-
|
|
31
|
-
}
|
|
24
|
+
Wrap your app with `FeatureFlareProvider`:
|
|
32
25
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (!config.sdkKey) {
|
|
37
|
-
return <div>Feature flags disabled</div>;
|
|
38
|
-
}
|
|
26
|
+
```typescript
|
|
27
|
+
import { FeatureFlareProvider } from '@featureflare/react';
|
|
39
28
|
|
|
29
|
+
export function App() {
|
|
40
30
|
return (
|
|
41
|
-
<FeatureFlareProvider
|
|
42
|
-
|
|
43
|
-
|
|
31
|
+
<FeatureFlareProvider
|
|
32
|
+
config={{
|
|
33
|
+
// Optional: explicit FeatureFlare API URL
|
|
34
|
+
apiBaseUrl: 'https://flags.your-company.com',
|
|
35
|
+
// SDK defaults to FeatureFlare Cloud Run unless overridden
|
|
36
|
+
sdkKey: 'your-client-key-here' // Client key for browser
|
|
37
|
+
}}
|
|
38
|
+
initialUser={{ id: 'user-123' }}
|
|
39
|
+
>
|
|
40
|
+
{/* Your app */}
|
|
44
41
|
</FeatureFlareProvider>
|
|
45
42
|
);
|
|
46
43
|
}
|
|
47
44
|
```
|
|
48
45
|
|
|
49
|
-
### Read
|
|
46
|
+
### Read Flags
|
|
47
|
+
|
|
48
|
+
Use the `useBoolFlag` hook to read boolean flags:
|
|
50
49
|
|
|
51
50
|
```typescript
|
|
52
51
|
import { useBoolFlag } from '@featureflare/react';
|
|
53
52
|
|
|
54
53
|
export function NewNav() {
|
|
55
54
|
const { value, loading, error } = useBoolFlag('new-nav', false);
|
|
56
|
-
|
|
55
|
+
|
|
57
56
|
if (loading) return <div>Loading...</div>;
|
|
58
|
-
if (error) return <div>Error: {error}</div>;
|
|
59
|
-
|
|
57
|
+
if (error) return <div>Error: {error.message}</div>;
|
|
58
|
+
|
|
60
59
|
return value ? <div>New nav ON</div> : <div>New nav OFF</div>;
|
|
61
60
|
}
|
|
62
61
|
```
|
|
63
62
|
|
|
64
|
-
|
|
63
|
+
### Update User
|
|
64
|
+
|
|
65
|
+
Use the `useFeatureFlareUser` hook to get and update the current user:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { useFeatureFlareUser } from '@featureflare/react';
|
|
69
|
+
|
|
70
|
+
export function UserSwitcher() {
|
|
71
|
+
const [user, setUser] = useFeatureFlareUser();
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<button
|
|
75
|
+
onClick={() =>
|
|
76
|
+
setUser({
|
|
77
|
+
...(user ?? { id: 'user-123' }),
|
|
78
|
+
meta: { ...(user?.meta ?? {}), companyId: 'northwind' }
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
>
|
|
82
|
+
Switch user
|
|
83
|
+
</button>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## API Reference
|
|
65
89
|
|
|
66
90
|
### `FeatureFlareProvider`
|
|
67
91
|
|
|
68
|
-
|
|
92
|
+
Provider component that wraps your app and provides the FeatureFlare client context.
|
|
93
|
+
|
|
94
|
+
#### Props
|
|
95
|
+
|
|
96
|
+
- `config: FeatureFlareReactConfig` - Configuration object
|
|
97
|
+
- `apiBaseUrl?: string` - Optional explicit FeatureFlare API base URL.
|
|
98
|
+
- `sdkKey?: string` - Client SDK key (recommended). If not provided, reads from `FEATUREFLARE_CLIENT_KEY` env var.
|
|
99
|
+
- `projectKey?: string` - Legacy: project key (requires `envKey`). Not recommended.
|
|
100
|
+
- `envKey?: string` - Environment key (default: `'production'`). Only used with `projectKey`.
|
|
101
|
+
- `initialUser: FeatureFlareUserPayload` - Initial user payload
|
|
102
|
+
- `user?: FeatureFlareUserPayload` - Controlled user (requires `onUserChange`)
|
|
103
|
+
- `onUserChange?: (user: FeatureFlareUserPayload) => void` - Callback for user changes (required if using controlled `user`)
|
|
104
|
+
- `children: React.ReactNode` - Your app components
|
|
69
105
|
|
|
70
|
-
|
|
71
|
-
- `initialUser: FeatureFlareUserPayload`
|
|
72
|
-
- `user?: FeatureFlareUserPayload` (controlled mode)
|
|
73
|
-
- `onUserChange?: (user: FeatureFlareUserPayload) => void` (required in controlled mode)
|
|
74
|
-
- `children: React.ReactNode`
|
|
106
|
+
### `useBoolFlag(flagKey: string, defaultValue: boolean)`
|
|
75
107
|
|
|
76
|
-
|
|
108
|
+
Hook to evaluate a boolean feature flag.
|
|
77
109
|
|
|
78
|
-
|
|
110
|
+
**Returns:**
|
|
79
111
|
|
|
80
|
-
|
|
112
|
+
- `value: boolean` - The flag value
|
|
113
|
+
- `loading: boolean` - Whether the evaluation is in progress
|
|
114
|
+
- `error: Error | null` - Error if evaluation failed
|
|
81
115
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
116
|
+
**Example:**
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
const { value, loading, error } = useBoolFlag('feature-flag', false);
|
|
120
|
+
```
|
|
85
121
|
|
|
86
122
|
### `useFeatureFlareUser()`
|
|
87
123
|
|
|
88
|
-
|
|
124
|
+
Hook to get and update the current user.
|
|
125
|
+
|
|
126
|
+
**Returns:**
|
|
89
127
|
|
|
90
|
-
|
|
128
|
+
`[user: FeatureFlareUserPayload | null, setUser: (user: FeatureFlareUserPayload) => void]`
|
|
91
129
|
|
|
92
|
-
|
|
130
|
+
**Example:**
|
|
93
131
|
|
|
94
|
-
|
|
132
|
+
```typescript
|
|
133
|
+
const [user, setUser] = useFeatureFlareUser();
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Environment Variables
|
|
137
|
+
|
|
138
|
+
The SDK automatically reads from environment variables if `sdkKey` is not provided:
|
|
139
|
+
|
|
140
|
+
- `FEATUREFLARE_CLIENT_KEY` - Client SDK key
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// This will use FEATUREFLARE_CLIENT_KEY from env if sdkKey is not provided
|
|
144
|
+
<FeatureFlareProvider config={{}} initialUser={{ id: 'user-123' }}>
|
|
145
|
+
{/* ... */}
|
|
146
|
+
</FeatureFlareProvider>
|
|
147
|
+
```
|
|
95
148
|
|
|
96
|
-
|
|
97
|
-
- `useFlags(defaultValue?: boolean, options?: { refreshIntervalMs?: number; hiddenRefreshIntervalMs?: number; pauseWhenHidden?: boolean; enabled?: boolean })`
|
|
149
|
+
## API Base URL
|
|
98
150
|
|
|
99
|
-
|
|
151
|
+
The SDK automatically uses `window.location.origin` in the browser (assumes API is on same origin). The API URL cannot be overridden.
|
|
100
152
|
|
|
101
|
-
|
|
102
|
-
- `loading: boolean`
|
|
103
|
-
- `error: string | null`
|
|
153
|
+
## SDK Keys
|
|
104
154
|
|
|
105
|
-
|
|
155
|
+
Use **client keys** for browser/mobile applications. Client keys are not secret and will be visible in your JavaScript bundle.
|
|
106
156
|
|
|
107
|
-
|
|
108
|
-
- Shared polling via provider context (multiple consumers do not duplicate requests).
|
|
109
|
-
- Optional polling controls with hidden-tab awareness.
|
|
110
|
-
- If `user` is provided in input form, hook syncs provider user automatically.
|
|
157
|
+
Get your SDK keys from your FeatureFlare Console → Environments.
|
|
111
158
|
|
|
112
|
-
##
|
|
159
|
+
## License
|
|
113
160
|
|
|
114
|
-
|
|
115
|
-
- If `config.sdkKey` is missing, skip initializing the provider.
|
|
161
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -31,12 +31,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
FeatureFlareProvider: () => FeatureFlareProvider,
|
|
34
|
-
resolveFeatureFlareBrowserConfig: () => resolveFeatureFlareBrowserConfig,
|
|
35
34
|
useBoolFlag: () => useBoolFlag,
|
|
36
35
|
useBoolFlags: () => useBoolFlags,
|
|
37
36
|
useFeatureFlareContext: () => useFeatureFlareContext,
|
|
38
|
-
useFeatureFlareUser: () => useFeatureFlareUser
|
|
39
|
-
useFlags: () => useFlags
|
|
37
|
+
useFeatureFlareUser: () => useFeatureFlareUser
|
|
40
38
|
});
|
|
41
39
|
module.exports = __toCommonJS(index_exports);
|
|
42
40
|
|
|
@@ -44,176 +42,6 @@ module.exports = __toCommonJS(index_exports);
|
|
|
44
42
|
var import_react = __toESM(require("react"), 1);
|
|
45
43
|
var import_sdk_js = require("@featureflare/sdk-js");
|
|
46
44
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
47
|
-
function resolveFeatureFlareBrowserConfig(input) {
|
|
48
|
-
const explicitEnv = input?.envKey;
|
|
49
|
-
const envFromVars = typeof process !== "undefined" ? process.env.NEXT_PUBLIC_FEATUREFLARE_ENV_KEY?.trim().toLowerCase() : "";
|
|
50
|
-
const resolvedEnv = explicitEnv ?? (envFromVars === "development" || envFromVars === "staging" || envFromVars === "production" ? envFromVars : typeof process !== "undefined" && process.env.NODE_ENV === "production" ? "production" : "development");
|
|
51
|
-
const apiBaseUrl = input?.apiBaseUrl ?? (typeof process !== "undefined" ? process.env.NEXT_PUBLIC_FEATUREFLARE_API_BASE_URL?.trim() : void 0);
|
|
52
|
-
const sdkKeyDevelopment = (typeof process !== "undefined" ? process.env.NEXT_PUBLIC_FEATUREFLARE_CLIENT_KEY_DEVELOPMENT?.trim() : "") || (typeof process !== "undefined" ? process.env.NEXT_PUBLIC_FEATUREFLARE_CLIENT_KEY_DEV?.trim() : "") || "";
|
|
53
|
-
const sdkKeyStaging = (typeof process !== "undefined" ? process.env.NEXT_PUBLIC_FEATUREFLARE_CLIENT_KEY_STAGING?.trim() : "") || "";
|
|
54
|
-
const sdkKeyProduction = (typeof process !== "undefined" ? process.env.NEXT_PUBLIC_FEATUREFLARE_CLIENT_KEY_PRODUCTION?.trim() : "") || (typeof process !== "undefined" ? process.env.NEXT_PUBLIC_FEATUREFLARE_CLIENT_KEY_PROD?.trim() : "") || "";
|
|
55
|
-
const sdkKeyDefault = (typeof process !== "undefined" ? process.env.NEXT_PUBLIC_FEATUREFLARE_CLIENT_KEY?.trim() : "") || "";
|
|
56
|
-
const sdkKey = (resolvedEnv === "development" ? sdkKeyDevelopment : "") || (resolvedEnv === "staging" ? sdkKeyStaging : "") || (resolvedEnv === "production" ? sdkKeyProduction : "") || sdkKeyDefault || void 0;
|
|
57
|
-
return {
|
|
58
|
-
apiBaseUrl,
|
|
59
|
-
envKey: resolvedEnv,
|
|
60
|
-
sdkKey
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
function normalizeSubscriptionOptions(options) {
|
|
64
|
-
const refreshIntervalMs = Number.isFinite(options?.refreshIntervalMs) && (options?.refreshIntervalMs ?? 0) > 0 ? Number(options?.refreshIntervalMs) : 1e4;
|
|
65
|
-
const hiddenRefreshIntervalMs = Number.isFinite(options?.hiddenRefreshIntervalMs) && (options?.hiddenRefreshIntervalMs ?? 0) > 0 ? Number(options?.hiddenRefreshIntervalMs) : Math.max(refreshIntervalMs * 6, 6e4);
|
|
66
|
-
return {
|
|
67
|
-
refreshIntervalMs,
|
|
68
|
-
hiddenRefreshIntervalMs,
|
|
69
|
-
pauseWhenHidden: options?.pauseWhenHidden ?? true,
|
|
70
|
-
enabled: options?.enabled ?? true
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
function createFlagsStore(client, getUser) {
|
|
74
|
-
const entries = /* @__PURE__ */ new Map();
|
|
75
|
-
let nextSubscriberId = 1;
|
|
76
|
-
const isHidden = () => typeof document !== "undefined" && document.visibilityState === "hidden";
|
|
77
|
-
const getEntry = (defaultValue) => {
|
|
78
|
-
const key = defaultValue ? "1" : "0";
|
|
79
|
-
const existing = entries.get(key);
|
|
80
|
-
if (existing) return existing;
|
|
81
|
-
const created = {
|
|
82
|
-
defaultValue,
|
|
83
|
-
snapshot: { flags: [], loading: true, error: null },
|
|
84
|
-
listeners: /* @__PURE__ */ new Set(),
|
|
85
|
-
subscribers: /* @__PURE__ */ new Map(),
|
|
86
|
-
timer: null,
|
|
87
|
-
inFlight: false
|
|
88
|
-
};
|
|
89
|
-
entries.set(key, created);
|
|
90
|
-
return created;
|
|
91
|
-
};
|
|
92
|
-
const emit = (entry) => {
|
|
93
|
-
for (const listener of entry.listeners) listener();
|
|
94
|
-
};
|
|
95
|
-
const getEffectiveOptions = (entry) => {
|
|
96
|
-
const active = [...entry.subscribers.values()].filter((s) => s.enabled);
|
|
97
|
-
if (active.length === 0) {
|
|
98
|
-
return { enabled: false, refreshIntervalMs: 0, hiddenRefreshIntervalMs: 0, pauseWhenHidden: true };
|
|
99
|
-
}
|
|
100
|
-
const refreshIntervalMs = active.reduce((min, s) => Math.min(min, s.refreshIntervalMs), Number.POSITIVE_INFINITY);
|
|
101
|
-
const hiddenActive = active.filter((s) => !s.pauseWhenHidden);
|
|
102
|
-
const hiddenRefreshIntervalMs = hiddenActive.length > 0 ? hiddenActive.reduce((min, s) => Math.min(min, s.hiddenRefreshIntervalMs), Number.POSITIVE_INFINITY) : 0;
|
|
103
|
-
return {
|
|
104
|
-
enabled: true,
|
|
105
|
-
refreshIntervalMs: Number.isFinite(refreshIntervalMs) ? refreshIntervalMs : 1e4,
|
|
106
|
-
hiddenRefreshIntervalMs,
|
|
107
|
-
pauseWhenHidden: hiddenActive.length === 0
|
|
108
|
-
};
|
|
109
|
-
};
|
|
110
|
-
const schedule = (entry) => {
|
|
111
|
-
if (entry.timer !== null) {
|
|
112
|
-
clearTimeout(entry.timer);
|
|
113
|
-
entry.timer = null;
|
|
114
|
-
}
|
|
115
|
-
const effective = getEffectiveOptions(entry);
|
|
116
|
-
if (!effective.enabled) return;
|
|
117
|
-
if (isHidden()) {
|
|
118
|
-
if (effective.pauseWhenHidden) return;
|
|
119
|
-
entry.timer = setTimeout(() => {
|
|
120
|
-
void refresh(entry.defaultValue);
|
|
121
|
-
}, effective.hiddenRefreshIntervalMs);
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
entry.timer = setTimeout(() => {
|
|
125
|
-
void refresh(entry.defaultValue);
|
|
126
|
-
}, effective.refreshIntervalMs);
|
|
127
|
-
};
|
|
128
|
-
const refresh = async (defaultValue, force = false) => {
|
|
129
|
-
const entry = getEntry(defaultValue);
|
|
130
|
-
const effective = getEffectiveOptions(entry);
|
|
131
|
-
if (!effective.enabled && !force) return;
|
|
132
|
-
if (!force && isHidden() && effective.pauseWhenHidden) {
|
|
133
|
-
schedule(entry);
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
if (entry.inFlight) return;
|
|
137
|
-
entry.inFlight = true;
|
|
138
|
-
try {
|
|
139
|
-
const flags = await client.flags(getUser(), defaultValue);
|
|
140
|
-
entry.snapshot = { flags, loading: false, error: null };
|
|
141
|
-
emit(entry);
|
|
142
|
-
} catch (error) {
|
|
143
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
144
|
-
entry.snapshot = { ...entry.snapshot, loading: false, error: message };
|
|
145
|
-
emit(entry);
|
|
146
|
-
} finally {
|
|
147
|
-
entry.inFlight = false;
|
|
148
|
-
schedule(entry);
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
const refreshNow = (defaultValue) => {
|
|
152
|
-
const entry = getEntry(defaultValue);
|
|
153
|
-
entry.snapshot = { ...entry.snapshot, loading: true, error: null };
|
|
154
|
-
emit(entry);
|
|
155
|
-
void refresh(defaultValue, true);
|
|
156
|
-
};
|
|
157
|
-
const subscribe = (defaultValue, listener, options) => {
|
|
158
|
-
const entry = getEntry(defaultValue);
|
|
159
|
-
const subscriberId = nextSubscriberId;
|
|
160
|
-
nextSubscriberId += 1;
|
|
161
|
-
entry.listeners.add(listener);
|
|
162
|
-
entry.subscribers.set(subscriberId, normalizeSubscriptionOptions(options));
|
|
163
|
-
const effective = getEffectiveOptions(entry);
|
|
164
|
-
if (effective.enabled && !entry.inFlight && entry.snapshot.loading) {
|
|
165
|
-
void refresh(defaultValue);
|
|
166
|
-
} else {
|
|
167
|
-
schedule(entry);
|
|
168
|
-
}
|
|
169
|
-
return () => {
|
|
170
|
-
entry.listeners.delete(listener);
|
|
171
|
-
entry.subscribers.delete(subscriberId);
|
|
172
|
-
schedule(entry);
|
|
173
|
-
};
|
|
174
|
-
};
|
|
175
|
-
const updateUser = () => {
|
|
176
|
-
for (const entry of entries.values()) {
|
|
177
|
-
entry.snapshot = { ...entry.snapshot, loading: true, error: null };
|
|
178
|
-
emit(entry);
|
|
179
|
-
void refresh(entry.defaultValue);
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
const handleVisibilityChange = () => {
|
|
183
|
-
if (isHidden()) return;
|
|
184
|
-
for (const entry of entries.values()) {
|
|
185
|
-
const effective = getEffectiveOptions(entry);
|
|
186
|
-
if (!effective.enabled) continue;
|
|
187
|
-
void refresh(entry.defaultValue);
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
|
-
if (typeof document !== "undefined") {
|
|
191
|
-
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
192
|
-
}
|
|
193
|
-
const dispose = () => {
|
|
194
|
-
if (typeof document !== "undefined") {
|
|
195
|
-
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
196
|
-
}
|
|
197
|
-
for (const entry of entries.values()) {
|
|
198
|
-
if (entry.timer !== null) {
|
|
199
|
-
clearTimeout(entry.timer);
|
|
200
|
-
}
|
|
201
|
-
entry.timer = null;
|
|
202
|
-
entry.listeners.clear();
|
|
203
|
-
entry.subscribers.clear();
|
|
204
|
-
}
|
|
205
|
-
entries.clear();
|
|
206
|
-
};
|
|
207
|
-
return {
|
|
208
|
-
getState(defaultValue) {
|
|
209
|
-
return getEntry(defaultValue).snapshot;
|
|
210
|
-
},
|
|
211
|
-
refreshNow,
|
|
212
|
-
subscribe,
|
|
213
|
-
updateUser,
|
|
214
|
-
dispose
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
45
|
var FeatureFlareContext = (0, import_react.createContext)(null);
|
|
218
46
|
function FeatureFlareProvider(props) {
|
|
219
47
|
if (props.user && !props.onUserChange) {
|
|
@@ -222,7 +50,6 @@ function FeatureFlareProvider(props) {
|
|
|
222
50
|
const [internalUser, setInternalUser] = (0, import_react.useState)(props.initialUser);
|
|
223
51
|
const user = props.user ?? internalUser;
|
|
224
52
|
const setUser = props.onUserChange ?? setInternalUser;
|
|
225
|
-
const userRef = (0, import_react.useRef)(user);
|
|
226
53
|
const client = (0, import_react.useMemo)(() => {
|
|
227
54
|
return new import_sdk_js.FeatureFlareClient({
|
|
228
55
|
apiBaseUrl: props.config.apiBaseUrl,
|
|
@@ -236,27 +63,7 @@ function FeatureFlareProvider(props) {
|
|
|
236
63
|
props.config.projectKey,
|
|
237
64
|
props.config.sdkKey
|
|
238
65
|
]);
|
|
239
|
-
const
|
|
240
|
-
(0, import_react.useEffect)(() => {
|
|
241
|
-
userRef.current = user;
|
|
242
|
-
flagsStore.updateUser();
|
|
243
|
-
}, [flagsStore, user]);
|
|
244
|
-
(0, import_react.useEffect)(() => {
|
|
245
|
-
return () => {
|
|
246
|
-
flagsStore.dispose();
|
|
247
|
-
};
|
|
248
|
-
}, [flagsStore]);
|
|
249
|
-
const value = (0, import_react.useMemo)(
|
|
250
|
-
() => ({
|
|
251
|
-
client,
|
|
252
|
-
user,
|
|
253
|
-
setUser,
|
|
254
|
-
getFlagsState: flagsStore.getState,
|
|
255
|
-
refreshFlags: flagsStore.refreshNow,
|
|
256
|
-
subscribeFlags: flagsStore.subscribe
|
|
257
|
-
}),
|
|
258
|
-
[client, flagsStore, setUser, user]
|
|
259
|
-
);
|
|
66
|
+
const value = (0, import_react.useMemo)(() => ({ client, user, setUser }), [client, user]);
|
|
260
67
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FeatureFlareContext.Provider, { value, children: props.children });
|
|
261
68
|
}
|
|
262
69
|
function useFeatureFlareContext() {
|
|
@@ -267,16 +74,6 @@ function useFeatureFlareContext() {
|
|
|
267
74
|
|
|
268
75
|
// src/hooks.ts
|
|
269
76
|
var import_react2 = require("react");
|
|
270
|
-
var EMPTY_FLAGS_STATE = { flags: [], loading: true, error: null };
|
|
271
|
-
function userFingerprint(user) {
|
|
272
|
-
if (!user) return "";
|
|
273
|
-
return JSON.stringify({
|
|
274
|
-
id: user.id ?? "",
|
|
275
|
-
key: user.key ?? "",
|
|
276
|
-
email: user.email ?? "",
|
|
277
|
-
meta: user.meta ?? {}
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
77
|
function useFeatureFlareUser() {
|
|
281
78
|
const { user, setUser } = useFeatureFlareContext();
|
|
282
79
|
return [user, setUser];
|
|
@@ -346,50 +143,12 @@ function useBoolFlags(flagKeys, defaultValue = false) {
|
|
|
346
143
|
}, [client, defaultValue, key, sortedKeys, user]);
|
|
347
144
|
return state;
|
|
348
145
|
}
|
|
349
|
-
function useFlags(defaultValueOrInput = false, options = {}) {
|
|
350
|
-
const { subscribeFlags, getFlagsState, refreshFlags, setUser } = useFeatureFlareContext();
|
|
351
|
-
const parsed = (0, import_react2.useMemo)(() => {
|
|
352
|
-
if (typeof defaultValueOrInput === "boolean") {
|
|
353
|
-
return { ...options, defaultValue: defaultValueOrInput };
|
|
354
|
-
}
|
|
355
|
-
return defaultValueOrInput ?? {};
|
|
356
|
-
}, [defaultValueOrInput, options]);
|
|
357
|
-
const defaultValue = parsed.defaultValue ?? false;
|
|
358
|
-
const normalizedOptions = (0, import_react2.useMemo)(
|
|
359
|
-
() => ({
|
|
360
|
-
enabled: parsed.enabled ?? true,
|
|
361
|
-
refreshIntervalMs: parsed.refreshIntervalMs,
|
|
362
|
-
hiddenRefreshIntervalMs: parsed.hiddenRefreshIntervalMs,
|
|
363
|
-
pauseWhenHidden: parsed.pauseWhenHidden ?? true
|
|
364
|
-
}),
|
|
365
|
-
[parsed.enabled, parsed.hiddenRefreshIntervalMs, parsed.pauseWhenHidden, parsed.refreshIntervalMs]
|
|
366
|
-
);
|
|
367
|
-
const appliedUserRef = (0, import_react2.useRef)("");
|
|
368
|
-
const parsedUserFingerprint = (0, import_react2.useMemo)(() => userFingerprint(parsed.user), [parsed.user]);
|
|
369
|
-
(0, import_react2.useEffect)(() => {
|
|
370
|
-
if (!parsed.user) return;
|
|
371
|
-
if (appliedUserRef.current === parsedUserFingerprint) return;
|
|
372
|
-
setUser(parsed.user);
|
|
373
|
-
appliedUserRef.current = parsedUserFingerprint;
|
|
374
|
-
}, [parsed.user, parsedUserFingerprint, setUser]);
|
|
375
|
-
(0, import_react2.useEffect)(() => {
|
|
376
|
-
if (normalizedOptions.enabled === false) return;
|
|
377
|
-
refreshFlags(defaultValue);
|
|
378
|
-
}, [defaultValue, normalizedOptions.enabled, parsedUserFingerprint, refreshFlags]);
|
|
379
|
-
const subscribe = (0, import_react2.useMemo)(
|
|
380
|
-
() => (onStoreChange) => subscribeFlags(defaultValue, onStoreChange, normalizedOptions),
|
|
381
|
-
[defaultValue, normalizedOptions, subscribeFlags]
|
|
382
|
-
);
|
|
383
|
-
return (0, import_react2.useSyncExternalStore)(subscribe, () => getFlagsState(defaultValue), () => EMPTY_FLAGS_STATE);
|
|
384
|
-
}
|
|
385
146
|
// Annotate the CommonJS export names for ESM import in node:
|
|
386
147
|
0 && (module.exports = {
|
|
387
148
|
FeatureFlareProvider,
|
|
388
|
-
resolveFeatureFlareBrowserConfig,
|
|
389
149
|
useBoolFlag,
|
|
390
150
|
useBoolFlags,
|
|
391
151
|
useFeatureFlareContext,
|
|
392
|
-
useFeatureFlareUser
|
|
393
|
-
useFlags
|
|
152
|
+
useFeatureFlareUser
|
|
394
153
|
});
|
|
395
154
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/provider.tsx","../src/hooks.ts"],"sourcesContent":["export * from './provider.js';\nexport * from './hooks.js';\nexport type { FeatureFlareUserPayload } from '@featureflare/sdk-js';\n","import React, { createContext, useEffect, useMemo, useRef, useState } from 'react';\nimport { FeatureFlareClient, type FeatureFlareUserPayload } from '@featureflare/sdk-js';\n\nexport type FeatureFlareEnvironmentKey = 'development' | 'staging' | 'production';\n\nexport type FeatureFlareReactConfig = {\n /** Optional: explicit FeatureFlare API base URL. */\n apiBaseUrl?: string;\n /** Recommended: use a client key (featureflare_cli_...). */\n sdkKey?: string;\n /** Legacy/insecure browser mode: uses /api/v1/eval (no sdkKey). */\n projectKey?: string;\n envKey?: FeatureFlareEnvironmentKey | string;\n};\n\nexport function resolveFeatureFlareBrowserConfig(input?: {\n envKey?: FeatureFlareEnvironmentKey;\n apiBaseUrl?: string;\n}): FeatureFlareReactConfig {\n const explicitEnv = input?.envKey;\n const envFromVars =\n typeof process !== 'undefined' ? process.env.NEXT_PUBLIC_FEATUREFLARE_ENV_KEY?.trim().toLowerCase() : '';\n\n const resolvedEnv: FeatureFlareEnvironmentKey =\n explicitEnv ??\n (envFromVars === 'development' || envFromVars === 'staging' || envFromVars === 'production'\n ? envFromVars\n : typeof process !== 'undefined' && process.env.NODE_ENV === 'production'\n ? 'production'\n : 'development');\n\n const apiBaseUrl =\n input?.apiBaseUrl ??\n (typeof process !== 'undefined' ? process.env.NEXT_PUBLIC_FEATUREFLARE_API_BASE_URL?.trim() : undefined);\n\n const sdkKeyDevelopment =\n (typeof process !== 'undefined' ? process.env.NEXT_PUBLIC_FEATUREFLARE_CLIENT_KEY_DEVELOPMENT?.trim() : '') ||\n (typeof process !== 'undefined' ? process.env.NEXT_PUBLIC_FEATUREFLARE_CLIENT_KEY_DEV?.trim() : '') ||\n '';\n const sdkKeyStaging =\n (typeof process !== 'undefined' ? process.env.NEXT_PUBLIC_FEATUREFLARE_CLIENT_KEY_STAGING?.trim() : '') || '';\n const sdkKeyProduction =\n (typeof process !== 'undefined' ? process.env.NEXT_PUBLIC_FEATUREFLARE_CLIENT_KEY_PRODUCTION?.trim() : '') ||\n (typeof process !== 'undefined' ? process.env.NEXT_PUBLIC_FEATUREFLARE_CLIENT_KEY_PROD?.trim() : '') ||\n '';\n const sdkKeyDefault =\n (typeof process !== 'undefined' ? process.env.NEXT_PUBLIC_FEATUREFLARE_CLIENT_KEY?.trim() : '') || '';\n\n const sdkKey =\n (resolvedEnv === 'development' ? sdkKeyDevelopment : '') ||\n (resolvedEnv === 'staging' ? sdkKeyStaging : '') ||\n (resolvedEnv === 'production' ? sdkKeyProduction : '') ||\n sdkKeyDefault ||\n undefined;\n\n return {\n apiBaseUrl,\n envKey: resolvedEnv,\n sdkKey\n };\n}\n\ntype FeatureFlareContextValue = {\n client: FeatureFlareClient;\n user: FeatureFlareUserPayload;\n setUser: (next: FeatureFlareUserPayload) => void;\n getFlagsState: (defaultValue: boolean) => FlagsState;\n refreshFlags: (defaultValue: boolean) => void;\n subscribeFlags: (\n defaultValue: boolean,\n listener: () => void,\n options?: FlagsSubscriptionOptions\n ) => () => void;\n};\n\nexport type FlagsState = {\n flags: Array<{ key: string; value: boolean }>;\n loading: boolean;\n error: string | null;\n};\n\nexport type FlagsSubscriptionOptions = {\n refreshIntervalMs?: number;\n hiddenRefreshIntervalMs?: number;\n pauseWhenHidden?: boolean;\n enabled?: boolean;\n};\n\ntype NormalizedFlagsSubscriptionOptions = {\n refreshIntervalMs: number;\n hiddenRefreshIntervalMs: number;\n pauseWhenHidden: boolean;\n enabled: boolean;\n};\n\ntype FlagsStoreEntry = {\n defaultValue: boolean;\n snapshot: FlagsState;\n listeners: Set<() => void>;\n subscribers: Map<number, NormalizedFlagsSubscriptionOptions>;\n timer: ReturnType<typeof setTimeout> | null;\n inFlight: boolean;\n};\n\nfunction normalizeSubscriptionOptions(options?: FlagsSubscriptionOptions): NormalizedFlagsSubscriptionOptions {\n const refreshIntervalMs =\n Number.isFinite(options?.refreshIntervalMs) && (options?.refreshIntervalMs ?? 0) > 0\n ? Number(options?.refreshIntervalMs)\n : 10000;\n const hiddenRefreshIntervalMs =\n Number.isFinite(options?.hiddenRefreshIntervalMs) && (options?.hiddenRefreshIntervalMs ?? 0) > 0\n ? Number(options?.hiddenRefreshIntervalMs)\n : Math.max(refreshIntervalMs * 6, 60000);\n\n return {\n refreshIntervalMs,\n hiddenRefreshIntervalMs,\n pauseWhenHidden: options?.pauseWhenHidden ?? true,\n enabled: options?.enabled ?? true\n };\n}\n\nfunction createFlagsStore(client: FeatureFlareClient, getUser: () => FeatureFlareUserPayload) {\n const entries = new Map<string, FlagsStoreEntry>();\n let nextSubscriberId = 1;\n\n const isHidden = () => typeof document !== 'undefined' && document.visibilityState === 'hidden';\n\n const getEntry = (defaultValue: boolean): FlagsStoreEntry => {\n const key = defaultValue ? '1' : '0';\n const existing = entries.get(key);\n if (existing) return existing;\n const created: FlagsStoreEntry = {\n defaultValue,\n snapshot: { flags: [], loading: true, error: null },\n listeners: new Set(),\n subscribers: new Map(),\n timer: null,\n inFlight: false\n };\n entries.set(key, created);\n return created;\n };\n\n const emit = (entry: FlagsStoreEntry) => {\n for (const listener of entry.listeners) listener();\n };\n\n const getEffectiveOptions = (entry: FlagsStoreEntry) => {\n const active = [...entry.subscribers.values()].filter((s) => s.enabled);\n if (active.length === 0) {\n return { enabled: false as const, refreshIntervalMs: 0, hiddenRefreshIntervalMs: 0, pauseWhenHidden: true };\n }\n\n const refreshIntervalMs = active.reduce((min, s) => Math.min(min, s.refreshIntervalMs), Number.POSITIVE_INFINITY);\n const hiddenActive = active.filter((s) => !s.pauseWhenHidden);\n const hiddenRefreshIntervalMs =\n hiddenActive.length > 0\n ? hiddenActive.reduce((min, s) => Math.min(min, s.hiddenRefreshIntervalMs), Number.POSITIVE_INFINITY)\n : 0;\n\n return {\n enabled: true as const,\n refreshIntervalMs: Number.isFinite(refreshIntervalMs) ? refreshIntervalMs : 10000,\n hiddenRefreshIntervalMs,\n pauseWhenHidden: hiddenActive.length === 0\n };\n };\n\n const schedule = (entry: FlagsStoreEntry) => {\n if (entry.timer !== null) {\n clearTimeout(entry.timer);\n entry.timer = null;\n }\n\n const effective = getEffectiveOptions(entry);\n if (!effective.enabled) return;\n\n if (isHidden()) {\n if (effective.pauseWhenHidden) return;\n entry.timer = setTimeout(() => {\n void refresh(entry.defaultValue);\n }, effective.hiddenRefreshIntervalMs);\n return;\n }\n\n entry.timer = setTimeout(() => {\n void refresh(entry.defaultValue);\n }, effective.refreshIntervalMs);\n };\n\n const refresh = async (defaultValue: boolean, force = false) => {\n const entry = getEntry(defaultValue);\n const effective = getEffectiveOptions(entry);\n if (!effective.enabled && !force) return;\n\n if (!force && isHidden() && effective.pauseWhenHidden) {\n schedule(entry);\n return;\n }\n\n if (entry.inFlight) return;\n entry.inFlight = true;\n\n try {\n const flags = await client.flags(getUser(), defaultValue);\n entry.snapshot = { flags, loading: false, error: null };\n emit(entry);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n entry.snapshot = { ...entry.snapshot, loading: false, error: message };\n emit(entry);\n } finally {\n entry.inFlight = false;\n schedule(entry);\n }\n };\n\n const refreshNow = (defaultValue: boolean) => {\n const entry = getEntry(defaultValue);\n entry.snapshot = { ...entry.snapshot, loading: true, error: null };\n emit(entry);\n void refresh(defaultValue, true);\n };\n\n const subscribe = (\n defaultValue: boolean,\n listener: () => void,\n options?: FlagsSubscriptionOptions\n ): (() => void) => {\n const entry = getEntry(defaultValue);\n const subscriberId = nextSubscriberId;\n nextSubscriberId += 1;\n\n entry.listeners.add(listener);\n entry.subscribers.set(subscriberId, normalizeSubscriptionOptions(options));\n\n const effective = getEffectiveOptions(entry);\n if (effective.enabled && !entry.inFlight && entry.snapshot.loading) {\n void refresh(defaultValue);\n } else {\n schedule(entry);\n }\n\n return () => {\n entry.listeners.delete(listener);\n entry.subscribers.delete(subscriberId);\n schedule(entry);\n };\n };\n\n const updateUser = () => {\n for (const entry of entries.values()) {\n entry.snapshot = { ...entry.snapshot, loading: true, error: null };\n emit(entry);\n void refresh(entry.defaultValue);\n }\n };\n\n const handleVisibilityChange = () => {\n if (isHidden()) return;\n for (const entry of entries.values()) {\n const effective = getEffectiveOptions(entry);\n if (!effective.enabled) continue;\n void refresh(entry.defaultValue);\n }\n };\n\n if (typeof document !== 'undefined') {\n document.addEventListener('visibilitychange', handleVisibilityChange);\n }\n\n const dispose = () => {\n if (typeof document !== 'undefined') {\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n }\n for (const entry of entries.values()) {\n if (entry.timer !== null) {\n clearTimeout(entry.timer);\n }\n entry.timer = null;\n entry.listeners.clear();\n entry.subscribers.clear();\n }\n entries.clear();\n };\n\n return {\n getState(defaultValue: boolean): FlagsState {\n return getEntry(defaultValue).snapshot;\n },\n refreshNow,\n subscribe,\n updateUser,\n dispose\n };\n}\n\nconst FeatureFlareContext = createContext<FeatureFlareContextValue | null>(null);\n\nexport function FeatureFlareProvider(props: {\n config: FeatureFlareReactConfig;\n initialUser: FeatureFlareUserPayload;\n user?: FeatureFlareUserPayload;\n onUserChange?: (next: FeatureFlareUserPayload) => void;\n children: React.ReactNode;\n}) {\n if (props.user && !props.onUserChange) {\n throw new Error('FeatureFlareProvider: when providing `user`, also provide `onUserChange` (controlled mode).');\n }\n\n const [internalUser, setInternalUser] = useState<FeatureFlareUserPayload>(props.initialUser);\n const user = props.user ?? internalUser;\n const setUser = props.onUserChange ?? setInternalUser;\n const userRef = useRef<FeatureFlareUserPayload>(user);\n\n const client = useMemo(() => {\n return new FeatureFlareClient({\n apiBaseUrl: props.config.apiBaseUrl,\n sdkKey: props.config.sdkKey,\n projectKey: props.config.projectKey,\n envKey: props.config.envKey\n });\n }, [\n props.config.apiBaseUrl,\n props.config.envKey,\n props.config.projectKey,\n props.config.sdkKey\n ]);\n\n const flagsStore = useMemo(() => createFlagsStore(client, () => userRef.current), [client]);\n\n useEffect(() => {\n userRef.current = user;\n flagsStore.updateUser();\n }, [flagsStore, user]);\n\n useEffect(() => {\n return () => {\n flagsStore.dispose();\n };\n }, [flagsStore]);\n\n const value = useMemo(\n () => ({\n client,\n user,\n setUser,\n getFlagsState: flagsStore.getState,\n refreshFlags: flagsStore.refreshNow,\n subscribeFlags: flagsStore.subscribe\n }),\n [client, flagsStore, setUser, user]\n );\n return <FeatureFlareContext.Provider value={value}>{props.children}</FeatureFlareContext.Provider>;\n}\n\nexport function useFeatureFlareContext(): FeatureFlareContextValue {\n const ctx = React.useContext(FeatureFlareContext);\n if (!ctx) throw new Error('useFeatureFlareContext must be used within <FeatureFlareProvider>.');\n return ctx;\n}\n","import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';\nimport type { FeatureFlareUserPayload } from '@featureflare/sdk-js';\nimport { useFeatureFlareContext, type FlagsSubscriptionOptions } from './provider.js';\n\ntype BoolFlagState = {\n value: boolean;\n loading: boolean;\n error: string | null;\n};\n\ntype BoolFlagsState = {\n values: Record<string, boolean>;\n loading: boolean;\n errors: Record<string, string>;\n};\n\ntype FlagsState = {\n flags: Array<{ key: string; value: boolean }>;\n loading: boolean;\n error: string | null;\n};\n\ntype UseFlagsOptions = FlagsSubscriptionOptions;\n\ntype UseFlagsInput = UseFlagsOptions & {\n defaultValue?: boolean;\n user?: FeatureFlareUserPayload;\n};\n\nconst EMPTY_FLAGS_STATE: FlagsState = { flags: [], loading: true, error: null };\n\nfunction userFingerprint(user?: FeatureFlareUserPayload): string {\n if (!user) return '';\n return JSON.stringify({\n id: user.id ?? '',\n key: user.key ?? '',\n email: user.email ?? '',\n meta: user.meta ?? {}\n });\n}\n\nexport function useFeatureFlareUser(): [FeatureFlareUserPayload, (next: FeatureFlareUserPayload) => void] {\n const { user, setUser } = useFeatureFlareContext();\n return [user, setUser];\n}\n\nexport function useBoolFlag(flagKey: string, defaultValue = false): BoolFlagState {\n const { client, user } = useFeatureFlareContext();\n const [state, setState] = useState<BoolFlagState>({ value: defaultValue, loading: true, error: null });\n const userId = user.id ?? user.key ?? '';\n const key = useMemo(() => `${flagKey}:${userId}`, [flagKey, userId]);\n const lastKey = useRef<string>('');\n\n useEffect(() => {\n let cancelled = false;\n const nextKey = key;\n lastKey.current = nextKey;\n setState((s) => ({ ...s, loading: true, error: null }));\n\n (async () => {\n try {\n const v = await client.bool(flagKey, user, defaultValue);\n if (cancelled) return;\n if (lastKey.current !== nextKey) return;\n setState({ value: v, loading: false, error: null });\n } catch (e) {\n if (cancelled) return;\n if (lastKey.current !== nextKey) return;\n const msg = e instanceof Error ? e.message : String(e);\n setState({ value: defaultValue, loading: false, error: msg });\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [client, defaultValue, flagKey, key, user]);\n\n return state;\n}\n\nexport function useBoolFlags(flagKeys: string[], defaultValue = false): BoolFlagsState {\n const { client, user } = useFeatureFlareContext();\n const sortedKeys = useMemo(() => [...flagKeys].map((k) => k.trim()).filter(Boolean).sort(), [flagKeys]);\n const userId = user.id ?? user.key ?? '';\n const key = useMemo(() => `${sortedKeys.join(',')}:${userId}`, [sortedKeys, userId]);\n const [state, setState] = useState<BoolFlagsState>({ values: {}, loading: true, errors: {} });\n const lastKey = useRef<string>('');\n\n useEffect(() => {\n let cancelled = false;\n const nextKey = key;\n lastKey.current = nextKey;\n setState({ values: {}, loading: true, errors: {} });\n\n (async () => {\n const values: Record<string, boolean> = {};\n const errors: Record<string, string> = {};\n await Promise.all(\n sortedKeys.map(async (flagKey) => {\n try {\n values[flagKey] = await client.bool(flagKey, user, defaultValue);\n } catch (e) {\n values[flagKey] = defaultValue;\n errors[flagKey] = e instanceof Error ? e.message : String(e);\n }\n })\n );\n\n if (cancelled) return;\n if (lastKey.current !== nextKey) return;\n setState({ values, loading: false, errors });\n })();\n\n return () => {\n cancelled = true;\n };\n }, [client, defaultValue, key, sortedKeys, user]);\n\n return state;\n}\n\nexport function useFlags(input?: UseFlagsInput): FlagsState;\nexport function useFlags(defaultValue?: boolean, options?: UseFlagsOptions): FlagsState;\nexport function useFlags(defaultValueOrInput: boolean | UseFlagsInput = false, options: UseFlagsOptions = {}): FlagsState {\n const { subscribeFlags, getFlagsState, refreshFlags, setUser } = useFeatureFlareContext();\n\n const parsed = useMemo<UseFlagsInput>(() => {\n if (typeof defaultValueOrInput === 'boolean') {\n return { ...options, defaultValue: defaultValueOrInput };\n }\n return defaultValueOrInput ?? {};\n }, [defaultValueOrInput, options]);\n\n const defaultValue = parsed.defaultValue ?? false;\n\n const normalizedOptions = useMemo<UseFlagsOptions>(\n () => ({\n enabled: parsed.enabled ?? true,\n refreshIntervalMs: parsed.refreshIntervalMs,\n hiddenRefreshIntervalMs: parsed.hiddenRefreshIntervalMs,\n pauseWhenHidden: parsed.pauseWhenHidden ?? true\n }),\n [parsed.enabled, parsed.hiddenRefreshIntervalMs, parsed.pauseWhenHidden, parsed.refreshIntervalMs]\n );\n\n const appliedUserRef = useRef<string>('');\n const parsedUserFingerprint = useMemo(() => userFingerprint(parsed.user), [parsed.user]);\n\n useEffect(() => {\n if (!parsed.user) return;\n if (appliedUserRef.current === parsedUserFingerprint) return;\n setUser(parsed.user);\n appliedUserRef.current = parsedUserFingerprint;\n }, [parsed.user, parsedUserFingerprint, setUser]);\n\n useEffect(() => {\n if (normalizedOptions.enabled === false) return;\n refreshFlags(defaultValue);\n }, [defaultValue, normalizedOptions.enabled, parsedUserFingerprint, refreshFlags]);\n\n const subscribe = useMemo(\n () =>\n (onStoreChange: () => void) =>\n subscribeFlags(defaultValue, onStoreChange, normalizedOptions),\n [defaultValue, normalizedOptions, subscribeFlags]\n );\n\n return useSyncExternalStore(subscribe, () => getFlagsState(defaultValue), () => EMPTY_FLAGS_STATE);\n}\n"],"mappings":"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","names":["React","import_react"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/provider.tsx","../src/hooks.ts"],"sourcesContent":["export * from './provider.js';\nexport * from './hooks.js';\nexport type { FeatureFlareUserPayload } from '@featureflare/sdk-js';\n","import React, { createContext, useMemo, useState } from 'react';\nimport { FeatureFlareClient, type FeatureFlareUserPayload } from '@featureflare/sdk-js';\n\ntype FeatureFlareReactConfig = {\n /** Optional: explicit FeatureFlare API base URL. */\n apiBaseUrl?: string;\n /** Recommended: use a client key (featureflare_cli_...). */\n sdkKey?: string;\n /** Legacy/insecure browser mode: uses /api/v1/eval (no sdkKey). */\n projectKey?: string;\n envKey?: string;\n};\n\ntype FeatureFlareContextValue = {\n client: FeatureFlareClient;\n user: FeatureFlareUserPayload;\n setUser: (next: FeatureFlareUserPayload) => void;\n};\n\nconst FeatureFlareContext = createContext<FeatureFlareContextValue | null>(null);\n\nexport function FeatureFlareProvider(props: {\n config: FeatureFlareReactConfig;\n initialUser: FeatureFlareUserPayload;\n user?: FeatureFlareUserPayload;\n onUserChange?: (next: FeatureFlareUserPayload) => void;\n children: React.ReactNode;\n}) {\n if (props.user && !props.onUserChange) {\n throw new Error('FeatureFlareProvider: when providing `user`, also provide `onUserChange` (controlled mode).');\n }\n\n const [internalUser, setInternalUser] = useState<FeatureFlareUserPayload>(props.initialUser);\n const user = props.user ?? internalUser;\n const setUser = props.onUserChange ?? setInternalUser;\n\n const client = useMemo(() => {\n return new FeatureFlareClient({\n apiBaseUrl: props.config.apiBaseUrl,\n sdkKey: props.config.sdkKey,\n projectKey: props.config.projectKey,\n envKey: props.config.envKey\n });\n }, [\n props.config.apiBaseUrl,\n props.config.envKey,\n props.config.projectKey,\n props.config.sdkKey\n ]);\n\n const value = useMemo(() => ({ client, user, setUser }), [client, user]);\n return <FeatureFlareContext.Provider value={value}>{props.children}</FeatureFlareContext.Provider>;\n}\n\nexport function useFeatureFlareContext(): FeatureFlareContextValue {\n const ctx = React.useContext(FeatureFlareContext);\n if (!ctx) throw new Error('useFeatureFlareContext must be used within <FeatureFlareProvider>.');\n return ctx;\n}\n","import { useEffect, useMemo, useRef, useState } from 'react';\nimport type { FeatureFlareUserPayload } from '@featureflare/sdk-js';\nimport { useFeatureFlareContext } from './provider.js';\n\ntype BoolFlagState = {\n value: boolean;\n loading: boolean;\n error: string | null;\n};\n\ntype BoolFlagsState = {\n values: Record<string, boolean>;\n loading: boolean;\n errors: Record<string, string>;\n};\n\nexport function useFeatureFlareUser(): [FeatureFlareUserPayload, (next: FeatureFlareUserPayload) => void] {\n const { user, setUser } = useFeatureFlareContext();\n return [user, setUser];\n}\n\nexport function useBoolFlag(flagKey: string, defaultValue = false): BoolFlagState {\n const { client, user } = useFeatureFlareContext();\n const [state, setState] = useState<BoolFlagState>({ value: defaultValue, loading: true, error: null });\n const userId = user.id ?? user.key ?? '';\n const key = useMemo(() => `${flagKey}:${userId}`, [flagKey, userId]);\n const lastKey = useRef<string>('');\n\n useEffect(() => {\n let cancelled = false;\n const nextKey = key;\n lastKey.current = nextKey;\n setState((s) => ({ ...s, loading: true, error: null }));\n\n (async () => {\n try {\n const v = await client.bool(flagKey, user, defaultValue);\n if (cancelled) return;\n if (lastKey.current !== nextKey) return;\n setState({ value: v, loading: false, error: null });\n } catch (e) {\n if (cancelled) return;\n if (lastKey.current !== nextKey) return;\n const msg = e instanceof Error ? e.message : String(e);\n setState({ value: defaultValue, loading: false, error: msg });\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [client, defaultValue, flagKey, key, user]);\n\n return state;\n}\n\nexport function useBoolFlags(flagKeys: string[], defaultValue = false): BoolFlagsState {\n const { client, user } = useFeatureFlareContext();\n const sortedKeys = useMemo(() => [...flagKeys].map((k) => k.trim()).filter(Boolean).sort(), [flagKeys]);\n const userId = user.id ?? user.key ?? '';\n const key = useMemo(() => `${sortedKeys.join(',')}:${userId}`, [sortedKeys, userId]);\n const [state, setState] = useState<BoolFlagsState>({ values: {}, loading: true, errors: {} });\n const lastKey = useRef<string>('');\n\n useEffect(() => {\n let cancelled = false;\n const nextKey = key;\n lastKey.current = nextKey;\n setState({ values: {}, loading: true, errors: {} });\n\n (async () => {\n const values: Record<string, boolean> = {};\n const errors: Record<string, string> = {};\n await Promise.all(\n sortedKeys.map(async (flagKey) => {\n try {\n values[flagKey] = await client.bool(flagKey, user, defaultValue);\n } catch (e) {\n values[flagKey] = defaultValue;\n errors[flagKey] = e instanceof Error ? e.message : String(e);\n }\n })\n );\n\n if (cancelled) return;\n if (lastKey.current !== nextKey) return;\n setState({ values, loading: false, errors });\n })();\n\n return () => {\n cancelled = true;\n };\n }, [client, defaultValue, key, sortedKeys, user]);\n\n return state;\n}\n"],"mappings":"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","names":["React","import_react"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -3,7 +3,6 @@ import React from 'react';
|
|
|
3
3
|
import { FeatureFlareUserPayload, FeatureFlareClient } from '@featureflare/sdk-js';
|
|
4
4
|
export { FeatureFlareUserPayload } from '@featureflare/sdk-js';
|
|
5
5
|
|
|
6
|
-
type FeatureFlareEnvironmentKey = 'development' | 'staging' | 'production';
|
|
7
6
|
type FeatureFlareReactConfig = {
|
|
8
7
|
/** Optional: explicit FeatureFlare API base URL. */
|
|
9
8
|
apiBaseUrl?: string;
|
|
@@ -11,33 +10,12 @@ type FeatureFlareReactConfig = {
|
|
|
11
10
|
sdkKey?: string;
|
|
12
11
|
/** Legacy/insecure browser mode: uses /api/v1/eval (no sdkKey). */
|
|
13
12
|
projectKey?: string;
|
|
14
|
-
envKey?:
|
|
13
|
+
envKey?: string;
|
|
15
14
|
};
|
|
16
|
-
declare function resolveFeatureFlareBrowserConfig(input?: {
|
|
17
|
-
envKey?: FeatureFlareEnvironmentKey;
|
|
18
|
-
apiBaseUrl?: string;
|
|
19
|
-
}): FeatureFlareReactConfig;
|
|
20
15
|
type FeatureFlareContextValue = {
|
|
21
16
|
client: FeatureFlareClient;
|
|
22
17
|
user: FeatureFlareUserPayload;
|
|
23
18
|
setUser: (next: FeatureFlareUserPayload) => void;
|
|
24
|
-
getFlagsState: (defaultValue: boolean) => FlagsState$1;
|
|
25
|
-
refreshFlags: (defaultValue: boolean) => void;
|
|
26
|
-
subscribeFlags: (defaultValue: boolean, listener: () => void, options?: FlagsSubscriptionOptions) => () => void;
|
|
27
|
-
};
|
|
28
|
-
type FlagsState$1 = {
|
|
29
|
-
flags: Array<{
|
|
30
|
-
key: string;
|
|
31
|
-
value: boolean;
|
|
32
|
-
}>;
|
|
33
|
-
loading: boolean;
|
|
34
|
-
error: string | null;
|
|
35
|
-
};
|
|
36
|
-
type FlagsSubscriptionOptions = {
|
|
37
|
-
refreshIntervalMs?: number;
|
|
38
|
-
hiddenRefreshIntervalMs?: number;
|
|
39
|
-
pauseWhenHidden?: boolean;
|
|
40
|
-
enabled?: boolean;
|
|
41
19
|
};
|
|
42
20
|
declare function FeatureFlareProvider(props: {
|
|
43
21
|
config: FeatureFlareReactConfig;
|
|
@@ -58,23 +36,8 @@ type BoolFlagsState = {
|
|
|
58
36
|
loading: boolean;
|
|
59
37
|
errors: Record<string, string>;
|
|
60
38
|
};
|
|
61
|
-
type FlagsState = {
|
|
62
|
-
flags: Array<{
|
|
63
|
-
key: string;
|
|
64
|
-
value: boolean;
|
|
65
|
-
}>;
|
|
66
|
-
loading: boolean;
|
|
67
|
-
error: string | null;
|
|
68
|
-
};
|
|
69
|
-
type UseFlagsOptions = FlagsSubscriptionOptions;
|
|
70
|
-
type UseFlagsInput = UseFlagsOptions & {
|
|
71
|
-
defaultValue?: boolean;
|
|
72
|
-
user?: FeatureFlareUserPayload;
|
|
73
|
-
};
|
|
74
39
|
declare function useFeatureFlareUser(): [FeatureFlareUserPayload, (next: FeatureFlareUserPayload) => void];
|
|
75
40
|
declare function useBoolFlag(flagKey: string, defaultValue?: boolean): BoolFlagState;
|
|
76
41
|
declare function useBoolFlags(flagKeys: string[], defaultValue?: boolean): BoolFlagsState;
|
|
77
|
-
declare function useFlags(input?: UseFlagsInput): FlagsState;
|
|
78
|
-
declare function useFlags(defaultValue?: boolean, options?: UseFlagsOptions): FlagsState;
|
|
79
42
|
|
|
80
|
-
export {
|
|
43
|
+
export { FeatureFlareProvider, useBoolFlag, useBoolFlags, useFeatureFlareContext, useFeatureFlareUser };
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,6 @@ import React from 'react';
|
|
|
3
3
|
import { FeatureFlareUserPayload, FeatureFlareClient } from '@featureflare/sdk-js';
|
|
4
4
|
export { FeatureFlareUserPayload } from '@featureflare/sdk-js';
|
|
5
5
|
|
|
6
|
-
type FeatureFlareEnvironmentKey = 'development' | 'staging' | 'production';
|
|
7
6
|
type FeatureFlareReactConfig = {
|
|
8
7
|
/** Optional: explicit FeatureFlare API base URL. */
|
|
9
8
|
apiBaseUrl?: string;
|
|
@@ -11,33 +10,12 @@ type FeatureFlareReactConfig = {
|
|
|
11
10
|
sdkKey?: string;
|
|
12
11
|
/** Legacy/insecure browser mode: uses /api/v1/eval (no sdkKey). */
|
|
13
12
|
projectKey?: string;
|
|
14
|
-
envKey?:
|
|
13
|
+
envKey?: string;
|
|
15
14
|
};
|
|
16
|
-
declare function resolveFeatureFlareBrowserConfig(input?: {
|
|
17
|
-
envKey?: FeatureFlareEnvironmentKey;
|
|
18
|
-
apiBaseUrl?: string;
|
|
19
|
-
}): FeatureFlareReactConfig;
|
|
20
15
|
type FeatureFlareContextValue = {
|
|
21
16
|
client: FeatureFlareClient;
|
|
22
17
|
user: FeatureFlareUserPayload;
|
|
23
18
|
setUser: (next: FeatureFlareUserPayload) => void;
|
|
24
|
-
getFlagsState: (defaultValue: boolean) => FlagsState$1;
|
|
25
|
-
refreshFlags: (defaultValue: boolean) => void;
|
|
26
|
-
subscribeFlags: (defaultValue: boolean, listener: () => void, options?: FlagsSubscriptionOptions) => () => void;
|
|
27
|
-
};
|
|
28
|
-
type FlagsState$1 = {
|
|
29
|
-
flags: Array<{
|
|
30
|
-
key: string;
|
|
31
|
-
value: boolean;
|
|
32
|
-
}>;
|
|
33
|
-
loading: boolean;
|
|
34
|
-
error: string | null;
|
|
35
|
-
};
|
|
36
|
-
type FlagsSubscriptionOptions = {
|
|
37
|
-
refreshIntervalMs?: number;
|
|
38
|
-
hiddenRefreshIntervalMs?: number;
|
|
39
|
-
pauseWhenHidden?: boolean;
|
|
40
|
-
enabled?: boolean;
|
|
41
19
|
};
|
|
42
20
|
declare function FeatureFlareProvider(props: {
|
|
43
21
|
config: FeatureFlareReactConfig;
|
|
@@ -58,23 +36,8 @@ type BoolFlagsState = {
|
|
|
58
36
|
loading: boolean;
|
|
59
37
|
errors: Record<string, string>;
|
|
60
38
|
};
|
|
61
|
-
type FlagsState = {
|
|
62
|
-
flags: Array<{
|
|
63
|
-
key: string;
|
|
64
|
-
value: boolean;
|
|
65
|
-
}>;
|
|
66
|
-
loading: boolean;
|
|
67
|
-
error: string | null;
|
|
68
|
-
};
|
|
69
|
-
type UseFlagsOptions = FlagsSubscriptionOptions;
|
|
70
|
-
type UseFlagsInput = UseFlagsOptions & {
|
|
71
|
-
defaultValue?: boolean;
|
|
72
|
-
user?: FeatureFlareUserPayload;
|
|
73
|
-
};
|
|
74
39
|
declare function useFeatureFlareUser(): [FeatureFlareUserPayload, (next: FeatureFlareUserPayload) => void];
|
|
75
40
|
declare function useBoolFlag(flagKey: string, defaultValue?: boolean): BoolFlagState;
|
|
76
41
|
declare function useBoolFlags(flagKeys: string[], defaultValue?: boolean): BoolFlagsState;
|
|
77
|
-
declare function useFlags(input?: UseFlagsInput): FlagsState;
|
|
78
|
-
declare function useFlags(defaultValue?: boolean, options?: UseFlagsOptions): FlagsState;
|
|
79
42
|
|
|
80
|
-
export {
|
|
43
|
+
export { FeatureFlareProvider, useBoolFlag, useBoolFlags, useFeatureFlareContext, useFeatureFlareUser };
|
package/dist/index.js
CHANGED
|
@@ -1,177 +1,7 @@
|
|
|
1
1
|
// src/provider.tsx
|
|
2
|
-
import React, { createContext,
|
|
2
|
+
import React, { createContext, useMemo, useState } from "react";
|
|
3
3
|
import { FeatureFlareClient } from "@featureflare/sdk-js";
|
|
4
4
|
import { jsx } from "react/jsx-runtime";
|
|
5
|
-
function resolveFeatureFlareBrowserConfig(input) {
|
|
6
|
-
const explicitEnv = input?.envKey;
|
|
7
|
-
const envFromVars = typeof process !== "undefined" ? process.env.NEXT_PUBLIC_FEATUREFLARE_ENV_KEY?.trim().toLowerCase() : "";
|
|
8
|
-
const resolvedEnv = explicitEnv ?? (envFromVars === "development" || envFromVars === "staging" || envFromVars === "production" ? envFromVars : typeof process !== "undefined" && process.env.NODE_ENV === "production" ? "production" : "development");
|
|
9
|
-
const apiBaseUrl = input?.apiBaseUrl ?? (typeof process !== "undefined" ? process.env.NEXT_PUBLIC_FEATUREFLARE_API_BASE_URL?.trim() : void 0);
|
|
10
|
-
const sdkKeyDevelopment = (typeof process !== "undefined" ? process.env.NEXT_PUBLIC_FEATUREFLARE_CLIENT_KEY_DEVELOPMENT?.trim() : "") || (typeof process !== "undefined" ? process.env.NEXT_PUBLIC_FEATUREFLARE_CLIENT_KEY_DEV?.trim() : "") || "";
|
|
11
|
-
const sdkKeyStaging = (typeof process !== "undefined" ? process.env.NEXT_PUBLIC_FEATUREFLARE_CLIENT_KEY_STAGING?.trim() : "") || "";
|
|
12
|
-
const sdkKeyProduction = (typeof process !== "undefined" ? process.env.NEXT_PUBLIC_FEATUREFLARE_CLIENT_KEY_PRODUCTION?.trim() : "") || (typeof process !== "undefined" ? process.env.NEXT_PUBLIC_FEATUREFLARE_CLIENT_KEY_PROD?.trim() : "") || "";
|
|
13
|
-
const sdkKeyDefault = (typeof process !== "undefined" ? process.env.NEXT_PUBLIC_FEATUREFLARE_CLIENT_KEY?.trim() : "") || "";
|
|
14
|
-
const sdkKey = (resolvedEnv === "development" ? sdkKeyDevelopment : "") || (resolvedEnv === "staging" ? sdkKeyStaging : "") || (resolvedEnv === "production" ? sdkKeyProduction : "") || sdkKeyDefault || void 0;
|
|
15
|
-
return {
|
|
16
|
-
apiBaseUrl,
|
|
17
|
-
envKey: resolvedEnv,
|
|
18
|
-
sdkKey
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
function normalizeSubscriptionOptions(options) {
|
|
22
|
-
const refreshIntervalMs = Number.isFinite(options?.refreshIntervalMs) && (options?.refreshIntervalMs ?? 0) > 0 ? Number(options?.refreshIntervalMs) : 1e4;
|
|
23
|
-
const hiddenRefreshIntervalMs = Number.isFinite(options?.hiddenRefreshIntervalMs) && (options?.hiddenRefreshIntervalMs ?? 0) > 0 ? Number(options?.hiddenRefreshIntervalMs) : Math.max(refreshIntervalMs * 6, 6e4);
|
|
24
|
-
return {
|
|
25
|
-
refreshIntervalMs,
|
|
26
|
-
hiddenRefreshIntervalMs,
|
|
27
|
-
pauseWhenHidden: options?.pauseWhenHidden ?? true,
|
|
28
|
-
enabled: options?.enabled ?? true
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
function createFlagsStore(client, getUser) {
|
|
32
|
-
const entries = /* @__PURE__ */ new Map();
|
|
33
|
-
let nextSubscriberId = 1;
|
|
34
|
-
const isHidden = () => typeof document !== "undefined" && document.visibilityState === "hidden";
|
|
35
|
-
const getEntry = (defaultValue) => {
|
|
36
|
-
const key = defaultValue ? "1" : "0";
|
|
37
|
-
const existing = entries.get(key);
|
|
38
|
-
if (existing) return existing;
|
|
39
|
-
const created = {
|
|
40
|
-
defaultValue,
|
|
41
|
-
snapshot: { flags: [], loading: true, error: null },
|
|
42
|
-
listeners: /* @__PURE__ */ new Set(),
|
|
43
|
-
subscribers: /* @__PURE__ */ new Map(),
|
|
44
|
-
timer: null,
|
|
45
|
-
inFlight: false
|
|
46
|
-
};
|
|
47
|
-
entries.set(key, created);
|
|
48
|
-
return created;
|
|
49
|
-
};
|
|
50
|
-
const emit = (entry) => {
|
|
51
|
-
for (const listener of entry.listeners) listener();
|
|
52
|
-
};
|
|
53
|
-
const getEffectiveOptions = (entry) => {
|
|
54
|
-
const active = [...entry.subscribers.values()].filter((s) => s.enabled);
|
|
55
|
-
if (active.length === 0) {
|
|
56
|
-
return { enabled: false, refreshIntervalMs: 0, hiddenRefreshIntervalMs: 0, pauseWhenHidden: true };
|
|
57
|
-
}
|
|
58
|
-
const refreshIntervalMs = active.reduce((min, s) => Math.min(min, s.refreshIntervalMs), Number.POSITIVE_INFINITY);
|
|
59
|
-
const hiddenActive = active.filter((s) => !s.pauseWhenHidden);
|
|
60
|
-
const hiddenRefreshIntervalMs = hiddenActive.length > 0 ? hiddenActive.reduce((min, s) => Math.min(min, s.hiddenRefreshIntervalMs), Number.POSITIVE_INFINITY) : 0;
|
|
61
|
-
return {
|
|
62
|
-
enabled: true,
|
|
63
|
-
refreshIntervalMs: Number.isFinite(refreshIntervalMs) ? refreshIntervalMs : 1e4,
|
|
64
|
-
hiddenRefreshIntervalMs,
|
|
65
|
-
pauseWhenHidden: hiddenActive.length === 0
|
|
66
|
-
};
|
|
67
|
-
};
|
|
68
|
-
const schedule = (entry) => {
|
|
69
|
-
if (entry.timer !== null) {
|
|
70
|
-
clearTimeout(entry.timer);
|
|
71
|
-
entry.timer = null;
|
|
72
|
-
}
|
|
73
|
-
const effective = getEffectiveOptions(entry);
|
|
74
|
-
if (!effective.enabled) return;
|
|
75
|
-
if (isHidden()) {
|
|
76
|
-
if (effective.pauseWhenHidden) return;
|
|
77
|
-
entry.timer = setTimeout(() => {
|
|
78
|
-
void refresh(entry.defaultValue);
|
|
79
|
-
}, effective.hiddenRefreshIntervalMs);
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
entry.timer = setTimeout(() => {
|
|
83
|
-
void refresh(entry.defaultValue);
|
|
84
|
-
}, effective.refreshIntervalMs);
|
|
85
|
-
};
|
|
86
|
-
const refresh = async (defaultValue, force = false) => {
|
|
87
|
-
const entry = getEntry(defaultValue);
|
|
88
|
-
const effective = getEffectiveOptions(entry);
|
|
89
|
-
if (!effective.enabled && !force) return;
|
|
90
|
-
if (!force && isHidden() && effective.pauseWhenHidden) {
|
|
91
|
-
schedule(entry);
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
if (entry.inFlight) return;
|
|
95
|
-
entry.inFlight = true;
|
|
96
|
-
try {
|
|
97
|
-
const flags = await client.flags(getUser(), defaultValue);
|
|
98
|
-
entry.snapshot = { flags, loading: false, error: null };
|
|
99
|
-
emit(entry);
|
|
100
|
-
} catch (error) {
|
|
101
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
102
|
-
entry.snapshot = { ...entry.snapshot, loading: false, error: message };
|
|
103
|
-
emit(entry);
|
|
104
|
-
} finally {
|
|
105
|
-
entry.inFlight = false;
|
|
106
|
-
schedule(entry);
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
const refreshNow = (defaultValue) => {
|
|
110
|
-
const entry = getEntry(defaultValue);
|
|
111
|
-
entry.snapshot = { ...entry.snapshot, loading: true, error: null };
|
|
112
|
-
emit(entry);
|
|
113
|
-
void refresh(defaultValue, true);
|
|
114
|
-
};
|
|
115
|
-
const subscribe = (defaultValue, listener, options) => {
|
|
116
|
-
const entry = getEntry(defaultValue);
|
|
117
|
-
const subscriberId = nextSubscriberId;
|
|
118
|
-
nextSubscriberId += 1;
|
|
119
|
-
entry.listeners.add(listener);
|
|
120
|
-
entry.subscribers.set(subscriberId, normalizeSubscriptionOptions(options));
|
|
121
|
-
const effective = getEffectiveOptions(entry);
|
|
122
|
-
if (effective.enabled && !entry.inFlight && entry.snapshot.loading) {
|
|
123
|
-
void refresh(defaultValue);
|
|
124
|
-
} else {
|
|
125
|
-
schedule(entry);
|
|
126
|
-
}
|
|
127
|
-
return () => {
|
|
128
|
-
entry.listeners.delete(listener);
|
|
129
|
-
entry.subscribers.delete(subscriberId);
|
|
130
|
-
schedule(entry);
|
|
131
|
-
};
|
|
132
|
-
};
|
|
133
|
-
const updateUser = () => {
|
|
134
|
-
for (const entry of entries.values()) {
|
|
135
|
-
entry.snapshot = { ...entry.snapshot, loading: true, error: null };
|
|
136
|
-
emit(entry);
|
|
137
|
-
void refresh(entry.defaultValue);
|
|
138
|
-
}
|
|
139
|
-
};
|
|
140
|
-
const handleVisibilityChange = () => {
|
|
141
|
-
if (isHidden()) return;
|
|
142
|
-
for (const entry of entries.values()) {
|
|
143
|
-
const effective = getEffectiveOptions(entry);
|
|
144
|
-
if (!effective.enabled) continue;
|
|
145
|
-
void refresh(entry.defaultValue);
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
if (typeof document !== "undefined") {
|
|
149
|
-
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
150
|
-
}
|
|
151
|
-
const dispose = () => {
|
|
152
|
-
if (typeof document !== "undefined") {
|
|
153
|
-
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
154
|
-
}
|
|
155
|
-
for (const entry of entries.values()) {
|
|
156
|
-
if (entry.timer !== null) {
|
|
157
|
-
clearTimeout(entry.timer);
|
|
158
|
-
}
|
|
159
|
-
entry.timer = null;
|
|
160
|
-
entry.listeners.clear();
|
|
161
|
-
entry.subscribers.clear();
|
|
162
|
-
}
|
|
163
|
-
entries.clear();
|
|
164
|
-
};
|
|
165
|
-
return {
|
|
166
|
-
getState(defaultValue) {
|
|
167
|
-
return getEntry(defaultValue).snapshot;
|
|
168
|
-
},
|
|
169
|
-
refreshNow,
|
|
170
|
-
subscribe,
|
|
171
|
-
updateUser,
|
|
172
|
-
dispose
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
5
|
var FeatureFlareContext = createContext(null);
|
|
176
6
|
function FeatureFlareProvider(props) {
|
|
177
7
|
if (props.user && !props.onUserChange) {
|
|
@@ -180,7 +10,6 @@ function FeatureFlareProvider(props) {
|
|
|
180
10
|
const [internalUser, setInternalUser] = useState(props.initialUser);
|
|
181
11
|
const user = props.user ?? internalUser;
|
|
182
12
|
const setUser = props.onUserChange ?? setInternalUser;
|
|
183
|
-
const userRef = useRef(user);
|
|
184
13
|
const client = useMemo(() => {
|
|
185
14
|
return new FeatureFlareClient({
|
|
186
15
|
apiBaseUrl: props.config.apiBaseUrl,
|
|
@@ -194,27 +23,7 @@ function FeatureFlareProvider(props) {
|
|
|
194
23
|
props.config.projectKey,
|
|
195
24
|
props.config.sdkKey
|
|
196
25
|
]);
|
|
197
|
-
const
|
|
198
|
-
useEffect(() => {
|
|
199
|
-
userRef.current = user;
|
|
200
|
-
flagsStore.updateUser();
|
|
201
|
-
}, [flagsStore, user]);
|
|
202
|
-
useEffect(() => {
|
|
203
|
-
return () => {
|
|
204
|
-
flagsStore.dispose();
|
|
205
|
-
};
|
|
206
|
-
}, [flagsStore]);
|
|
207
|
-
const value = useMemo(
|
|
208
|
-
() => ({
|
|
209
|
-
client,
|
|
210
|
-
user,
|
|
211
|
-
setUser,
|
|
212
|
-
getFlagsState: flagsStore.getState,
|
|
213
|
-
refreshFlags: flagsStore.refreshNow,
|
|
214
|
-
subscribeFlags: flagsStore.subscribe
|
|
215
|
-
}),
|
|
216
|
-
[client, flagsStore, setUser, user]
|
|
217
|
-
);
|
|
26
|
+
const value = useMemo(() => ({ client, user, setUser }), [client, user]);
|
|
218
27
|
return /* @__PURE__ */ jsx(FeatureFlareContext.Provider, { value, children: props.children });
|
|
219
28
|
}
|
|
220
29
|
function useFeatureFlareContext() {
|
|
@@ -224,17 +33,7 @@ function useFeatureFlareContext() {
|
|
|
224
33
|
}
|
|
225
34
|
|
|
226
35
|
// src/hooks.ts
|
|
227
|
-
import { useEffect
|
|
228
|
-
var EMPTY_FLAGS_STATE = { flags: [], loading: true, error: null };
|
|
229
|
-
function userFingerprint(user) {
|
|
230
|
-
if (!user) return "";
|
|
231
|
-
return JSON.stringify({
|
|
232
|
-
id: user.id ?? "",
|
|
233
|
-
key: user.key ?? "",
|
|
234
|
-
email: user.email ?? "",
|
|
235
|
-
meta: user.meta ?? {}
|
|
236
|
-
});
|
|
237
|
-
}
|
|
36
|
+
import { useEffect, useMemo as useMemo2, useRef, useState as useState2 } from "react";
|
|
238
37
|
function useFeatureFlareUser() {
|
|
239
38
|
const { user, setUser } = useFeatureFlareContext();
|
|
240
39
|
return [user, setUser];
|
|
@@ -244,8 +43,8 @@ function useBoolFlag(flagKey, defaultValue = false) {
|
|
|
244
43
|
const [state, setState] = useState2({ value: defaultValue, loading: true, error: null });
|
|
245
44
|
const userId = user.id ?? user.key ?? "";
|
|
246
45
|
const key = useMemo2(() => `${flagKey}:${userId}`, [flagKey, userId]);
|
|
247
|
-
const lastKey =
|
|
248
|
-
|
|
46
|
+
const lastKey = useRef("");
|
|
47
|
+
useEffect(() => {
|
|
249
48
|
let cancelled = false;
|
|
250
49
|
const nextKey = key;
|
|
251
50
|
lastKey.current = nextKey;
|
|
@@ -275,8 +74,8 @@ function useBoolFlags(flagKeys, defaultValue = false) {
|
|
|
275
74
|
const userId = user.id ?? user.key ?? "";
|
|
276
75
|
const key = useMemo2(() => `${sortedKeys.join(",")}:${userId}`, [sortedKeys, userId]);
|
|
277
76
|
const [state, setState] = useState2({ values: {}, loading: true, errors: {} });
|
|
278
|
-
const lastKey =
|
|
279
|
-
|
|
77
|
+
const lastKey = useRef("");
|
|
78
|
+
useEffect(() => {
|
|
280
79
|
let cancelled = false;
|
|
281
80
|
const nextKey = key;
|
|
282
81
|
lastKey.current = nextKey;
|
|
@@ -304,49 +103,11 @@ function useBoolFlags(flagKeys, defaultValue = false) {
|
|
|
304
103
|
}, [client, defaultValue, key, sortedKeys, user]);
|
|
305
104
|
return state;
|
|
306
105
|
}
|
|
307
|
-
function useFlags(defaultValueOrInput = false, options = {}) {
|
|
308
|
-
const { subscribeFlags, getFlagsState, refreshFlags, setUser } = useFeatureFlareContext();
|
|
309
|
-
const parsed = useMemo2(() => {
|
|
310
|
-
if (typeof defaultValueOrInput === "boolean") {
|
|
311
|
-
return { ...options, defaultValue: defaultValueOrInput };
|
|
312
|
-
}
|
|
313
|
-
return defaultValueOrInput ?? {};
|
|
314
|
-
}, [defaultValueOrInput, options]);
|
|
315
|
-
const defaultValue = parsed.defaultValue ?? false;
|
|
316
|
-
const normalizedOptions = useMemo2(
|
|
317
|
-
() => ({
|
|
318
|
-
enabled: parsed.enabled ?? true,
|
|
319
|
-
refreshIntervalMs: parsed.refreshIntervalMs,
|
|
320
|
-
hiddenRefreshIntervalMs: parsed.hiddenRefreshIntervalMs,
|
|
321
|
-
pauseWhenHidden: parsed.pauseWhenHidden ?? true
|
|
322
|
-
}),
|
|
323
|
-
[parsed.enabled, parsed.hiddenRefreshIntervalMs, parsed.pauseWhenHidden, parsed.refreshIntervalMs]
|
|
324
|
-
);
|
|
325
|
-
const appliedUserRef = useRef2("");
|
|
326
|
-
const parsedUserFingerprint = useMemo2(() => userFingerprint(parsed.user), [parsed.user]);
|
|
327
|
-
useEffect2(() => {
|
|
328
|
-
if (!parsed.user) return;
|
|
329
|
-
if (appliedUserRef.current === parsedUserFingerprint) return;
|
|
330
|
-
setUser(parsed.user);
|
|
331
|
-
appliedUserRef.current = parsedUserFingerprint;
|
|
332
|
-
}, [parsed.user, parsedUserFingerprint, setUser]);
|
|
333
|
-
useEffect2(() => {
|
|
334
|
-
if (normalizedOptions.enabled === false) return;
|
|
335
|
-
refreshFlags(defaultValue);
|
|
336
|
-
}, [defaultValue, normalizedOptions.enabled, parsedUserFingerprint, refreshFlags]);
|
|
337
|
-
const subscribe = useMemo2(
|
|
338
|
-
() => (onStoreChange) => subscribeFlags(defaultValue, onStoreChange, normalizedOptions),
|
|
339
|
-
[defaultValue, normalizedOptions, subscribeFlags]
|
|
340
|
-
);
|
|
341
|
-
return useSyncExternalStore(subscribe, () => getFlagsState(defaultValue), () => EMPTY_FLAGS_STATE);
|
|
342
|
-
}
|
|
343
106
|
export {
|
|
344
107
|
FeatureFlareProvider,
|
|
345
|
-
resolveFeatureFlareBrowserConfig,
|
|
346
108
|
useBoolFlag,
|
|
347
109
|
useBoolFlags,
|
|
348
110
|
useFeatureFlareContext,
|
|
349
|
-
useFeatureFlareUser
|
|
350
|
-
useFlags
|
|
111
|
+
useFeatureFlareUser
|
|
351
112
|
};
|
|
352
113
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/provider.tsx","../src/hooks.ts"],"sourcesContent":["import React, { createContext, useEffect, useMemo, useRef, useState } from 'react';\nimport { FeatureFlareClient, type FeatureFlareUserPayload } from '@featureflare/sdk-js';\n\nexport type FeatureFlareEnvironmentKey = 'development' | 'staging' | 'production';\n\nexport type FeatureFlareReactConfig = {\n /** Optional: explicit FeatureFlare API base URL. */\n apiBaseUrl?: string;\n /** Recommended: use a client key (featureflare_cli_...). */\n sdkKey?: string;\n /** Legacy/insecure browser mode: uses /api/v1/eval (no sdkKey). */\n projectKey?: string;\n envKey?: FeatureFlareEnvironmentKey | string;\n};\n\nexport function resolveFeatureFlareBrowserConfig(input?: {\n envKey?: FeatureFlareEnvironmentKey;\n apiBaseUrl?: string;\n}): FeatureFlareReactConfig {\n const explicitEnv = input?.envKey;\n const envFromVars =\n typeof process !== 'undefined' ? process.env.NEXT_PUBLIC_FEATUREFLARE_ENV_KEY?.trim().toLowerCase() : '';\n\n const resolvedEnv: FeatureFlareEnvironmentKey =\n explicitEnv ??\n (envFromVars === 'development' || envFromVars === 'staging' || envFromVars === 'production'\n ? envFromVars\n : typeof process !== 'undefined' && process.env.NODE_ENV === 'production'\n ? 'production'\n : 'development');\n\n const apiBaseUrl =\n input?.apiBaseUrl ??\n (typeof process !== 'undefined' ? process.env.NEXT_PUBLIC_FEATUREFLARE_API_BASE_URL?.trim() : undefined);\n\n const sdkKeyDevelopment =\n (typeof process !== 'undefined' ? process.env.NEXT_PUBLIC_FEATUREFLARE_CLIENT_KEY_DEVELOPMENT?.trim() : '') ||\n (typeof process !== 'undefined' ? process.env.NEXT_PUBLIC_FEATUREFLARE_CLIENT_KEY_DEV?.trim() : '') ||\n '';\n const sdkKeyStaging =\n (typeof process !== 'undefined' ? process.env.NEXT_PUBLIC_FEATUREFLARE_CLIENT_KEY_STAGING?.trim() : '') || '';\n const sdkKeyProduction =\n (typeof process !== 'undefined' ? process.env.NEXT_PUBLIC_FEATUREFLARE_CLIENT_KEY_PRODUCTION?.trim() : '') ||\n (typeof process !== 'undefined' ? process.env.NEXT_PUBLIC_FEATUREFLARE_CLIENT_KEY_PROD?.trim() : '') ||\n '';\n const sdkKeyDefault =\n (typeof process !== 'undefined' ? process.env.NEXT_PUBLIC_FEATUREFLARE_CLIENT_KEY?.trim() : '') || '';\n\n const sdkKey =\n (resolvedEnv === 'development' ? sdkKeyDevelopment : '') ||\n (resolvedEnv === 'staging' ? sdkKeyStaging : '') ||\n (resolvedEnv === 'production' ? sdkKeyProduction : '') ||\n sdkKeyDefault ||\n undefined;\n\n return {\n apiBaseUrl,\n envKey: resolvedEnv,\n sdkKey\n };\n}\n\ntype FeatureFlareContextValue = {\n client: FeatureFlareClient;\n user: FeatureFlareUserPayload;\n setUser: (next: FeatureFlareUserPayload) => void;\n getFlagsState: (defaultValue: boolean) => FlagsState;\n refreshFlags: (defaultValue: boolean) => void;\n subscribeFlags: (\n defaultValue: boolean,\n listener: () => void,\n options?: FlagsSubscriptionOptions\n ) => () => void;\n};\n\nexport type FlagsState = {\n flags: Array<{ key: string; value: boolean }>;\n loading: boolean;\n error: string | null;\n};\n\nexport type FlagsSubscriptionOptions = {\n refreshIntervalMs?: number;\n hiddenRefreshIntervalMs?: number;\n pauseWhenHidden?: boolean;\n enabled?: boolean;\n};\n\ntype NormalizedFlagsSubscriptionOptions = {\n refreshIntervalMs: number;\n hiddenRefreshIntervalMs: number;\n pauseWhenHidden: boolean;\n enabled: boolean;\n};\n\ntype FlagsStoreEntry = {\n defaultValue: boolean;\n snapshot: FlagsState;\n listeners: Set<() => void>;\n subscribers: Map<number, NormalizedFlagsSubscriptionOptions>;\n timer: ReturnType<typeof setTimeout> | null;\n inFlight: boolean;\n};\n\nfunction normalizeSubscriptionOptions(options?: FlagsSubscriptionOptions): NormalizedFlagsSubscriptionOptions {\n const refreshIntervalMs =\n Number.isFinite(options?.refreshIntervalMs) && (options?.refreshIntervalMs ?? 0) > 0\n ? Number(options?.refreshIntervalMs)\n : 10000;\n const hiddenRefreshIntervalMs =\n Number.isFinite(options?.hiddenRefreshIntervalMs) && (options?.hiddenRefreshIntervalMs ?? 0) > 0\n ? Number(options?.hiddenRefreshIntervalMs)\n : Math.max(refreshIntervalMs * 6, 60000);\n\n return {\n refreshIntervalMs,\n hiddenRefreshIntervalMs,\n pauseWhenHidden: options?.pauseWhenHidden ?? true,\n enabled: options?.enabled ?? true\n };\n}\n\nfunction createFlagsStore(client: FeatureFlareClient, getUser: () => FeatureFlareUserPayload) {\n const entries = new Map<string, FlagsStoreEntry>();\n let nextSubscriberId = 1;\n\n const isHidden = () => typeof document !== 'undefined' && document.visibilityState === 'hidden';\n\n const getEntry = (defaultValue: boolean): FlagsStoreEntry => {\n const key = defaultValue ? '1' : '0';\n const existing = entries.get(key);\n if (existing) return existing;\n const created: FlagsStoreEntry = {\n defaultValue,\n snapshot: { flags: [], loading: true, error: null },\n listeners: new Set(),\n subscribers: new Map(),\n timer: null,\n inFlight: false\n };\n entries.set(key, created);\n return created;\n };\n\n const emit = (entry: FlagsStoreEntry) => {\n for (const listener of entry.listeners) listener();\n };\n\n const getEffectiveOptions = (entry: FlagsStoreEntry) => {\n const active = [...entry.subscribers.values()].filter((s) => s.enabled);\n if (active.length === 0) {\n return { enabled: false as const, refreshIntervalMs: 0, hiddenRefreshIntervalMs: 0, pauseWhenHidden: true };\n }\n\n const refreshIntervalMs = active.reduce((min, s) => Math.min(min, s.refreshIntervalMs), Number.POSITIVE_INFINITY);\n const hiddenActive = active.filter((s) => !s.pauseWhenHidden);\n const hiddenRefreshIntervalMs =\n hiddenActive.length > 0\n ? hiddenActive.reduce((min, s) => Math.min(min, s.hiddenRefreshIntervalMs), Number.POSITIVE_INFINITY)\n : 0;\n\n return {\n enabled: true as const,\n refreshIntervalMs: Number.isFinite(refreshIntervalMs) ? refreshIntervalMs : 10000,\n hiddenRefreshIntervalMs,\n pauseWhenHidden: hiddenActive.length === 0\n };\n };\n\n const schedule = (entry: FlagsStoreEntry) => {\n if (entry.timer !== null) {\n clearTimeout(entry.timer);\n entry.timer = null;\n }\n\n const effective = getEffectiveOptions(entry);\n if (!effective.enabled) return;\n\n if (isHidden()) {\n if (effective.pauseWhenHidden) return;\n entry.timer = setTimeout(() => {\n void refresh(entry.defaultValue);\n }, effective.hiddenRefreshIntervalMs);\n return;\n }\n\n entry.timer = setTimeout(() => {\n void refresh(entry.defaultValue);\n }, effective.refreshIntervalMs);\n };\n\n const refresh = async (defaultValue: boolean, force = false) => {\n const entry = getEntry(defaultValue);\n const effective = getEffectiveOptions(entry);\n if (!effective.enabled && !force) return;\n\n if (!force && isHidden() && effective.pauseWhenHidden) {\n schedule(entry);\n return;\n }\n\n if (entry.inFlight) return;\n entry.inFlight = true;\n\n try {\n const flags = await client.flags(getUser(), defaultValue);\n entry.snapshot = { flags, loading: false, error: null };\n emit(entry);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n entry.snapshot = { ...entry.snapshot, loading: false, error: message };\n emit(entry);\n } finally {\n entry.inFlight = false;\n schedule(entry);\n }\n };\n\n const refreshNow = (defaultValue: boolean) => {\n const entry = getEntry(defaultValue);\n entry.snapshot = { ...entry.snapshot, loading: true, error: null };\n emit(entry);\n void refresh(defaultValue, true);\n };\n\n const subscribe = (\n defaultValue: boolean,\n listener: () => void,\n options?: FlagsSubscriptionOptions\n ): (() => void) => {\n const entry = getEntry(defaultValue);\n const subscriberId = nextSubscriberId;\n nextSubscriberId += 1;\n\n entry.listeners.add(listener);\n entry.subscribers.set(subscriberId, normalizeSubscriptionOptions(options));\n\n const effective = getEffectiveOptions(entry);\n if (effective.enabled && !entry.inFlight && entry.snapshot.loading) {\n void refresh(defaultValue);\n } else {\n schedule(entry);\n }\n\n return () => {\n entry.listeners.delete(listener);\n entry.subscribers.delete(subscriberId);\n schedule(entry);\n };\n };\n\n const updateUser = () => {\n for (const entry of entries.values()) {\n entry.snapshot = { ...entry.snapshot, loading: true, error: null };\n emit(entry);\n void refresh(entry.defaultValue);\n }\n };\n\n const handleVisibilityChange = () => {\n if (isHidden()) return;\n for (const entry of entries.values()) {\n const effective = getEffectiveOptions(entry);\n if (!effective.enabled) continue;\n void refresh(entry.defaultValue);\n }\n };\n\n if (typeof document !== 'undefined') {\n document.addEventListener('visibilitychange', handleVisibilityChange);\n }\n\n const dispose = () => {\n if (typeof document !== 'undefined') {\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n }\n for (const entry of entries.values()) {\n if (entry.timer !== null) {\n clearTimeout(entry.timer);\n }\n entry.timer = null;\n entry.listeners.clear();\n entry.subscribers.clear();\n }\n entries.clear();\n };\n\n return {\n getState(defaultValue: boolean): FlagsState {\n return getEntry(defaultValue).snapshot;\n },\n refreshNow,\n subscribe,\n updateUser,\n dispose\n };\n}\n\nconst FeatureFlareContext = createContext<FeatureFlareContextValue | null>(null);\n\nexport function FeatureFlareProvider(props: {\n config: FeatureFlareReactConfig;\n initialUser: FeatureFlareUserPayload;\n user?: FeatureFlareUserPayload;\n onUserChange?: (next: FeatureFlareUserPayload) => void;\n children: React.ReactNode;\n}) {\n if (props.user && !props.onUserChange) {\n throw new Error('FeatureFlareProvider: when providing `user`, also provide `onUserChange` (controlled mode).');\n }\n\n const [internalUser, setInternalUser] = useState<FeatureFlareUserPayload>(props.initialUser);\n const user = props.user ?? internalUser;\n const setUser = props.onUserChange ?? setInternalUser;\n const userRef = useRef<FeatureFlareUserPayload>(user);\n\n const client = useMemo(() => {\n return new FeatureFlareClient({\n apiBaseUrl: props.config.apiBaseUrl,\n sdkKey: props.config.sdkKey,\n projectKey: props.config.projectKey,\n envKey: props.config.envKey\n });\n }, [\n props.config.apiBaseUrl,\n props.config.envKey,\n props.config.projectKey,\n props.config.sdkKey\n ]);\n\n const flagsStore = useMemo(() => createFlagsStore(client, () => userRef.current), [client]);\n\n useEffect(() => {\n userRef.current = user;\n flagsStore.updateUser();\n }, [flagsStore, user]);\n\n useEffect(() => {\n return () => {\n flagsStore.dispose();\n };\n }, [flagsStore]);\n\n const value = useMemo(\n () => ({\n client,\n user,\n setUser,\n getFlagsState: flagsStore.getState,\n refreshFlags: flagsStore.refreshNow,\n subscribeFlags: flagsStore.subscribe\n }),\n [client, flagsStore, setUser, user]\n );\n return <FeatureFlareContext.Provider value={value}>{props.children}</FeatureFlareContext.Provider>;\n}\n\nexport function useFeatureFlareContext(): FeatureFlareContextValue {\n const ctx = React.useContext(FeatureFlareContext);\n if (!ctx) throw new Error('useFeatureFlareContext must be used within <FeatureFlareProvider>.');\n return ctx;\n}\n","import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';\nimport type { FeatureFlareUserPayload } from '@featureflare/sdk-js';\nimport { useFeatureFlareContext, type FlagsSubscriptionOptions } from './provider.js';\n\ntype BoolFlagState = {\n value: boolean;\n loading: boolean;\n error: string | null;\n};\n\ntype BoolFlagsState = {\n values: Record<string, boolean>;\n loading: boolean;\n errors: Record<string, string>;\n};\n\ntype FlagsState = {\n flags: Array<{ key: string; value: boolean }>;\n loading: boolean;\n error: string | null;\n};\n\ntype UseFlagsOptions = FlagsSubscriptionOptions;\n\ntype UseFlagsInput = UseFlagsOptions & {\n defaultValue?: boolean;\n user?: FeatureFlareUserPayload;\n};\n\nconst EMPTY_FLAGS_STATE: FlagsState = { flags: [], loading: true, error: null };\n\nfunction userFingerprint(user?: FeatureFlareUserPayload): string {\n if (!user) return '';\n return JSON.stringify({\n id: user.id ?? '',\n key: user.key ?? '',\n email: user.email ?? '',\n meta: user.meta ?? {}\n });\n}\n\nexport function useFeatureFlareUser(): [FeatureFlareUserPayload, (next: FeatureFlareUserPayload) => void] {\n const { user, setUser } = useFeatureFlareContext();\n return [user, setUser];\n}\n\nexport function useBoolFlag(flagKey: string, defaultValue = false): BoolFlagState {\n const { client, user } = useFeatureFlareContext();\n const [state, setState] = useState<BoolFlagState>({ value: defaultValue, loading: true, error: null });\n const userId = user.id ?? user.key ?? '';\n const key = useMemo(() => `${flagKey}:${userId}`, [flagKey, userId]);\n const lastKey = useRef<string>('');\n\n useEffect(() => {\n let cancelled = false;\n const nextKey = key;\n lastKey.current = nextKey;\n setState((s) => ({ ...s, loading: true, error: null }));\n\n (async () => {\n try {\n const v = await client.bool(flagKey, user, defaultValue);\n if (cancelled) return;\n if (lastKey.current !== nextKey) return;\n setState({ value: v, loading: false, error: null });\n } catch (e) {\n if (cancelled) return;\n if (lastKey.current !== nextKey) return;\n const msg = e instanceof Error ? e.message : String(e);\n setState({ value: defaultValue, loading: false, error: msg });\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [client, defaultValue, flagKey, key, user]);\n\n return state;\n}\n\nexport function useBoolFlags(flagKeys: string[], defaultValue = false): BoolFlagsState {\n const { client, user } = useFeatureFlareContext();\n const sortedKeys = useMemo(() => [...flagKeys].map((k) => k.trim()).filter(Boolean).sort(), [flagKeys]);\n const userId = user.id ?? user.key ?? '';\n const key = useMemo(() => `${sortedKeys.join(',')}:${userId}`, [sortedKeys, userId]);\n const [state, setState] = useState<BoolFlagsState>({ values: {}, loading: true, errors: {} });\n const lastKey = useRef<string>('');\n\n useEffect(() => {\n let cancelled = false;\n const nextKey = key;\n lastKey.current = nextKey;\n setState({ values: {}, loading: true, errors: {} });\n\n (async () => {\n const values: Record<string, boolean> = {};\n const errors: Record<string, string> = {};\n await Promise.all(\n sortedKeys.map(async (flagKey) => {\n try {\n values[flagKey] = await client.bool(flagKey, user, defaultValue);\n } catch (e) {\n values[flagKey] = defaultValue;\n errors[flagKey] = e instanceof Error ? e.message : String(e);\n }\n })\n );\n\n if (cancelled) return;\n if (lastKey.current !== nextKey) return;\n setState({ values, loading: false, errors });\n })();\n\n return () => {\n cancelled = true;\n };\n }, [client, defaultValue, key, sortedKeys, user]);\n\n return state;\n}\n\nexport function useFlags(input?: UseFlagsInput): FlagsState;\nexport function useFlags(defaultValue?: boolean, options?: UseFlagsOptions): FlagsState;\nexport function useFlags(defaultValueOrInput: boolean | UseFlagsInput = false, options: UseFlagsOptions = {}): FlagsState {\n const { subscribeFlags, getFlagsState, refreshFlags, setUser } = useFeatureFlareContext();\n\n const parsed = useMemo<UseFlagsInput>(() => {\n if (typeof defaultValueOrInput === 'boolean') {\n return { ...options, defaultValue: defaultValueOrInput };\n }\n return defaultValueOrInput ?? {};\n }, [defaultValueOrInput, options]);\n\n const defaultValue = parsed.defaultValue ?? false;\n\n const normalizedOptions = useMemo<UseFlagsOptions>(\n () => ({\n enabled: parsed.enabled ?? true,\n refreshIntervalMs: parsed.refreshIntervalMs,\n hiddenRefreshIntervalMs: parsed.hiddenRefreshIntervalMs,\n pauseWhenHidden: parsed.pauseWhenHidden ?? true\n }),\n [parsed.enabled, parsed.hiddenRefreshIntervalMs, parsed.pauseWhenHidden, parsed.refreshIntervalMs]\n );\n\n const appliedUserRef = useRef<string>('');\n const parsedUserFingerprint = useMemo(() => userFingerprint(parsed.user), [parsed.user]);\n\n useEffect(() => {\n if (!parsed.user) return;\n if (appliedUserRef.current === parsedUserFingerprint) return;\n setUser(parsed.user);\n appliedUserRef.current = parsedUserFingerprint;\n }, [parsed.user, parsedUserFingerprint, setUser]);\n\n useEffect(() => {\n if (normalizedOptions.enabled === false) return;\n refreshFlags(defaultValue);\n }, [defaultValue, normalizedOptions.enabled, parsedUserFingerprint, refreshFlags]);\n\n const subscribe = useMemo(\n () =>\n (onStoreChange: () => void) =>\n subscribeFlags(defaultValue, onStoreChange, normalizedOptions),\n [defaultValue, normalizedOptions, subscribeFlags]\n );\n\n return useSyncExternalStore(subscribe, () => getFlagsState(defaultValue), () => EMPTY_FLAGS_STATE);\n}\n"],"mappings":"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","names":["useEffect","useMemo","useRef","useState","useState","useMemo","useRef","useEffect"]}
|
|
1
|
+
{"version":3,"sources":["../src/provider.tsx","../src/hooks.ts"],"sourcesContent":["import React, { createContext, useMemo, useState } from 'react';\nimport { FeatureFlareClient, type FeatureFlareUserPayload } from '@featureflare/sdk-js';\n\ntype FeatureFlareReactConfig = {\n /** Optional: explicit FeatureFlare API base URL. */\n apiBaseUrl?: string;\n /** Recommended: use a client key (featureflare_cli_...). */\n sdkKey?: string;\n /** Legacy/insecure browser mode: uses /api/v1/eval (no sdkKey). */\n projectKey?: string;\n envKey?: string;\n};\n\ntype FeatureFlareContextValue = {\n client: FeatureFlareClient;\n user: FeatureFlareUserPayload;\n setUser: (next: FeatureFlareUserPayload) => void;\n};\n\nconst FeatureFlareContext = createContext<FeatureFlareContextValue | null>(null);\n\nexport function FeatureFlareProvider(props: {\n config: FeatureFlareReactConfig;\n initialUser: FeatureFlareUserPayload;\n user?: FeatureFlareUserPayload;\n onUserChange?: (next: FeatureFlareUserPayload) => void;\n children: React.ReactNode;\n}) {\n if (props.user && !props.onUserChange) {\n throw new Error('FeatureFlareProvider: when providing `user`, also provide `onUserChange` (controlled mode).');\n }\n\n const [internalUser, setInternalUser] = useState<FeatureFlareUserPayload>(props.initialUser);\n const user = props.user ?? internalUser;\n const setUser = props.onUserChange ?? setInternalUser;\n\n const client = useMemo(() => {\n return new FeatureFlareClient({\n apiBaseUrl: props.config.apiBaseUrl,\n sdkKey: props.config.sdkKey,\n projectKey: props.config.projectKey,\n envKey: props.config.envKey\n });\n }, [\n props.config.apiBaseUrl,\n props.config.envKey,\n props.config.projectKey,\n props.config.sdkKey\n ]);\n\n const value = useMemo(() => ({ client, user, setUser }), [client, user]);\n return <FeatureFlareContext.Provider value={value}>{props.children}</FeatureFlareContext.Provider>;\n}\n\nexport function useFeatureFlareContext(): FeatureFlareContextValue {\n const ctx = React.useContext(FeatureFlareContext);\n if (!ctx) throw new Error('useFeatureFlareContext must be used within <FeatureFlareProvider>.');\n return ctx;\n}\n","import { useEffect, useMemo, useRef, useState } from 'react';\nimport type { FeatureFlareUserPayload } from '@featureflare/sdk-js';\nimport { useFeatureFlareContext } from './provider.js';\n\ntype BoolFlagState = {\n value: boolean;\n loading: boolean;\n error: string | null;\n};\n\ntype BoolFlagsState = {\n values: Record<string, boolean>;\n loading: boolean;\n errors: Record<string, string>;\n};\n\nexport function useFeatureFlareUser(): [FeatureFlareUserPayload, (next: FeatureFlareUserPayload) => void] {\n const { user, setUser } = useFeatureFlareContext();\n return [user, setUser];\n}\n\nexport function useBoolFlag(flagKey: string, defaultValue = false): BoolFlagState {\n const { client, user } = useFeatureFlareContext();\n const [state, setState] = useState<BoolFlagState>({ value: defaultValue, loading: true, error: null });\n const userId = user.id ?? user.key ?? '';\n const key = useMemo(() => `${flagKey}:${userId}`, [flagKey, userId]);\n const lastKey = useRef<string>('');\n\n useEffect(() => {\n let cancelled = false;\n const nextKey = key;\n lastKey.current = nextKey;\n setState((s) => ({ ...s, loading: true, error: null }));\n\n (async () => {\n try {\n const v = await client.bool(flagKey, user, defaultValue);\n if (cancelled) return;\n if (lastKey.current !== nextKey) return;\n setState({ value: v, loading: false, error: null });\n } catch (e) {\n if (cancelled) return;\n if (lastKey.current !== nextKey) return;\n const msg = e instanceof Error ? e.message : String(e);\n setState({ value: defaultValue, loading: false, error: msg });\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [client, defaultValue, flagKey, key, user]);\n\n return state;\n}\n\nexport function useBoolFlags(flagKeys: string[], defaultValue = false): BoolFlagsState {\n const { client, user } = useFeatureFlareContext();\n const sortedKeys = useMemo(() => [...flagKeys].map((k) => k.trim()).filter(Boolean).sort(), [flagKeys]);\n const userId = user.id ?? user.key ?? '';\n const key = useMemo(() => `${sortedKeys.join(',')}:${userId}`, [sortedKeys, userId]);\n const [state, setState] = useState<BoolFlagsState>({ values: {}, loading: true, errors: {} });\n const lastKey = useRef<string>('');\n\n useEffect(() => {\n let cancelled = false;\n const nextKey = key;\n lastKey.current = nextKey;\n setState({ values: {}, loading: true, errors: {} });\n\n (async () => {\n const values: Record<string, boolean> = {};\n const errors: Record<string, string> = {};\n await Promise.all(\n sortedKeys.map(async (flagKey) => {\n try {\n values[flagKey] = await client.bool(flagKey, user, defaultValue);\n } catch (e) {\n values[flagKey] = defaultValue;\n errors[flagKey] = e instanceof Error ? e.message : String(e);\n }\n })\n );\n\n if (cancelled) return;\n if (lastKey.current !== nextKey) return;\n setState({ values, loading: false, errors });\n })();\n\n return () => {\n cancelled = true;\n };\n }, [client, defaultValue, key, sortedKeys, user]);\n\n return state;\n}\n"],"mappings":"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","names":["useMemo","useState","useState","useMemo"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@featureflare/react",
|
|
3
|
-
"version": "0.0.5
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -33,12 +33,12 @@
|
|
|
33
33
|
"react-dom": ">=18"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@featureflare/sdk-js": "0.0.5
|
|
36
|
+
"@featureflare/sdk-js": "0.0.5"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^22.10.2",
|
|
40
|
-
"@types/react": "^
|
|
41
|
-
"@types/react-dom": "^
|
|
40
|
+
"@types/react": "^18.3.12",
|
|
41
|
+
"@types/react-dom": "^18.3.1",
|
|
42
42
|
"eslint": "^9.16.0",
|
|
43
43
|
"prettier": "^3.4.2",
|
|
44
44
|
"tsup": "^8.3.5",
|