This is page 1 of 7. Use http://codebase.md/bucketco/bucket-javascript-sdk?lines=false&page={x} to view the full context.
# Directory Structure
```
├── .editorconfig
├── .gitattributes
├── .github
│ └── workflows
│ ├── package-ci.yml
│ └── publish.yml
├── .gitignore
├── .nvmrc
├── .prettierignore
├── .vscode
│ ├── extensions.json
│ └── settings.json
├── .yarnrc.yml
├── docs.sh
├── lerna.json
├── LICENSE
├── package.json
├── packages
│ ├── browser-sdk
│ │ ├── .prettierignore
│ │ ├── eslint.config.js
│ │ ├── example
│ │ │ ├── feedback
│ │ │ │ ├── feedback.html
│ │ │ │ └── Feedback.jsx
│ │ │ └── typescript
│ │ │ ├── app.ts
│ │ │ └── index.html
│ │ ├── FEEDBACK.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── playwright.config.ts
│ │ ├── postcss.config.js
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── client.ts
│ │ │ ├── config.ts
│ │ │ ├── context.ts
│ │ │ ├── feedback
│ │ │ │ ├── feedback.ts
│ │ │ │ ├── prompts.ts
│ │ │ │ ├── promptStorage.ts
│ │ │ │ └── ui
│ │ │ │ ├── Button.css
│ │ │ │ ├── Button.tsx
│ │ │ │ ├── config
│ │ │ │ │ └── defaultTranslations.tsx
│ │ │ │ ├── css.d.ts
│ │ │ │ ├── FeedbackDialog.css
│ │ │ │ ├── FeedbackDialog.tsx
│ │ │ │ ├── FeedbackForm.css
│ │ │ │ ├── FeedbackForm.tsx
│ │ │ │ ├── hooks
│ │ │ │ │ └── useTimer.ts
│ │ │ │ ├── index.css
│ │ │ │ ├── index.ts
│ │ │ │ ├── Plug.tsx
│ │ │ │ ├── RadialProgress.css
│ │ │ │ ├── RadialProgress.tsx
│ │ │ │ ├── StarRating.css
│ │ │ │ ├── StarRating.tsx
│ │ │ │ └── types.ts
│ │ │ ├── flag
│ │ │ │ ├── flagCache.ts
│ │ │ │ └── flags.ts
│ │ │ ├── hooksManager.ts
│ │ │ ├── httpClient.ts
│ │ │ ├── index.ts
│ │ │ ├── logger.ts
│ │ │ ├── rateLimiter.ts
│ │ │ ├── sse.ts
│ │ │ ├── toolbar
│ │ │ │ ├── Flags.css
│ │ │ │ ├── Flags.tsx
│ │ │ │ ├── index.css
│ │ │ │ ├── index.ts
│ │ │ │ ├── Switch.css
│ │ │ │ ├── Switch.tsx
│ │ │ │ ├── Toolbar.css
│ │ │ │ └── Toolbar.tsx
│ │ │ └── ui
│ │ │ ├── constants.ts
│ │ │ ├── Dialog.css
│ │ │ ├── Dialog.tsx
│ │ │ ├── icons
│ │ │ │ ├── Check.tsx
│ │ │ │ ├── CheckCircle.tsx
│ │ │ │ ├── Close.tsx
│ │ │ │ ├── Dissatisfied.tsx
│ │ │ │ ├── Logo.tsx
│ │ │ │ ├── Neutral.tsx
│ │ │ │ ├── Satisfied.tsx
│ │ │ │ ├── VeryDissatisfied.tsx
│ │ │ │ └── VerySatisfied.tsx
│ │ │ ├── packages
│ │ │ │ └── floating-ui-preact-dom
│ │ │ │ ├── arrow.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── README.md
│ │ │ │ ├── types.ts
│ │ │ │ ├── useFloating.ts
│ │ │ │ └── utils
│ │ │ │ ├── deepEqual.ts
│ │ │ │ ├── getDPR.ts
│ │ │ │ ├── roundByDPR.ts
│ │ │ │ └── useLatestRef.ts
│ │ │ ├── types.ts
│ │ │ └── utils.ts
│ │ ├── test
│ │ │ ├── client.test.ts
│ │ │ ├── e2e
│ │ │ │ ├── acceptance.browser.spec.ts
│ │ │ │ ├── empty.html
│ │ │ │ ├── feedback-widget.browser.spec.ts
│ │ │ │ └── give-feedback-button.html
│ │ │ ├── flagCache.test.ts
│ │ │ ├── flags.test.ts
│ │ │ ├── hooksManager.test.ts
│ │ │ ├── httpClient.test.ts
│ │ │ ├── init.test.ts
│ │ │ ├── mocks
│ │ │ │ ├── handlers.ts
│ │ │ │ └── server.ts
│ │ │ ├── prompts.test.ts
│ │ │ ├── promptStorage.test.ts
│ │ │ ├── rateLimiter.test.ts
│ │ │ ├── sse.test.ts
│ │ │ ├── testLogger.ts
│ │ │ └── usage.test.ts
│ │ ├── tsconfig.build.json
│ │ ├── tsconfig.eslint.json
│ │ ├── tsconfig.json
│ │ ├── typedoc.json
│ │ ├── vite.config.mjs
│ │ ├── vite.e2e.config.js
│ │ └── vitest.setup.ts
│ ├── cli
│ │ ├── .prettierignore
│ │ ├── commands
│ │ │ ├── apps.ts
│ │ │ ├── auth.ts
│ │ │ ├── flags.ts
│ │ │ ├── init.ts
│ │ │ ├── mcp.ts
│ │ │ ├── new.ts
│ │ │ └── rules.ts
│ │ ├── eslint.config.js
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── schema.json
│ │ ├── services
│ │ │ ├── bootstrap.ts
│ │ │ ├── flags.ts
│ │ │ ├── mcp.ts
│ │ │ └── rules.ts
│ │ ├── stores
│ │ │ ├── auth.ts
│ │ │ └── config.ts
│ │ ├── test
│ │ │ └── json.test.ts
│ │ ├── tsconfig.eslint.json
│ │ ├── tsconfig.json
│ │ ├── utils
│ │ │ ├── auth.ts
│ │ │ ├── commander.ts
│ │ │ ├── constants.ts
│ │ │ ├── errors.ts
│ │ │ ├── file.ts
│ │ │ ├── gen.ts
│ │ │ ├── json.ts
│ │ │ ├── options.ts
│ │ │ ├── schemas.ts
│ │ │ ├── types.ts
│ │ │ ├── urls.ts
│ │ │ └── version.ts
│ │ └── vite.config.js
│ ├── eslint-config
│ │ ├── base.js
│ │ └── package.json
│ ├── flag-evaluation
│ │ ├── .prettierignore
│ │ ├── eslint.config.js
│ │ ├── jest.config.js
│ │ ├── package.json
│ │ ├── src
│ │ │ └── index.ts
│ │ ├── test
│ │ │ └── index.test.ts
│ │ ├── tsconfig.build.json
│ │ ├── tsconfig.eslint.json
│ │ └── tsconfig.json
│ ├── node-sdk
│ │ ├── .prettierignore
│ │ ├── docs
│ │ │ ├── type-check-failed.png
│ │ │ └── type-check-payload-failed.png
│ │ ├── eslint.config.js
│ │ ├── examples
│ │ │ ├── cloudflare-worker
│ │ │ │ ├── .gitignore
│ │ │ │ ├── .prettierignore
│ │ │ │ ├── .vscode
│ │ │ │ │ └── settings.json
│ │ │ │ ├── package.json
│ │ │ │ ├── README.md
│ │ │ │ ├── src
│ │ │ │ │ └── index.ts
│ │ │ │ ├── tsconfig.json
│ │ │ │ ├── vitest.config.mts
│ │ │ │ ├── worker-configuration.d.ts
│ │ │ │ ├── wrangler.jsonc
│ │ │ │ └── yarn.lock
│ │ │ └── express
│ │ │ ├── app.test.ts
│ │ │ ├── app.ts
│ │ │ ├── bucket.ts
│ │ │ ├── bucketConfig.json
│ │ │ ├── package.json
│ │ │ ├── README.md
│ │ │ ├── serve.ts
│ │ │ ├── tsconfig.json
│ │ │ └── yarn.lock
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── batch-buffer.ts
│ │ │ ├── client.ts
│ │ │ ├── config.ts
│ │ │ ├── edgeClient.ts
│ │ │ ├── fetch-http-client.ts
│ │ │ ├── flusher.ts
│ │ │ ├── index.ts
│ │ │ ├── inRequestCache.ts
│ │ │ ├── periodicallyUpdatingCache.ts
│ │ │ ├── rate-limiter.ts
│ │ │ ├── types.ts
│ │ │ └── utils.ts
│ │ ├── test
│ │ │ ├── batch-buffer.test.ts
│ │ │ ├── client.test.ts
│ │ │ ├── config.test.ts
│ │ │ ├── fetch-http-client.test.ts
│ │ │ ├── flusher.test.ts
│ │ │ ├── inRequestCache.test.ts
│ │ │ ├── periodicallyUpdatingCache.test.ts
│ │ │ ├── rate-limiter.test.ts
│ │ │ ├── testConfig.json
│ │ │ └── utils.test.ts
│ │ ├── tsconfig.build.json
│ │ ├── tsconfig.eslint.json
│ │ ├── tsconfig.json
│ │ ├── typedoc.json
│ │ └── vite.config.js
│ ├── openfeature-browser-provider
│ │ ├── .prettierignore
│ │ ├── eslint.config.js
│ │ ├── example
│ │ │ ├── .eslintrc.json
│ │ │ ├── .gitignore
│ │ │ ├── app
│ │ │ │ ├── featureManagement.ts
│ │ │ │ ├── globals.css
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.tsx
│ │ │ ├── components
│ │ │ │ ├── Context.tsx
│ │ │ │ ├── HuddleFeature.tsx
│ │ │ │ └── OpenFeatureProvider.tsx
│ │ │ ├── next.config.mjs
│ │ │ ├── package.json
│ │ │ ├── postcss.config.mjs
│ │ │ ├── README.md
│ │ │ ├── tailwind.config.ts
│ │ │ └── tsconfig.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── index.test.ts
│ │ │ └── index.ts
│ │ ├── tsconfig.build.json
│ │ ├── tsconfig.eslint.json
│ │ ├── tsconfig.json
│ │ └── vite.config.js
│ ├── openfeature-node-provider
│ │ ├── .prettierignore
│ │ ├── eslint.config.js
│ │ ├── example
│ │ │ ├── app.ts
│ │ │ ├── package.json
│ │ │ ├── README.md
│ │ │ ├── reflag.ts
│ │ │ ├── serve.ts
│ │ │ ├── tsconfig.json
│ │ │ └── yarn.lock
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── index.test.ts
│ │ │ └── index.ts
│ │ ├── tsconfig.build.json
│ │ ├── tsconfig.eslint.json
│ │ ├── tsconfig.json
│ │ └── vite.config.js
│ ├── react-sdk
│ │ ├── .prettierignore
│ │ ├── dev
│ │ │ ├── .env
│ │ │ ├── nextjs-bootstrap-demo
│ │ │ │ ├── .eslintrc.json
│ │ │ │ ├── .gitignore
│ │ │ │ ├── app
│ │ │ │ │ ├── client.ts
│ │ │ │ │ ├── favicon.ico
│ │ │ │ │ ├── globals.css
│ │ │ │ │ ├── layout.tsx
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── components
│ │ │ │ │ └── Flags.tsx
│ │ │ │ ├── next.config.mjs
│ │ │ │ ├── package.json
│ │ │ │ ├── postcss.config.mjs
│ │ │ │ ├── public
│ │ │ │ │ ├── next.svg
│ │ │ │ │ └── vercel.svg
│ │ │ │ ├── README.md
│ │ │ │ ├── tailwind.config.ts
│ │ │ │ └── tsconfig.json
│ │ │ ├── nextjs-flag-demo
│ │ │ │ ├── .eslintrc.json
│ │ │ │ ├── .gitignore
│ │ │ │ ├── app
│ │ │ │ │ ├── favicon.ico
│ │ │ │ │ ├── globals.css
│ │ │ │ │ ├── layout.tsx
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── components
│ │ │ │ │ ├── Flags.tsx
│ │ │ │ │ └── Providers.tsx
│ │ │ │ ├── next.config.mjs
│ │ │ │ ├── package.json
│ │ │ │ ├── postcss.config.mjs
│ │ │ │ ├── public
│ │ │ │ │ ├── next.svg
│ │ │ │ │ └── vercel.svg
│ │ │ │ ├── README.md
│ │ │ │ ├── tailwind.config.ts
│ │ │ │ └── tsconfig.json
│ │ │ └── plain
│ │ │ ├── app.tsx
│ │ │ ├── index.html
│ │ │ ├── index.tsx
│ │ │ ├── tsconfig.json
│ │ │ └── vite-env.d.ts
│ │ ├── eslint.config.js
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ └── index.tsx
│ │ ├── test
│ │ │ └── usage.test.tsx
│ │ ├── tsconfig.build.json
│ │ ├── tsconfig.eslint.json
│ │ ├── tsconfig.json
│ │ ├── typedoc.json
│ │ └── vite.config.mjs
│ ├── tsconfig
│ │ ├── library.json
│ │ └── package.json
│ └── vue-sdk
│ ├── .prettierignore
│ ├── dev
│ │ └── plain
│ │ ├── App.vue
│ │ ├── components
│ │ │ ├── Events.vue
│ │ │ ├── FlagsList.vue
│ │ │ ├── MissingKeyMessage.vue
│ │ │ ├── RequestFeedback.vue
│ │ │ ├── Section.vue
│ │ │ ├── StartHuddlesButton.vue
│ │ │ └── Track.vue
│ │ ├── env.d.ts
│ │ ├── index.html
│ │ └── index.ts
│ ├── eslint.config.js
│ ├── package.json
│ ├── README.md
│ ├── src
│ │ ├── hooks.ts
│ │ ├── index.ts
│ │ ├── ReflagBootstrappedProvider.vue
│ │ ├── ReflagClientProvider.vue
│ │ ├── ReflagProvider.vue
│ │ ├── types.ts
│ │ ├── version.ts
│ │ └── vue.d.ts
│ ├── test
│ │ └── usage.test.ts
│ ├── tsconfig.build.json
│ ├── tsconfig.eslint.json
│ ├── tsconfig.json
│ ├── typedoc.json
│ └── vite.config.mjs
├── README.md
├── typedoc.json
├── vitest.workspace.js
└── yarn.lock
```
# Files
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
```
20.15.1
```
--------------------------------------------------------------------------------
/packages/flag-evaluation/.prettierignore:
--------------------------------------------------------------------------------
```
dist
coverage
```
--------------------------------------------------------------------------------
/packages/node-sdk/.prettierignore:
--------------------------------------------------------------------------------
```
dist
eslint-report.json
```
--------------------------------------------------------------------------------
/packages/openfeature-node-provider/.prettierignore:
--------------------------------------------------------------------------------
```
dist
eslint-report.json
```
--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
```yaml
nodeLinker: node-modules
```
--------------------------------------------------------------------------------
/packages/node-sdk/examples/cloudflare-worker/.prettierignore:
--------------------------------------------------------------------------------
```
worker-configuration.d.ts
```
--------------------------------------------------------------------------------
/packages/cli/.prettierignore:
--------------------------------------------------------------------------------
```
dist
eslint-report.json
gen
```
--------------------------------------------------------------------------------
/packages/react-sdk/.prettierignore:
--------------------------------------------------------------------------------
```
dist
eslint-report.json
dev
```
--------------------------------------------------------------------------------
/packages/vue-sdk/.prettierignore:
--------------------------------------------------------------------------------
```
dist
eslint-report.json
dev
```
--------------------------------------------------------------------------------
/packages/browser-sdk/.prettierignore:
--------------------------------------------------------------------------------
```
dist
eslint-report.json
test-results
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/.env:
--------------------------------------------------------------------------------
```
PUBLISHABLE_KEY=your_publishable_key
```
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
```
**/build
**/gen
**/node_modules
**/dist
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/example/.eslintrc.json:
--------------------------------------------------------------------------------
```json
{
"extends": "next/core-web-vitals"
}
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-bootstrap-demo/.eslintrc.json:
--------------------------------------------------------------------------------
```json
{
"extends": "next/core-web-vitals"
}
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-flag-demo/.eslintrc.json:
--------------------------------------------------------------------------------
```json
{
"extends": "next/core-web-vitals"
}
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/.prettierignore:
--------------------------------------------------------------------------------
```
dist
eslint-report.json
test-results
.next
```
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
```
/.yarn/** linguist-vendored
/.yarn/releases/* binary
/.yarn/plugins/**/* binary
/.pnp.* binary linguist-generated
```
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
```
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
[*.{js,ts,jsx,tsx,html,css,json,yml,md}]
charset = utf-8
indent_style = space
indent_size = 2
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/example/.gitignore:
--------------------------------------------------------------------------------
```
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-bootstrap-demo/.gitignore:
--------------------------------------------------------------------------------
```
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-flag-demo/.gitignore:
--------------------------------------------------------------------------------
```
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
coverage
node_modules
gen
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
!.vscode/settings.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
test-results/
playwright-report/
playwright/.cache/
.pnp.*
.yarn
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
junit.xml
.next
eslint-report.json
reflag.config.json
```
--------------------------------------------------------------------------------
/packages/node-sdk/examples/cloudflare-worker/.gitignore:
--------------------------------------------------------------------------------
```
# Logs
logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
\*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
\*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
\*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
\*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.\*
# wrangler project
.dev.vars
.wrangler/
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/ui/packages/floating-ui-preact-dom/README.md:
--------------------------------------------------------------------------------
```markdown
# @floating-ui-preact-dom
This contains a modified version of https://github.com/floating-ui/floating-ui/tree/master/packages/react-dom to support Preact.
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-bootstrap-demo/README.md:
--------------------------------------------------------------------------------
```markdown
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
The purpose of this project is to demonstrate usage integration with the Reflag React SDK.
## Getting Started
Run the development server:
```bash
yarn dev
```
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-flag-demo/README.md:
--------------------------------------------------------------------------------
```markdown
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
The purpose of this project is to demonstrate usage integration with the Reflag React SDK.
## Getting Started
Run the development server:
```bash
yarn dev
```
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/example/README.md:
--------------------------------------------------------------------------------
```markdown
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
The purpose of this project is to demonstrate usage integration with the Reflag OpenFeature React SDK.
## Getting Started
Run the development server:
```bash
NEXT_PUBLIC_REFLAG_PUBLISHABLE_KEY=<publishable-key> yarn dev
```
```
--------------------------------------------------------------------------------
/packages/node-sdk/examples/cloudflare-worker/README.md:
--------------------------------------------------------------------------------
```markdown
# cloudflare-worker
This is a simple example of how to use the Reflag SDK in a Cloudflare Worker.
It demonstrates how to initialize the client and evaluate flags.
It also shows how to flush the client and wait for any in-flight requests to complete.
- Set the REFLAG_SECRET_KEY environment variable in wrangler.jsonc to get started.
- Run `yarn dev` in your terminal to start a development server
- Open a browser tab at http://localhost:8787/ to see your worker in action
- Run `yarn run deploy` to publish your worker
```
--------------------------------------------------------------------------------
/packages/node-sdk/examples/express/README.md:
--------------------------------------------------------------------------------
```markdown
# Reflag Node-SDK Express example
This directory contains a simple example of how to use Reflag's `node-sdk` with
`Express` framework. The example code sets up a Reflag SDK client, starts a
simple REST API service, and uses a set of predefined features to control
a user's access to the API.
The Reflag SDK client is initialized before the API is started and then, instances
of the client are bound to each individual user's request, to allow for fetching
the relevant features for each request.
To get started, create an app on [Reflag](https://reflag.com) and take a note of the
secret key which is found under _"Settings"_ -> _"Environments"_.
## Running
The following code snippet should be enough to demonstrate the functionality
of the SDK:
```sh
yarn install
REFLAG_SECRET_KEY=<secretKey> yarn start
```
```
--------------------------------------------------------------------------------
/packages/openfeature-node-provider/example/README.md:
--------------------------------------------------------------------------------
```markdown
# Reflag Node OpenFeature provider express example
This directory contains a simple example of how to use Reflag's `node-sdk`
with and the OpenFeature Reflag Node Provider with the `Express` framework.
The example code sets up a Reflag Node Provider, starts a
simple REST API service, and uses a set of predefined features to control
a user's access to the API.
The provider is initialized before the API is started and then, instances
of the client are bound to each individual user's request, to allow for fetching
the relevant features for each request.
To get started, create an app on [Reflag](https://reflag.com) and take a note of the
secret key which is found under _"Settings"_ -> _"Environments"_.
## Context
See [defaultTranslator](https://github.com/reflagcom/javascript/blob/7d108db2d1bde6e40d9eda92b66d06a1fbb7fa3f/packages/openfeature-node-provider/src/index.ts#L23-L45) for how OpenFeature context is translated into Reflag context
by default
## Running
The following code snippet should be enough to demonstrate the functionality
of the SDK:
```sh
yarn install
REFLAG_SECRET_KEY=<secretKey> yarn start
```
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# Reflag
Feature flags for SaaS that run on TypeScript. [Learn more and get started](https://reflag.com/)
## React SDK
Client side React SDK
[Read the docs](packages/react-sdk/README.md)
## Vue SDK (beta)
Client side Vue SDK
[Read the docs](packages/vue-sdk/README.md)
## Browser SDK
Browser SDK for use in non-React web applications
[Read the docs](packages/browser-sdk/README.md)
## Node.js SDK
Node.js SDK for use on the server side.
Use this for Cloudflare Workers as well.
[Read the docs](packages/node-sdk/README.md)
## Reflag CLI
CLI to interact with Reflag and generate types
[Read the docs](packages/cli/README.md)
## OpenFeature Browser Provider
Use Reflag with OpenFeature in the browser through the Reflag OpenFeature Browser Provider
[Read the docs](packages/openfeature-browser-provider/README.md)
## OpenFeature Node.js Provider
Use the Reflag with OpenFeature on the server in Node.js through the Reflag OpenFeature Node.js Provider
[Read the docs](packages/openfeature-node-provider/README.md)
## Development
### Versioning
1. Create a new branch locally
2. Run `yarn run version`
3. Push and PR
### Publishing
The [Github Action](.github/workflows/publish.yml) will automatically publish any versioned packages when merging to `main`
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/README.md:
--------------------------------------------------------------------------------
```markdown
# Reflag Browser OpenFeature Provider
The official OpenFeature Browser provider for [Reflag.com](https://reflag.com) flag management service.
It uses the Reflag Browser SDK internally and thus allow you to collect [automated feedback surveys](https://github.com/reflagcom/javascript/tree/main/packages/browser-sdk#qualitative-feedback)
when people use your flag as well as tracking which customers use which features.
If you're using React, you'll be better off with the [Reflag React SDK](https://github.com/reflagcom/javascript/blob/main/packages/react-sdk/README.md) or the [OpenFeature React SDK](https://openfeature.dev/docs/reference/technologies/client/web/react/).
See the `example` folder for how to use the OpenFeature React SDK with Next.js.
## Installation
The OpenFeature SDK is required as peer dependency.
The minimum required version of `@openfeature/web-sdk` currently is `1.0`.
```shell
npm install @openfeature/web-sdk @reflag/openfeature-browser-provider
```
## Migrating from Bucket OpenFeature SDK
If you have been using the Bucket SDKs, the following list will help you migrate to Reflag SDK:
- `Bucket*` classes, and types have been renamed to `Reflag*` (e.g. `BucketClient` is now `ReflagClient`)
- The `fallbackFeatures` property in client constructor and configuration files has been renamed to `fallbackFlags`
- `featureKey` has been renamed to `flagKey` in all methods that accepts that argument
- The SDKs will not emit `evaluate` and `evaluate-config` events anymore
- The new cookies that are stored in the client's browser are now `reflag-*` prefixed instead og `bucket-*`
If you are running with strict Content Security Policies active on your website, you will need change them as follows:
- `connect-src https://front.bucket.co` to `connect-src https://front.reflag.com`
Finally, if you have customized the look & feel of the Feedback component, update `--bucket-feedback-*` CSS classes to `--reflag-feedback-*`
## Sample initialization
```ts
import { ReflagBrowserProvider } from "@reflag/openfeature-browser-provider";
import { OpenFeature } from "@openfeature/web-sdk";
// initialize provider
const publishableKey = "<your-reflag-publishable-key>";
const reflagProvider = new ReflagBrowserProvider({ publishableKey });
// set open feature provider and get client
await OpenFeature.setProviderAndWait(reflagProvider);
const client = OpenFeature.getClient();
// use client
const boolValue = client.getBooleanValue("huddles", false);
// use more complex, dynamic config-enabled functionality.
const feedbackConfig = client.getObjectValue("ask-feedback", {
question: "How are you enjoying this feature?",
});
```
Initializing the Reflag Browser Provider will
also initialize [automatic feedback surveys](https://github.com/reflagcom/javascript/tree/main/packages/browser-sdk#qualitative-feedback).
## Feature resolution methods
The Reflag OpenFeature Provider implements the OpenFeature evaluation interface for different value types. Each method handles the resolution of flags according to the OpenFeature specification.
### Common behavior
All resolution methods share these behaviors:
- Return default value with `PROVIDER_NOT_READY` if client is not initialized,
- Return default value with `FLAG_NOT_FOUND` if flag doesn't exist,
- Return default value with `ERROR` if there was a type mismatch,
- Return evaluated value with `TARGETING_MATCH` on successful resolution.
### Type-Specific Methods
#### Boolean Resolution
```ts
client.getBooleanValue("my-flag", false);
```
Returns the flag's enabled state. This is the most common use case for flags.
#### String Resolution
```ts
client.getStringValue("my-flag", "default");
```
Returns the flag's remote config key (also known as "variant"). Useful for multi-variate use cases.
#### Number Resolution
```ts
client.getNumberValue("my-flag", 0);
```
Not directly supported by Reflag. Use `getObjectValue` instead for numeric configurations.
#### Object Resolution
```ts
// works for any type:
client.getObjectValue("my-flag", { defaultValue: true });
client.getObjectValue("my-flag", "string-value");
client.getObjectValue("my-flag", 199);
```
Returns the flag's remote config payload with type validation. This is the most flexible method,
allowing for complex configuration objects or simple types.
The object resolution performs runtime type checking between the default value and the flag payload to ensure type safety.
## Context
To convert the OpenFeature context to a Reflag appropriate context
pass a translation function along to the `ReflagBrowserProvider` constructor
like so:
```ts
import { ReflagBrowserProvider } from "@reflag/openfeature-browser-provider";
import { EvaluationContext, OpenFeature } from "@openfeature/web-sdk";
// initialize provider
const publishableKey = "<your-reflag-publishable-key>";
// this converts the context to a Reflag compatible context
// adapt it to fit your need
const contextTranslator = (context?: EvaluationContext) => {
return {
user: {
id: context.targetingKey ?? context["userId"],
email: context["email"]?.toString(),
name: context["name"]?.toString(),
avatar: context["avatar"]?.toString(),
country: context["country"]?.toString(),
},
company: {
id: context["companyId"],
name: context["companyName"]?.toString(),
avatar: context["companyAvatar"]?.toString(),
plan: context["companyPlan"]?.toString(),
},
};
};
const reflagOpenFeatureProvider = new ReflagBrowserProvider({
publishableKey,
contextTranslator,
});
```
To update the context, call `OpenFeature.setContext(myNewContext);`
```ts
await OpenFeature.setContext({ userId: "my-key" });
```
## Tracking flag usage
The Reflag OpenFeature Provider supports the OpenFeature tracking API
natively.
```ts
import { ReflagBrowserProvider } from "@reflag/openfeature-browser-provider";
import { OpenFeature } from "@openfeature/web-sdk";
// initialize provider
const publishableKey = "<your-reflag-publishable-key>";
const reflagProvider = new ReflagBrowserProvider({ publishableKey });
// set OpenFeature provider and get client
await OpenFeature.setProviderAndWait(reflagProvider);
const client = OpenFeature.getClient();
// use client to send an event when user uses a flag
client.track("huddles");
```
## License
> MIT License
> Copyright (c) 2025 Bucket ApS
```
--------------------------------------------------------------------------------
/packages/openfeature-node-provider/README.md:
--------------------------------------------------------------------------------
```markdown
# Reflag Node.js OpenFeature Provider
The official OpenFeature Node.js provider for [Reflag](https://reflag.com) feature management service.
## Installation
```shell
npm install @reflag/openfeature-node-provider
```
### Required peer dependencies
The OpenFeature SDK is required as peer dependency.
The minimum required version of `@openfeature/server-sdk` currently is `1.13.5`.
The minimum required version of `@reflag/node-sdk` currently is `2.0.0`.
```shell
npm install @openfeature/server-sdk @reflag/node-sdk
```
## Migrating from Bucket OpenFeature SDK
If you have been using the Bucket SDKs, the following list will help you migrate to Reflag SDK:
- `Bucket*` classes, and types have been renamed to `Reflag*` (e.g. `BucketClient` is now `ReflagClient`)
- All environment variables that were prefixed with `BUCKET_` are now prefixed with `REFLAG_`
- The `BUCKET_HOST` environment variable and `host` option have been removed from `ReflagClient` constructor, use `REFLAG_API_BASE_URL` instead
- The `BUCKET_FEATURES_ENABLED` and `BUCKET_FEATURES_DISABLED` have been renamed to `REFLAG_FLAGS_ENABLED` and `REFLAG_FLAGS_DISABLED`
- The default configuration file has been renamed from `bucketConfig.json` to `reflag.config.json`
- The `fallbackFeatures` property in client constructor and configuration files has been renamed to `fallbackFlags`
- `featureKey` has been renamed to `flagKey` in all methods that accepts that argument
- The SDKs will not emit `evaluate` and `evaluate-config` events anymore
## Usage
The provider uses the [Reflag Node.js SDK](https://docs.reflag.com/quickstart/supported-languages-frameworks/node.js-sdk).
The available options can be found in the [Reflag Node.js SDK](https://github.com/reflagcom/javascript/tree/main/packages/node-sdk#initialization-options).
### Example using the default configuration
```typescript
import { ReflagNodeProvider } from "@reflag/openfeature-node-provider";
import { OpenFeature } from "@openfeature/server-sdk";
const provider = new ReflagNodeProvider({ secretKey });
await OpenFeature.setProviderAndWait(provider);
// set a value to the global context
OpenFeature.setContext({ region: "us-east-1" });
// set a value to the invocation context
// this is merged with the global context
const requestContext = {
targetingKey: req.user.id,
email: req.user.email,
companyPlan: req.locals.plan,
};
const client = OpenFeature.getClient();
const enterpriseFlagEnabled = await client.getBooleanValue(
"enterpriseFlag",
false,
requestContext,
);
```
## Feature resolution methods
The Reflag OpenFeature Provider implements the OpenFeature evaluation interface for different value types. Each method handles the resolution of flags according to the OpenFeature specification.
### Common behavior
All resolution methods share these behaviors:
- Return default value with `PROVIDER_NOT_READY` if client is not initialized,
- Return default value with `FLAG_NOT_FOUND` if flag doesn't exist,
- Return default value with `ERROR` if there was a type mismatch,
- Return evaluated value with `TARGETING_MATCH` on successful resolution.
### Type-Specific Methods
#### Boolean Resolution
```ts
client.getBooleanValue("my-flag", false);
```
Returns the flags's enabled state. This is the most common use case for flags.
#### String Resolution
```ts
client.getStringValue("my-flag", "default");
```
Returns the flags's remote config key (also known as "variant"). Useful for multi-variate use cases.
#### Number Resolution
```ts
client.getNumberValue("my-flag", 0);
```
Not directly supported by Reflag. Use `getObjectValue` instead for numeric configurations.
#### Object Resolution
```ts
// works for any type:
client.getObjectValue("my-flag", { defaultValue: true });
client.getObjectValue("my-flag", "string-value");
client.getObjectValue("my-flag", 199);
```
Returns the flag's remote config payload with type validation. This is the most flexible method,
allowing for complex configuration objects or simple types.
The object resolution performs runtime type checking between the default value and the flag payload to ensure type safety.
## Translating Evaluation Context
Reflag uses a context object of the following shape:
```ts
/**
* Describes the current user context, company context, and other context.
* This is used to determine if flag targeting matches and to track events.
**/
export type ReflagContext = {
/**
* The user context. If the user is set, the user ID is required.
*/
user?: {
id: string;
name?: string;
email?: string;
avatar?: string;
[k: string]: any;
};
/**
* The company context. If the company is set, the company ID is required.
*/
company?: { id: string; name?: string; avatar?: string; [k: string]: any };
/**
* The other context. This is used for any additional context that is not related to user or company.
*/
other?: Record<string, any>;
};
```
To use the Reflag Node.js OpenFeature provider, you must convert your OpenFeature contexts to Reflag contexts.
You can achieve this by supplying a context translation function which takes the OpenFeature context and returns
a corresponding Reflag Context:
```ts
import { ReflagNodeProvider } from "@openfeature/reflag-node-provider";
const contextTranslator = (context: EvaluationContext): ReflagContext => {
return {
user: {
id: context.targetingKey ?? context["userId"]?.toString(),
name: context["name"]?.toString(),
email: context["email"]?.toString(),
avatar: context["avatar"]?.toString(),
country: context["country"]?.toString(),
},
company: {
id: context["companyId"]?.toString(),
name: context["companyName"]?.toString(),
avatar: context["companyAvatar"]?.toString(),
plan: context["companyPlan"]?.toString(),
},
};
};
const provider = new ReflagNodeProvider({ secretKey, contextTranslator });
OpenFeature.setProvider(provider);
```
## Tracking feature adoption
The Reflag OpenFeature provider supports the OpenFeature Tracking API.
It's straight forward to start sending tracking events through OpenFeature.
Simply call the "track" method on the OpenFeature client:
```typescript
import { ReflagNodeProvider } from "@reflag/openfeature-node-provider";
import { OpenFeature } from "@openfeature/server-sdk";
const provider = new ReflagNodeProvider({ secretKey });
await OpenFeature.setProviderAndWait(provider);
const client = OpenFeature.getClient();
// `evaluationContext` is whatever you use to evaluate features based off
const enterpriseFlagEnabled = await client.track("huddles", evaluationContext);
```
## License
> MIT License
> Copyright (c) 2025 Bucket ApS
```
--------------------------------------------------------------------------------
/packages/cli/README.md:
--------------------------------------------------------------------------------
```markdown
# Reflag CLI
Command-line interface for interacting with Reflag services. The CLI allows you to manage apps,
flags, authentication, and generate TypeScript types for your Reflag flags. With this tool,
you can streamline your flagging workflow directly from your terminal.
## Installation
Install the CLI as a development dependency in your project:
```bash
# npm
npm install --save-dev @reflag/cli
# yarn
yarn add --dev @reflag/cli
```
Run the `new` command from your project's root directory to initialize the CLI, create a flag, and generate TypeScript types in one step:
```bash
# npm
npx reflag new
# yarn
yarn reflag new
```
## Migrating from Bucket SDK
If you're migrating from the Bucket CLI, here are the key changes to be aware of:
- **Command name**: Changed from `bucket` to `reflag`
- **Type definitions file**: Renamed from `features.d.ts` to `flags.d.ts` (manually remove the old file if it was committed)
- **Authentication file**: Changed from `.bucket-auth` to `.reflag-auth` (rename or remove the old file)
- **Configuration file**: Changed from `bucket.config.json` to `reflag.config.json` (rename or remove the old file)
- **Command**: `features` command is now `flags`
- **Environment variable**: Use `REFLAG_API_KEY` instead of `BUCKET_API_KEY`
**Important**: Update your scripts, build steps, and `.gitignore` patterns to reflect these changes.
### Individual Commands
For more control, you can run each command individually:
```bash
# Initialize Reflag in your project (if not already setup)
npx reflag init
# Create a new flag
npx reflag flags create "My Flag"
# Generate TypeScript types for your flags
npx reflag flags types
```
## Configuration
The CLI creates a `reflag.config.json` file in your project directory when you run `reflag init`. This file contains all the necessary settings for your Reflag integration.
### Configuration File Structure
Here are all the configuration options available in the `reflag.config.json` file:
```json
{
"$schema": "https://unpkg.com/@reflag/cli@latest/schema.json",
"baseUrl": "https://app.reflag.com",
"apiUrl": "https://app.reflag.com/api",
"appId": "ap123456789",
"typesOutput": [
{
"path": "gen/flags.d.ts",
"format": "react"
}
]
}
```
| Option | Description | Default |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- |
| `$schema` | Autocompletion for the config. `latest` can be replaced with a specific version. | "https://unpkg.com/@reflag/cli@latest/schema.json" |
| `baseUrl` | Base URL for Reflag services. | "https://app.reflag.com" |
| `apiUrl` | API URL for Reflag services (overrides baseUrl for API calls). | "https://app.reflag.com/api" |
| `appId` | Your Reflag application ID. | Required |
| `typesOutput` | Path(s) where TypeScript types will be generated. Can be a string or an array of objects with `path` and `format` properties. Available formats: `react` and `node`. | "gen/flags.ts" with format "react" |
You can override these settings using command-line options for individual commands.
## Commands
### `reflag init`
Initialize a new Reflag configuration in your project. This creates a `reflag.config.json` file with your settings and prompts for any required information not provided via options.
```bash
npx reflag init [--overwrite]
```
Options:
- `--overwrite`: Overwrite existing configuration file if one exists.
- `--app-id <id>`: Set the application ID.
- `--key-format <format>`: Set the key format for flags.
### `reflag new [flagName]`
All-in-one command to get started quickly. This command combines `init`, flag creation, and type generation in a single step. Use this for the fastest way to get up and running with Reflag.
```bash
npx reflag new "My Flag" [--app-id ap123456789] [--key my-flag] [--key-format custom] [--out gen/flags.ts] [--format react]
```
Options:
- `--key`: Specific key for the flag.
- `--app-id`: App ID to use.
- `--key-format`: Format for flag keys (custom, snake, camel, etc.).
- `--out`: Path to generate TypeScript types.
- `--format`: Format of the generated types (react or node).
If you prefer more control over each step, you can use the individual commands (`init`, `flags create`, `flags types`) instead.
### `reflag login`
Authenticate with your Reflag account. This stores your credentials securely for subsequent operations.
```bash
npx reflag login
```
### `reflag logout`
Sign out from your Reflag account and remove stored credentials.
```bash
npx reflag logout
```
### `reflag flags`
Manage your Reflag flags with these subcommands:
#### `reflag flags create [flagName]`
Create a new flag in your Reflag app. The command guides you through the flag creation process with interactive prompts if options are not provided.
```bash
npx reflag flags create "My Flag" [--app-id ap123456789] [--key my-flag] [--key-format custom]
```
Options:
- `--key`: Specific key for the flag.
- `--app-id`: App ID to use.
- `--key-format`: Format for flag keys.
#### `reflag flags list`
List all flags for the current app. This helps you visualize what flags are available and their current configurations.
```bash
npx reflag flags list [--app-id ap123456789]
```
Options:
- `--app-id`: App ID to use.
#### `reflag flags types`
Generate TypeScript types for your flags. This ensures type safety when using Reflag flags in your TypeScript/JavaScript applications.
```bash
npx reflag flags types [--app-id ap123456789] [--out gen/flags.ts] [--format react]
```
Options:
- `--app-id`: App ID to use.
- `--out`: Path to generate TypeScript types.
- `--format`: Format of the generated types (react or node).
### `reflag apps`
Commands for managing Reflag apps.
## Global Options
These options can be used with any command:
- `--debug`: Enable debug mode for verbose output.
- `--base-url <url>`: Set the base URL for Reflag API.
- `--api-url <url>`: Set the API URL directly (overrides base URL).
- `--api-key <key>`: Reflag API key for non-interactive authentication.
- `--help`: Display help information for a command.
## AI-Assisted Development
Reflag provides powerful AI-assisted development capabilities through rules and Model Context Protocol (MCP). These features help your AI development tools better understand your flags and provide more accurate assistance.
### Reflag Rules (Recommended)
The `rules` command helps you set up AI-specific rules for your project. These rules enable AI tools to better understand how to work with Reflag flags and how they should be used in your codebase.
```bash
npx reflag rules [--format <cursor|copilot>] [--yes]
```
Options:
- `--format`: Format to add rules in:
- `cursor`: Adds rules to `.cursor/rules/reflag.mdc` for Cursor IDE integration.
- `copilot`: Adds rules to `.github/copilot-instructions.md` for GitHub Copilot integration.
- `--yes`: Skip confirmation prompts and overwrite existing files without asking.
This command adds rules to your project that provide AI tools with context about how to set up and use Reflag flags. For the copilot format, the rules are added to a dedicated section in the file, allowing you to maintain other copilot instructions alongside Reflag's rules.
## Model Context Protocol
The Model Context Protocol (MCP) is an open protocol that provides a standardized way to connect AI models to different data sources and tools. In the context of Reflag, MCP enables your code editor to understand your flags, their states, and their relationships within your codebase. This creates a seamless bridge between your flag management workflow and AI-powered development tools. The MCP server is hosted by Reflag, making it easy to get started.
_\*\*Note: The Reflag `mcp` CLI command was previously used for a \_local_ server. However, in recent versions of the Reflag CLI, the `mcp` command has been repurposed to help you connect to the new remote MCP server.\*\*\_
### Setting up MCP
The `mcp` command helps you configure your editor or AI client to connect with Reflag's remote MCP server. This allows your AI tools to understand your flags and provide more contextual assistance.
```bash
npx reflag mcp [--editor <editor>] [--scope <local|global>]
```
Options:
- `--editor`: The editor/client to configure:
- `cursor`: [Cursor IDE](https://www.cursor.com/)
- `vscode`: [Visual Studio Code](https://code.visualstudio.com/)
- `claude`: [Claude Desktop](https://claude.ai/download)
- `windsurf`: [Windsurf](https://windsurf.com/editor)
- `--scope`: Whether to configure settings globally or locally for the project.
The command will guide you through:
1. Selecting which editor/client to configure.
2. Choosing which Reflag app to connect to.
3. Deciding between global or project-local configuration.
4. Setting up the appropriate configuration file for your chosen editor .
_**Note: The setup uses [mcp-remote](https://github.com/geelen/mcp-remote) as a compatibility layer allowing the remote hosted Reflag MCP server to work with all editors/clients that support MCP STDIO servers. If your editor/client supports HTTP Streaming with OAuth you can connect to the Reflag MCP server directly.**_
## Using in CI/CD Pipelines (Beta)
The Reflag CLI is designed to work seamlessly in CI/CD pipelines. For automated environments where interactive login is not possible, use the `--api-key` option or specify the API key in the `REFLAG_API_KEY` environment variable.
```bash
# Generate types in CI/CD
npx reflag apps list --api-key $REFLAG_API_KEY
```
**Important restrictions:**
- When using `--api-key`, the `login` and `logout` commands are disabled
- API keys bypass all interactive authentication flows
- API keys are bound to one app only. Commands such as `apps list` will only return the bound app
- Store API keys securely using your CI/CD platform's secret management
Example CI workflow:
```yaml
# GitHub Actions example
- name: Generate types
run: npx reflag flags types --api-key ${{ secrets.REFLAG_API_KEY }}
# GitHub Actions example (using environment):
- name: Generate types (environment)
run: npx reflag flags types
env:
REFLAG_API_KEY: ${{ secrets.REFLAG_CI_API_KEY }}
```
## Development
```bash
# Build the CLI
yarn build
# Run the CLI locally
yarn reflag [command]
# Lint and format code
yarn lint
yarn format
```
## Requirements
- Node.js >=18.0.0
## License
> MIT License
> Copyright (c) 2025 Bucket ApS
```
--------------------------------------------------------------------------------
/packages/vue-sdk/README.md:
--------------------------------------------------------------------------------
```markdown
# Reflag Vue SDK (beta)
Vue client side library for [Reflag.com](https://reflag.com)
Reflag supports flag toggling, tracking flag usage, requesting feedback on features and remotely configuring flags.
The Reflag Vue SDK comes with the same built-in toolbar as the browser SDK which appears on `localhost` by default.
## Install
Install via npm:
```shell
npm i @reflag/vue-sdk
```
## Migrating from Bucket SDK
If you have been using the Bucket SDKs, the following list will help you migrate to Reflag SDK:
- `Bucket*` classes, and types have been renamed to `Reflag*` (e.g. `BucketClient` is now `ReflagClient`)
- `Feature*` classes, and types have been renamed to `Feature*` (e.g. `Feature` is now `Flag`, `RawFeatures` is now `RawFlags`)
- All methods that contained `feature` in the name have been renamed to use the `flag` terminology (e.g. `getFeature` is `getFlag`)
- The `fallbackFeatures` property in client constructor and configuration files has been renamed to `fallbackFlags`
- `featureKey` has been renamed to `flagKey` in all methods that accepts that argument
- The SDKs will not emit `evaluate` and `evaluate-config` events anymore
- The new cookies that are stored in the client's browser are now `reflag-*` prefixed instead og `bucket-*`
- The `featuresUpdated` hook has been renamed to `flagsUpdated`
- The `checkIsEnabled` and `checkConfig` hooks have been removed, use `check` from now on
To ease in transition to Reflag SDK, some of the old methods have been preserved as aliases to the new methods:
- `getFeature` method is an alias for `getFlag`
- `getFeatures` method is an alias for `getFlags`
- `featuresUpdated` hook is an alias for `flagsUpdated`
If you are running with strict Content Security Policies active on your website, you will need change them as follows:
- `connect-src https://front.bucket.co` to `connect-src https://front.reflag.com`
Finally, if you have customized the look & feel of the Feedback component, update `--bucket-feedback-*` CSS classes to `--reflag-feedback-*`
## Get started
### 1. Add the `ReflagProvider` context provider
Add the `ReflagProvider` context provider to your application:
**Example:**
```vue
<script setup lang="ts">
import { ReflagProvider } from "@reflag/vue-sdk";
</script>
<ReflagProvider
:publishable-key="publishableKey"
:context="{
user: { id: 'user_123', name: 'John Doe', email: '[email protected]' },
company: { id: 'acme_inc', plan: 'pro' },
}"
>
<!-- your app -->
</ReflagProvider>
```
If using Nuxt, wrap `<ReflagProvider>` in `<ClientOnly>`. `<ReflagProvider>` only renders client-side currently.
### 2. Use `useFlag get flag status
```vue
<script setup lang="ts">
import { useFlag } from "@reflag/vue-sdk";
const { isEnabled } = useFlag("huddles");
</script>
<template>
<div v-if="isEnabled">
<button>Start huddles!</button>
</div>
</template>
```
See [useFlag()](#useflag) for a full example
## Setting context
Reflag determines which flags are active for a given `user`, `company`, or `other` context.
You can pass these to the `ReflagProvider` using the `context` prop.
### Using the `context` prop
```vue
<ReflagProvider
:publishable-key="publishableKey"
:context="{
user: { id: 'user_123', name: 'John Doe', email: '[email protected]' },
company: { id: 'acme_inc', plan: 'pro' },
other: { source: 'web' },
}"
>
<!-- your app -->
</ReflagProvider>
```
### Legacy individual props (deprecated)
For backward compatibility, you can still use individual props, but these are deprecated and will be removed in the next major version:
```vue
<ReflagProvider
:publishable-key="publishableKey"
:user="{ id: 'user_123', name: 'John Doe', email: '[email protected]' }"
:company="{ id: 'acme_inc', plan: 'pro' }"
:other-context="{ source: 'web' }"
>
<!-- your app -->
</ReflagProvider>
```
> [!Important]
> The `user`, `company`, and `otherContext` props are deprecated. Use the `context` prop instead, which provides the same functionality in a more structured way.
### Context requirements
If you supply `user` or `company` objects, they must include at least the `id` property otherwise they will be ignored in their entirety.
In addition to the `id`, you must also supply anything additional that you want to be able to evaluate flag targeting rules against.
Attributes which are not properties of the `user` or `company` can be supplied using the `other` property.
Attributes cannot be nested (multiple levels) and must be either strings, numbers or booleans.
A number of special attributes exist:
- `name` -- display name for `user`/`company`,
- `email` -- the email of the user,
- `avatar` -- the URL for `user`/`company` avatar image.
To retrieve flags along with their targeting information, use `useFlag(key: string)` hook (described in a section below).
Note that accessing `isEnabled` on the object returned by `useFlag()` automatically
generates a `check` event.
## Remote config
Remote config is a dynamic and flexible approach to configuring flag behavior outside of your app – without needing to re-deploy it.
Similar to `isEnabled`, each flag accessed using the `useFlag()` hook, has a `config` property. This configuration is managed from within Reflag. It is managed similar to the way access to flags is managed, but instead of the
binary `isEnabled` you can have multiple configuration values which are given to different user/companies.
### Get started with Remote config
```ts
const {
isEnabled,
config: { key, payload },
} = useFlag("huddles");
// isEnabled: true,
// key: "gpt-3.5",
// payload: { maxTokens: 10000, model: "gpt-3.5-beta1" }
```
`key` is mandatory for a config, but if a flag has no config or no config value was matched against the context, the `key` will be `undefined`. Make sure to check against this case when trying to use the configuration in your application. `payload` is an optional JSON value for arbitrary configuration needs.
Note that, similar to `isEnabled`, accessing `config` on the object returned by `useFlag()` automatically
generates a `check` event.
## `<ReflagProvider>` component
The `<ReflagProvider>` initializes the Reflag SDK, fetches flags and starts listening for automated feedback survey events. The component can be configured using a number of props:
- `publishableKey` is used to connect the provider to an _environment_ on Reflag. Find your `publishableKey` under [environment settings](https://app.reflag.com/env-current/settings/app-environments) in Reflag,
- `context`: An object containing `user`, `company`, and `other` properties that make up the evaluation context used to determine if a flag is enabled or not. `company` and `user` contexts are automatically transmitted to Reflag servers so the Reflag app can show you which companies have access to which flags etc.
- `company`, `user` and `otherContext` (deprecated): Individual props for context. These are deprecated in favor of the `context` prop and will be removed in the next major version.
> [!Note]
> If you specify `company` and/or `user` they must have at least the `id` property, otherwise they will be ignored in their entirety. You should also supply anything additional you want to be able to evaluate flag targeting against,
- `timeoutMs`: Timeout in milliseconds when fetching flags from the server,
- `staleWhileRevalidate`: If set to `true`, stale flags will be returned while refetching flags in the background,
- `expireTimeMs`: If set, flags will be cached between page loads for this duration (in milliseconds),
- `staleTimeMs`: Maximum time (in milliseconds) that stale flags will be returned if `staleWhileRevalidate` is true and new flags cannot be fetched.
- `enableTracking`: Set to `false` to stop sending tracking events and user/company updates to Reflag. Useful when you're impersonating a user (defaults to `true`),
- `apiBaseUrl`: Optional base URL for the Reflag API. Use this to override the default API endpoint,
- `appBaseUrl`: Optional base URL for the Reflag application. Use this to override the default app URL,
- `sseBaseUrl`: Optional base URL for Server-Sent Events. Use this to override the default SSE endpoint,
- `debug`: Set to `true` to enable debug logging to the console,
- `toolbar`: Optional [configuration](https://docs.reflag.com/supported-languages/browser-sdk/globals#toolbaroptions) for the Reflag toolbar,
- `feedback`: Optional configuration for feedback collection
### Loading states
ReflagProvider lets you define a template to be shown while ReflagProvider is initializing:
```vue
<template>
<ReflagProvider
:publishable-key="publishableKey"
:user="user"
:company="{ id: 'acme_inc', plan: 'pro' }"
>
<template #loading>Loading...</template>
<StartHuddlesButton />
</ReflagProvider>
</template>
```
If you want more control over loading screens, `useIsLoading()` returns a `Ref<boolean>` which you can use to customize the loading experience.
## `<ReflagBootstrappedProvider>` component
The `<ReflagBootstrappedProvider>` component is a specialized version of `ReflagProvider` designed for server-side rendering and preloaded flag scenarios. Instead of fetching flags on initialization, it uses pre-fetched flags, resulting in faster initial page loads and better SSR compatibility.
### Usage
```vue
<script setup lang="ts">
import { ReflagBootstrappedProvider } from "@reflag/vue-sdk";
// Pre-fetched flags (typically from your server/SSR layer)
const bootstrappedFlags = {
context: {
user: { id: "user123", name: "John Doe", email: "[email protected]" },
company: { id: "company456", name: "Acme Inc", plan: "enterprise" },
},
flags: {
huddles: {
isEnabled: true,
config: {
key: "enhanced",
payload: { maxParticipants: 50, videoQuality: "hd" },
},
},
},
};
</script>
<template>
<ReflagBootstrappedProvider
:publishable-key="publishableKey"
:flags="bootstrappedFlags"
>
<StartHuddlesButton />
</ReflagBootstrappedProvider>
</template>
```
### Getting bootstrapped flags
You'll typically generate the `bootstrappedFlags` object on your server using the Node.js SDK or by fetching from the Reflag API. Here's an example using the Node.js SDK:
```js
// server.js (Node.js/SSR)
import { ReflagClient } from "@reflag/node-sdk";
const client = new ReflagClient({
secretKey: "your-secret-key", // Use secret key on server
});
await client.initialize();
// Fetch flags for specific context
const context = {
user: { id: "user123", name: "John Doe", email: "[email protected]" },
company: { id: "company456", name: "Acme Inc", plan: "enterprise" },
};
const bootstrappedFlags = client.getFlagsForBootstrap(context);
// Pass to your Vue app
```
### ReflagBootstrappedProvider Props
`ReflagBootstrappedProvider` accepts all the same props as `ReflagProvider` except:
- `flags`: The pre-fetched flags object containing context and flag data
- All other props available in `ReflagProvider` are supported except `context`, `user`, `company`, and `otherContext` (which are extracted from `flags.context`)
If the `flags` prop is not provided or is undefined, the provider will not initialize the client and will render in a non-loading state.
## `<ReflagClientProvider>` component
The `<ReflagClientProvider>` is a lower-level component that accepts a pre-initialized `ReflagClient` instance. This is useful for advanced use cases where you need full control over client initialization or want to share a client instance across multiple parts of your application.
### ReflagClientProvider Usage
```vue
<script setup lang="ts">
import { ReflagClient } from "@reflag/browser-sdk";
import { ReflagClientProvider } from "@reflag/vue-sdk";
// Initialize the client yourself
const client = new ReflagClient({
publishableKey: "your-publishable-key",
user: { id: "user123", name: "John Doe" },
company: { id: "company456", name: "Acme Inc" },
// ... other configuration options
});
// Initialize the client
await client.initialize();
</script>
<template>
<ReflagClientProvider :client="client">
<template #loading>Loading...</template>
<Router />
</ReflagClientProvider>
</template>
```
### ReflagClientProvider Props
The `ReflagClientProvider` accepts the following props:
- `client`: A pre-initialized `ReflagClient` instance
### Slots
- `loading`: Optional slot to show while the client is initializing (same as `ReflagProvider`)
> [!Note]
> Most applications should use `ReflagProvider` or `ReflagBootstrappedProvider` instead of `ReflagClientProvider`. Only use this component when you need the advanced control it provides.
## Hooks
### `useFlag()`
Returns the state of a given flag for the current context. The composable provides access to flags and their configurations.
`useFlag()` returns an object with this shape:
```ts
{
isEnabled: boolean, // is the flag enabled
track: () => void, // send a track event when the flag is used
requestFeedback: (...) => void // open up a feedback dialog
config: {key: string, payload: any}, // remote configuration for this flag
isLoading: boolean // if you want to manage loading state at the flag level
}
```
Example:
```vue
<script setup lang="ts">
import { useFlag } from "@reflag/vue-sdk";
const { isEnabled, track, requestFeedback, config } = useFlag("huddles");
</script>
<template>
<div v-if="isLoading">Loading...</div>
<div v-else-if="!isEnabled">Flag not available</div>
<div v-else>
<button @click="track()">Start huddles!</button>
<button
@click="
(e) =>
requestFeedback({
title:
config.payload?.question ??
'How do you like the Huddles feature?',
position: {
type: 'POPOVER',
anchor: e.currentTarget as HTMLElement,
},
})
"
>
Give feedback!
</button>
</div>
</template>
```
See the reference docs for details.
### `useTrack()`
`useTrack()` returns a function which lets you send custom events to Reflag. It takes a string argument with the event name and optionally an object with properties to attach the event.
Using `track` returned from `useFlag()` calls this track function with the flag key as the event name.
```vue
<script setup lang="ts">
import { useTrack } from "@reflag/vue-sdk";
const track = useTrack();
</script>
<template>
<div>
<button @click="track('Huddles Started', { huddlesType: 'voice' })">
Start voice huddles!
</button>
</div>
</template>
```
### `useRequestFeedback()`
Returns a function that lets you open up a dialog to ask for feedback on a specific feature. This is useful for collecting targeted feedback about specific features.
See [Automated Feedback Surveys](https://docs.reflag.com/product-handbook/live-satisfaction) for how to do this automatically, without code.
When using the `useRequestFeedback` you must pass the flag key to `requestFeedback`.
The example below shows how to use `position` to ensure the popover appears next to the "Give feedback!" button.
```vue
<script setup lang="ts">
import { useRequestFeedback } from "@reflag/vue-sdk";
const requestFeedback = useRequestFeedback();
</script>
<template>
<button
@click="
(e) =>
requestFeedback({
flagKey: 'huddles',
title: 'How satisfied are you with file uploads?',
position: {
type: 'POPOVER',
anchor: e.currentTarget as HTMLElement,
},
// Optional custom styling
style: {
theme: 'light',
primaryColor: '#007AFF',
},
})
"
>
Give feedback!
</button>
</template>
```
See the [Feedback Documentation](https://github.com/reflagcom/javascript/blob/main/packages/browser-sdk/FEEDBACK.md#manual-feedback-collection) for more information on `requestFeedback` options.
### `useSendFeedback()`
Returns a function that lets you send feedback to Reflag. This is useful if you've manually collected feedback through your own UI and want to send it to Reflag.
```vue
<script setup lang="ts">
import { useSendFeedback } from "@reflag/vue-sdk";
const sendFeedback = useSendFeedback();
const handleSubmit = async (data: FormData) => {
await sendFeedback({
flagKey: "reflag-flag-key",
score: parseInt(data.get("score") as string),
comment: data.get("comment") as string,
});
};
</script>
<template>
<form @submit="handleSubmit">
<!-- form content -->
</form>
</template>
```
### `useUpdateUser()`, `useUpdateCompany()` and `useUpdateOtherContext()`
These composables return functions that let you update the attributes for the currently set user, company, or other context. Updates to user/company are stored remotely and affect flag targeting, while "other" context updates only affect the current session.
```vue
<script setup lang="ts">
import {
useUpdateUser,
useUpdateCompany,
useUpdateOtherContext,
} from "@reflag/vue-sdk";
const updateUser = useUpdateUser();
const updateCompany = useUpdateCompany();
const updateOtherContext = useUpdateOtherContext();
const handleUserUpdate = async () => {
await updateUser({
role: "admin",
betaFeatures: "enabled",
});
};
const handleCompanyUpdate = async () => {
await updateCompany({
plan: "enterprise",
employees: 500,
});
};
const handleContextUpdate = async () => {
await updateOtherContext({
currentWorkspace: "workspace-123",
theme: "dark",
});
};
</script>
<template>
<div>
<button @click="handleUserUpdate">Update User</button>
<button @click="handleCompanyUpdate">Update Company</button>
<button @click="handleContextUpdate">Update Context</button>
</div>
</template>
```
Note: To change the `user.id` or `company.id`, you need to update the props passed to `ReflagProvider` instead of using these composables.
### `useClient()`
Returns the `ReflagClient` used by the `ReflagProvider`. The client offers more functionality that
is not directly accessible through the other composables.
```vue
<script setup>
import { useClient } from "@reflag/vue-sdk";
import { onMounted } from "vue";
const client = useClient();
console.log(client.getContext());
</script>
<template>
<!-- your component content -->
</template>
```
### `useIsLoading()`
Returns a `Ref<boolean>` to indicate if Reflag has finished loading.
Initially, the value will be `true` if no bootstrap flags have been provided and the client has not be initialized.
```vue
<script setup>
import { useIsLoading } from "@reflag/vue-sdk";
import { Spinner } from "./Spinner";
const isLoading = useIsLoading();
</script>
<template>
<!-- your component content -->
</template>
```
### `useOnEvent()`
Vue composable for listening to Reflag client events. This composable automatically handles mounting and unmounting of event listeners.
Available events include:
- `flagsUpdated`: Triggered when flags are updated
- `track`: Triggered when tracking events are sent
- `feedback`: Triggered when feedback is sent
```vue
<script setup lang="ts">
import { useOnEvent } from "@reflag/vue-sdk";
// Listen to flag updates
useOnEvent("flagsUpdated", () => {
console.log("Flags have been updated");
});
</script>
<template>
<!-- your component content -->
</template>
```
You can also provide a specific client instance if needed:
```vue
<script setup lang="ts">
import { ReflagClient } from "@reflag/browser-sdk";
const myReflagClient = new ReflagClient();
useOnEvent(
"flagsUpdated",
() => {
console.log("flags updated");
},
myReflagClient,
);
</script>
<template>
<!-- your component content -->
</template>
```
## Content Security Policy (CSP)
See [CSP](https://github.com/reflagcom/javascript/blob/main/packages/browser-sdk/README.md#content-security-policy-csp) for info on using Reflag React SDK with CSP
## License
MIT License
Copyright (c) 2025 Bucket ApS
```
--------------------------------------------------------------------------------
/packages/browser-sdk/README.md:
--------------------------------------------------------------------------------
```markdown
# Reflag Browser SDK
Basic client for [Reflag.com](https://reflag.com). If you're using React, you'll be better off with the Reflag React SDK.
Reflag supports flag toggling, tracking flag usage, [collecting feedback](#qualitative-feedback-on-beta-flags) on flags, and [remotely configuring flags](#remote-config).
## Install
First find your `publishableKey` under [environment settings](https://app.reflag.com/env-current/settings/app-environments) in Reflag.
The package can be imported or used directly in a HTML script tag:
A. Import module:
```typescript
import { ReflagClient } from "@reflag/browser-sdk";
const user = {
id: 42,
role: "manager",
};
const company = {
id: 99,
plan: "enterprise",
};
const reflagClient = new ReflagClient({ publishableKey, user, company });
await reflagClient.initialize();
const {
isEnabled,
config: { payload: question },
track,
requestFeedback,
} = reflagClient.getFlag("huddle");
if (isEnabled) {
// Show flag. When retrieving `isEnabled` the client automatically
// sends a "check" event for the "huddle" flag which is shown in the
// Reflag UI.
// On usage, call `track` to let Reflag know that a user interacted with the flag
track();
// The `payload` is a user-supplied JSON in Reflag that is dynamically picked
// out depending on the user/company.
const question = payload?.question ?? "Tell us what you think of Huddles";
// Use `requestFeedback` to create "Send feedback" buttons easily for specific
// flags. This is not related to `track` and you can call them individually.
requestFeedback({ title: question });
}
// `track` just calls `reflagClient.track(<flagKey>)` to send an event using the same flag key
// You can also use `track` on the client directly to send any custom event.
reflagClient.track("huddle");
// similarly, `requestFeedback` just calls `reflagClient.requestFeedback({flagKey: <flagKey>})`
// which you can also call directly:
reflagClient.requestFeedback({ flagKey: "huddle" });
```
B. Script tag (client-side directly in html)
See [example/browser.html](https://github.com/reflagcom/javascript/tree/main/packages/browser-sdk/example/browser.html) for a working example:
```html
<script src="https://cdn.jsdelivr.net/npm/@reflag/browser-sdk@2"></script>
<script>
const reflag = new ReflagBrowserSDK.ReflagClient({
publishableKey: "publishableKey",
user: { id: "42" },
company: { id: "1" },
});
reflag.initialize().then(() => {
console.log("Reflag initialized");
document.getElementById("loading").style.display = "none";
document.getElementById("start-huddle").style.display = "block";
});
</script>
<span id="loading">Loading...</span>
<button
id="start-huddle"
style="display: none"
onClick="reflag.track('Started huddle')"
>
Click me
</button>
```
### Init options
Supply these to the constructor call:
```typescript
type Configuration = {
logger: console; // by default only logs warn/error, by passing `console` you'll log everything
apiBaseUrl?: "https://front.reflag.com";
sseBaseUrl?: "https://livemessaging.bucket.co";
feedback?: undefined; // See FEEDBACK.md
enableTracking?: true; // set to `false` to stop sending track events and user/company updates to Reflag servers. Useful when you're impersonating a user
fallbackFlags?:
| string[]
| Record<string, { key: string; payload: any } | true>; // Enable these flags if unable to contact reflag.com. Can be a list of flag keys or a record with configuration values
timeoutMs?: number; // Timeout for fetching flags (default: 5000ms)
staleWhileRevalidate?: boolean; // Revalidate in the background when cached flags turn stale to avoid latency in the UI (default: false)
staleTimeMs?: number; // at initialization time flags are loaded from the cache unless they have gone stale. Defaults to 0 which means the cache is disabled. Increase this in the case of a non-SPA
expireTimeMs?: number; // In case we're unable to fetch flags from Reflag, cached/stale flags will be used instead until they expire after `expireTimeMs`. Default is 30 days
offline?: boolean; // Use the SDK in offline mode. Offline mode is useful during testing and local development
};
```
## Migrating from Bucket SDK
If you have been using the Bucket SDKs, the following list will help you migrate to Reflag SDK:
- `Bucket*` classes, and types have been renamed to `Reflag*` (e.g. `BucketClient` is now `ReflagClient`)
- `Feature*` classes, and types have been renamed to `Feature*` (e.g. `Feature` is now `Flag`, `RawFeatures` is now `RawFlags`)
- All methods that contained `feature` in the name have been renamed to use the `flag` terminology (e.g. `getFeature` is `getFlag`)
- The `fallbackFeatures` property in client constructor and configuration files has been renamed to `fallbackFlags`
- `featureKey` has been renamed to `flagKey` in all methods that accepts that argument
- The new cookies that are stored in the client's browser are now `reflag-*` prefixed instead og `bucket-*`
- The `featuresUpdated` hook has been renamed to `flagsUpdated`
- The `checkIsEnabled` and `checkConfig` hooks have been removed, use `check` from now on
To ease in transition to Reflag SDK, some of the old methods have been preserved as aliases to the new methods:
- `getFeature` method is an alias for `getFlag`
- `getFeatures` method is an alias for `getFlags`
- `featuresUpdated` hook is an alias for `flagsUpdated`
If you are running with strict Content Security Policies active on your website, you will need change them as follows:
- `connect-src https://front.bucket.co` to `connect-src https://front.reflag.com`
Finally, if you have customized the look & feel of the Feedback component, update `--bucket-feedback-*` CSS classes to `--reflag-feedback-*`
## Flag toggles
Reflag determines which flags are active for a given user/company. The user/company is given in the ReflagClient constructor.
If you supply `user` or `company` objects, they must include at least the `id` property otherwise they will be ignored in their entirety.
In addition to the `id`, you must also supply anything additional that you want to be able to evaluate flag targeting rules against.
Attributes cannot be nested (multiple levels) and must be either strings, integers or booleans.
Some attributes are special and used in Reflag UI:
- `name` -- display name for `user`/`company`,
- `email` -- is accepted for `user`s and will be highlighted in the Reflag UI if available,
- `avatar` -- can be provided for both `user` and `company` and should be an URL to an image.
```ts
const reflagClient = new ReflagClient({
publishableKey,
user: {
id: "user_123",
name: "John Doe",
email: "[email protected]"
avatar: "https://example.com/images/udsy6363"
},
company: {
id: "company_123",
name: "Acme, Inc",
avatar: "https://example.com/images/31232ds"
},
});
```
To retrieve flags along with their targeting information, use `getFlag(key: string)`:
```ts
const huddle = reflagClient.getFlag("huddle");
// {
// isEnabled: true,
// config: { key: "zoom", payload: { ... } },
// track: () => Promise<Response>
// requestFeedback: (options: RequestFeedbackData) => void
// }
```
You can use `getFlags()` to retrieve all enabled flags currently.
```ts
const flags = reflagClient.getFlags();
// {
// huddle: {
// isEnabled: true,
// targetingVersion: 42,
// config: ...
// }
// }
```
`getFlags()` is meant to be more low-level than `getFlag()` and it typically used
by down-stream clients, like the React SDK.
Note that accessing `isEnabled` on the object returned by `getFlags` does not automatically
generate a `check` event, contrary to the `isEnabled` property on the object returned by `getFlag`.
## Remote config
Remote config is a dynamic and flexible approach to configuring flag behavior outside of your app – without needing to re-deploy it.
Similar to `isEnabled`, each flag has a `config` property. This configuration is managed from within Reflag.
It is managed similar to the way access to flags is managed, but instead of the binary `isEnabled` you can have
multiple configuration values which are given to different user/companies.
```ts
const flags = reflagClient.getFlags();
// {
// huddle: {
// isEnabled: true,
// targetingVersion: 42,
// config: {
// key: "gpt-3.5",
// payload: { maxTokens: 10000, model: "gpt-3.5-beta1" }
// }
// }
// }
```
`key` is mandatory for a config, but if a flag has no config or no config value was matched against the context, the `key` will be `undefined`. Make sure to check against this case when trying to use the configuration in your application. `payload` is an optional JSON value for arbitrary configuration needs.
Just as `isEnabled`, accessing `config` on the object returned by `getFlags` does not automatically
generate a `check` event, contrary to the `config` property on the object returned by `getFlag`.
## Server-side rendering and bootstrapping
For server-side rendered applications, you can eliminate the initial network request by bootstrapping the client with pre-fetched flag data.
### Init options bootstrapped
```typescript
type Configuration = {
logger: console; // by default only logs warn/error, by passing `console` you'll log everything
apiBaseUrl?: "https://front.reflag.com";
sseBaseUrl?: "https://livemessaging.bucket.co";
feedback?: undefined; // See FEEDBACK.md
enableTracking?: true; // set to `false` to stop sending track events and user/company updates to Reflag servers. Useful when you're impersonating a user
offline?: boolean; // Use the SDK in offline mode. Offline mode is useful during testing and local development
bootstrappedFlags?: FetchedFlags; // Pre-fetched flags from server-side (see Server-side rendering section)
};
```
### Using bootstrappedFlags
Use the Node SDK's `getFlagsForBootstrap()` method to pre-fetch flags server-side, then pass them to the browser client:
```typescript
// Server-side: Get flags using Node SDK
import { ReflagClient as ReflagNodeClient } from "@reflag/node-sdk";
const serverClient = new ReflagNodeClient({ secretKey: "your-secret-key" });
await serverClient.initialize();
const { flags } = serverClient.getFlagsForBootstrap({
user: { id: "user123", name: "John Doe", email: "[email protected]" },
company: { id: "company456", name: "Acme Inc", plan: "enterprise" },
});
// Pass flags data to client using your framework's preferred method
// or for example in a script tag
app.get("/", (req, res) => {
res.set("Content-Type", "text/html");
res.send(
Buffer.from(
`<script>var flags = ${JSON.stringify(flags)};</script>
<main id="app"></main>`,
),
);
});
// Client-side: Initialize with pre-fetched flags
import { ReflagClient } from "@reflag/browser-sdk";
const reflagClient = new ReflagClient({
publishableKey: "your-publishable-key",
user: { id: "user123", name: "John Doe", email: "[email protected]" },
company: { id: "company456", name: "Acme Inc", plan: "enterprise" },
bootstrappedFlags: flags, // No network request needed
});
await reflagClient.initialize(); // Initializes all but flags
const { isEnabled } = reflagClient.getFlag("huddle");
```
This eliminates loading states and improves performance by avoiding the initial flags API call.
## Context management
### Updating user/company/other context
Attributes given for the user/company/other context in the ReflagClient constructor can be updated for use in flag targeting evaluation with the `updateUser()`, `updateCompany()` and `updateOtherContext()` methods.
They return a promise which resolves once the flags have been re-evaluated follow the update of the attributes.
The following shows how to let users self-opt-in for a new flag. The flag must have the rule `voiceHuddleOptIn IS true` set in the Reflag UI.
```ts
// toggle opt-in for the voiceHuddle flag:
const { isEnabled } = reflagClient.getFlag("voiceHuddle");
// this toggles the flag on/off. The promise returns once flag targeting has been
// re-evaluated.
await reflagClient.updateUser({ voiceHuddleOptIn: (!isEnabled).toString() });
```
> [!NOTE] > `user`/`company` attributes are also stored remotely on the Reflag servers and will automatically be used to evaluate flag targeting if the page is refreshed.
### setContext()
The `setContext()` method allows you to replace the entire context (user, company, and other attributes) at once. This method is useful when you need to completely change the context, such as when a user logs in or switches between different accounts.
```ts
await reflagClient.setContext({
user: {
id: "new-user-123",
name: "Jane Doe",
email: "[email protected]",
role: "admin",
},
company: {
id: "company-456",
name: "New Company Inc",
plan: "enterprise",
},
other: {
feature: "beta",
locale: "en-US",
},
});
```
The method will:
- Replace the entire context with the new values
- Re-evaluate all flags based on the new context
- Update the user and company information on Reflag servers
- Return a promise that resolves once the flags have been re-evaluated
### getContext()
The `getContext()` method returns the current context being used for flag evaluation. This is useful for debugging or when you need to inspect the current user, company, and other attributes.
```ts
const currentContext = reflagClient.getContext();
console.log(currentContext);
// {
// user: { id: "user-123", name: "John Doe", email: "[email protected]" },
// company: { id: "company-456", name: "Acme Inc", plan: "enterprise" },
// other: { locale: "en-US", feature: "beta" }
// }
```
The returned context object contains:
- `user`: Current user attributes (if any)
- `company`: Current company attributes (if any)
- `other`: Additional context attributes not related to user or company
## Toolbar
The Reflag Toolbar is great for toggling flags on/off for yourself to ensure that everything works both when a flag is on and when it's off.
<img width="352" alt="Toolbar screenshot" src="https://github.com/user-attachments/assets/c223df5a-4bd8-49a1-8b4a-ad7001357693" />
The toolbar will automatically appear on `localhost`. However, it can also be incredibly useful in production.
You have full control over when it appears through the `toolbar` configuration option passed to the `ReflagClient`.
You can pass a simple boolean to force the toolbar to appear/disappear:
```typescript
const client = new ReflagClient({
// show the toolbar even in production if the user is an internal/admin user
toolbar: user?.isInternal,
...
});
```
You can also configure the position of the toolbar on the screen:
```typescript
const client = new ReflagClient({
toolbar: {
show: true;
position: {
placement: "bottom-left",
offset: {x: "1rem", y: "1rem"}
}
}
...
})
```
See [the reference](https://docs.reflag.com/supported-languages/browser-sdk/globals#toolbaroptions) for details.
## Qualitative feedback on beta flags
Reflag can collect qualitative feedback from your users in the form of a [Customer Satisfaction Score](https://en.wikipedia.org/wiki/Customer_satisfaction) and a comment.
### Automated feedback collection
The Reflag Browser SDK comes with automated feedback collection mode enabled by default, which lets the Reflag service ask your users for feedback for relevant flags just after they've used them.
> [!NOTE]
> To get started with automatic feedback collection, make sure you've set `user` in the `ReflagClient` constructor.
Automated feedback surveys work even if you're not using the SDK to send events to Reflag.
It works because the Reflag Browser SDK maintains a live connection to Reflag's servers and can automatically show a feedback prompt whenever the Reflag servers determines that an event should trigger a prompt - regardless of how this event is sent to Reflag.
You can find all the options to make changes to the default behavior in the [Reflag feedback documentation](./FEEDBACK.md).
### Reflag feedback UI
Reflag can assist you with collecting your user's feedback by offering a pre-built UI, allowing you to get started with minimal code and effort.
[Read the Reflag feedback UI documentation](./FEEDBACK.md)
### Reflag feedback SDK
Feedback can be submitted to Reflag using the SDK:
```ts
reflagClient.feedback({
flagKey: "my-flag-key", // String (required), copy from Flag feedback tab
score: 5, // Number: 1-5 (optional)
comment: "Absolutely stellar work!", // String (optional)
});
```
### Reflag feedback API
If you are not using the Reflag Browser SDK, you can still submit feedback using the HTTP API.
See details in [Feedback HTTP API](https://docs.reflag.com/api/http-api#post-feedback)
## Tracking flag usage
The `track` function lets you send events to Reflag to denote flag usage.
By default Reflag expects event names to align with the flag keys, but
you can customize it as you wish.
```ts
reflagClient.track("huddle", { voiceHuddle: true });
```
## Event listeners
Event listeners allow for capturing various events occurring in the `ReflagClient`. This is useful to build integrations with other system or for various debugging purposes. There are 5 kinds of events:
- `check`: Your code used `isEnabled` or `config` for a flag
- `flagsUpdated`: Flags were updated. Either because they were loaded as part of initialization or because the user/company updated
- `user`: User information updated (similar to the `identify` call used in tracking terminology)
- `company`: Company information updated (sometimes to the `group` call used in tracking terminology)
- `track`: Track event occurred.
Use the `on()` method to add an event listener to respond to certain events. See the API reference for details on each hook.
```ts
import { ReflagClient, CheckEvent, RawFlags } from "@reflag/browser-sdk";
const client = new ReflagClient({
// options
});
// or add the hooks after construction:
const unsub = client.on("check", (check: CheckEvent) =>
console.log(`Check event ${check}`),
);
// use the returned function to unsubscribe, or call `off()` with the same arguments again
unsub();
```
## Zero PII
The Reflag Browser SDK doesn't collect any metadata and HTTP IP addresses are _not_ being stored.
For tracking individual users, we recommend using something like database ID as userId, as it's unique and doesn't include any PII (personal identifiable information). If, however, you're using e.g. email address as userId, but prefer not to send any PII to Reflag, you can hash the sensitive data before sending it to Reflag:
```ts
import reflag from "@reflag/browser-sdk";
import { sha256 } from "crypto-hash";
reflag.user(await sha256("john_doe"));
```
## Use of cookies
The Reflag Browser SDK uses a couple of cookies to support automated feedback surveys. These cookies are not used for tracking purposes and thus should not need to appear in cookie consent forms.
The two cookies are:
- `reflag-prompt-${userId}`: store the last automated feedback prompt message ID received to avoid repeating surveys
- `reflag-token-${userId}`: caching a token used to connect to Reflag's live messaging infrastructure that is used to deliver automated feedback surveys in real time.
## TypeScript
Types are bundled together with the library and exposed automatically when importing through a package manager.
## Content Security Policy (CSP)
If you are running with strict Content Security Policies active on your website, you will need to enable these directives in order to use the SDK:
| Directive | Values | Reason |
| ----------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| connect-src | [https://front.reflag.com](https://front.reflag.com) | Basic functionality` |
| connect-src | [https://livemessaging.bucket.co](https://livemessaging.bucket.co) | Server sent events for use in automated feedback surveys, which allows for automatically collecting feedback when a user used a flag. |
| style-src | 'unsafe-inline' | The feedback UI is styled with inline styles. Not having this directive results unstyled HTML elements. |
If you are including the Reflag tracking SDK with a `<script>`-tag from `jsdelivr.net` you will also need:
| Directive | Values | Reason |
| --------------- | ---------------------------------------------------- | ------------------------------- |
| script-src-elem | [https://cdn.jsdelivr.net](https://cdn.jsdelivr.net) | Loads the Reflag SDK from a CDN |
## License
> MIT License
> Copyright (c) 2025 Bucket ApS
```
--------------------------------------------------------------------------------
/packages/react-sdk/README.md:
--------------------------------------------------------------------------------
```markdown
# Reflag React SDK
React client side library for [Reflag.com](https://reflag.com)
Reflag supports flag toggling, tracking flag usage, [requesting feedback](#userequestfeedback) on features, and [remotely configuring flags](#remote-config).
The Reflag React SDK comes with a [built-in toolbar](https://docs.reflag.com/supported-languages/browser-sdk#toolbar) which appears on `localhost` by default.
## Install
Install via npm:
```shell
npm i @reflag/react-sdk
```
## Get started
### 1. Add the `ReflagProvider` context provider
Add the `ReflagProvider` context provider to your application:
**Example:**
```tsx
import { ReflagProvider } from "@reflag/react-sdk";
<ReflagProvider
publishableKey="{YOUR_PUBLISHABLE_KEY}"
context={{
company: { id: "acme_inc", plan: "pro" },
user: { id: "john doe" },
}}
loadingComponent={<Loading />}
>
{/* children here are shown when loading finishes or immediately if no `loadingComponent` is given */}
</ReflagProvider>;
```
### 2. Create a new flag and set up type safety
Install the Reflag CLI:
```shell
npm i --save-dev @reflag/cli
```
Run `npx reflag new` to create your first flag!
On the first run, it will sign into Reflag and set up type generation for your project:
```shell
❯ npx reflag new
Opened web browser to facilitate login: https://app.reflag.com/api/oauth/cli/authorize
Welcome to ◪ Reflag!
? Where should we generate the types? gen/flags.d.ts
? What is the output format? react
✔ Configuration created at reflag.config.json.
Creating flag for app Slick app.
? New flag name: Huddle
? New flag key: huddle
✔ Created flag Huddle with key huddle (https://app.reflag.com/features/huddles)
✔ Generated react types in gen/flags.d.ts.
```
> [!Note]
> By default, types will be generated in `gen/flags.d.ts`.
> The default `tsconfig.json` file `include`s this file by default, but if your `tsconfig.json` is different, make sure the file is covered in the `include` property.
### 3. Use `useFlag(<flagKey>)` to get flag status
Using the `useFlag` hook from your components lets you toggle flags on/off and track flag usage:
**Example:**
```tsx
function StartHuddleButton() {
const {
isEnabled, // boolean indicating if the flag is enabled
track, // track usage of the flag
} = useFlag("huddle");
if (!isEnabled) {
return null;
}
return <button onClick={track}>Start huddle!</button>;
}
```
`useFlag` can help you do much more. See a full example for `useFlag` [see below](#useflag).
## Setting context
Reflag determines which flags are active for a given `user`, `company`, or `other` context.
You can pass these to the `ReflagProvider` using the `context` prop.
### Using the `context` prop
```tsx
<ReflagProvider
publishableKey={YOUR_PUBLISHABLE_KEY}
context={{
user: { id: "user_123", name: "John Doe", email: "[email protected]" },
company: { id: "company_123", name: "Acme, Inc" },
other: { source: "web" },
}}
>
<LoadingReflag>
{/* children here are shown when loading finishes */}
</LoadingReflag>
</ReflagProvider>
```
### Legacy individual props (deprecated)
For backward compatibility, you can still use individual props, but these are deprecated and will be removed in the next major version:
```tsx
<ReflagProvider
publishableKey={YOUR_PUBLISHABLE_KEY}
user={{ id: "user_123", name: "John Doe", email: "[email protected]" }}
company={{ id: "company_123", name: "Acme, Inc" }}
otherContext={{ source: "web" }}
>
<LoadingReflag>
{/* children here are shown when loading finishes */}
</LoadingReflag>
</ReflagProvider>
```
> [!Important]
> The `user`, `company`, and `otherContext` props are deprecated. Use the `context` prop instead, which provides the same functionality in a more structured way.
### Context requirements
If you supply `user` or `company` objects, they must include at least the `id` property otherwise they will be ignored in their entirety.
In addition to the `id`, you must also supply anything additional that you want to be able to evaluate flag targeting rules against.
Attributes which are not properties of the `user` or `company` can be supplied using the `other` property.
Attributes cannot be nested (multiple levels) and must be either strings, numbers or booleans.
A number of special attributes exist:
- `name` -- display name for `user`/`company`,
- `email` -- the email of the user,
- `avatar` -- the URL for `user`/`company` avatar image.
To retrieve flags along with their targeting information, use `useFlag(key: string)` hook (described in a section below).
Note that accessing `isEnabled` on the object returned by `useFlag()` automatically
generates a `check` event.
## Remote config
Remote config is a dynamic and flexible approach to configuring flag behavior outside of your app – without needing to re-deploy it.
Similar to `isEnabled`, each flag accessed using the `useFlag()` hook, has a `config` property. This configuration is managed from within Reflag. It is managed similar to the way access to flags is managed, but instead of the
binary `isEnabled` you can have multiple configuration values which are given to different user/companies.
### Get started with Remote config
1. Update your flag definitions:
```typescript
import "@reflag/react-sdk";
// Define your flags by extending the `Flags` interface in @reflag/react-sdk
declare module "@reflag/react-sdk" {
interface Flags {
huddle: {
// change from `boolean` to an object which sets
// a type for the remote config for `questionnaire`
maxTokens: number;
model: string;
};
}
}
```
```ts
const {
isEnabled,
config: { key, payload },
} = useFlag("huddles");
// isEnabled: true,
// key: "gpt-3.5",
// payload: { maxTokens: 10000, model: "gpt-3.5-beta1" }
```
`key` is mandatory for a config, but if a flag has no config or no config value was matched against the context, the `key` will be `undefined`. Make sure to check against this case when trying to use the configuration in your application. `payload` is an optional JSON value for arbitrary configuration needs.
Note that, similar to `isEnabled`, accessing `config` on the object returned by `useFlag()` automatically
generates a `check` event.
## Toolbar
The Reflag Toolbar is great for toggling flags on/off for yourself to ensure that everything works both when a flag is on and when it's off.
<img width="310" height="265" alt="Toolbar" src="https://github.com/user-attachments/assets/61492915-0d30-446d-a163-3eb16d9024b2" />
The toolbar will automatically appear on `localhost`. However, it can also be incredibly useful in production. You have full control over when it appears through the `toolbar` configuration option passed to the ReflagProvider.
You can pass a simple boolean to force the toolbar to appear/disappear:
```ts
<ReflagProvider
...
// show the toolbar even in production if the user is an internal/admin user
toolbar={user?.isInternal}
...
});
```
## Server-side rendering and bootstrapping
For server-side rendered applications, you can eliminate the initial network request by bootstrapping the client with pre-fetched flag data using the `ReflagBootstrappedProvider`.
### Using `ReflagBootstrappedProvider`
The `<ReflagBootstrappedProvider>` component is a specialized version of `ReflagProvider` designed for server-side rendering and preloaded flag scenarios. Instead of fetching flags on initialization, it uses pre-fetched flags, resulting in faster initial page loads and better SSR compatibility.
```tsx
import { useState, useEffect } from "react";
import { BootstrappedFlags } from "@reflag/react-sdk";
interface BootstrapData {
user: User;
flags: BootstrappedFlags;
}
function useBootstrap() {
const [data, setData] = useState<BootstrapData | null>(null);
useEffect(() => {
fetch("/bootstrap")
.then((res) => res.json())
.then(setData);
}, []);
return data;
}
// Usage in your app
function App() {
const { user, flags } = useBootstrap();
return (
<AuthProvider user={user}>
<ReflagBootstrappedProvider
publishableKey="your-publishable-key"
flags={flags}
>
<Router />
</ReflagBootstrappedProvider>
</AuthProvider>
);
}
```
### Server-side endpoint setup
Create an endpoint that provides bootstrap data to your client application:
```typescript
// server.js or your Express app
import { ReflagClient as ReflagNodeClient } from "@reflag/node-sdk";
const reflagClient = new ReflagNodeClient({
secretKey: process.env.REFLAG_SECRET_KEY,
});
await reflagClient.initialize();
app.get("/bootstrap", (req, res) => {
const user = getUser(req); // Get user from your auth system
const company = getCompany(req); // Get company from your auth system
const flags = reflagClient.getFlagsForBootstrap({
user: { id: "user123", name: "John Doe", email: "[email protected]" },
company: { id: "company456", name: "Acme Inc", plan: "enterprise" },
other: { source: "web" },
});
res.status(200).json({
user,
flags,
});
});
```
### Next.js Page Router SSR example
For Next.js applications using server-side rendering, you can pre-fetch flags in `getServerSideProps`:
```typescript
// pages/index.tsx
import { GetServerSideProps } from "next";
import { ReflagClient as ReflagNodeClient } from "@reflag/node-sdk";
import { ReflagBootstrappedProvider, BootstrappedFlags, useFlag } from "@reflag/react-sdk";
interface PageProps {
bootstrapData: BootstrappedFlags;
}
export const getServerSideProps: GetServerSideProps = async (context) => {
const serverClient = new ReflagNodeClient({
secretKey: process.env.REFLAG_SECRET_KEY
});
await serverClient.initialize();
const user = await getUserFromSession(context.req);
const company = await getCompanyFromUser(user);
const bootstrapData = serverClient.getFlagsForBootstrap({
user: { id: "user123", name: "John Doe", email: "[email protected]" },
company: { id: "company456", name: "Acme Inc", plan: "enterprise" },
other: { page: "homepage" }
});
return { props: { bootstrapData } };
};
export default function HomePage({ bootstrapData }: PageProps) {
return (
<ReflagBootstrappedProvider
publishableKey={process.env.NEXT_PUBLIC_REFLAG_PUBLISHABLE_KEY}
flags={bootstrapData}
>
<HuddleFeature />
</ReflagBootstrappedProvider>
);
}
function HuddleFeature() {
const { isEnabled, track, config } = useFlag("huddle");
if (!isEnabled) return null;
return (
<div>
<h2>Start a Huddle</h2>
<p>Max participants: {config.payload?.maxParticipants ?? 10}</p>
<p>Video quality: {config.payload?.videoQuality ?? "standard"}</p>
<button onClick={track}>Start Huddle</button>
</div>
);
}
```
This approach eliminates loading states and improves performance by avoiding the initial flags API call.
### Next.js App Router example
For Next.js applications using the App Router (Next.js 13+), you can pre-fetch flags in Server Components and pass them to client components:
```typescript
// app/layout.tsx (Server Component)
import { ReflagClient as ReflagNodeClient } from "@reflag/node-sdk";
import { ClientProviders } from "./providers";
async function getBootstrapData() {
const serverClient = new ReflagNodeClient({
secretKey: process.env.REFLAG_SECRET_KEY!
});
await serverClient.initialize();
// In a real app, you'd get user/company from your auth system
const bootstrapData = serverClient.getFlagsForBootstrap({
user: { id: "user123", name: "John Doe", email: "[email protected]" },
company: { id: "company456", name: "Acme Inc", plan: "enterprise" },
other: { source: "web" }
});
return bootstrapData;
}
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const bootstrapData = await getBootstrapData();
return (
<html lang="en">
<body>
<ClientProviders bootstrapData={bootstrapData}>
{children}
</ClientProviders>
</body>
</html>
);
}
```
```typescript
// app/providers.tsx (Client Component)
"use client";
import { ReflagBootstrappedProvider, BootstrappedFlags } from "@reflag/react-sdk";
interface ClientProvidersProps {
children: React.ReactNode;
bootstrapData: BootstrappedFlags;
}
export function ClientProviders({ children, bootstrapData }: ClientProvidersProps) {
return (
<ReflagBootstrappedProvider
publishableKey={process.env.NEXT_PUBLIC_REFLAG_PUBLISHABLE_KEY!}
flags={bootstrapData}
>
{children}
</ReflagBootstrappedProvider>
);
}
```
```typescript
// app/page.tsx (Server Component)
import { HuddleFeature } from "./huddle-feature";
export default function HomePage() {
return (
<main>
<h1>My App</h1>
<HuddleFeature />
</main>
);
}
```
```typescript
// app/huddle-feature.tsx (Client Component)
"use client";
import { useFlag } from "@reflag/react-sdk";
export function HuddleFeature() {
const { isEnabled, track, config } = useFlag("huddle");
if (!isEnabled) return null;
return (
<div>
<h2>Start a Huddle</h2>
<p>Max participants: {config.payload?.maxParticipants ?? 10}</p>
<p>Video quality: {config.payload?.videoQuality ?? "standard"}</p>
<button onClick={track}>Start Huddle</button>
</div>
);
}
```
This App Router approach leverages Server Components for server-side flag fetching while using Client Components only where React state and hooks are needed.
## `<ReflagClientProvider>` component
The `<ReflagClientProvider>` is a lower-level component that accepts a pre-initialized `ReflagClient` instance. This is useful for advanced use cases where you need full control over client initialization or want to share a client instance across multiple parts of your application.
### Usage
```tsx
import { ReflagClient } from "@reflag/browser-sdk";
import { ReflagClientProvider } from "@reflag/react-sdk";
// Initialize the client yourself
const client = new ReflagClient({
publishableKey: "your-publishable-key",
user: { id: "user123", name: "John Doe" },
company: { id: "company456", name: "Acme Inc" },
// ... other configuration options
});
// Initialize the client
await client.initialize();
function App() {
return (
<ReflagClientProvider client={client} loadingComponent={<Loading />}>
<Router />
</ReflagClientProvider>
);
}
```
### Props
The `ReflagClientProvider` accepts the following props:
- `client`: A pre-initialized `ReflagClient` instance
- `loadingComponent`: Optional React component to show while the client is initializing (same as `ReflagProvider`)
> [!Note]
> Most applications should use `ReflagProvider` or `ReflagBootstrappedProvider` instead of `ReflagClientProvider`. Only use this component when you need the advanced control it provides.
## `<ReflagProvider>` component
The `<ReflagProvider>` initializes the Reflag SDK, fetches flags and starts listening for automated feedback survey events. The component can be configured using a number of props:
- `publishableKey` is used to connect the provider to an _environment_ on Reflag. Find your `publishableKey` under [environment settings](https://app.reflag.com/env-current/settings/app-environments) in Reflag,
- `context` (recommended): An object containing `user`, `company`, and `other` properties that make up the evaluation context used to determine if a flag is enabled or not. `company` and `user` contexts are automatically transmitted to Reflag servers so the Reflag app can show you which companies have access to which flags etc.
- `company`, `user` and `other` (deprecated): Individual props for context. These are deprecated in favor of the `context` prop and will be removed in the next major version.
> [!Note]
> If you specify `company` and/or `user` they must have at least the `id` property, otherwise they will be ignored in their entirety. You should also supply anything additional you want to be able to evaluate flag targeting against,
- `fallbackFlags`: A list of strings which specify which flags to consider enabled if the SDK is unable to fetch flags. Can be provided in two formats:
```ts
// Simple array of flag keys
fallbackFlags={["flag1", "flag2"]}
// Or with configuration overrides
fallbackFlags: {
"flag1": true, // just enable the flag
"flag2": { // enable with configuration
key: "variant-a",
payload: {
limit: 100,
mode: "test"
}
}
}
```
- `timeoutMs`: Timeout in milliseconds when fetching flags from the server.
- `staleWhileRevalidate`: If set to `true`, stale flags will be returned while refetching flags in the background.
- `expireTimeMs`: If set, flags will be cached between page loads for this duration (in milliseconds).
- `staleTimeMs`: Maximum time (in milliseconds) that stale flags will be returned if `staleWhileRevalidate` is true and new flags cannot be fetched.
- `offline`: Provide this option when testing or in local development environments to avoid contacting Reflag servers.
- `loadingComponent` lets you specify an React component to be rendered instead of the children while the Reflag provider is initializing. If you want more control over loading screens, `useFlag()` and `useIsLoading` returns `isLoading` which you can use to customize the loading experience.
- `enableTracking`: Set to `false` to stop sending tracking events and user/company updates to Reflag. Useful when you're impersonating a user (defaults to `true`),
- `apiBaseUrl`: Optional base URL for the Reflag API. Use this to override the default API endpoint,
- `appBaseUrl`: Optional base URL for the Reflag application. Use this to override the default app URL,
- `sseBaseUrl`: Optional base URL for Server-Sent Events. Use this to override the default SSE endpoint,
- `debug`: Set to `true` to enable debug logging to the console,
- `toolbar`: Optional [configuration](https://docs.reflag.com/supported-languages/browser-sdk/globals#toolbaroptions) for the Reflag toolbar,
- `feedback`: Optional configuration for feedback collection
## `<ReflagBootstrappedProvider>` component
The `<ReflagBootstrappedProvider>` is a specialized version of the `ReflagProvider` that uses pre-fetched flag data instead of making network requests during initialization. This is ideal for server-side rendering scenarios.
The component accepts the following props:
- `flags`: Pre-fetched flags data of type `BootstrappedFlags` obtained from the Node SDK's `getFlagsForBootstrap()` method. This contains both the context (user, company, other) and the flags data.
- All other props available in [`ReflagProvider`](#reflagprovider-component) are supported except `context`, `user`, `company`, and `other` (which are extracted from `flags.context`).
**Example:**
```tsx
import {
ReflagBootstrappedProvider,
BootstrappedFlags,
} from "@reflag/react-sdk";
interface AppProps {
bootstrapData: BootstrappedFlags;
}
function App({ bootstrapData }: AppProps) {
return (
<ReflagBootstrappedProvider
publishableKey="your-publishable-key"
flags={bootstrapData}
loadingComponent={<Loading />}
debug={process.env.NODE_ENV === "development"}
>
<Router />
</ReflagBootstrappedProvider>
);
}
```
> [!Note]
> When using `ReflagBootstrappedProvider`, the context (user, company, and other) is extracted from the `flags.context` property and doesn't need to be passed separately.
## Hooks
### `useFlag()`
Returns the state of a given flag for the current context. The hook provides type-safe access to flags and their configurations.
```tsx
import { useFlag } from "@reflag/react-sdk";
import { Loading } from "./Loading";
function StartHuddleButton() {
const {
isLoading, // true while flags are being loaded
isEnabled, // boolean indicating if the flag is enabled
config: {
// flag configuration
key, // string identifier for the config variant
payload, // type-safe configuration object
},
track, // function to track flag usage
requestFeedback, // function to request feedback for this flag
} = useFlag("huddle");
if (isLoading) {
return <Loading />;
}
if (!isEnabled) {
return null;
}
return (
<>
<button onClick={track}>Start huddle!</button>
<button
onClick={(e) =>
requestFeedback({
title: payload?.question ?? "How do you like the Huddles feature?",
position: {
type: "POPOVER",
anchor: e.currentTarget as HTMLElement,
},
})
}
>
Give feedback!
</button>
</>
);
}
```
### `useTrack()`
`useTrack()` lets you send custom events to Reflag. Use this whenever a user _uses_ a feature. These events can be used to analyze feature usage in Reflag.
```tsx
import { useTrack } from "@reflag/react-sdk";
function StartHuddle() {
const { track } = useTrack();
<div>
<button onClick={() => track("Huddle Started", { huddleType: "voice" })}>
Start voice huddle!
</button>
</div>;
}
```
### `useRequestFeedback()`
`useRequestFeedback()` returns a function that lets you open up a dialog to ask for feedback on a specific feature. This is useful for collecting targeted feedback about specific features as part of roll out. See [Automated Feedback Surveys](https://docs.reflag.com/product-handbook/live-satisfaction) for how to do this automatically, without code.
When using the `useRequestFeedback` you must pass the flag key to `requestFeedback`.
The example below shows how to use `position` to ensure the popover appears next to the "Give feedback!" button.
```tsx
import { useRequestFeedback } from "@reflag/react-sdk";
function FeedbackButton() {
const requestFeedback = useRequestFeedback();
return (
<button
onClick={(e) =>
requestFeedback({
flagKey: "huddle-flag",
title: "How satisfied are you with file uploads?",
position: {
type: "POPOVER",
anchor: e.currentTarget as HTMLElement,
},
// Optional custom styling
style: {
theme: "light",
primaryColor: "#007AFF",
},
})
}
>
Give feedback!
</button>
);
}
```
See the [Feedback Documentation](https://github.com/reflagcom/javascript/blob/main/packages/browser-sdk/FEEDBACK.md#manual-feedback-collection) for more information on `requestFeedback` options.
### `useSendFeedback()`
Returns a function that lets you send feedback to Reflag. This is useful if you've manually collected feedback through your own UI and want to send it to Reflag.
```tsx
import { useSendFeedback } from "@reflag/react-sdk";
function CustomFeedbackForm() {
const sendFeedback = useSendFeedback();
const handleSubmit = async (data: FormData) => {
await sendFeedback({
flagKey: "reflag-flag-key",
score: parseInt(data.get("score") as string),
comment: data.get("comment") as string,
});
};
return <form onSubmit={handleSubmit}>...</form>;
}
```
### `useUpdateUser()`, `useUpdateCompany()` and `useUpdateOtherContext()`
These hooks return functions that let you update the attributes for the currently set user, company, or other context. Updates to user/company are stored remotely and affect flag targeting, while "other" context updates only affect the current session.
```tsx
import {
useUpdateUser,
useUpdateCompany,
useUpdateOtherContext,
} from "@reflag/react-sdk";
function FlagOptIn() {
const updateUser = useUpdateUser();
const updateCompany = useUpdateCompany();
const updateOtherContext = useUpdateOtherContext();
const handleUserUpdate = async () => {
await updateUser({
role: "admin",
betaFlags: "enabled",
});
};
const handleCompanyUpdate = async () => {
await updateCompany({
plan: "enterprise",
employees: 500,
});
};
const handleContextUpdate = async () => {
await updateOtherContext({
currentWorkspace: "workspace-123",
theme: "dark",
});
};
return (
<div>
<button onClick={handleUserUpdate}>Update User</button>
<button onClick={handleCompanyUpdate}>Update Company</button>
<button onClick={handleContextUpdate}>Update Context</button>
</div>
);
}
```
### `useClient()`
Returns the `ReflagClient` used by the `ReflagProvider`. The client offers more functionality that
is not directly accessible thorough the other hooks.
```tsx
import { useClient } from "@reflag/react-sdk";
function LoggingWrapper({ children }: { children: ReactNode }) {
const client = useClient();
console.log(client.getContext());
return children;
}
```
### `useIsLoading()`
Returns the loading state of the flags in the `ReflagClient`.
Initially, the value will be `true` if no bootstrap flags have been provided and the client has not be initialized.
```tsx
import { useIsLoading } from "@reflag/react-sdk";
import { Spinner } from "./Spinner";
function LoadingWrapper({ children }: { children: ReactNode }) {
const isLoading = useIsLoading();
if (isLoading) {
return <Spinner />;
}
return children;
}
```
### `useOnEvent()`
Attach a callback handler to client events to act on changes. It automatically disposes itself on unmount.
```tsx
import { useOnEvent } from "@reflag/react-sdk";
function LoggingWrapper({ children }: { children: ReactNode }) {
useOnEvent("flagsUpdated", (newFlags) => {
console.log(newFlags);
});
return children;
}
```
## Migrating from Bucket SDK
If you have been using the Bucket SDKs, the following list will help you migrate to Reflag SDK:
- `Bucket*` classes, and types have been renamed to `Reflag*` (e.g. `BucketClient` is now `ReflagClient`)
- `Feature*` classes, and types have been renamed to `Flag*` (e.g. `Feature` is now `Flag`, `RawFeatures` is now `RawFlags`)
- When using strongly-typed flags, the new `Flags` interface replaced `Features` interface
- All methods that contained `feature` in the name have been renamed to use the `flag` terminology (e.g. `getFeature` is `getFlag`)
- The `fallbackFeatures` property in client constructor and configuration files has been renamed to `fallbackFlags`
- `featureKey` has been renamed to `flagKey` in all methods that accepts that argument
- The SDKs will not emit `evaluate` and `evaluate-config` events anymore
- The new cookies that are stored in the client's browser are now `reflag-*` prefixed instead of `bucket-*`
- The `featuresUpdated` hook has been renamed to `flagsUpdated`
- The `checkIsEnabled` and `checkConfig` hooks have been removed, use `check` from now on
To ease in transition to Reflag SDK, some of the old methods have been preserved as aliases to the new methods:
- `getFeature` method is an alias for `getFlag`
- `getFeatures` method is an alias for `getFlags`
- `useFeature` method is an alias for `useFlag`
- `featuresUpdated` hook is an alias for `flagsUpdated`
If you are running with strict Content Security Policies active on your website, you will need change them as follows:
- `connect-src https://front.bucket.co` to `connect-src https://front.reflag.com`
## Content Security Policy (CSP)
See [CSP](https://github.com/reflagcom/javascript/blob/main/packages/browser-sdk/README.md#content-security-policy-csp) for info on using Reflag React SDK with CSP
## License
MIT License
Copyright (c) 2025 Bucket ApS
```
--------------------------------------------------------------------------------
/packages/node-sdk/README.md:
--------------------------------------------------------------------------------
```markdown
# Reflag Node.js SDK
Node.js, JavaScript/TypeScript client for [Reflag.com](https://reflag.com).
Reflag supports flag toggling, tracking flag usage, collecting feedback on features, and [remotely configuring flags](#remote-config).
## Installation
Install using your favorite package manager:
{% tabs %}
{% tab title="npm" %}
```sh
npm i @reflag/node-sdk
```
{% endtab %}
{% tab title="yarn" %}
```sh
yarn add @reflag/node-sdk
```
{% endtab %}
{% tab title="bun" %}
```sh
bun add @reflag/node-sdk
```
{% endtab %}
{% tab title="pnpm" %}
```sh
pnpm add @reflag/node-sdk
```
{% endtab %}
{% tab title="deno" %}
```sh
deno add npm:@reflag/node-sdk
```
{% endtab %}
{% endtabs %}
Other supported languages/frameworks are in the [Supported languages](https://docs.reflag.com/quickstart/supported-languages) documentation pages.
You can also [use the HTTP API directly](https://docs.reflag.com/api/http-api)
## Basic usage
To get started you need to obtain your secret key from the [environment settings](https://app.reflag.com/env-current/settings/app-environments)
in Reflag.
> [!CAUTION]
> Secret keys are meant for use in server side SDKs only. Secret keys offer the users the ability to obtain
> information that is often sensitive and thus should not be used in client-side applications.
Reflag will load settings through the various environment variables automatically (see [Configuring](#configuring) below).
1. Find the Reflag secret key for your development environment under [environment settings](https://app.reflag.com/env-current/settings/app-environments) in Reflag.
2. Set `REFLAG_SECRET_KEY` in your `.env` file
3. Create a `reflag.ts` file containing the following:
```typescript
import { ReflagClient } from "@reflag/node-sdk";
// Create a new instance of the client with the secret key. Additional options
// are available, such as supplying a logger and other custom properties.
//
// We recommend that only one global instance of `client` should be created
// to avoid multiple round-trips to our servers.
export const reflagClient = new ReflagClient();
// Initialize the client and begin fetching flag targeting definitions.
// You must call this method prior to any calls to `getFlags()`,
// otherwise an empty object will be returned.
reflagClient.initialize().then(() => {
console.log("Reflag initialized!");
});
```
Once the client is initialized, you can obtain flags along with the `isEnabled`
status to indicate whether the flag is targeted for this user/company:
> [!IMPORTANT]
> If `user.id` or `company.id` is not given, the whole `user` or `company` object is ignored.
```typescript
// configure the client
const boundClient = reflagClient.bindClient({
user: {
id: "john_doe",
name: "John Doe",
email: "[email protected]",
avatar: "https://example.com/users/jdoe",
},
company: {
id: "acme_inc",
name: "Acme, Inc.",
avatar: "https://example.com/companies/acme",
},
});
// get the huddle flag using company, user and custom context to
// evaluate the targeting.
const { isEnabled, track, config } = boundClient.getFlag("huddle");
if (isEnabled) {
// this is your flag gated code ...
// send an event when the flag is used:
track();
if (config?.key === "zoom") {
// this code will run if a given remote configuration
// is set up.
}
// CAUTION: if you plan to use the event for automated feedback surveys
// call `flush` immediately after `track`. It can optionally be awaited
// to guarantee the sent happened.
boundClient.flush();
}
```
You can also use the `getFlags()` method which returns a map of all flags:
```typescript
// get the current flags (uses company, user and custom context to
// evaluate the flags).
const flags = boundClient.getFlags();
const bothEnabled = flags.huddle?.isEnabled && flags.voiceHuddle?.isEnabled;
```
## High performance flag targeting
The SDK contacts the Reflag servers when you call `initialize()`
and downloads the flags with their targeting rules.
These rules are then matched against the user/company information you provide
to `getFlags()` (or through `bindClient(..).getFlags()`). That means the
`getFlags()` call does not need to contact the Reflag servers once
`initialize()` has completed. `ReflagClient` will continue to periodically
download the targeting rules from the Reflag servers in the background.
### Batch Operations
The SDK automatically batches operations like user/company updates and flag tracking events to minimize API calls.
The batch buffer is configurable through the client options:
```typescript
const client = new ReflagClient({
batchOptions: {
maxSize: 100, // Maximum number of events to batch
intervalMs: 1000, // Flush interval in milliseconds
},
});
```
You can manually flush the batch buffer at any time:
```typescript
await client.flush();
```
> [!TIP]
> It's recommended to call `flush()` before your application shuts down to ensure all events are sent.
### Rate Limiting
The SDK includes automatic rate limiting for flag events to prevent overwhelming the API.
Rate limiting is applied per unique combination of flag key and context. The rate limiter window size is configurable:
```typescript
const client = new ReflagClient({
rateLimiterOptions: {
windowSizeMs: 60000, // Rate limiting window size in milliseconds
},
});
```
### Flag definitions
Flag definitions include the rules needed to determine which flags should be enabled and which config values should be applied to any given user/company.
Flag definitions are automatically fetched when calling `initialize()`.
They are then cached and refreshed in the background.
It's also possible to get the currently in use flag definitions:
```typescript
import fs from "fs";
const client = new ReflagClient();
const flagDefs = await client.getFlagDefinitions();
// [{
// key: "huddle",
// description: "Live voice conversations with colleagues."
// flag: { ... }
// config: { ... }
// }]
```
## Bootstrapping client-side applications
The `getFlagsForBootstrap()` method is designed for server-side rendering (SSR) scenarios where you need to pass flag data to client-side applications. This method returns raw flag data without wrapper functions, making it suitable for serialization and client-side hydration.
```typescript
const client = new ReflagClient();
await client.initialize();
// Get flags for bootstrapping with full context
const { context, flags } = client.getFlagsForBootstrap({
user: {
id: "user123",
name: "John Doe",
email: "[email protected]",
},
company: {
id: "company456",
name: "Acme Inc",
plan: "enterprise",
},
other: {
source: "web",
platform: "desktop",
},
});
// Pass this data to your client-side application
// The flags object contains raw flag data suitable for JSON serialization
console.log(flags);
// {
// "huddle": {
// "key": "huddle",
// "isEnabled": true,
// "config": {
// "key": "enhanced",
// "payload": { "maxParticipants": 50, "videoQuality": "hd" },
// }
// }
// }
```
You can also use a bound client for simpler API:
```typescript
const boundClient = client.bindClient({
user: { id: "user123", name: "John Doe", email: "[email protected]" },
company: { id: "company456", name: "Acme Inc", plan: "enterprise" },
});
const { context, flags } = boundClient.getFlagsForBootstrap();
```
### Key differences from `getFlags()`
- **Raw data**: Returns plain objects without `track()` functions, making them JSON serializable
- **Context included**: Returns both the evaluated flags and the context used for evaluation
- **Bootstrapping focus**: Designed specifically for passing data to client-side applications
## Edge-runtimes like Cloudflare Workers
To use the Reflag NodeSDK with Cloudflare workers, set the `node_compat` flag [in your wrangler file](https://developers.cloudflare.com/workers/runtime-apis/nodejs/#get-started).
Instead of using `ReflagClient`, use `EdgeClient` and make sure you call `ctx.waitUntil(reflag.flush());` before returning from your worker function.
```typescript
import { EdgeClient } from "@reflag/node-sdk";
// set the REFLAG_SECRET_KEY environment variable or pass the secret key in the constructor
const reflag = new EdgeClient();
export default {
async fetch(request, _env, ctx): Promise<Response> {
// initialize the client and wait for it to complete
// if the client was initialized on a previous invocation, this is a no-op.
await reflag.initialize();
const flags = reflag.getFlags({
user: { id: "userId" },
company: { id: "companyId" },
});
// ensure all events are flushed and any requests to refresh the flag cache
// have completed after the response is sent
ctx.waitUntil(reflag.flush());
return new Response(
`Flags for user ${userId} and company ${companyId}: ${JSON.stringify(flags, null, 2)}`,
);
},
};
```
See [examples/cloudflare-worker](https://github.com/reflagcom/javascript/tree/main/packages/node-sdk/examples/cloudflare-worker/src/index.ts) for a deployable example.
Reflag maintains a cached set of flag definitions in the memory of your worker which it uses to decide which flags to turn on for which users/companies.
The SDK caches flag definitions in memory for fast performance. The first request to a new worker instance fetches definitions from Reflag's servers, while subsequent requests use the cache. When the cache expires, it's updated in the background. `ctx.waitUntil(reflag.flush())` ensures completion of the background work, so response times are not affected. This background work may increase wall-clock time for your worker, but it will not measurably increase billable CPU time on platforms like Cloudflare.
## Error Handling
The SDK is designed to fail gracefully and never throw exceptions to the caller. Instead, it logs errors and provides
fallback behavior:
1. **Flag Evaluation Failures**:
```typescript
const { isEnabled } = client.getFlag("my-flag");
// If flag evaluation fails, isEnabled will be false
```
2. **Network Errors**:
```typescript
// Network errors during tracking are logged but don't affect your application
const { isEnabled, track } = client.getFlag("my-flag");
if (isEnabled) {
// network errors are caught internally and logged and never bubbled up to your application
// no need to try/catch around "track" or "getFlag"
await track();
}
```
3. **Missing Context**:
```typescript
// The SDK tracks missing context fields but continues operation
const flags = client.getFlags({
user: { id: "user123" },
// Missing company context will be logged but won't cause errors
});
```
4. **Offline Mode**:
```typescript
// In offline mode, the SDK uses flag overrides
const client = new ReflagClient({
offline: true,
flagOverrides: () => ({
"my-flag": true,
}),
});
```
The SDK logs all errors with appropriate severity levels. You can customize logging by providing your own logger:
```typescript
const client = new ReflagClient({
logger: {
debug: (msg) => console.debug(msg),
info: (msg) => console.info(msg),
warn: (msg) => console.warn(msg),
error: (msg, error) => {
console.error(msg, error);
// Send to your error tracking service
errorTracker.capture(error);
},
},
});
```
## Remote config
Remote config is a dynamic and flexible approach to configuring flag behavior outside of your app – without needing to re-deploy it.
Similar to `isEnabled`, each flag has a `config` property. This configuration is managed from within Reflag.
It is managed similar to the way access to flags is managed, but instead of the binary `isEnabled` you can have
multiple configuration values which are given to different user/companies.
```ts
const flags = reflagClient.getFlags();
// {
// huddle: {
// isEnabled: true,
// targetingVersion: 42,
// config: {
// key: "gpt-3.5",
// payload: { maxTokens: 10000, model: "gpt-3.5-beta1" }
// }
// }
// }
```
`key` is mandatory for a config, but if a flag has no config or no config value was matched against the context, the `key` will be `undefined`. Make sure to check against this case when trying to use the configuration in your application. `payload` is an optional JSON value for arbitrary configuration needs.
Just as `isEnabled`, accessing `config` on the object returned by `getFlags` does not automatically
generate a `check` event, contrary to the `config` property on the object returned by `getFlag`.
## Configuring
The Reflag `Node.js` SDK can be configured through environment variables,
a configuration file on disk or by passing options to the `ReflagClient`
constructor. By default, the SDK searches for `reflag.config.json` in the
current working directory.
| Option | Type | Description | Env Var |
| --------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
| `secretKey` | string | The secret key used for authentication with Reflag's servers. | REFLAG_SECRET_KEY |
| `logLevel` | string | The log level for the SDK (e.g., `"DEBUG"`, `"INFO"`, `"WARN"`, `"ERROR"`). Default: `INFO` | REFLAG_LOG_LEVEL |
| `offline` | boolean | Operate in offline mode. Default: `false`, except in tests it will default to `true` based off of the `TEST` env. var. | REFLAG_OFFLINE |
| `apiBaseUrl` | string | The base API URL for the Reflag servers. | REFLAG_API_BASE_URL |
| `flagOverrides` | Record<string, boolean> | An object specifying flag overrides for testing or local development. See [examples/express/app.test.ts](https://github.com/reflagcom/javascript/tree/main/packages/node-sdk/examples/express/app.test.ts) for how to use `flagOverrides` in tests. | REFLAG_FLAGS_ENABLED, REFLAG_FLAGS_DISABLED |
| `configFile` | string | Load this config file from disk. Default: `reflag.config.json` | REFLAG_CONFIG_FILE |
> [!NOTE] > `REFLAG_FLAGS_ENABLED` and `REFLAG_FLAGS_DISABLED` are comma separated lists of flags which will be enabled or disabled respectively.
`reflag.config.json` example:
```json
{
"secretKey": "...",
"logLevel": "warn",
"offline": true,
"apiBaseUrl": "https://proxy.slick-demo.com",
"flagOverrides": {
"huddles": true,
"voiceChat": { "isEnabled": false },
"aiAssist": {
"isEnabled": true,
"config": {
"key": "gpt-4.0",
"payload": {
"maxTokens": 50000
}
}
}
}
}
```
When using a `reflag.config.json` for local development, make sure you add it to your
`.gitignore` file. You can also set these options directly in the `ReflagClient`
constructor. The precedence for configuration options is as follows, listed in the
order of importance:
1. Options passed along to the constructor directly,
2. Environment variable,
3. The config file.
## Type safe flags
To get type checked flags, install the Reflag CLI:
```sh
npm i --save-dev @reflag/cli
```
then generate the types:
```sh
npx reflag flags types
```
This will generate a `reflag.d.ts` containing all your flags.
Any flag look ups will now be checked against the flags that exist in Reflag.
Here's an example of a failed type check:
```typescript
import { ReflagClient } from "@reflag/node-sdk";
export const reflagClient = new ReflagClient();
reflagClient.initialize().then(() => {
console.log("Reflag initialized!");
// TypeScript will catch this error: "invalid-flag" doesn't exist
reflagClient.getFlag("invalid-flag");
const {
isEnabled,
config: { payload },
} = reflagClient.getFlag("create-todos");
});
```

This is an example of a failed config payload check:
```typescript
reflagClient.initialize().then(() => {
// TypeScript will catch this error as well: "minLength" is not part of the payload.
if (isEnabled && todo.length > config.payload.minLength) {
// ...
}
});
```

## Testing
When writing tests that cover code with flags, you can toggle flags on/off programmatically to test the different behavior.
`reflag.ts`:
```typescript
import { ReflagClient } from "@reflag/node-sdk";
export const reflag = new ReflagClient();
```
`app.test.ts`:
```typescript
import { reflag } from "./reflag.ts";
beforeAll(async () => await reflag.initialize());
afterEach(() => {
reflag.clearFlagOverrides();
});
describe("API Tests", () => {
it("should return 200 for the root endpoint", async () => {
reflag.flagOverrides = {
"show-todo": true,
};
const response = await request(app).get("/");
expect(response.status).toBe(200);
expect(response.body).toEqual({ message: "Ready to manage some TODOs!" });
});
});
```
See more on flag overrides in the section below.
## Flag Overrides
Flag overrides allow you to override flags and their configurations locally. This is particularly useful for development and testing. You can specify overrides in three ways:
1. Through environment variables:
```bash
REFLAG_FLAGS_ENABLED=flag1,flag2
REFLAG_FLAGS_DISABLED=flag3,flag4
```
1. Through `reflag.config.json`:
```json
{
"flagOverrides": {
"delete-todos": {
"isEnabled": true,
"config": {
"key": "dev-config",
"payload": {
"requireConfirmation": true,
"maxDeletionsPerDay": 5
}
}
}
}
}
```
1. Programmatically through the client options:
You can use a simple `Record<string, boolean>` and pass it either in the constructor or by setting `client.flagOverrides`:
```typescript
// pass directly in the constructor
const client = new ReflagClient({ flagOverrides: { myFlag: true } });
// or set on the client at a later time
client.flagOverrides = { myFlag: false };
// clear flag overrides. Same as setting to {}.
client.clearFlagOverrides();
```
To get dynamic overrides, use a function which takes a context and returns a boolean or an object with the shape of `{isEnabled, config}`:
```typescript
import { ReflagClient, Context } from "@reflag/node-sdk";
const flagOverrides = (context: Context) => ({
"delete-todos": {
isEnabled: true,
config: {
key: "dev-config",
payload: {
requireConfirmation: true,
maxDeletionsPerDay: 5,
},
},
},
});
const client = new ReflagClient({
flagOverrides,
});
```
## Remote Flag Evaluation
In addition to local flag evaluation, Reflag supports remote evaluation using stored context. This is useful when you want to evaluate flags using user/company attributes that were previously sent to Reflag:
```typescript
// First, update user and company attributes
await client.updateUser("user123", {
attributes: {
role: "admin",
subscription: "premium",
},
});
await client.updateCompany("company456", {
attributes: {
tier: "enterprise",
employees: 1000,
},
});
// Later, evaluate flags remotely using stored context
const flags = await client.getFlagsRemote("company456", "user123");
// Or evaluate a single flag
const flag = await client.getFlagRemote(
"create-todos",
"company456",
"user123",
);
// You can also provide additional context
const flagsWithContext = await client.getFlagsRemote("company456", "user123", {
other: {
location: "US",
platform: "mobile",
},
});
```
Remote evaluation is particularly useful when:
- You want to use the most up-to-date user/company attributes stored in Reflag
- You don't want to pass all context attributes with every evaluation
- You need to ensure consistent flag evaluation across different services
## Using with Express
A popular way to integrate the Reflag Node.js SDK is through an express middleware.
```typescript
import reflag from "./reflag";
import express from "express";
import { BoundReflagClient } from "@reflag/node-sdk";
// Augment the Express types to include a `boundReflagClient` property on the
// `res.locals` object.
// This will allow us to access the ReflagClient instance in our route handlers
// without having to pass it around manually
declare global {
namespace Express {
interface Locals {
boundReflagClient: BoundReflagClient;
}
}
}
// Add express middleware
app.use((req, res, next) => {
// Extract the user and company IDs from the request
// You'll want to use a proper authentication and identification
// mechanism in a real-world application
const user = {
id: req.user?.id,
name: req.user?.name,
email: req.user?.email
}
const company = {
id: req.user?.companyId,
name: req.user?.companyName
}
// Create a new BoundReflagClient instance by calling the `bindClient`
// method on a `ReflagClient` instance
// This will create a new instance that is bound to the user/company given.
const boundReflagClient = reflag.bindClient({ user, company });
// Store the BoundReflagClient instance in the `res.locals` object so we
// can access it in our route handlers
res.locals.boundReflagClient = boundReflagClient;
next();
});
// Now use res.locals.boundReflagClient in your handlers
app.get("/todos", async (_req, res) => {
const { track, isEnabled } = res.locals.boundReflagClient.getFlag("show-todos");
if (!isEnabled) {
res.status(403).send({"error": "flag inaccessible"})
return
}
...
}
```
See [examples/express/app.ts](https://github.com/reflagcom/javascript/tree/main/packages/node-sdk/example/express/app.ts) for a full example.
## Remote flag evaluation with stored context
If you don't want to provide context each time when evaluating flags but
rather you would like to utilize the attributes you sent to Reflag previously
(by calling `updateCompany` and `updateUser`) you can do so by calling `getFlagsRemote`
(or `getFlagRemote` for a specific flag) with providing just `userId` and `companyId`.
These methods will call Reflag's servers and flags will be evaluated remotely
using the stored attributes.
```typescript
// Update user and company attributes
client.updateUser("john_doe", {
attributes: {
name: "John O.",
role: "admin",
},
});
client.updateCompany("acme_inc", {
attributes: {
name: "Acme, Inc",
tier: "premium"
},
});
...
// This will evaluate flags with respecting the attributes sent previously
const flags = await client.getFlagsRemote("acme_inc", "john_doe");
```
> [!IMPORTANT]
> User and company attribute updates are processed asynchronously, so there might
> be a small delay between when attributes are updated and when they are available
> for evaluation.
## Opting out of tracking
There are use cases in which you not want to be sending `user`, `company` and
`track` events to [Reflag.com](https://reflag.com). These are usually cases where you could be impersonating
another user in the system and do not want to interfere with the data being
collected by Reflag.
To disable tracking, bind the client using `bindClient()` as follows:
```typescript
// binds the client to a given user and company and set `enableTracking` to `false`.
const boundClient = client.bindClient({ user, company, enableTracking: false });
boundClient.track("some event"); // this will not actually send the event to Reflag.
// the following code will not update the `user` nor `company` in Reflag and will
// not send `track` events either.
const { isEnabled, track } = boundClient.getFlag("user-menu");
if (isEnabled) {
track();
}
```
Another way way to disable tracking without employing a bound client is to call `getFlag()`
or `getFlags()` by supplying `enableTracking: false` in the arguments passed to
these functions.
> [!IMPORTANT]
> Note, however, that calling `track()`, `updateCompany()` or `updateUser()` in the `ReflagClient`
> will still send tracking data. As such, it is always recommended to use `bindClient()`
> when using this SDK.
## Flushing
ReflagClient employs a batching technique to minimize the number of calls that are sent to
Reflag's servers.
By default, the SDK automatically subscribes to process exit signals and attempts to flush
any pending events. This behavior is controlled by the `flushOnExit` option in the client configuration:
```typescript
const client = new ReflagClient({
batchOptions: {
flushOnExit: false, // disable automatic flushing on exit
},
});
```
## Tracking custom events and setting custom attributes
Tracking allows events and updating user/company attributes in Reflag.
For example, if a customer changes their plan, you'll want Reflag to know about it,
in order to continue to provide up-do-date targeting information in the Reflag interface.
The following example shows how to register a new user, associate it with a company
and finally update the plan they are on.
```typescript
// registers the user with Reflag using the provided unique ID, and
// providing a set of custom attributes (can be anything)
client.updateUser("user_id", {
attributes: { longTimeUser: true, payingCustomer: false },
});
client.updateCompany("company_id", { userId: "user_id" });
// the user started a voice huddle
client.track("user_id", "huddle", { attributes: { voice: true } });
```
It's also possible to achieve the same through a bound client in the following manner:
```typescript
const boundClient = client.bindClient({
user: { id: "user_id", longTimeUser: true, payingCustomer: false },
company: { id: "company_id" },
});
boundClient.track("huddle", { attributes: { voice: true } });
```
Some attributes are used by Reflag to improve the UI, and are recommended
to provide for easier navigation:
- `name` -- display name for `user`/`company`,
- `email` -- the email of the user,
- `avatar` -- the URL for `user`/`company` avatar image.
Attributes cannot be nested (multiple levels) and must be either strings,
integers or booleans.
## Managing `Last seen`
By default `updateUser`/`updateCompany` calls automatically update the given
user/company `Last seen` property on Reflag servers.
You can control if `Last seen` should be updated when the events are sent by setting
`meta.active = false`. This is often useful if you
have a background job that goes through a set of companies just to update their
attributes but not their activity.
Example:
```typescript
client.updateUser("john_doe", {
attributes: { name: "John O." },
meta: { active: true },
});
client.updateCompany("acme_inc", {
attributes: { name: "Acme, Inc" },
meta: { active: false },
});
```
`bindClient()` updates attributes on the Reflag servers but does not automatically
update `Last seen`.
## Zero PII
The Reflag SDK doesn't collect any metadata and HTTP IP addresses are _not_ being
stored. For tracking individual users, we recommend using something like database
ID as userId, as it's unique and doesn't include any PII (personal identifiable
information). If, however, you're using e.g. email address as userId, but prefer
not to send any PII to Reflag, you can hash the sensitive data before sending
it to Reflag:
```typescript
import { sha256 } from 'crypto-hash';
client.updateUser({ userId: await sha256("john_doe"), ... });
```
## Migrating from Bucket SDK
If you have been using the Bucket SDKs previously, the following list will help you migrate to Reflag SDK:
- `Bucket*` classes, and types have been renamed to `Reflag*` (e.g. `BucketClient` is now `ReflagClient`)
- `Feature*` classes, and types have been renamed to `Flag*` (e.g. `Feature` is now `Flag`, `RawFeatures` is now `RawFlags`)
- When using strongly-typed flags, the new `Flags` interface replaced `Features` interface
- All methods that contained `feature` in the name have been renamed to use the `flag` terminology (e.g. `getFeature` is `getFlag`)
- All environment variables that were prefixed with `BUCKET_` are now prefixed with `REFLAG_`
- The `BUCKET_HOST` environment variable and `host` option have been removed from `ReflagClient` constructor, use `REFLAG_API_BASE_URL` instead
- The `BUCKET_FEATURES_ENABLED` and `BUCKET_FEATURES_DISABLED` have been renamed to `REFLAG_FLAGS_ENABLED` and `REFLAG_FLAGS_DISABLED`
- The default configuration file has been renamed from `bucketConfig.json` to `reflag.config.json`
- The `fallbackFeatures` property in client constructor and configuration files has been renamed to `fallbackFlags`
- `featureKey` has been renamed to `flagKey` in all methods that accepts that argument
- The SDKs will not emit `evaluate` and `evaluate-config` events anymore
## Typescript
Types are bundled together with the library and exposed automatically when importing
through a package manager.
## License
> MIT License
> Copyright (c) 2025 Bucket ApS
```
--------------------------------------------------------------------------------
/packages/cli/tsconfig.eslint.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.json"
}
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/plain/vite-env.d.ts:
--------------------------------------------------------------------------------
```typescript
/// <reference types="vite/client" />
```
--------------------------------------------------------------------------------
/packages/browser-sdk/typedoc.json:
--------------------------------------------------------------------------------
```json
{
"tsconfig": "tsconfig.build.json"
}
```
--------------------------------------------------------------------------------
/packages/node-sdk/typedoc.json:
--------------------------------------------------------------------------------
```json
{
"tsconfig": "tsconfig.build.json"
}
```
--------------------------------------------------------------------------------
/packages/browser-sdk/tsconfig.build.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.json",
"include": ["src"]
}
```
--------------------------------------------------------------------------------
/packages/flag-evaluation/tsconfig.build.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.json",
"include": ["src"]
}
```
--------------------------------------------------------------------------------
/packages/node-sdk/tsconfig.build.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.json",
"include": ["src"]
}
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/tsconfig.build.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.json",
"include": ["src"]
}
```
--------------------------------------------------------------------------------
/packages/openfeature-node-provider/tsconfig.build.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.json",
"include": ["src"]
}
```
--------------------------------------------------------------------------------
/packages/react-sdk/tsconfig.build.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.json",
"include": ["src"]
}
```
--------------------------------------------------------------------------------
/packages/vue-sdk/tsconfig.build.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.json",
"include": ["src"]
}
```
--------------------------------------------------------------------------------
/packages/node-sdk/examples/cloudflare-worker/.vscode/settings.json:
--------------------------------------------------------------------------------
```json
{
"files.associations": {
"wrangler.json": "jsonc"
}
}
```
--------------------------------------------------------------------------------
/packages/node-sdk/examples/express/bucketConfig.json:
--------------------------------------------------------------------------------
```json
{
"overrides": {
"show-todos": true,
"create-todos": true
}
}
```
--------------------------------------------------------------------------------
/packages/vue-sdk/typedoc.json:
--------------------------------------------------------------------------------
```json
{
"tsconfig": "tsconfig.build.json",
"entryPoints": ["src/index.ts"]
}
```
--------------------------------------------------------------------------------
/packages/react-sdk/typedoc.json:
--------------------------------------------------------------------------------
```json
{
"tsconfig": "tsconfig.build.json",
"entryPoints": ["src/index.tsx"]
}
```
--------------------------------------------------------------------------------
/packages/flag-evaluation/tsconfig.eslint.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.json",
"include": ["./src", "./test", "./*.ts"]
}
```
--------------------------------------------------------------------------------
/packages/node-sdk/tsconfig.eslint.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.json",
"include": ["./src", "./test", "./*.ts"]
}
```
--------------------------------------------------------------------------------
/packages/openfeature-node-provider/tsconfig.eslint.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.json",
"include": ["./src", "./test", "./*.ts"]
}
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/toolbar/index.css:
--------------------------------------------------------------------------------
```css
@import url(./Toolbar.css);
@import url(./Flags.css);
@import url(./Switch.css);
```
--------------------------------------------------------------------------------
/packages/browser-sdk/tsconfig.eslint.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.json",
"include": ["./src", "./test", "./dev", "./*.ts"]
}
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/tsconfig.eslint.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.json",
"include": ["./src", "./test", "./dev", "./*.ts"]
}
```
--------------------------------------------------------------------------------
/packages/react-sdk/tsconfig.eslint.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.json",
"include": ["./src", "./test", "./dev", "./*.ts"]
}
```
--------------------------------------------------------------------------------
/packages/vue-sdk/tsconfig.eslint.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.json",
"include": ["./src", "./test", "./dev", "./*.ts"]
}
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/example/next.config.mjs:
--------------------------------------------------------------------------------
```
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default nextConfig;
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-bootstrap-demo/next.config.mjs:
--------------------------------------------------------------------------------
```
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default nextConfig;
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-flag-demo/next.config.mjs:
--------------------------------------------------------------------------------
```
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default nextConfig;
```
--------------------------------------------------------------------------------
/packages/vue-sdk/src/version.ts:
--------------------------------------------------------------------------------
```typescript
import { version } from "../package.json";
export const SDK_VERSION = `vue-sdk/${version}`;
```
--------------------------------------------------------------------------------
/packages/browser-sdk/postcss.config.js:
--------------------------------------------------------------------------------
```javascript
module.exports = {
plugins: [require("postcss-nesting"), require("postcss-preset-env")],
};
```
--------------------------------------------------------------------------------
/packages/tsconfig/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@reflag/tsconfig",
"version": "0.0.2",
"license": "MIT",
"private": true
}
```
--------------------------------------------------------------------------------
/packages/flag-evaluation/eslint.config.js:
--------------------------------------------------------------------------------
```javascript
const base = require("@reflag/eslint-config");
module.exports = [...base, { ignores: ["dist/"] }];
```
--------------------------------------------------------------------------------
/packages/cli/eslint.config.js:
--------------------------------------------------------------------------------
```javascript
import base from "@reflag/eslint-config/base.js";
export default [...base, { ignores: ["dist/", "gen/"] }];
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/plain/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"module": "ESNext"
},
"include": ["."]
}
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/eslint.config.js:
--------------------------------------------------------------------------------
```javascript
const base = require("@reflag/eslint-config");
module.exports = [...base, { ignores: ["dist/", "example/"] }];
```
--------------------------------------------------------------------------------
/packages/openfeature-node-provider/eslint.config.js:
--------------------------------------------------------------------------------
```javascript
const base = require("@reflag/eslint-config");
module.exports = [...base, { ignores: ["dist/", "example/"] }];
```
--------------------------------------------------------------------------------
/packages/node-sdk/eslint.config.js:
--------------------------------------------------------------------------------
```javascript
const base = require("@reflag/eslint-config");
module.exports = [...base, { ignores: ["dist/", "examples/"] }];
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/feedback/ui/RadialProgress.css:
--------------------------------------------------------------------------------
```css
.radial-progress {
> circle {
stroke: var(--reflag-feedback-dialog-color, #1e1f24);
opacity: 0.2;
}
}
```
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
```json
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"version": "independent",
"npmClient": "yarn"
}
```
--------------------------------------------------------------------------------
/packages/browser-sdk/test/mocks/server.ts:
--------------------------------------------------------------------------------
```typescript
import { setupServer } from "msw/node";
import { handlers } from "./handlers";
export const server = setupServer(...handlers);
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/example/postcss.config.mjs:
--------------------------------------------------------------------------------
```
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};
export default config;
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-bootstrap-demo/postcss.config.mjs:
--------------------------------------------------------------------------------
```
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};
export default config;
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-flag-demo/postcss.config.mjs:
--------------------------------------------------------------------------------
```
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};
export default config;
```
--------------------------------------------------------------------------------
/packages/browser-sdk/vite.e2e.config.js:
--------------------------------------------------------------------------------
```javascript
import { defineConfig } from "vite";
export default defineConfig({
test: {
include: ["test/e2e/**/*.test.?(c|m)[jt]s?(x)"],
},
});
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/feedback/ui/css.d.ts:
--------------------------------------------------------------------------------
```typescript
declare module "*.css" {
const src: string;
export default src;
}
declare module "*?inline" {
const src: string;
export default src;
}
```
--------------------------------------------------------------------------------
/packages/browser-sdk/test/testLogger.ts:
--------------------------------------------------------------------------------
```typescript
import { vi } from "vitest";
export const testLogger = {
log: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
};
```
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
```json
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"editorconfig.editorconfig",
"rohit-gohri.format-code-action"
]
}
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/feedback/ui/index.css:
--------------------------------------------------------------------------------
```css
@import url(./Button.css);
@import url(./FeedbackDialog.css);
@import url(./FeedbackForm.css);
@import url(./RadialProgress.css);
@import url(./StarRating.css);
```
--------------------------------------------------------------------------------
/packages/vue-sdk/dev/plain/env.d.ts:
--------------------------------------------------------------------------------
```typescript
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_PUBLISHABLE_KEY: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
```
--------------------------------------------------------------------------------
/packages/vue-sdk/dev/plain/index.ts:
--------------------------------------------------------------------------------
```typescript
import { createApp } from "vue";
import App from "./App.vue";
const el = document.getElementById("app");
if (el) {
const app = createApp(App);
app.mount(el);
}
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/ui/packages/floating-ui-preact-dom/utils/roundByDPR.ts:
--------------------------------------------------------------------------------
```typescript
import { getDPR } from "./getDPR";
export function roundByDPR(element: Element, value: number) {
const dpr = getDPR(element);
return Math.round(value * dpr) / dpr;
}
```
--------------------------------------------------------------------------------
/packages/flag-evaluation/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "@reflag/tsconfig/library",
"compilerOptions": {
"lib": [],
"outDir": "./dist/"
},
"include": ["src"],
"typeRoots": ["./node_modules/@types", "./types"]
}
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/ui/packages/floating-ui-preact-dom/utils/useLatestRef.ts:
--------------------------------------------------------------------------------
```typescript
import { useLayoutEffect, useRef } from "preact/hooks";
export function useLatestRef<T>(value: T) {
const ref = useRef(value);
useLayoutEffect(() => {
ref.current = value;
});
return ref;
}
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/ui/packages/floating-ui-preact-dom/utils/getDPR.ts:
--------------------------------------------------------------------------------
```typescript
export function getDPR(element: Element): number {
if (typeof window === "undefined") {
return 1;
}
const win = element.ownerDocument.defaultView || window;
return win.devicePixelRatio || 1;
}
```
--------------------------------------------------------------------------------
/packages/node-sdk/vite.config.js:
--------------------------------------------------------------------------------
```javascript
import { defineConfig } from "vite";
import { defaultExclude } from "vitest/config";
export default defineConfig({
test: {
environment: "node",
exclude: [...defaultExclude, "**/examples/**"],
},
});
```
--------------------------------------------------------------------------------
/packages/browser-sdk/test/e2e/empty.html:
--------------------------------------------------------------------------------
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Reflag Test</title>
</head>
<body></body>
</html>
```
--------------------------------------------------------------------------------
/packages/vue-sdk/src/vue.d.ts:
--------------------------------------------------------------------------------
```typescript
declare module "*.vue" {
import type { DefineComponent } from "vue";
const component: DefineComponent<
Record<string, unknown>,
Record<string, unknown>,
unknown
>;
export default component;
}
```
--------------------------------------------------------------------------------
/packages/flag-evaluation/jest.config.js:
--------------------------------------------------------------------------------
```javascript
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: "ts-jest",
testPathIgnorePatterns: ["<rootDir>/dist/", "<rootDir>/node_modules/"],
clearMocks: true,
maxWorkers: 1,
};
```
--------------------------------------------------------------------------------
/packages/openfeature-node-provider/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "@reflag/tsconfig/library",
"compilerOptions": {
"outDir": "./dist/",
"declarationDir": "./dist/types",
"skipLibCheck": true
},
"include": ["src"],
"typeRoots": ["./node_modules/@types", "./types"]
}
```
--------------------------------------------------------------------------------
/packages/vue-sdk/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "@reflag/tsconfig/library",
"compilerOptions": {
"lib": [],
"outDir": "./dist/",
"declarationDir": "./dist/types",
"declarationMap": true
},
"include": ["src", "src/**/*.d.ts"],
"typeRoots": ["./node_modules/@types"]
}
```
--------------------------------------------------------------------------------
/packages/cli/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "@reflag/tsconfig/library",
"compilerOptions": {
"outDir": "./dist/",
"target": "ESNext",
"module": "NodeNext",
"moduleResolution": "NodeNext"
},
"include": ["**/*.ts"],
"exclude": ["node_modules", "dist", "**/*.spec.ts"]
}
```
--------------------------------------------------------------------------------
/packages/react-sdk/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "@reflag/tsconfig/library",
"compilerOptions": {
"lib": [],
"outDir": "./dist/",
"declarationDir": "./dist/types",
"jsx": "react",
"declarationMap": true
},
"include": ["src", "dev"],
"typeRoots": ["./node_modules/@types"]
}
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/plain/index.tsx:
--------------------------------------------------------------------------------
```typescript
import React from "react";
import ReactDOM from "react-dom/client";
import { App } from "./app";
const el = document.getElementById("app");
if (el) {
const root = ReactDOM.createRoot(el);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
}
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/ui/packages/floating-ui-preact-dom/index.ts:
--------------------------------------------------------------------------------
```typescript
export { arrow } from "./arrow";
export { useFloating } from "./useFloating";
export {
autoPlacement,
autoUpdate,
computePosition,
detectOverflow,
flip,
getOverflowAncestors,
hide,
inline,
limitShift,
offset,
platform,
shift,
size,
} from "@floating-ui/dom";
```
--------------------------------------------------------------------------------
/packages/cli/utils/commander.ts:
--------------------------------------------------------------------------------
```typescript
import { Command } from "commander";
export function commandName(command: Command) {
if (command.parent) {
const parentName = command.parent.name();
if (parentName && parentName !== "index") {
return `${parentName} ${command.name()}`;
}
}
return command.name();
}
```
--------------------------------------------------------------------------------
/packages/node-sdk/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "@reflag/tsconfig/library",
"compilerOptions": {
"lib": [],
"outDir": "./dist/",
"declarationDir": "./dist/types",
"declarationMap": true,
"noUncheckedIndexedAccess": true
},
"include": ["src"],
"typeRoots": ["./node_modules/@types", "./types"]
}
```
--------------------------------------------------------------------------------
/packages/openfeature-node-provider/example/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "example",
"packageManager": "[email protected]",
"scripts": {
"start": "tsx serve.ts"
},
"type": "commonjs",
"main": "serve.ts",
"dependencies": {
"express": "^4.20.0",
"tsx": "^4.16.2",
"typescript": "^5.5.3"
},
"devDependencies": {
"@types/express": "^4.17.21"
}
}
```
--------------------------------------------------------------------------------
/packages/openfeature-node-provider/vite.config.js:
--------------------------------------------------------------------------------
```javascript
import { defineConfig } from "vite";
export default defineConfig({
test: {
environment: "node",
exclude: [
"**/node_modules/**",
"**/dist/**",
"**/.{idea,git,cache,output,temp}/**",
"**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*",
],
},
});
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "@reflag/tsconfig/library",
"compilerOptions": {
"outDir": "./dist/",
"jsx": "react",
"jsxFactory": "h",
"jsxFragmentFactory": "Fragment",
"declarationDir": "./dist/types",
"skipLibCheck": true
},
"include": ["src", "dev"],
"typeRoots": ["./node_modules/@types", "./types"]
}
```
--------------------------------------------------------------------------------
/packages/browser-sdk/test/e2e/give-feedback-button.html:
--------------------------------------------------------------------------------
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Reflag Test</title>
</head>
<body>
<button data-testid="give-feedback-button" id="give-feedback-button">
Give Feedback
</button>
</body>
</html>
```
--------------------------------------------------------------------------------
/packages/tsconfig/library.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"strict": true,
"target": "ES6",
"module": "commonjs",
"moduleResolution": "node",
"resolveJsonModule": true,
"esModuleInterop": true,
"sourceMap": true,
"declaration": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true
}
}
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/example/app/page.tsx:
--------------------------------------------------------------------------------
```typescript
import Image from "next/image";
import { HuddleFeature } from "@/components/HuddleFeature";
import { Context } from "@/components/Context";
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<Context />
<HuddleFeature />
</main>
);
}
```
--------------------------------------------------------------------------------
/packages/vue-sdk/dev/plain/index.html:
--------------------------------------------------------------------------------
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Reflag Vue SDK playground</title>
</head>
<body>
<div id="app"></div>
<!-- Playground -->
<script type="module" src="index.ts"></script>
</body>
</html>
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/plain/index.html:
--------------------------------------------------------------------------------
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Reflag React SDK playground</title>
</head>
<body>
<div id="app"></div>
<!-- Playground -->
<script type="module" src="index.tsx"></script>
</body>
</html>
```
--------------------------------------------------------------------------------
/packages/vue-sdk/dev/plain/components/Track.vue:
--------------------------------------------------------------------------------
```vue
<script setup lang="ts">
import { useTrack } from "../../../src";
import Section from "./Section.vue";
const track = useTrack();
</script>
<template>
<Section title="Custom track event">
<button @click="track('Huddles Started', { huddlesType: 'voice' })">
Send custom track event
</button>
</Section>
</template>
```
--------------------------------------------------------------------------------
/packages/node-sdk/test/testConfig.json:
--------------------------------------------------------------------------------
```json
{
"flagOverrides": {
"myFlag": { "isEnabled": true },
"myFlagFalse": false,
"myFlagWithConfig": {
"isEnabled": true,
"config": {
"key": "config-1",
"payload": { "something": "else" }
}
}
},
"secretKey": "mySecretKey",
"offline": true,
"apiBaseUrl": "http://localhost:3000"
}
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/feedback/ui/Plug.tsx:
--------------------------------------------------------------------------------
```typescript
import { FunctionComponent, h } from "preact";
import { Logo } from "../../ui/icons/Logo";
export const Plug: FunctionComponent = () => {
return (
<footer class="plug">
<a href="https://reflag.com" rel="noreferrer" target="_blank">
Powered by <Logo height="10px" width="10px" /> Reflag
</a>
</footer>
);
};
```
--------------------------------------------------------------------------------
/packages/cli/vite.config.js:
--------------------------------------------------------------------------------
```javascript
import { defineConfig } from "vite";
export default defineConfig({
test: {
environment: "node",
exclude: [
"**/node_modules/**",
"**/dist/**",
"**/.{idea,git,cache,output,temp}/**",
"**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*",
"**/example/**",
],
},
});
```
--------------------------------------------------------------------------------
/packages/browser-sdk/vitest.setup.ts:
--------------------------------------------------------------------------------
```typescript
import { afterAll, afterEach, beforeAll } from "vitest";
import { server } from "./test/mocks/server.js";
beforeAll(() => {
server.listen({
onUnhandledRequest(request) {
console.error("Unhandled %s %s", request.method, request.url);
},
});
});
afterEach(() => {
server.resetHandlers();
});
afterAll(() => {
server.close();
});
```
--------------------------------------------------------------------------------
/packages/node-sdk/examples/express/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES6",
"module": "CommonJS",
"moduleResolution": "Node",
"allowImportingTsExtensions": true,
"allowArbitraryExtensions": true,
"noEmit": true,
"verbatimModuleSyntax": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
```
--------------------------------------------------------------------------------
/packages/browser-sdk/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "@reflag/tsconfig/library",
"compilerOptions": {
"lib": [],
"outDir": "./dist/",
"jsx": "react",
"jsxFactory": "h",
"jsxFragmentFactory": "Fragment",
"declarationDir": "./dist/types",
"skipLibCheck": true,
"declarationMap": true
},
"include": ["src", "test", "dev"],
"typeRoots": ["./node_modules/@types", "./types"]
}
```
--------------------------------------------------------------------------------
/packages/vue-sdk/dev/plain/components/Section.vue:
--------------------------------------------------------------------------------
```vue
<script setup lang="ts">
defineProps<{
title: string;
}>();
</script>
<template>
<div
style="
padding: 20px;
margin: 10px;
border: 2px solid;
border-radius: 8px;
font-family: Arial, sans-serif;
"
>
<h3 style="margin: 0 0 10px 0">{{ title }}</h3>
<div style="margin: 10px 0 0 0">
<slot />
</div>
</div>
</template>
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/example/components/OpenFeatureProvider.tsx:
--------------------------------------------------------------------------------
```typescript
"use client";
import { initOpenFeature } from "@/app/featureManagement";
import { OpenFeatureProvider as OFProvider } from "@openfeature/react-sdk";
import { useEffect } from "react";
export const OpenFeatureProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
useEffect(() => {
initOpenFeature();
}, []);
return <OFProvider>{children}</OFProvider>;
};
```
--------------------------------------------------------------------------------
/packages/node-sdk/examples/cloudflare-worker/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "cloudflare-worker",
"version": "0.0.0",
"private": true,
"scripts": {
"deploy": "wrangler deploy",
"dev": "wrangler dev",
"start": "wrangler dev",
"test": "vitest",
"cf-typegen": "wrangler types"
},
"devDependencies": {
"@cloudflare/vitest-pool-workers": "^0.8.19",
"typescript": "^5.5.2",
"vitest": "~3.2.0",
"wrangler": "^4.20.5"
}
}
```
--------------------------------------------------------------------------------
/packages/openfeature-node-provider/example/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES6",
"module": "CommonJS",
"moduleResolution": "Node",
"allowImportingTsExtensions": true,
"customConditions": ["module"],
"allowArbitraryExtensions": true,
"noEmit": true,
"verbatimModuleSyntax": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
```
--------------------------------------------------------------------------------
/vitest.workspace.js:
--------------------------------------------------------------------------------
```javascript
import { defineWorkspace } from "vitest/config";
export default defineWorkspace([
"./packages/openfeature-browser-provider/vite.config.js",
"./packages/browser-sdk/vitest.config.ts",
"./packages/openfeature-node-provider/vite.config.js",
"./packages/node-sdk/vite.config.js",
"./packages/react-sdk/vite.config.mjs",
"./packages/vue-sdk/vite.config.mjs",
"./packages/cli/vite.config.js",
]);
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/feedback/ui/Button.tsx:
--------------------------------------------------------------------------------
```typescript
import { FunctionComponent, h } from "preact";
export type ButtonProps = h.JSX.HTMLAttributes<HTMLButtonElement> & {
variant?: "primary" | "secondary";
};
export const Button: FunctionComponent<ButtonProps> = ({
variant = "primary",
children,
...rest
}) => {
const classes = ["button", variant].join(" ");
return (
<button class={classes} {...rest}>
{children}
</button>
);
};
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/ui/icons/Check.tsx:
--------------------------------------------------------------------------------
```typescript
import { FunctionComponent, h } from "preact";
export const Check: FunctionComponent<h.JSX.SVGAttributes<SVGSVGElement>> = (
props,
) => (
<svg
height="24px"
viewBox="0 0 24 24"
width="24px"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="m10 15.17l9.193-9.191l1.414 1.414l-10.606 10.606l-6.364-6.364l1.414-1.414l4.95 4.95Z"
fill="currentColor"
/>
</svg>
);
```
--------------------------------------------------------------------------------
/packages/openfeature-node-provider/example/serve.ts:
--------------------------------------------------------------------------------
```typescript
import reflag from "./reflag";
import app from "./app";
// Initialize Reflag SDK before starting the server,
// so that features are available when the server starts.
reflag.initialize().then(() => {
// Start listening for requests only after Reflag is initialized,
// which guarantees that features are available.
app.listen(process.env.PORT ?? 3000, () => {
console.log("Server is running on port 3000");
});
});
```
--------------------------------------------------------------------------------
/packages/node-sdk/examples/express/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "example",
"packageManager": "[email protected]",
"scripts": {
"start": "tsx serve.ts",
"test": "vitest"
},
"type": "commonjs",
"main": "serve.ts",
"dependencies": {
"express": "^4.20.0",
"tsx": "^4.16.2",
"typescript": "^5.5.3",
"wrangler": "^4.20.5"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/supertest": "^6.0.2",
"supertest": "^7.0.0",
"vitest": "^2.1.9"
}
}
```
--------------------------------------------------------------------------------
/packages/node-sdk/examples/express/serve.ts:
--------------------------------------------------------------------------------
```typescript
import "dotenv/config";
import reflag from "./reflag";
import app from "./app";
// Initialize Reflag SDK before starting the server,
// so that features are available when the server starts.
reflag.initialize().then(() => {
// Start listening for requests only after Reflag is initialized,
// which guarantees that features are available.
app.listen(process.env.PORT ?? 3000, () => {
console.log("Server is running on port 3000");
});
});
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-bootstrap-demo/components/Flags.tsx:
--------------------------------------------------------------------------------
```typescript
"use client";
import React from "react";
import { useFlag } from "@reflag/react-sdk";
export const Flags = () => {
const { isEnabled } = useFlag("huddles");
return (
<div className="border border-gray-300 p-6 rounded-xl dark:border-neutral-800 dark:bg-zinc-800/30">
<h3 className="text-xl mb-4">Huddles feature enabled:</h3>
<pre>
<code className="font-mono font-bold">{JSON.stringify(isEnabled)}</code>
</pre>
</div>
);
};
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-flag-demo/components/Flags.tsx:
--------------------------------------------------------------------------------
```typescript
"use client";
import React from "react";
import { useFlag } from "@reflag/react-sdk";
export const Flags = () => {
const { isEnabled } = useFlag("huddles");
return (
<div className="border border-gray-300 p-6 rounded-xl dark:border-neutral-800 dark:bg-zinc-800/30">
<h3 className="text-xl mb-4">Huddles feature enabled:</h3>
<pre>
<code className="font-mono font-bold">{JSON.stringify(isEnabled)}</code>
</pre>
</div>
);
};
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/example/tailwind.config.ts:
--------------------------------------------------------------------------------
```typescript
import type { Config } from "tailwindcss";
const config: Config = {
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic":
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
},
},
},
plugins: [],
};
export default config;
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-bootstrap-demo/tailwind.config.ts:
--------------------------------------------------------------------------------
```typescript
import type { Config } from "tailwindcss";
const config: Config = {
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic":
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
},
},
},
plugins: [],
};
export default config;
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-flag-demo/tailwind.config.ts:
--------------------------------------------------------------------------------
```typescript
import type { Config } from "tailwindcss";
const config: Config = {
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic":
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
},
},
},
plugins: [],
};
export default config;
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/config.ts:
--------------------------------------------------------------------------------
```typescript
import { version } from "../package.json";
export const API_BASE_URL = "https://front.reflag.com";
export const APP_BASE_URL = "https://app.reflag.com";
export const SSE_REALTIME_BASE_URL = "https://livemessaging.bucket.co";
export const SDK_VERSION_HEADER_NAME = "reflag-sdk-version";
export const SDK_VERSION = `browser-sdk/${version}`;
export const FLAG_EVENTS_PER_MIN = 1;
export const FLAGS_EXPIRE_MS = 30 * 24 * 60 * 60 * 1000; // expire entirely after 30 days
export const IS_SERVER = typeof window === "undefined";
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/ui/icons/CheckCircle.tsx:
--------------------------------------------------------------------------------
```typescript
import { FunctionComponent, h } from "preact";
export const CheckCircle: FunctionComponent<
h.JSX.SVGAttributes<SVGSVGElement>
> = (props) => (
<svg
height="24px"
viewBox="0 0 24 24"
width="24px"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M12 24C5.3724 24 0 18.6276 0 12C0 5.3724 5.3724 0 12 0C18.6276 0 24 5.3724 24 12C24 18.6276 18.6276 24 12 24ZM10.8036 16.8L19.2876 8.3148L17.5908 6.618L10.8036 13.4064L7.4088 10.0116L5.712 11.7084L10.8036 16.8Z"
fill="currentColor"
/>
</svg>
);
```
--------------------------------------------------------------------------------
/packages/cli/utils/types.ts:
--------------------------------------------------------------------------------
```typescript
export const FunnelStepList = [
"company",
"segment",
"tried",
"adopted",
"retained",
] as const;
export type FunnelStep = (typeof FunnelStepList)[number];
export type FeedbackSource = "api" | "manual" | "prompt" | "sdk" | "widget";
export type SatisfactionScore = 0 | 1 | 2 | 3 | 4 | 5;
export type ParamType = string | number | boolean | Date;
export type PaginatedResponse<T> = {
data: T[];
metadata: Record<string, any>;
totalCount: number;
pageIndex: number;
pageSize: number;
sortBy: string;
sortOrder: string;
};
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/ui/icons/Close.tsx:
--------------------------------------------------------------------------------
```typescript
import { FunctionComponent, h } from "preact";
export const Close: FunctionComponent<h.JSX.SVGAttributes<SVGSVGElement>> = (
props,
) => (
<svg
fill="none"
height="22"
viewBox="0 0 24 24"
width="22"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M12.0007 10.5865L16.9504 5.63672L18.3646 7.05093L13.4149 12.0007L18.3646 16.9504L16.9504 18.3646L12.0007 13.4149L7.05093 18.3646L5.63672 16.9504L10.5865 12.0007L5.63672 7.05093L7.05093 5.63672L12.0007 10.5865Z"
fill="currentColor"
/>
</svg>
);
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/example/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-bootstrap-demo/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-flag-demo/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-flag-demo/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "nextjs-flag-demo",
"version": "0.2.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@reflag/react-sdk": "workspace:^",
"next": "14.2.21",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@types/node": "^22.12.0",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.5",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5.7.3"
}
}
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/example/app/layout.tsx:
--------------------------------------------------------------------------------
```typescript
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { OpenFeatureProvider } from "@/components/OpenFeatureProvider";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={inter.className}>
<OpenFeatureProvider>{children}</OpenFeatureProvider>
</body>
</html>
);
}
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/example/app/globals.css:
--------------------------------------------------------------------------------
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-bootstrap-demo/app/globals.css:
--------------------------------------------------------------------------------
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-flag-demo/app/globals.css:
--------------------------------------------------------------------------------
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
```
--------------------------------------------------------------------------------
/packages/browser-sdk/example/typescript/index.html:
--------------------------------------------------------------------------------
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Reflag flag management</title>
</head>
<body>
<span id="loading">Loading...</span>
<style>
#start-huddle {
border: 1px solid black;
padding: 10px;
}
</style>
<div id="start-huddle" style="display: none">
Start huddle!<br />
<button id="startHuddle">Click me</button>
<button id="giveFeedback">Give feedback!</button>
</div>
<script src="app.ts" type="module"></script>
</body>
</html>
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-bootstrap-demo/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "nextjs-bootstrap-demo",
"version": "0.2.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@reflag/node-sdk": "workspace:^",
"@reflag/react-sdk": "workspace:^",
"next": "14.2.21",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@types/node": "^22.12.0",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.5",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5.7.3"
}
}
```
--------------------------------------------------------------------------------
/packages/vue-sdk/src/ReflagClientProvider.vue:
--------------------------------------------------------------------------------
```vue
<script setup lang="ts">
import { provide, ref } from "vue";
import { ProviderSymbol, useOnEvent } from "./hooks";
import { ReflagClientProviderProps } from "./types";
const { client, initialLoading = true } =
defineProps<ReflagClientProviderProps>();
const isLoading = ref(
client.getState() !== "initialized" ? initialLoading : false,
);
useOnEvent(
"stateUpdated",
(state) => {
isLoading.value = state === "initializing";
},
client,
);
provide(ProviderSymbol, {
isLoading,
client,
});
</script>
<template>
<slot v-if="isLoading && $slots.loading" name="loading" />
<slot v-else />
</template>
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/ui/icons/Logo.tsx:
--------------------------------------------------------------------------------
```typescript
import { FunctionComponent, h } from "preact";
export const Logo: FunctionComponent<h.JSX.SVGAttributes<SVGSVGElement>> = (
props = { height: "10px", width: "10px" },
) => (
<svg
width="128"
height="128"
viewBox="0 0 128 128"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M117.333 0C123.224 0 128 4.77563 128 10.6667V117.333C128 123.224 123.224 128 117.333 128H10.6667C4.77563 128 1.71804e-07 123.224 0 117.333V10.6667C0 4.77563 4.77563 1.71801e-07 10.6667 0H117.333ZM10.6667 10.6667V117.333L117.333 10.6667H10.6667Z"
fill="currentColor"
/>
</svg>
);
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-bootstrap-demo/public/vercel.svg:
--------------------------------------------------------------------------------
```
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-flag-demo/public/vercel.svg:
--------------------------------------------------------------------------------
```
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-flag-demo/components/Providers.tsx:
--------------------------------------------------------------------------------
```typescript
"use client";
import React, { ReactNode } from "react";
import { ReflagProvider } from "@reflag/react-sdk";
type Props = {
publishableKey: string;
children: ReactNode;
};
export const Providers = ({ publishableKey, children }: Props) => {
return (
<ReflagProvider
publishableKey={publishableKey}
context={{
company: { id: "demo-company", name: "Demo Company" },
user: {
id: "demo-user",
email: "[email protected]",
"optin-huddles": "true",
},
}}
fallbackFlags={["fallback-feature"]}
>
{children}
</ReflagProvider>
);
};
```
--------------------------------------------------------------------------------
/packages/node-sdk/src/index.ts:
--------------------------------------------------------------------------------
```typescript
export { BoundReflagClient, ReflagClient } from "./client";
export { EdgeClient, EdgeClientOptions } from "./edgeClient";
export type {
Attributes,
BatchBufferOptions,
BootstrappedFlags,
CacheStrategy,
ClientOptions,
Context,
ContextWithTracking,
EmptyFlagRemoteConfig,
Flag,
FlagConfigVariant,
FlagDefinition,
FlagOverride,
FlagOverrides,
FlagOverridesFn,
FlagRemoteConfig,
Flags,
FlagType,
HttpClient,
HttpClientResponse,
IdType,
LOG_LEVELS,
Logger,
LogLevel,
RawFlag,
RawFlagRemoteConfig,
RawFlags,
TrackingMeta,
TrackOptions,
TypedFlagKey,
TypedFlags,
} from "./types";
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-flag-demo/app/layout.tsx:
--------------------------------------------------------------------------------
```typescript
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { Providers } from "@/components/Providers";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
const publishableKey = process.env.PUBLISHABLE_KEY || "";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={inter.className}>
<Providers publishableKey={publishableKey}>{children}</Providers>
</body>
</html>
);
}
```
--------------------------------------------------------------------------------
/packages/openfeature-node-provider/example/reflag.ts:
--------------------------------------------------------------------------------
```typescript
import { OpenFeature } from "@openfeature/server-sdk";
import { ReflagNodeProvider } from "../src";
if (!process.env.REFLAG_SECRET_KEY) {
throw new Error("REFLAG_SECRET_KEY is required");
}
export type CreateTodosConfig = {
maxLength: number;
};
const provider = new ReflagNodeProvider({
secretKey: process.env.REFLAG_SECRET_KEY!,
fallbackFlags: {
"show-todos": {
isEnabled: true,
},
"create-todos": {
isEnabled: true,
config: {
key: "default",
payload: {
maxLength: 100,
},
},
},
},
logger: console,
});
OpenFeature.setProvider(provider);
export default provider;
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-bootstrap-demo/app/client.ts:
--------------------------------------------------------------------------------
```typescript
import { ReflagClient as ReflagNodeClient } from "@reflag/node-sdk";
const secretKey = process.env.REFLAG_SECRET_KEY || "";
const offline = process.env.CI === "true";
declare global {
var serverClient: ReflagNodeClient;
}
/**
* Create a singleton server client and store it in globalThis.
* This avoids creating multiple instances of the client in each loaded chunk.
* @returns The server client.
*/
export async function getServerClient() {
if (!globalThis.serverClient) {
globalThis.serverClient = new ReflagNodeClient({
secretKey,
offline,
});
}
await globalThis.serverClient.initialize();
return globalThis.serverClient;
}
```
--------------------------------------------------------------------------------
/packages/eslint-config/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@reflag/eslint-config",
"version": "0.0.2",
"license": "MIT",
"private": true,
"main": "./base.js",
"dependencies": {
"@eslint/js": "^9.21.0",
"@typescript-eslint/eslint-plugin": "^8.25.0",
"@typescript-eslint/parser": "^8.25.0",
"eslint": "^9.21.0",
"eslint-config-prettier": "^10.0.1",
"eslint-import-resolver-typescript": "^3.8.3",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unused-imports": "^4.1.4",
"globals": "^16.2.0",
"prettier": "^3.5.2",
"typescript": "^5.7.3"
}
}
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/example/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "nextjs-openfeature-example",
"version": "0.2.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@openfeature/core": "1.3.0",
"@openfeature/react-sdk": "^0.4.5",
"@openfeature/web-sdk": "^1.2.3",
"@reflag/react-sdk": "workspace:^",
"next": "14.2.26",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@types/node": "^22.12.0",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.5",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5.7.3"
}
}
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/toolbar/index.ts:
--------------------------------------------------------------------------------
```typescript
import { h, render } from "preact";
import { ReflagClient } from "../client";
import { toolbarContainerId } from "../ui/constants";
import { ToolbarPosition } from "../ui/types";
import { attachContainer } from "../ui/utils";
import Toolbar from "./Toolbar";
type showToolbarToggleOptions = {
reflagClient: ReflagClient;
position?: ToolbarPosition;
};
export const DEFAULT_PLACEMENT = "bottom-right" as const;
export function showToolbarToggle(options: showToolbarToggleOptions) {
const shadowRoot = attachContainer(toolbarContainerId);
const position: ToolbarPosition = options.position ?? {
placement: DEFAULT_PLACEMENT,
};
render(h(Toolbar, { ...options, position }), shadowRoot);
}
```
--------------------------------------------------------------------------------
/packages/vue-sdk/dev/plain/components/MissingKeyMessage.vue:
--------------------------------------------------------------------------------
```vue
<template>
<div
style="
padding: 20px;
border: 2px solid #ff6b6b;
border-radius: 8px;
background-color: #fff5f5;
color: #d63031;
font-family: Arial, sans-serif;
"
>
<h3 style="margin: 0 0 10px 0; color: #d63031">Missing Publishable Key</h3>
<p style="margin: 0; line-height: 1.5">
The <code>VITE_PUBLISHABLE_KEY</code> environment variable is not set.
Please set this variable in your <code>.env</code> file to use the Reflag
SDK.
</p>
<p style="margin: 10px 0 0 0; font-size: 14px; opacity: 0.8">
Example: <code>VITE_PUBLISHABLE_KEY=your_key_here</code>
</p>
</div>
</template>
<script setup lang="ts"></script>
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/feedback/ui/RadialProgress.tsx:
--------------------------------------------------------------------------------
```typescript
import { FunctionComponent, h } from "preact";
export const RadialProgress: FunctionComponent<{
diameter: number;
progress: number;
}> = ({ diameter, progress }) => {
const stroke = 2;
const radius = diameter / 2 - stroke;
const circumference = 2 * Math.PI * radius;
const filled = circumference * progress;
return (
<svg class="radial-progress" height={diameter} width={diameter}>
<circle
cx={radius + stroke}
cy={radius + stroke}
fill="transparent"
r={radius}
stroke-dasharray={circumference}
stroke-dashoffset={filled}
stroke-width={stroke}
transform={`rotate(-90) translate(-${radius * 2 + stroke * 2} 0)`}
/>
</svg>
);
};
```