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