@flagdown/react 0.0.2 → 0.0.3
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 +34 -28
- package/dist/index.d.ts +7 -5
- package/dist/index.js +57 -5
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,42 +1,48 @@
|
|
|
1
1
|
# @flagdown/react
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
flags.
|
|
5
|
-
|
|
6
|
-
## Installation
|
|
7
|
-
|
|
8
|
-
```bash
|
|
9
|
-
npm install @flagdown/react
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
## Current bootstrap API
|
|
13
|
-
|
|
14
|
-
Wrap the part of the React tree that reads flags in `FlagdownProvider`, then
|
|
15
|
-
read a flag with `useFlag`:
|
|
3
|
+
Read boolean feature flags from a Flagdown project in React 18+.
|
|
16
4
|
|
|
17
5
|
```tsx
|
|
18
|
-
import { FlagdownProvider, useFlag } from
|
|
6
|
+
import { FlagdownProvider, useFlag } from '@flagdown/react';
|
|
19
7
|
|
|
20
|
-
function
|
|
21
|
-
|
|
8
|
+
function Checkout() {
|
|
9
|
+
const enabled = useFlag('new-checkout');
|
|
10
|
+
return enabled ? <NewCheckout /> : <CurrentCheckout />;
|
|
22
11
|
}
|
|
23
12
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
13
|
+
<FlagdownProvider
|
|
14
|
+
apiUrl="https://your-flagdown-api.example.com"
|
|
15
|
+
clientToken="COPY_PUBLIC_CLIENT_TOKEN_FROM_DASHBOARD"
|
|
16
|
+
>
|
|
17
|
+
<Checkout />
|
|
18
|
+
</FlagdownProvider>
|
|
31
19
|
```
|
|
32
20
|
|
|
33
|
-
|
|
34
|
-
|
|
21
|
+
Create a project and flag in the dashboard, then copy the generated integration
|
|
22
|
+
snippet. `apiUrl` is the Worker base URL without `/v1`. Install the SDK build that
|
|
23
|
+
includes the remote adapter; the previously published 0.0.2 build only provides
|
|
24
|
+
an injectable evaluator. This source must be released before registry installs
|
|
25
|
+
can use remote reads.
|
|
26
|
+
|
|
27
|
+
Flags refresh every 10 seconds, without reloading the page. Set
|
|
28
|
+
`refreshIntervalMs` to change this (minimum 1000ms). Requests time out after 10
|
|
29
|
+
seconds. Initial values, unknown flags and refresh failures evaluate `false`;
|
|
30
|
+
a successful later refresh restores values. An optional `onError(error)` callback
|
|
31
|
+
can report failures. Changing project configuration immediately discards the
|
|
32
|
+
previous project's values. Unmounting cancels in-flight work and future refreshes.
|
|
35
33
|
|
|
36
|
-
|
|
34
|
+
Client tokens are public, project-scoped read configuration. They cannot manage
|
|
35
|
+
flags. Replacing a token in the dashboard invalidates the previous token; update
|
|
36
|
+
apps using it. Client-side flags are not an authorization boundary.
|
|
37
|
+
|
|
38
|
+
For tests or local values, the existing evaluator port remains available:
|
|
37
39
|
|
|
38
40
|
```tsx
|
|
39
|
-
<FlagdownProvider evaluator={{ isEnabled:
|
|
40
|
-
<
|
|
41
|
+
<FlagdownProvider evaluator={{ isEnabled: key => key === 'new-checkout' }}>
|
|
42
|
+
<Checkout />
|
|
41
43
|
</FlagdownProvider>
|
|
42
44
|
```
|
|
45
|
+
|
|
46
|
+
An explicit evaluator takes precedence over remote configuration. A provider
|
|
47
|
+
without either configuration evaluates every flag off. `useFlag` must be called
|
|
48
|
+
inside a provider.
|
package/dist/index.d.ts
CHANGED
|
@@ -14,13 +14,15 @@ interface FlagEvaluator {
|
|
|
14
14
|
declare const disabledFlagEvaluator: FlagEvaluator;
|
|
15
15
|
|
|
16
16
|
interface FlagdownProviderProps extends PropsWithChildren {
|
|
17
|
-
/**
|
|
18
|
-
* Optional evaluation port. This enables a future Flagdown API adapter and
|
|
19
|
-
* keeps consumers independent from the transport that supplies flag values.
|
|
20
|
-
*/
|
|
21
17
|
evaluator?: FlagEvaluator;
|
|
18
|
+
apiUrl?: string;
|
|
19
|
+
/** Public, read-only project token copied from the dashboard. */
|
|
20
|
+
clientToken?: string;
|
|
21
|
+
/** Refresh interval; minimum 1 second, default 10 seconds. */
|
|
22
|
+
refreshIntervalMs?: number;
|
|
23
|
+
onError?: (error: Error) => void;
|
|
22
24
|
}
|
|
23
|
-
declare function FlagdownProvider({ children, evaluator, }: FlagdownProviderProps): react.JSX.Element;
|
|
25
|
+
declare function FlagdownProvider({ children, evaluator, apiUrl, clientToken, refreshIntervalMs, onError }: FlagdownProviderProps): react.JSX.Element;
|
|
24
26
|
|
|
25
27
|
declare function useFlag(flagKey: string): boolean;
|
|
26
28
|
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// src/FlagdownProvider.tsx
|
|
2
|
+
import { useEffect, useMemo, useState, useRef } from "react";
|
|
3
|
+
|
|
1
4
|
// src/FlagdownContext.tsx
|
|
2
5
|
import { createContext, useContext } from "react";
|
|
3
6
|
var FlagdownContext = createContext(void 0);
|
|
@@ -14,13 +17,62 @@ var disabledFlagEvaluator = {
|
|
|
14
17
|
isEnabled: () => false
|
|
15
18
|
};
|
|
16
19
|
|
|
20
|
+
// src/remote.ts
|
|
21
|
+
async function fetchFlagState(apiUrl, clientToken, signal) {
|
|
22
|
+
const response = await fetch(apiUrl.replace(/\/$/, "") + "/v1/client/flags", {
|
|
23
|
+
headers: { "x-flagdown-client-token": clientToken },
|
|
24
|
+
cache: "no-store",
|
|
25
|
+
signal
|
|
26
|
+
});
|
|
27
|
+
if (!response.ok) throw new Error(`Flagdown request failed (${response.status})`);
|
|
28
|
+
const data = await response.json();
|
|
29
|
+
if (!data || typeof data !== "object" || !("flags" in data) || !data.flags || typeof data.flags !== "object" || Array.isArray(data.flags) || Object.values(data.flags).some((value) => typeof value !== "boolean")) {
|
|
30
|
+
throw new Error("Invalid Flagdown response");
|
|
31
|
+
}
|
|
32
|
+
return data.flags;
|
|
33
|
+
}
|
|
34
|
+
|
|
17
35
|
// src/FlagdownProvider.tsx
|
|
18
36
|
import { jsx } from "react/jsx-runtime";
|
|
19
|
-
function FlagdownProvider({
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
37
|
+
function FlagdownProvider({ children, evaluator, apiUrl, clientToken, refreshIntervalMs = 1e4, onError }) {
|
|
38
|
+
const errorHandler = useRef(onError);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
errorHandler.current = onError;
|
|
41
|
+
}, [onError]);
|
|
42
|
+
const [snapshot, setSnapshot] = useState();
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (evaluator || !apiUrl || !clientToken) return;
|
|
45
|
+
let active = true;
|
|
46
|
+
let timer;
|
|
47
|
+
let controller;
|
|
48
|
+
async function refresh() {
|
|
49
|
+
controller = new AbortController();
|
|
50
|
+
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
51
|
+
try {
|
|
52
|
+
const flags = await fetchFlagState(apiUrl, clientToken, controller.signal);
|
|
53
|
+
if (active) setSnapshot({ apiUrl, clientToken, flags });
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (active) {
|
|
56
|
+
setSnapshot(void 0);
|
|
57
|
+
try {
|
|
58
|
+
errorHandler.current?.(error instanceof Error ? error : new Error("Flagdown request failed"));
|
|
59
|
+
} catch {
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} finally {
|
|
63
|
+
clearTimeout(timeout);
|
|
64
|
+
if (active) timer = setTimeout(() => void refresh(), Number.isFinite(refreshIntervalMs) ? Math.max(1e3, refreshIntervalMs) : 1e4);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
void refresh();
|
|
68
|
+
return () => {
|
|
69
|
+
active = false;
|
|
70
|
+
clearTimeout(timer);
|
|
71
|
+
controller?.abort();
|
|
72
|
+
};
|
|
73
|
+
}, [apiUrl, clientToken, evaluator, refreshIntervalMs]);
|
|
74
|
+
const value = useMemo(() => evaluator || (snapshot && snapshot.apiUrl === apiUrl && snapshot.clientToken === clientToken ? { isEnabled: (key) => Object.hasOwn(snapshot.flags, key) && snapshot.flags[key] === true } : disabledFlagEvaluator), [evaluator, snapshot, apiUrl, clientToken]);
|
|
75
|
+
return /* @__PURE__ */ jsx(FlagdownContext.Provider, { value, children });
|
|
24
76
|
}
|
|
25
77
|
export {
|
|
26
78
|
FlagdownProvider,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flagdown/react",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "React SDK for evaluating Flagdown feature flags.",
|
|
@@ -22,7 +22,11 @@
|
|
|
22
22
|
"types": "./dist/index.d.ts"
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
|
-
"files": [
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE"
|
|
29
|
+
],
|
|
26
30
|
"scripts": {
|
|
27
31
|
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
28
32
|
"test": "bun test",
|