@coframe/sdk 1.1.23 → 1.1.24

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 CHANGED
@@ -136,6 +136,148 @@ const projectData = serverData?.project
136
136
 
137
137
  Pass `projectData` into `<CoframeProvider>` to avoid the extra client-side project fetch and personalize on first paint.
138
138
 
139
+ ## Feature flags and remote config
140
+
141
+ Browser Feature Flags use the existing public `projectId` and receive only
142
+ flags marked **Public**. `selector` is optional; use `names` for
143
+ individual flag keys and `groups` to evaluate every flag assigned to a page or
144
+ business area.
145
+
146
+ ```ts
147
+ import { setUserToken } from '@coframe/sdk'
148
+ import {
149
+ featureFlags,
150
+ getFeatureFlag,
151
+ getFeatureFlagPayload,
152
+ isFeatureEnabled,
153
+ onFeatureFlags,
154
+ } from '@coframe/sdk/feature-flags'
155
+
156
+ setUserToken('customer-123')
157
+
158
+ await featureFlags.init({
159
+ projectId: 'your-project-id',
160
+ selector: {
161
+ names: ['checkout-redesign'],
162
+ groups: ['homepage_campaigns'],
163
+ },
164
+ })
165
+
166
+ onFeatureFlags(() => {
167
+ if (isFeatureEnabled('checkout-redesign')) {
168
+ showNewCheckout()
169
+ }
170
+
171
+ const content = getFeatureFlagPayload('homepage-content')
172
+ const checkoutVariant = getFeatureFlag('checkout-layout')
173
+ })
174
+ ```
175
+
176
+ Feature Flags are a separate browser entry point and do not increase or initialize
177
+ the main SDK bundle. When both clients are used, Feature Flags reuse the latest
178
+ `setUserToken()` command from Coframe's shared command queue and follow later
179
+ identity changes automatically.
180
+
181
+ Loading does not delay Coframe page modification. Synchronous getters return
182
+ `undefined` until a cached or fresh evaluation is available. The browser client
183
+ always keeps the latest successful response in memory. Before consent, no
184
+ Feature Flag result or evaluation context is persisted. After consent, the
185
+ client also caches the response in `localStorage` for five minutes and keys it
186
+ with an opaque SHA-256 fingerprint derived from the user token and evaluation
187
+ context. It can fall back to that persistent cache if Edge is temporarily
188
+ unavailable. Call `reloadFeatureFlags()` to request fresh evaluations
189
+ explicitly.
190
+
191
+ Payloads support any JSON value up to 32 KB. A payload is returned only when its
192
+ flag is enabled. Remote Config uses the same transport and payload API and
193
+ returns the same payload to all users while active:
194
+
195
+ ```ts
196
+ import { getRemoteConfig } from '@coframe/sdk/feature-flags'
197
+
198
+ const navigation = getRemoteConfig('navigation-config')
199
+ ```
200
+
201
+ Multivariate flags return a stable variant key from `getFeatureFlag()`. Their
202
+ weights divide users into deterministic buckets, and each variant can carry an
203
+ independent optional payload. `isFeatureEnabled()` remains a boolean
204
+ convenience and returns `true` whenever a variant is assigned:
205
+
206
+ ```ts
207
+ import {
208
+ getFeatureFlag,
209
+ getFeatureFlagPayload,
210
+ isFeatureEnabled,
211
+ } from '@coframe/sdk/feature-flags'
212
+
213
+ const variant = getFeatureFlag('checkout-layout')
214
+ const config = getFeatureFlagPayload('checkout-layout')
215
+ const enabled = isFeatureEnabled('checkout-layout')
216
+ ```
217
+
218
+ React apps can read evaluated values from the existing provider:
219
+
220
+ ```tsx
221
+ import {
222
+ useFeatureFlag,
223
+ useFeatureFlagEnabled,
224
+ useFeatureFlagPayload,
225
+ useFeatureFlagVariantKey,
226
+ } from '@coframe/sdk/react'
227
+
228
+ function Checkout() {
229
+ const enabled = useFeatureFlag('checkout-redesign')
230
+ const payload = useFeatureFlagPayload('checkout-redesign')
231
+
232
+ if (enabled === undefined) return <CheckoutSkeleton />
233
+ return enabled ? <NewCheckout config={payload} /> : <OldCheckout />
234
+ }
235
+
236
+ function CheckoutLayout() {
237
+ const variant = useFeatureFlagVariantKey('checkout-layout')
238
+ const enabled = useFeatureFlagEnabled('checkout-layout')
239
+ const payload = useFeatureFlagPayload('checkout-layout')
240
+ return <Checkout variant={variant} enabled={enabled} config={payload} />
241
+ }
242
+ ```
243
+
244
+ For server and edge runtimes, use a secret Feature Flags Server API key. It is
245
+ project-scoped, can read **Private** flags, and must never be included in a
246
+ browser bundle or a `NEXT_PUBLIC_*` environment variable. Each single-key
247
+ helper sends a key-filtered request and fails closed unless a `defaultValue` is
248
+ supplied. Browser and server helpers both call `/api/v1/flags`; server helpers
249
+ include the API key as a Bearer credential so the response may include both
250
+ **Public** and **Private** flags:
251
+
252
+ ```ts
253
+ import {
254
+ evaluateFlags,
255
+ getFeatureFlag,
256
+ getRemoteConfig,
257
+ isFeatureEnabled,
258
+ } from '@coframe/sdk/server'
259
+
260
+ const options = {
261
+ projectId: 'your-project-id',
262
+ apiKey: process.env.COFRAME_FEATURE_FLAGS_SERVER_KEY!,
263
+ userToken: user.id,
264
+ currentUrl: request.url,
265
+ userContext: { plan: user.plan },
266
+ }
267
+
268
+ const checkoutFlags = await evaluateFlags({
269
+ ...options,
270
+ selector: { groups: ['cart_checkout'] },
271
+ })
272
+
273
+ const enabled = await isFeatureEnabled('checkout-redesign', {
274
+ ...options,
275
+ defaultValue: false,
276
+ })
277
+ const variant = await getFeatureFlag('checkout-layout', options)
278
+ const content = await getRemoteConfig('homepage-content', options)
279
+ ```
280
+
139
281
  ## Common React usage
140
282
 
141
283
  ### Read variants in components
@@ -259,9 +401,21 @@ Modern resolution modes handle package subpath exports more reliably.
259
401
  - `onTreatmentApplied(...)`
260
402
  - `onLog(...)`
261
403
 
404
+ ### `@coframe/sdk/feature-flags`
405
+
406
+ - `featureFlags.init(options)`
407
+ - `featureFlags.setUserToken(userToken)` / `featureFlags.reset()`
408
+ - `featureFlags.getUserToken()`
409
+ - `getFeatureFlag(key)` / `isFeatureEnabled(key)`
410
+ - `getFeatureFlagPayload(key)` / `getRemoteConfig(key)`
411
+ - `onFeatureFlags(callback)` / `reloadFeatureFlags()`
412
+
262
413
  ### `@coframe/sdk/server`
263
414
 
264
415
  - `getProjectData(options)`
416
+ - `evaluateFlags(options)`
417
+ - `getFeatureFlag(key, options)` / `isFeatureEnabled(key, options)`
418
+ - `getFeatureFlagPayload(key, options)` / `getRemoteConfig(key, options)`
265
419
 
266
420
  Returns a payload that may include:
267
421
 
@@ -275,3 +429,6 @@ Returns a payload that may include:
275
429
  - `useCoframeTreatments()`
276
430
  - `useCoframeTreatment(experimentId)`
277
431
  - `useCoframeVariant(experimentId)`
432
+ - `useFeatureFlag(key)` / `useFeatureFlagVariantKey(key)`
433
+ - `useFeatureFlagEnabled(key)`
434
+ - `useFeatureFlagResult(key)` / `useFeatureFlagPayload(key)`
@@ -2277,7 +2277,7 @@ function isElementVisible(el) {
2277
2277
  return rect.top >= 0 && rect.bottom <= window.innerHeight;
2278
2278
  }
2279
2279
  const VALUE_UNKNOWN_YET = 'n/a';
2280
- function checkActivationCondition(condition, personalizationHeaders, currentUrl, languages, browserPlatform, userToken, userVisitCount, runJsFunction, logger) {
2280
+ function checkActivationCondition(condition, personalizationHeaders, currentUrl, languages, browserPlatform, userToken, userVisitCount, runJsFunction, logger, options) {
2281
2281
  if (condition.and) {
2282
2282
  const results = condition.and.map((c) => checkActivationCondition(c, personalizationHeaders, currentUrl, languages, browserPlatform, userToken, userVisitCount, runJsFunction, logger));
2283
2283
  const hasUnknown = results.some((r) => r === VALUE_UNKNOWN_YET);
@@ -2275,7 +2275,7 @@ function isElementVisible(el) {
2275
2275
  return rect.top >= 0 && rect.bottom <= window.innerHeight;
2276
2276
  }
2277
2277
  const VALUE_UNKNOWN_YET = 'n/a';
2278
- function checkActivationCondition(condition, personalizationHeaders, currentUrl, languages, browserPlatform, userToken, userVisitCount, runJsFunction, logger) {
2278
+ function checkActivationCondition(condition, personalizationHeaders, currentUrl, languages, browserPlatform, userToken, userVisitCount, runJsFunction, logger, options) {
2279
2279
  if (condition.and) {
2280
2280
  const results = condition.and.map((c) => checkActivationCondition(c, personalizationHeaders, currentUrl, languages, browserPlatform, userToken, userVisitCount, runJsFunction, logger));
2281
2281
  const hasUnknown = results.some((r) => r === VALUE_UNKNOWN_YET);