@featureflare/react 0.0.3 → 0.0.5-beta.186
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 +61 -104
- package/dist/index.cjs +267 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -18
- package/dist/index.d.ts +57 -18
- package/dist/index.js +269 -24
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @featureflare/react
|
|
2
2
|
|
|
3
|
-
React hooks and provider for
|
|
3
|
+
React hooks and provider for FeatureFlare feature flags.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -12,147 +12,104 @@ pnpm add @featureflare/react
|
|
|
12
12
|
yarn add @featureflare/react
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
**Peer Dependencies:**
|
|
16
|
-
|
|
17
|
-
- `react >= 18`
|
|
18
|
-
- `react-dom >= 18`
|
|
19
|
-
|
|
20
15
|
## Usage
|
|
21
16
|
|
|
22
|
-
### Setup
|
|
23
|
-
|
|
24
|
-
Wrap your app with `ShipItProvider`:
|
|
17
|
+
### Minimal Setup
|
|
25
18
|
|
|
26
19
|
```typescript
|
|
27
|
-
import {
|
|
20
|
+
import { FeatureFlareProvider, resolveFeatureFlareBrowserConfig, useFlags } from '@featureflare/react';
|
|
21
|
+
|
|
22
|
+
function FlagsBootstrap() {
|
|
23
|
+
useFlags({
|
|
24
|
+
user: { id: 'user-123', email: 'dev@company.com' },
|
|
25
|
+
defaultValue: false,
|
|
26
|
+
refreshIntervalMs: 10000,
|
|
27
|
+
pauseWhenHidden: true,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
28
32
|
|
|
29
33
|
export function App() {
|
|
34
|
+
const config = resolveFeatureFlareBrowserConfig();
|
|
35
|
+
|
|
36
|
+
if (!config.sdkKey) {
|
|
37
|
+
return <div>Feature flags disabled</div>;
|
|
38
|
+
}
|
|
39
|
+
|
|
30
40
|
return (
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}}
|
|
36
|
-
initialUser={{ id: 'user-123' }}
|
|
37
|
-
>
|
|
38
|
-
{/* Your app */}
|
|
39
|
-
</ShipItProvider>
|
|
41
|
+
<FeatureFlareProvider config={config} initialUser={{ id: 'user-123', key: 'user-123' }}>
|
|
42
|
+
<FlagsBootstrap />
|
|
43
|
+
{/* app */}
|
|
44
|
+
</FeatureFlareProvider>
|
|
40
45
|
);
|
|
41
46
|
}
|
|
42
47
|
```
|
|
43
48
|
|
|
44
|
-
### Read
|
|
45
|
-
|
|
46
|
-
Use the `useBoolFlag` hook to read boolean flags:
|
|
49
|
+
### Read One Flag
|
|
47
50
|
|
|
48
51
|
```typescript
|
|
49
52
|
import { useBoolFlag } from '@featureflare/react';
|
|
50
53
|
|
|
51
54
|
export function NewNav() {
|
|
52
55
|
const { value, loading, error } = useBoolFlag('new-nav', false);
|
|
53
|
-
|
|
54
|
-
if (loading) return <div>Loading...</div>;
|
|
55
|
-
if (error) return <div>Error: {error.message}</div>;
|
|
56
|
-
|
|
57
|
-
return value ? <div>New nav ON</div> : <div>New nav OFF</div>;
|
|
58
|
-
}
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### Update User
|
|
62
56
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
```typescript
|
|
66
|
-
import { useShipItUser } from '@featureflare/react';
|
|
67
|
-
|
|
68
|
-
export function UserSwitcher() {
|
|
69
|
-
const [user, setUser] = useShipItUser();
|
|
57
|
+
if (loading) return <div>Loading...</div>;
|
|
58
|
+
if (error) return <div>Error: {error}</div>;
|
|
70
59
|
|
|
71
|
-
return
|
|
72
|
-
<button
|
|
73
|
-
onClick={() =>
|
|
74
|
-
setUser({
|
|
75
|
-
...(user ?? { id: 'user-123' }),
|
|
76
|
-
meta: { ...(user?.meta ?? {}), companyId: 'northwind' }
|
|
77
|
-
})
|
|
78
|
-
}
|
|
79
|
-
>
|
|
80
|
-
Switch user
|
|
81
|
-
</button>
|
|
82
|
-
);
|
|
60
|
+
return value ? <div>New nav ON</div> : <div>New nav OFF</div>;
|
|
83
61
|
}
|
|
84
62
|
```
|
|
85
63
|
|
|
86
|
-
## API
|
|
87
|
-
|
|
88
|
-
### `ShipItProvider`
|
|
89
|
-
|
|
90
|
-
Provider component that wraps your app and provides the ShipIt client context.
|
|
64
|
+
## API
|
|
91
65
|
|
|
92
|
-
|
|
66
|
+
### `FeatureFlareProvider`
|
|
93
67
|
|
|
94
|
-
|
|
95
|
-
- `sdkKey?: string` - Client SDK key (recommended). If not provided, reads from `SHIPIT_CLIENT_KEY` env var.
|
|
96
|
-
- `projectKey?: string` - Legacy: project key (requires `envKey`). Not recommended.
|
|
97
|
-
- `envKey?: string` - Environment key (default: `'production'`). Only used with `projectKey`.
|
|
98
|
-
- `initialUser: ShipItUserPayload` - Initial user payload
|
|
99
|
-
- `user?: ShipItUserPayload` - Controlled user (requires `onUserChange`)
|
|
100
|
-
- `onUserChange?: (user: ShipItUserPayload) => void` - Callback for user changes (required if using controlled `user`)
|
|
101
|
-
- `children: React.ReactNode` - Your app components
|
|
68
|
+
Props:
|
|
102
69
|
|
|
103
|
-
|
|
70
|
+
- `config: FeatureFlareReactConfig`
|
|
71
|
+
- `initialUser: FeatureFlareUserPayload`
|
|
72
|
+
- `user?: FeatureFlareUserPayload` (controlled mode)
|
|
73
|
+
- `onUserChange?: (user: FeatureFlareUserPayload) => void` (required in controlled mode)
|
|
74
|
+
- `children: React.ReactNode`
|
|
104
75
|
|
|
105
|
-
|
|
76
|
+
### `resolveFeatureFlareBrowserConfig(input?)`
|
|
106
77
|
|
|
107
|
-
|
|
78
|
+
Builds provider config from common `NEXT_PUBLIC_FEATUREFLARE_*` vars.
|
|
108
79
|
|
|
109
|
-
|
|
110
|
-
- `loading: boolean` - Whether the evaluation is in progress
|
|
111
|
-
- `error: Error | null` - Error if evaluation failed
|
|
112
|
-
|
|
113
|
-
**Example:**
|
|
114
|
-
|
|
115
|
-
```typescript
|
|
116
|
-
const { value, loading, error } = useBoolFlag('feature-flag', false);
|
|
117
|
-
```
|
|
80
|
+
Returns:
|
|
118
81
|
|
|
119
|
-
|
|
82
|
+
- `apiBaseUrl?: string`
|
|
83
|
+
- `envKey: 'development' | 'staging' | 'production'`
|
|
84
|
+
- `sdkKey?: string`
|
|
120
85
|
|
|
121
|
-
|
|
86
|
+
### `useFeatureFlareUser()`
|
|
122
87
|
|
|
123
|
-
|
|
88
|
+
Returns:
|
|
124
89
|
|
|
125
|
-
`[user
|
|
90
|
+
- `[user, setUser]`
|
|
126
91
|
|
|
127
|
-
|
|
92
|
+
### `useFlags(...)`
|
|
128
93
|
|
|
129
|
-
|
|
130
|
-
const [user, setUser] = useShipItUser();
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
## Environment Variables
|
|
134
|
-
|
|
135
|
-
The SDK automatically reads from environment variables if `sdkKey` is not provided:
|
|
136
|
-
|
|
137
|
-
- `SHIPIT_CLIENT_KEY` - Client SDK key
|
|
138
|
-
|
|
139
|
-
```typescript
|
|
140
|
-
// This will use SHIPIT_CLIENT_KEY from env if sdkKey is not provided
|
|
141
|
-
<ShipItProvider config={{}} initialUser={{ id: 'user-123' }}>
|
|
142
|
-
{/* ... */}
|
|
143
|
-
</ShipItProvider>
|
|
144
|
-
```
|
|
94
|
+
Signatures:
|
|
145
95
|
|
|
146
|
-
|
|
96
|
+
- `useFlags(input?: { user?: FeatureFlareUserPayload; defaultValue?: boolean; refreshIntervalMs?: number; hiddenRefreshIntervalMs?: number; pauseWhenHidden?: boolean; enabled?: boolean })`
|
|
97
|
+
- `useFlags(defaultValue?: boolean, options?: { refreshIntervalMs?: number; hiddenRefreshIntervalMs?: number; pauseWhenHidden?: boolean; enabled?: boolean })`
|
|
147
98
|
|
|
148
|
-
|
|
99
|
+
Returns:
|
|
149
100
|
|
|
150
|
-
|
|
101
|
+
- `flags: Array<{ key: string; value: boolean }>`
|
|
102
|
+
- `loading: boolean`
|
|
103
|
+
- `error: string | null`
|
|
151
104
|
|
|
152
|
-
|
|
105
|
+
Behavior:
|
|
153
106
|
|
|
154
|
-
|
|
107
|
+
- Immediate fetch on mount/user change.
|
|
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.
|
|
155
111
|
|
|
156
|
-
##
|
|
112
|
+
## Notes
|
|
157
113
|
|
|
158
|
-
|
|
114
|
+
- Use client SDK keys in browser apps.
|
|
115
|
+
- If `config.sdkKey` is missing, skip initializing the provider.
|
package/dist/index.cjs
CHANGED
|
@@ -30,11 +30,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
-
|
|
33
|
+
FeatureFlareProvider: () => FeatureFlareProvider,
|
|
34
|
+
resolveFeatureFlareBrowserConfig: () => resolveFeatureFlareBrowserConfig,
|
|
34
35
|
useBoolFlag: () => useBoolFlag,
|
|
35
36
|
useBoolFlags: () => useBoolFlags,
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
useFeatureFlareContext: () => useFeatureFlareContext,
|
|
38
|
+
useFeatureFlareUser: () => useFeatureFlareUser,
|
|
39
|
+
useFlags: () => useFlags
|
|
38
40
|
});
|
|
39
41
|
module.exports = __toCommonJS(index_exports);
|
|
40
42
|
|
|
@@ -42,38 +44,245 @@ module.exports = __toCommonJS(index_exports);
|
|
|
42
44
|
var import_react = __toESM(require("react"), 1);
|
|
43
45
|
var import_sdk_js = require("@featureflare/sdk-js");
|
|
44
46
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
45
|
-
|
|
46
|
-
|
|
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
|
+
var FeatureFlareContext = (0, import_react.createContext)(null);
|
|
218
|
+
function FeatureFlareProvider(props) {
|
|
47
219
|
if (props.user && !props.onUserChange) {
|
|
48
|
-
throw new Error("
|
|
220
|
+
throw new Error("FeatureFlareProvider: when providing `user`, also provide `onUserChange` (controlled mode).");
|
|
49
221
|
}
|
|
50
222
|
const [internalUser, setInternalUser] = (0, import_react.useState)(props.initialUser);
|
|
51
223
|
const user = props.user ?? internalUser;
|
|
52
224
|
const setUser = props.onUserChange ?? setInternalUser;
|
|
225
|
+
const userRef = (0, import_react.useRef)(user);
|
|
53
226
|
const client = (0, import_react.useMemo)(() => {
|
|
54
|
-
return new import_sdk_js.
|
|
227
|
+
return new import_sdk_js.FeatureFlareClient({
|
|
228
|
+
apiBaseUrl: props.config.apiBaseUrl,
|
|
55
229
|
sdkKey: props.config.sdkKey,
|
|
56
230
|
projectKey: props.config.projectKey,
|
|
57
231
|
envKey: props.config.envKey
|
|
58
232
|
});
|
|
59
|
-
}, [
|
|
60
|
-
|
|
61
|
-
|
|
233
|
+
}, [
|
|
234
|
+
props.config.apiBaseUrl,
|
|
235
|
+
props.config.envKey,
|
|
236
|
+
props.config.projectKey,
|
|
237
|
+
props.config.sdkKey
|
|
238
|
+
]);
|
|
239
|
+
const flagsStore = (0, import_react.useMemo)(() => createFlagsStore(client, () => userRef.current), [client]);
|
|
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
|
+
);
|
|
260
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FeatureFlareContext.Provider, { value, children: props.children });
|
|
62
261
|
}
|
|
63
|
-
function
|
|
64
|
-
const ctx = import_react.default.useContext(
|
|
65
|
-
if (!ctx) throw new Error("
|
|
262
|
+
function useFeatureFlareContext() {
|
|
263
|
+
const ctx = import_react.default.useContext(FeatureFlareContext);
|
|
264
|
+
if (!ctx) throw new Error("useFeatureFlareContext must be used within <FeatureFlareProvider>.");
|
|
66
265
|
return ctx;
|
|
67
266
|
}
|
|
68
267
|
|
|
69
268
|
// src/hooks.ts
|
|
70
269
|
var import_react2 = require("react");
|
|
71
|
-
|
|
72
|
-
|
|
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
|
+
function useFeatureFlareUser() {
|
|
281
|
+
const { user, setUser } = useFeatureFlareContext();
|
|
73
282
|
return [user, setUser];
|
|
74
283
|
}
|
|
75
284
|
function useBoolFlag(flagKey, defaultValue = false) {
|
|
76
|
-
const { client, user } =
|
|
285
|
+
const { client, user } = useFeatureFlareContext();
|
|
77
286
|
const [state, setState] = (0, import_react2.useState)({ value: defaultValue, loading: true, error: null });
|
|
78
287
|
const userId = user.id ?? user.key ?? "";
|
|
79
288
|
const key = (0, import_react2.useMemo)(() => `${flagKey}:${userId}`, [flagKey, userId]);
|
|
@@ -103,7 +312,7 @@ function useBoolFlag(flagKey, defaultValue = false) {
|
|
|
103
312
|
return state;
|
|
104
313
|
}
|
|
105
314
|
function useBoolFlags(flagKeys, defaultValue = false) {
|
|
106
|
-
const { client, user } =
|
|
315
|
+
const { client, user } = useFeatureFlareContext();
|
|
107
316
|
const sortedKeys = (0, import_react2.useMemo)(() => [...flagKeys].map((k) => k.trim()).filter(Boolean).sort(), [flagKeys]);
|
|
108
317
|
const userId = user.id ?? user.key ?? "";
|
|
109
318
|
const key = (0, import_react2.useMemo)(() => `${sortedKeys.join(",")}:${userId}`, [sortedKeys, userId]);
|
|
@@ -137,12 +346,50 @@ function useBoolFlags(flagKeys, defaultValue = false) {
|
|
|
137
346
|
}, [client, defaultValue, key, sortedKeys, user]);
|
|
138
347
|
return state;
|
|
139
348
|
}
|
|
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
|
+
}
|
|
140
385
|
// Annotate the CommonJS export names for ESM import in node:
|
|
141
386
|
0 && (module.exports = {
|
|
142
|
-
|
|
387
|
+
FeatureFlareProvider,
|
|
388
|
+
resolveFeatureFlareBrowserConfig,
|
|
143
389
|
useBoolFlag,
|
|
144
390
|
useBoolFlags,
|
|
145
|
-
|
|
146
|
-
|
|
391
|
+
useFeatureFlareContext,
|
|
392
|
+
useFeatureFlareUser,
|
|
393
|
+
useFlags
|
|
147
394
|
});
|
|
148
395
|
//# 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 { ShipItUserPayload } from '@featureflare/sdk-js';\n","import React, { createContext, useMemo, useState } from 'react';\nimport { ShipItClient, type ShipItUserPayload } from '@featureflare/sdk-js';\n\ntype ShipItReactConfig = {\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 ShipItContextValue = {\n client: ShipItClient;\n user: ShipItUserPayload;\n setUser: (next: ShipItUserPayload) => void;\n};\n\nconst ShipItContext = createContext<ShipItContextValue | null>(null);\n\nexport function ShipItProvider(props: {\n config: ShipItReactConfig;\n initialUser: ShipItUserPayload;\n user?: ShipItUserPayload;\n onUserChange?: (next: ShipItUserPayload) => void;\n children: React.ReactNode;\n}) {\n if (props.user && !props.onUserChange) {\n throw new Error('ShipItProvider: when providing `user`, also provide `onUserChange` (controlled mode).');\n }\n\n const [internalUser, setInternalUser] = useState<ShipItUserPayload>(props.initialUser);\n const user = props.user ?? internalUser;\n const setUser = props.onUserChange ?? setInternalUser;\n\n const client = useMemo(() => {\n return new ShipItClient({\n sdkKey: props.config.sdkKey,\n projectKey: props.config.projectKey,\n envKey: props.config.envKey\n });\n }, [props.config.envKey, props.config.projectKey, props.config.sdkKey]);\n\n const value = useMemo(() => ({ client, user, setUser }), [client, user]);\n return <ShipItContext.Provider value={value}>{props.children}</ShipItContext.Provider>;\n}\n\nexport function useShipItContext(): ShipItContextValue {\n const ctx = React.useContext(ShipItContext);\n if (!ctx) throw new Error('useShipItContext must be used within <ShipItProvider>.');\n return ctx;\n}\n","import { useEffect, useMemo, useRef, useState } from 'react';\nimport type { ShipItUserPayload } from '@featureflare/sdk-js';\nimport { useShipItContext } 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 useShipItUser(): [ShipItUserPayload, (next: ShipItUserPayload) => void] {\n const { user, setUser } = useShipItContext();\n return [user, setUser];\n}\n\nexport function useBoolFlag(flagKey: string, defaultValue = false): BoolFlagState {\n const { client, user } = useShipItContext();\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 } = useShipItContext();\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"]}
|
|
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"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,28 +1,52 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import {
|
|
4
|
-
export {
|
|
3
|
+
import { FeatureFlareUserPayload, FeatureFlareClient } from '@featureflare/sdk-js';
|
|
4
|
+
export { FeatureFlareUserPayload } from '@featureflare/sdk-js';
|
|
5
5
|
|
|
6
|
-
type
|
|
6
|
+
type FeatureFlareEnvironmentKey = 'development' | 'staging' | 'production';
|
|
7
|
+
type FeatureFlareReactConfig = {
|
|
8
|
+
/** Optional: explicit FeatureFlare API base URL. */
|
|
9
|
+
apiBaseUrl?: string;
|
|
7
10
|
/** Recommended: use a client key (featureflare_cli_...). */
|
|
8
11
|
sdkKey?: string;
|
|
9
12
|
/** Legacy/insecure browser mode: uses /api/v1/eval (no sdkKey). */
|
|
10
13
|
projectKey?: string;
|
|
11
|
-
envKey?: string;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
envKey?: FeatureFlareEnvironmentKey | string;
|
|
15
|
+
};
|
|
16
|
+
declare function resolveFeatureFlareBrowserConfig(input?: {
|
|
17
|
+
envKey?: FeatureFlareEnvironmentKey;
|
|
18
|
+
apiBaseUrl?: string;
|
|
19
|
+
}): FeatureFlareReactConfig;
|
|
20
|
+
type FeatureFlareContextValue = {
|
|
21
|
+
client: FeatureFlareClient;
|
|
22
|
+
user: FeatureFlareUserPayload;
|
|
23
|
+
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
|
+
};
|
|
42
|
+
declare function FeatureFlareProvider(props: {
|
|
43
|
+
config: FeatureFlareReactConfig;
|
|
44
|
+
initialUser: FeatureFlareUserPayload;
|
|
45
|
+
user?: FeatureFlareUserPayload;
|
|
46
|
+
onUserChange?: (next: FeatureFlareUserPayload) => void;
|
|
23
47
|
children: React.ReactNode;
|
|
24
48
|
}): react_jsx_runtime.JSX.Element;
|
|
25
|
-
declare function
|
|
49
|
+
declare function useFeatureFlareContext(): FeatureFlareContextValue;
|
|
26
50
|
|
|
27
51
|
type BoolFlagState = {
|
|
28
52
|
value: boolean;
|
|
@@ -34,8 +58,23 @@ type BoolFlagsState = {
|
|
|
34
58
|
loading: boolean;
|
|
35
59
|
errors: Record<string, string>;
|
|
36
60
|
};
|
|
37
|
-
|
|
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
|
+
declare function useFeatureFlareUser(): [FeatureFlareUserPayload, (next: FeatureFlareUserPayload) => void];
|
|
38
75
|
declare function useBoolFlag(flagKey: string, defaultValue?: boolean): BoolFlagState;
|
|
39
76
|
declare function useBoolFlags(flagKeys: string[], defaultValue?: boolean): BoolFlagsState;
|
|
77
|
+
declare function useFlags(input?: UseFlagsInput): FlagsState;
|
|
78
|
+
declare function useFlags(defaultValue?: boolean, options?: UseFlagsOptions): FlagsState;
|
|
40
79
|
|
|
41
|
-
export {
|
|
80
|
+
export { type FeatureFlareEnvironmentKey, FeatureFlareProvider, type FeatureFlareReactConfig, type FlagsState$1 as FlagsState, type FlagsSubscriptionOptions, resolveFeatureFlareBrowserConfig, useBoolFlag, useBoolFlags, useFeatureFlareContext, useFeatureFlareUser, useFlags };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,28 +1,52 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import {
|
|
4
|
-
export {
|
|
3
|
+
import { FeatureFlareUserPayload, FeatureFlareClient } from '@featureflare/sdk-js';
|
|
4
|
+
export { FeatureFlareUserPayload } from '@featureflare/sdk-js';
|
|
5
5
|
|
|
6
|
-
type
|
|
6
|
+
type FeatureFlareEnvironmentKey = 'development' | 'staging' | 'production';
|
|
7
|
+
type FeatureFlareReactConfig = {
|
|
8
|
+
/** Optional: explicit FeatureFlare API base URL. */
|
|
9
|
+
apiBaseUrl?: string;
|
|
7
10
|
/** Recommended: use a client key (featureflare_cli_...). */
|
|
8
11
|
sdkKey?: string;
|
|
9
12
|
/** Legacy/insecure browser mode: uses /api/v1/eval (no sdkKey). */
|
|
10
13
|
projectKey?: string;
|
|
11
|
-
envKey?: string;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
envKey?: FeatureFlareEnvironmentKey | string;
|
|
15
|
+
};
|
|
16
|
+
declare function resolveFeatureFlareBrowserConfig(input?: {
|
|
17
|
+
envKey?: FeatureFlareEnvironmentKey;
|
|
18
|
+
apiBaseUrl?: string;
|
|
19
|
+
}): FeatureFlareReactConfig;
|
|
20
|
+
type FeatureFlareContextValue = {
|
|
21
|
+
client: FeatureFlareClient;
|
|
22
|
+
user: FeatureFlareUserPayload;
|
|
23
|
+
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
|
+
};
|
|
42
|
+
declare function FeatureFlareProvider(props: {
|
|
43
|
+
config: FeatureFlareReactConfig;
|
|
44
|
+
initialUser: FeatureFlareUserPayload;
|
|
45
|
+
user?: FeatureFlareUserPayload;
|
|
46
|
+
onUserChange?: (next: FeatureFlareUserPayload) => void;
|
|
23
47
|
children: React.ReactNode;
|
|
24
48
|
}): react_jsx_runtime.JSX.Element;
|
|
25
|
-
declare function
|
|
49
|
+
declare function useFeatureFlareContext(): FeatureFlareContextValue;
|
|
26
50
|
|
|
27
51
|
type BoolFlagState = {
|
|
28
52
|
value: boolean;
|
|
@@ -34,8 +58,23 @@ type BoolFlagsState = {
|
|
|
34
58
|
loading: boolean;
|
|
35
59
|
errors: Record<string, string>;
|
|
36
60
|
};
|
|
37
|
-
|
|
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
|
+
declare function useFeatureFlareUser(): [FeatureFlareUserPayload, (next: FeatureFlareUserPayload) => void];
|
|
38
75
|
declare function useBoolFlag(flagKey: string, defaultValue?: boolean): BoolFlagState;
|
|
39
76
|
declare function useBoolFlags(flagKeys: string[], defaultValue?: boolean): BoolFlagsState;
|
|
77
|
+
declare function useFlags(input?: UseFlagsInput): FlagsState;
|
|
78
|
+
declare function useFlags(defaultValue?: boolean, options?: UseFlagsOptions): FlagsState;
|
|
40
79
|
|
|
41
|
-
export {
|
|
80
|
+
export { type FeatureFlareEnvironmentKey, FeatureFlareProvider, type FeatureFlareReactConfig, type FlagsState$1 as FlagsState, type FlagsSubscriptionOptions, resolveFeatureFlareBrowserConfig, useBoolFlag, useBoolFlags, useFeatureFlareContext, useFeatureFlareUser, useFlags };
|
package/dist/index.js
CHANGED
|
@@ -1,44 +1,251 @@
|
|
|
1
1
|
// src/provider.tsx
|
|
2
|
-
import React, { createContext, useMemo, useState } from "react";
|
|
3
|
-
import {
|
|
2
|
+
import React, { createContext, useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { FeatureFlareClient } from "@featureflare/sdk-js";
|
|
4
4
|
import { jsx } from "react/jsx-runtime";
|
|
5
|
-
|
|
6
|
-
|
|
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
|
+
var FeatureFlareContext = createContext(null);
|
|
176
|
+
function FeatureFlareProvider(props) {
|
|
7
177
|
if (props.user && !props.onUserChange) {
|
|
8
|
-
throw new Error("
|
|
178
|
+
throw new Error("FeatureFlareProvider: when providing `user`, also provide `onUserChange` (controlled mode).");
|
|
9
179
|
}
|
|
10
180
|
const [internalUser, setInternalUser] = useState(props.initialUser);
|
|
11
181
|
const user = props.user ?? internalUser;
|
|
12
182
|
const setUser = props.onUserChange ?? setInternalUser;
|
|
183
|
+
const userRef = useRef(user);
|
|
13
184
|
const client = useMemo(() => {
|
|
14
|
-
return new
|
|
185
|
+
return new FeatureFlareClient({
|
|
186
|
+
apiBaseUrl: props.config.apiBaseUrl,
|
|
15
187
|
sdkKey: props.config.sdkKey,
|
|
16
188
|
projectKey: props.config.projectKey,
|
|
17
189
|
envKey: props.config.envKey
|
|
18
190
|
});
|
|
19
|
-
}, [
|
|
20
|
-
|
|
21
|
-
|
|
191
|
+
}, [
|
|
192
|
+
props.config.apiBaseUrl,
|
|
193
|
+
props.config.envKey,
|
|
194
|
+
props.config.projectKey,
|
|
195
|
+
props.config.sdkKey
|
|
196
|
+
]);
|
|
197
|
+
const flagsStore = useMemo(() => createFlagsStore(client, () => userRef.current), [client]);
|
|
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
|
+
);
|
|
218
|
+
return /* @__PURE__ */ jsx(FeatureFlareContext.Provider, { value, children: props.children });
|
|
22
219
|
}
|
|
23
|
-
function
|
|
24
|
-
const ctx = React.useContext(
|
|
25
|
-
if (!ctx) throw new Error("
|
|
220
|
+
function useFeatureFlareContext() {
|
|
221
|
+
const ctx = React.useContext(FeatureFlareContext);
|
|
222
|
+
if (!ctx) throw new Error("useFeatureFlareContext must be used within <FeatureFlareProvider>.");
|
|
26
223
|
return ctx;
|
|
27
224
|
}
|
|
28
225
|
|
|
29
226
|
// src/hooks.ts
|
|
30
|
-
import { useEffect, useMemo as useMemo2, useRef, useState as useState2 } from "react";
|
|
31
|
-
|
|
32
|
-
|
|
227
|
+
import { useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2, useSyncExternalStore } from "react";
|
|
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
|
+
}
|
|
238
|
+
function useFeatureFlareUser() {
|
|
239
|
+
const { user, setUser } = useFeatureFlareContext();
|
|
33
240
|
return [user, setUser];
|
|
34
241
|
}
|
|
35
242
|
function useBoolFlag(flagKey, defaultValue = false) {
|
|
36
|
-
const { client, user } =
|
|
243
|
+
const { client, user } = useFeatureFlareContext();
|
|
37
244
|
const [state, setState] = useState2({ value: defaultValue, loading: true, error: null });
|
|
38
245
|
const userId = user.id ?? user.key ?? "";
|
|
39
246
|
const key = useMemo2(() => `${flagKey}:${userId}`, [flagKey, userId]);
|
|
40
|
-
const lastKey =
|
|
41
|
-
|
|
247
|
+
const lastKey = useRef2("");
|
|
248
|
+
useEffect2(() => {
|
|
42
249
|
let cancelled = false;
|
|
43
250
|
const nextKey = key;
|
|
44
251
|
lastKey.current = nextKey;
|
|
@@ -63,13 +270,13 @@ function useBoolFlag(flagKey, defaultValue = false) {
|
|
|
63
270
|
return state;
|
|
64
271
|
}
|
|
65
272
|
function useBoolFlags(flagKeys, defaultValue = false) {
|
|
66
|
-
const { client, user } =
|
|
273
|
+
const { client, user } = useFeatureFlareContext();
|
|
67
274
|
const sortedKeys = useMemo2(() => [...flagKeys].map((k) => k.trim()).filter(Boolean).sort(), [flagKeys]);
|
|
68
275
|
const userId = user.id ?? user.key ?? "";
|
|
69
276
|
const key = useMemo2(() => `${sortedKeys.join(",")}:${userId}`, [sortedKeys, userId]);
|
|
70
277
|
const [state, setState] = useState2({ values: {}, loading: true, errors: {} });
|
|
71
|
-
const lastKey =
|
|
72
|
-
|
|
278
|
+
const lastKey = useRef2("");
|
|
279
|
+
useEffect2(() => {
|
|
73
280
|
let cancelled = false;
|
|
74
281
|
const nextKey = key;
|
|
75
282
|
lastKey.current = nextKey;
|
|
@@ -97,11 +304,49 @@ function useBoolFlags(flagKeys, defaultValue = false) {
|
|
|
97
304
|
}, [client, defaultValue, key, sortedKeys, user]);
|
|
98
305
|
return state;
|
|
99
306
|
}
|
|
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
|
+
}
|
|
100
343
|
export {
|
|
101
|
-
|
|
344
|
+
FeatureFlareProvider,
|
|
345
|
+
resolveFeatureFlareBrowserConfig,
|
|
102
346
|
useBoolFlag,
|
|
103
347
|
useBoolFlags,
|
|
104
|
-
|
|
105
|
-
|
|
348
|
+
useFeatureFlareContext,
|
|
349
|
+
useFeatureFlareUser,
|
|
350
|
+
useFlags
|
|
106
351
|
};
|
|
107
352
|
//# 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, useMemo, useState } from 'react';\nimport { ShipItClient, type ShipItUserPayload } from '@featureflare/sdk-js';\n\ntype ShipItReactConfig = {\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 ShipItContextValue = {\n client: ShipItClient;\n user: ShipItUserPayload;\n setUser: (next: ShipItUserPayload) => void;\n};\n\nconst ShipItContext = createContext<ShipItContextValue | null>(null);\n\nexport function ShipItProvider(props: {\n config: ShipItReactConfig;\n initialUser: ShipItUserPayload;\n user?: ShipItUserPayload;\n onUserChange?: (next: ShipItUserPayload) => void;\n children: React.ReactNode;\n}) {\n if (props.user && !props.onUserChange) {\n throw new Error('ShipItProvider: when providing `user`, also provide `onUserChange` (controlled mode).');\n }\n\n const [internalUser, setInternalUser] = useState<ShipItUserPayload>(props.initialUser);\n const user = props.user ?? internalUser;\n const setUser = props.onUserChange ?? setInternalUser;\n\n const client = useMemo(() => {\n return new ShipItClient({\n sdkKey: props.config.sdkKey,\n projectKey: props.config.projectKey,\n envKey: props.config.envKey\n });\n }, [props.config.envKey, props.config.projectKey, props.config.sdkKey]);\n\n const value = useMemo(() => ({ client, user, setUser }), [client, user]);\n return <ShipItContext.Provider value={value}>{props.children}</ShipItContext.Provider>;\n}\n\nexport function useShipItContext(): ShipItContextValue {\n const ctx = React.useContext(ShipItContext);\n if (!ctx) throw new Error('useShipItContext must be used within <ShipItProvider>.');\n return ctx;\n}\n","import { useEffect, useMemo, useRef, useState } from 'react';\nimport type { ShipItUserPayload } from '@featureflare/sdk-js';\nimport { useShipItContext } 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 useShipItUser(): [ShipItUserPayload, (next: ShipItUserPayload) => void] {\n const { user, setUser } = useShipItContext();\n return [user, setUser];\n}\n\nexport function useBoolFlag(flagKey: string, defaultValue = false): BoolFlagState {\n const { client, user } = useShipItContext();\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 } = useShipItContext();\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"]}
|
|
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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@featureflare/react",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5-beta.186",
|
|
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.
|
|
36
|
+
"@featureflare/sdk-js": "0.0.5-beta.186"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^22.10.2",
|
|
40
|
-
"@types/react": "^
|
|
41
|
-
"@types/react-dom": "^
|
|
40
|
+
"@types/react": "^19.0.0",
|
|
41
|
+
"@types/react-dom": "^19.0.0",
|
|
42
42
|
"eslint": "^9.16.0",
|
|
43
43
|
"prettier": "^3.4.2",
|
|
44
44
|
"tsup": "^8.3.5",
|