@featureflare/react 0.0.5 → 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 +24 -27
- package/dist/index.cjs +19 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -17
- package/dist/index.d.ts +15 -17
- package/dist/index.js +17 -23
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
|
|
@@ -21,24 +21,22 @@ yarn add @featureflare/react
|
|
|
21
21
|
|
|
22
22
|
### Setup Provider
|
|
23
23
|
|
|
24
|
-
Wrap your app with `
|
|
24
|
+
Wrap your app with `ShipItProvider`:
|
|
25
25
|
|
|
26
26
|
```typescript
|
|
27
|
-
import {
|
|
27
|
+
import { ShipItProvider } from '@featureflare/react';
|
|
28
28
|
|
|
29
29
|
export function App() {
|
|
30
30
|
return (
|
|
31
|
-
<
|
|
31
|
+
<ShipItProvider
|
|
32
32
|
config={{
|
|
33
|
-
//
|
|
34
|
-
apiBaseUrl: 'https://flags.your-company.com',
|
|
35
|
-
// SDK defaults to FeatureFlare Cloud Run unless overridden
|
|
33
|
+
// SDK automatically uses window.location.origin in browser
|
|
36
34
|
sdkKey: 'your-client-key-here' // Client key for browser
|
|
37
35
|
}}
|
|
38
36
|
initialUser={{ id: 'user-123' }}
|
|
39
37
|
>
|
|
40
38
|
{/* Your app */}
|
|
41
|
-
</
|
|
39
|
+
</ShipItProvider>
|
|
42
40
|
);
|
|
43
41
|
}
|
|
44
42
|
```
|
|
@@ -62,13 +60,13 @@ export function NewNav() {
|
|
|
62
60
|
|
|
63
61
|
### Update User
|
|
64
62
|
|
|
65
|
-
Use the `
|
|
63
|
+
Use the `useShipItUser` hook to get and update the current user:
|
|
66
64
|
|
|
67
65
|
```typescript
|
|
68
|
-
import {
|
|
66
|
+
import { useShipItUser } from '@featureflare/react';
|
|
69
67
|
|
|
70
68
|
export function UserSwitcher() {
|
|
71
|
-
const [user, setUser] =
|
|
69
|
+
const [user, setUser] = useShipItUser();
|
|
72
70
|
|
|
73
71
|
return (
|
|
74
72
|
<button
|
|
@@ -87,20 +85,19 @@ export function UserSwitcher() {
|
|
|
87
85
|
|
|
88
86
|
## API Reference
|
|
89
87
|
|
|
90
|
-
### `
|
|
88
|
+
### `ShipItProvider`
|
|
91
89
|
|
|
92
|
-
Provider component that wraps your app and provides the
|
|
90
|
+
Provider component that wraps your app and provides the ShipIt client context.
|
|
93
91
|
|
|
94
92
|
#### Props
|
|
95
93
|
|
|
96
|
-
- `config:
|
|
97
|
-
- `
|
|
98
|
-
- `sdkKey?: string` - Client SDK key (recommended). If not provided, reads from `FEATUREFLARE_CLIENT_KEY` env var.
|
|
94
|
+
- `config: ShipItReactConfig` - Configuration object
|
|
95
|
+
- `sdkKey?: string` - Client SDK key (recommended). If not provided, reads from `SHIPIT_CLIENT_KEY` env var.
|
|
99
96
|
- `projectKey?: string` - Legacy: project key (requires `envKey`). Not recommended.
|
|
100
97
|
- `envKey?: string` - Environment key (default: `'production'`). Only used with `projectKey`.
|
|
101
|
-
- `initialUser:
|
|
102
|
-
- `user?:
|
|
103
|
-
- `onUserChange?: (user:
|
|
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`)
|
|
104
101
|
- `children: React.ReactNode` - Your app components
|
|
105
102
|
|
|
106
103
|
### `useBoolFlag(flagKey: string, defaultValue: boolean)`
|
|
@@ -119,31 +116,31 @@ Hook to evaluate a boolean feature flag.
|
|
|
119
116
|
const { value, loading, error } = useBoolFlag('feature-flag', false);
|
|
120
117
|
```
|
|
121
118
|
|
|
122
|
-
### `
|
|
119
|
+
### `useShipItUser()`
|
|
123
120
|
|
|
124
121
|
Hook to get and update the current user.
|
|
125
122
|
|
|
126
123
|
**Returns:**
|
|
127
124
|
|
|
128
|
-
`[user:
|
|
125
|
+
`[user: ShipItUserPayload | null, setUser: (user: ShipItUserPayload) => void]`
|
|
129
126
|
|
|
130
127
|
**Example:**
|
|
131
128
|
|
|
132
129
|
```typescript
|
|
133
|
-
const [user, setUser] =
|
|
130
|
+
const [user, setUser] = useShipItUser();
|
|
134
131
|
```
|
|
135
132
|
|
|
136
133
|
## Environment Variables
|
|
137
134
|
|
|
138
135
|
The SDK automatically reads from environment variables if `sdkKey` is not provided:
|
|
139
136
|
|
|
140
|
-
- `
|
|
137
|
+
- `SHIPIT_CLIENT_KEY` - Client SDK key
|
|
141
138
|
|
|
142
139
|
```typescript
|
|
143
|
-
// This will use
|
|
144
|
-
<
|
|
140
|
+
// This will use SHIPIT_CLIENT_KEY from env if sdkKey is not provided
|
|
141
|
+
<ShipItProvider config={{}} initialUser={{ id: 'user-123' }}>
|
|
145
142
|
{/* ... */}
|
|
146
|
-
</
|
|
143
|
+
</ShipItProvider>
|
|
147
144
|
```
|
|
148
145
|
|
|
149
146
|
## API Base URL
|
|
@@ -154,7 +151,7 @@ The SDK automatically uses `window.location.origin` in the browser (assumes API
|
|
|
154
151
|
|
|
155
152
|
Use **client keys** for browser/mobile applications. Client keys are not secret and will be visible in your JavaScript bundle.
|
|
156
153
|
|
|
157
|
-
Get your SDK keys from your
|
|
154
|
+
Get your SDK keys from your ShipIt Console → Environments.
|
|
158
155
|
|
|
159
156
|
## License
|
|
160
157
|
|
package/dist/index.cjs
CHANGED
|
@@ -30,11 +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
|
-
|
|
33
|
+
ShipItProvider: () => ShipItProvider,
|
|
34
34
|
useBoolFlag: () => useBoolFlag,
|
|
35
35
|
useBoolFlags: () => useBoolFlags,
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
useShipItContext: () => useShipItContext,
|
|
37
|
+
useShipItUser: () => useShipItUser
|
|
38
38
|
});
|
|
39
39
|
module.exports = __toCommonJS(index_exports);
|
|
40
40
|
|
|
@@ -42,44 +42,38 @@ module.exports = __toCommonJS(index_exports);
|
|
|
42
42
|
var import_react = __toESM(require("react"), 1);
|
|
43
43
|
var import_sdk_js = require("@featureflare/sdk-js");
|
|
44
44
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
45
|
-
var
|
|
46
|
-
function
|
|
45
|
+
var ShipItContext = (0, import_react.createContext)(null);
|
|
46
|
+
function ShipItProvider(props) {
|
|
47
47
|
if (props.user && !props.onUserChange) {
|
|
48
|
-
throw new Error("
|
|
48
|
+
throw new Error("ShipItProvider: when providing `user`, also provide `onUserChange` (controlled mode).");
|
|
49
49
|
}
|
|
50
50
|
const [internalUser, setInternalUser] = (0, import_react.useState)(props.initialUser);
|
|
51
51
|
const user = props.user ?? internalUser;
|
|
52
52
|
const setUser = props.onUserChange ?? setInternalUser;
|
|
53
53
|
const client = (0, import_react.useMemo)(() => {
|
|
54
|
-
return new import_sdk_js.
|
|
55
|
-
apiBaseUrl: props.config.apiBaseUrl,
|
|
54
|
+
return new import_sdk_js.ShipItClient({
|
|
56
55
|
sdkKey: props.config.sdkKey,
|
|
57
56
|
projectKey: props.config.projectKey,
|
|
58
57
|
envKey: props.config.envKey
|
|
59
58
|
});
|
|
60
|
-
}, [
|
|
61
|
-
props.config.apiBaseUrl,
|
|
62
|
-
props.config.envKey,
|
|
63
|
-
props.config.projectKey,
|
|
64
|
-
props.config.sdkKey
|
|
65
|
-
]);
|
|
59
|
+
}, [props.config.envKey, props.config.projectKey, props.config.sdkKey]);
|
|
66
60
|
const value = (0, import_react.useMemo)(() => ({ client, user, setUser }), [client, user]);
|
|
67
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
61
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ShipItContext.Provider, { value, children: props.children });
|
|
68
62
|
}
|
|
69
|
-
function
|
|
70
|
-
const ctx = import_react.default.useContext(
|
|
71
|
-
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>.");
|
|
72
66
|
return ctx;
|
|
73
67
|
}
|
|
74
68
|
|
|
75
69
|
// src/hooks.ts
|
|
76
70
|
var import_react2 = require("react");
|
|
77
|
-
function
|
|
78
|
-
const { user, setUser } =
|
|
71
|
+
function useShipItUser() {
|
|
72
|
+
const { user, setUser } = useShipItContext();
|
|
79
73
|
return [user, setUser];
|
|
80
74
|
}
|
|
81
75
|
function useBoolFlag(flagKey, defaultValue = false) {
|
|
82
|
-
const { client, user } =
|
|
76
|
+
const { client, user } = useShipItContext();
|
|
83
77
|
const [state, setState] = (0, import_react2.useState)({ value: defaultValue, loading: true, error: null });
|
|
84
78
|
const userId = user.id ?? user.key ?? "";
|
|
85
79
|
const key = (0, import_react2.useMemo)(() => `${flagKey}:${userId}`, [flagKey, userId]);
|
|
@@ -109,7 +103,7 @@ function useBoolFlag(flagKey, defaultValue = false) {
|
|
|
109
103
|
return state;
|
|
110
104
|
}
|
|
111
105
|
function useBoolFlags(flagKeys, defaultValue = false) {
|
|
112
|
-
const { client, user } =
|
|
106
|
+
const { client, user } = useShipItContext();
|
|
113
107
|
const sortedKeys = (0, import_react2.useMemo)(() => [...flagKeys].map((k) => k.trim()).filter(Boolean).sort(), [flagKeys]);
|
|
114
108
|
const userId = user.id ?? user.key ?? "";
|
|
115
109
|
const key = (0, import_react2.useMemo)(() => `${sortedKeys.join(",")}:${userId}`, [sortedKeys, userId]);
|
|
@@ -145,10 +139,10 @@ function useBoolFlags(flagKeys, defaultValue = false) {
|
|
|
145
139
|
}
|
|
146
140
|
// Annotate the CommonJS export names for ESM import in node:
|
|
147
141
|
0 && (module.exports = {
|
|
148
|
-
|
|
142
|
+
ShipItProvider,
|
|
149
143
|
useBoolFlag,
|
|
150
144
|
useBoolFlags,
|
|
151
|
-
|
|
152
|
-
|
|
145
|
+
useShipItContext,
|
|
146
|
+
useShipItUser
|
|
153
147
|
});
|
|
154
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 {
|
|
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,30 +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
|
-
/** Optional: explicit FeatureFlare API base URL. */
|
|
8
|
-
apiBaseUrl?: string;
|
|
6
|
+
type ShipItReactConfig = {
|
|
9
7
|
/** Recommended: use a client key (featureflare_cli_...). */
|
|
10
8
|
sdkKey?: string;
|
|
11
9
|
/** Legacy/insecure browser mode: uses /api/v1/eval (no sdkKey). */
|
|
12
10
|
projectKey?: string;
|
|
13
11
|
envKey?: string;
|
|
14
12
|
};
|
|
15
|
-
type
|
|
16
|
-
client:
|
|
17
|
-
user:
|
|
18
|
-
setUser: (next:
|
|
13
|
+
type ShipItContextValue = {
|
|
14
|
+
client: ShipItClient;
|
|
15
|
+
user: ShipItUserPayload;
|
|
16
|
+
setUser: (next: ShipItUserPayload) => void;
|
|
19
17
|
};
|
|
20
|
-
declare function
|
|
21
|
-
config:
|
|
22
|
-
initialUser:
|
|
23
|
-
user?:
|
|
24
|
-
onUserChange?: (next:
|
|
18
|
+
declare function ShipItProvider(props: {
|
|
19
|
+
config: ShipItReactConfig;
|
|
20
|
+
initialUser: ShipItUserPayload;
|
|
21
|
+
user?: ShipItUserPayload;
|
|
22
|
+
onUserChange?: (next: ShipItUserPayload) => void;
|
|
25
23
|
children: React.ReactNode;
|
|
26
24
|
}): react_jsx_runtime.JSX.Element;
|
|
27
|
-
declare function
|
|
25
|
+
declare function useShipItContext(): ShipItContextValue;
|
|
28
26
|
|
|
29
27
|
type BoolFlagState = {
|
|
30
28
|
value: boolean;
|
|
@@ -36,8 +34,8 @@ type BoolFlagsState = {
|
|
|
36
34
|
loading: boolean;
|
|
37
35
|
errors: Record<string, string>;
|
|
38
36
|
};
|
|
39
|
-
declare function
|
|
37
|
+
declare function useShipItUser(): [ShipItUserPayload, (next: ShipItUserPayload) => void];
|
|
40
38
|
declare function useBoolFlag(flagKey: string, defaultValue?: boolean): BoolFlagState;
|
|
41
39
|
declare function useBoolFlags(flagKeys: string[], defaultValue?: boolean): BoolFlagsState;
|
|
42
40
|
|
|
43
|
-
export {
|
|
41
|
+
export { ShipItProvider, useBoolFlag, useBoolFlags, useShipItContext, useShipItUser };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,30 +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
|
-
/** Optional: explicit FeatureFlare API base URL. */
|
|
8
|
-
apiBaseUrl?: string;
|
|
6
|
+
type ShipItReactConfig = {
|
|
9
7
|
/** Recommended: use a client key (featureflare_cli_...). */
|
|
10
8
|
sdkKey?: string;
|
|
11
9
|
/** Legacy/insecure browser mode: uses /api/v1/eval (no sdkKey). */
|
|
12
10
|
projectKey?: string;
|
|
13
11
|
envKey?: string;
|
|
14
12
|
};
|
|
15
|
-
type
|
|
16
|
-
client:
|
|
17
|
-
user:
|
|
18
|
-
setUser: (next:
|
|
13
|
+
type ShipItContextValue = {
|
|
14
|
+
client: ShipItClient;
|
|
15
|
+
user: ShipItUserPayload;
|
|
16
|
+
setUser: (next: ShipItUserPayload) => void;
|
|
19
17
|
};
|
|
20
|
-
declare function
|
|
21
|
-
config:
|
|
22
|
-
initialUser:
|
|
23
|
-
user?:
|
|
24
|
-
onUserChange?: (next:
|
|
18
|
+
declare function ShipItProvider(props: {
|
|
19
|
+
config: ShipItReactConfig;
|
|
20
|
+
initialUser: ShipItUserPayload;
|
|
21
|
+
user?: ShipItUserPayload;
|
|
22
|
+
onUserChange?: (next: ShipItUserPayload) => void;
|
|
25
23
|
children: React.ReactNode;
|
|
26
24
|
}): react_jsx_runtime.JSX.Element;
|
|
27
|
-
declare function
|
|
25
|
+
declare function useShipItContext(): ShipItContextValue;
|
|
28
26
|
|
|
29
27
|
type BoolFlagState = {
|
|
30
28
|
value: boolean;
|
|
@@ -36,8 +34,8 @@ type BoolFlagsState = {
|
|
|
36
34
|
loading: boolean;
|
|
37
35
|
errors: Record<string, string>;
|
|
38
36
|
};
|
|
39
|
-
declare function
|
|
37
|
+
declare function useShipItUser(): [ShipItUserPayload, (next: ShipItUserPayload) => void];
|
|
40
38
|
declare function useBoolFlag(flagKey: string, defaultValue?: boolean): BoolFlagState;
|
|
41
39
|
declare function useBoolFlags(flagKeys: string[], defaultValue?: boolean): BoolFlagsState;
|
|
42
40
|
|
|
43
|
-
export {
|
|
41
|
+
export { ShipItProvider, useBoolFlag, useBoolFlags, useShipItContext, useShipItUser };
|
package/dist/index.js
CHANGED
|
@@ -1,45 +1,39 @@
|
|
|
1
1
|
// src/provider.tsx
|
|
2
2
|
import React, { createContext, useMemo, useState } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { ShipItClient } from "@featureflare/sdk-js";
|
|
4
4
|
import { jsx } from "react/jsx-runtime";
|
|
5
|
-
var
|
|
6
|
-
function
|
|
5
|
+
var ShipItContext = createContext(null);
|
|
6
|
+
function ShipItProvider(props) {
|
|
7
7
|
if (props.user && !props.onUserChange) {
|
|
8
|
-
throw new Error("
|
|
8
|
+
throw new Error("ShipItProvider: when providing `user`, also provide `onUserChange` (controlled mode).");
|
|
9
9
|
}
|
|
10
10
|
const [internalUser, setInternalUser] = useState(props.initialUser);
|
|
11
11
|
const user = props.user ?? internalUser;
|
|
12
12
|
const setUser = props.onUserChange ?? setInternalUser;
|
|
13
13
|
const client = useMemo(() => {
|
|
14
|
-
return new
|
|
15
|
-
apiBaseUrl: props.config.apiBaseUrl,
|
|
14
|
+
return new ShipItClient({
|
|
16
15
|
sdkKey: props.config.sdkKey,
|
|
17
16
|
projectKey: props.config.projectKey,
|
|
18
17
|
envKey: props.config.envKey
|
|
19
18
|
});
|
|
20
|
-
}, [
|
|
21
|
-
props.config.apiBaseUrl,
|
|
22
|
-
props.config.envKey,
|
|
23
|
-
props.config.projectKey,
|
|
24
|
-
props.config.sdkKey
|
|
25
|
-
]);
|
|
19
|
+
}, [props.config.envKey, props.config.projectKey, props.config.sdkKey]);
|
|
26
20
|
const value = useMemo(() => ({ client, user, setUser }), [client, user]);
|
|
27
|
-
return /* @__PURE__ */ jsx(
|
|
21
|
+
return /* @__PURE__ */ jsx(ShipItContext.Provider, { value, children: props.children });
|
|
28
22
|
}
|
|
29
|
-
function
|
|
30
|
-
const ctx = React.useContext(
|
|
31
|
-
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>.");
|
|
32
26
|
return ctx;
|
|
33
27
|
}
|
|
34
28
|
|
|
35
29
|
// src/hooks.ts
|
|
36
30
|
import { useEffect, useMemo as useMemo2, useRef, useState as useState2 } from "react";
|
|
37
|
-
function
|
|
38
|
-
const { user, setUser } =
|
|
31
|
+
function useShipItUser() {
|
|
32
|
+
const { user, setUser } = useShipItContext();
|
|
39
33
|
return [user, setUser];
|
|
40
34
|
}
|
|
41
35
|
function useBoolFlag(flagKey, defaultValue = false) {
|
|
42
|
-
const { client, user } =
|
|
36
|
+
const { client, user } = useShipItContext();
|
|
43
37
|
const [state, setState] = useState2({ value: defaultValue, loading: true, error: null });
|
|
44
38
|
const userId = user.id ?? user.key ?? "";
|
|
45
39
|
const key = useMemo2(() => `${flagKey}:${userId}`, [flagKey, userId]);
|
|
@@ -69,7 +63,7 @@ function useBoolFlag(flagKey, defaultValue = false) {
|
|
|
69
63
|
return state;
|
|
70
64
|
}
|
|
71
65
|
function useBoolFlags(flagKeys, defaultValue = false) {
|
|
72
|
-
const { client, user } =
|
|
66
|
+
const { client, user } = useShipItContext();
|
|
73
67
|
const sortedKeys = useMemo2(() => [...flagKeys].map((k) => k.trim()).filter(Boolean).sort(), [flagKeys]);
|
|
74
68
|
const userId = user.id ?? user.key ?? "";
|
|
75
69
|
const key = useMemo2(() => `${sortedKeys.join(",")}:${userId}`, [sortedKeys, userId]);
|
|
@@ -104,10 +98,10 @@ function useBoolFlags(flagKeys, defaultValue = false) {
|
|
|
104
98
|
return state;
|
|
105
99
|
}
|
|
106
100
|
export {
|
|
107
|
-
|
|
101
|
+
ShipItProvider,
|
|
108
102
|
useBoolFlag,
|
|
109
103
|
useBoolFlags,
|
|
110
|
-
|
|
111
|
-
|
|
104
|
+
useShipItContext,
|
|
105
|
+
useShipItUser
|
|
112
106
|
};
|
|
113
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, useMemo, useState } from 'react';\nimport {
|
|
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,7 +33,7 @@
|
|
|
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",
|