This is page 2 of 9. Use http://codebase.md/bucketco/bucket-javascript-sdk?lines=true&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
--------------------------------------------------------------------------------
/packages/flag-evaluation/jest.config.js:
--------------------------------------------------------------------------------
```javascript
1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
2 | module.exports = {
3 | preset: "ts-jest",
4 | testPathIgnorePatterns: ["<rootDir>/dist/", "<rootDir>/node_modules/"],
5 | clearMocks: true,
6 | maxWorkers: 1,
7 | };
8 |
```
--------------------------------------------------------------------------------
/packages/openfeature-node-provider/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "extends": "@reflag/tsconfig/library",
3 | "compilerOptions": {
4 | "outDir": "./dist/",
5 | "declarationDir": "./dist/types",
6 | "skipLibCheck": true
7 | },
8 | "include": ["src"],
9 | "typeRoots": ["./node_modules/@types", "./types"]
10 | }
11 |
```
--------------------------------------------------------------------------------
/packages/vue-sdk/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "extends": "@reflag/tsconfig/library",
3 | "compilerOptions": {
4 | "lib": [],
5 | "outDir": "./dist/",
6 | "declarationDir": "./dist/types",
7 | "declarationMap": true
8 | },
9 | "include": ["src", "src/**/*.d.ts"],
10 | "typeRoots": ["./node_modules/@types"]
11 | }
12 |
```
--------------------------------------------------------------------------------
/packages/cli/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "extends": "@reflag/tsconfig/library",
3 | "compilerOptions": {
4 | "outDir": "./dist/",
5 | "target": "ESNext",
6 | "module": "NodeNext",
7 | "moduleResolution": "NodeNext"
8 | },
9 | "include": ["**/*.ts"],
10 | "exclude": ["node_modules", "dist", "**/*.spec.ts"]
11 | }
12 |
```
--------------------------------------------------------------------------------
/packages/react-sdk/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "extends": "@reflag/tsconfig/library",
3 | "compilerOptions": {
4 | "lib": [],
5 | "outDir": "./dist/",
6 | "declarationDir": "./dist/types",
7 | "jsx": "react",
8 | "declarationMap": true
9 | },
10 | "include": ["src", "dev"],
11 | "typeRoots": ["./node_modules/@types"]
12 | }
13 |
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/plain/index.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 |
4 | import { App } from "./app";
5 |
6 | const el = document.getElementById("app");
7 |
8 | if (el) {
9 | const root = ReactDOM.createRoot(el);
10 | root.render(
11 | <React.StrictMode>
12 | <App />
13 | </React.StrictMode>,
14 | );
15 | }
16 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/ui/packages/floating-ui-preact-dom/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | export { arrow } from "./arrow";
2 | export { useFloating } from "./useFloating";
3 | export {
4 | autoPlacement,
5 | autoUpdate,
6 | computePosition,
7 | detectOverflow,
8 | flip,
9 | getOverflowAncestors,
10 | hide,
11 | inline,
12 | limitShift,
13 | offset,
14 | platform,
15 | shift,
16 | size,
17 | } from "@floating-ui/dom";
18 |
```
--------------------------------------------------------------------------------
/packages/cli/utils/commander.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Command } from "commander";
2 |
3 | export function commandName(command: Command) {
4 | if (command.parent) {
5 | const parentName = command.parent.name();
6 | if (parentName && parentName !== "index") {
7 | return `${parentName} ${command.name()}`;
8 | }
9 | }
10 | return command.name();
11 | }
12 |
```
--------------------------------------------------------------------------------
/packages/node-sdk/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "extends": "@reflag/tsconfig/library",
3 | "compilerOptions": {
4 | "lib": [],
5 | "outDir": "./dist/",
6 | "declarationDir": "./dist/types",
7 | "declarationMap": true,
8 | "noUncheckedIndexedAccess": true
9 | },
10 | "include": ["src"],
11 | "typeRoots": ["./node_modules/@types", "./types"]
12 | }
13 |
```
--------------------------------------------------------------------------------
/packages/openfeature-node-provider/example/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "example",
3 | "packageManager": "[email protected]",
4 | "scripts": {
5 | "start": "tsx serve.ts"
6 | },
7 | "type": "commonjs",
8 | "main": "serve.ts",
9 | "dependencies": {
10 | "express": "^4.20.0",
11 | "tsx": "^4.16.2",
12 | "typescript": "^5.5.3"
13 | },
14 | "devDependencies": {
15 | "@types/express": "^4.17.21"
16 | }
17 | }
18 |
```
--------------------------------------------------------------------------------
/packages/openfeature-node-provider/vite.config.js:
--------------------------------------------------------------------------------
```javascript
1 | import { defineConfig } from "vite";
2 |
3 | export default defineConfig({
4 | test: {
5 | environment: "node",
6 | exclude: [
7 | "**/node_modules/**",
8 | "**/dist/**",
9 | "**/.{idea,git,cache,output,temp}/**",
10 | "**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*",
11 | ],
12 | },
13 | });
14 |
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "extends": "@reflag/tsconfig/library",
3 | "compilerOptions": {
4 | "outDir": "./dist/",
5 | "jsx": "react",
6 | "jsxFactory": "h",
7 | "jsxFragmentFactory": "Fragment",
8 | "declarationDir": "./dist/types",
9 | "skipLibCheck": true
10 | },
11 | "include": ["src", "dev"],
12 | "typeRoots": ["./node_modules/@types", "./types"]
13 | }
14 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/test/e2e/give-feedback-button.html:
--------------------------------------------------------------------------------
```html
1 | <!doctype html>
2 | <html lang="en">
3 | <head>
4 | <meta charset="UTF-8" />
5 | <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6 | <title>Reflag Test</title>
7 | </head>
8 | <body>
9 | <button data-testid="give-feedback-button" id="give-feedback-button">
10 | Give Feedback
11 | </button>
12 | </body>
13 | </html>
14 |
```
--------------------------------------------------------------------------------
/packages/tsconfig/library.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "target": "ES6",
5 | "module": "commonjs",
6 | "moduleResolution": "node",
7 | "resolveJsonModule": true,
8 | "esModuleInterop": true,
9 | "sourceMap": true,
10 | "declaration": true,
11 | "noUnusedLocals": true,
12 | "noUnusedParameters": true,
13 | "noImplicitReturns": true
14 | }
15 | }
16 |
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/example/app/page.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import Image from "next/image";
2 | import { HuddleFeature } from "@/components/HuddleFeature";
3 | import { Context } from "@/components/Context";
4 |
5 | export default function Home() {
6 | return (
7 | <main className="flex min-h-screen flex-col items-center justify-between p-24">
8 | <Context />
9 | <HuddleFeature />
10 | </main>
11 | );
12 | }
13 |
```
--------------------------------------------------------------------------------
/packages/vue-sdk/dev/plain/index.html:
--------------------------------------------------------------------------------
```html
1 | <!doctype html>
2 | <html lang="en">
3 | <head>
4 | <meta charset="UTF-8" />
5 | <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6 | <title>Reflag Vue SDK playground</title>
7 | </head>
8 |
9 | <body>
10 | <div id="app"></div>
11 | <!-- Playground -->
12 | <script type="module" src="index.ts"></script>
13 | </body>
14 | </html>
15 |
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/plain/index.html:
--------------------------------------------------------------------------------
```html
1 | <!doctype html>
2 | <html lang="en">
3 | <head>
4 | <meta charset="UTF-8" />
5 | <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6 | <title>Reflag React SDK playground</title>
7 | </head>
8 |
9 | <body>
10 | <div id="app"></div>
11 | <!-- Playground -->
12 | <script type="module" src="index.tsx"></script>
13 | </body>
14 | </html>
15 |
```
--------------------------------------------------------------------------------
/packages/vue-sdk/dev/plain/components/Track.vue:
--------------------------------------------------------------------------------
```vue
1 | <script setup lang="ts">
2 | import { useTrack } from "../../../src";
3 |
4 | import Section from "./Section.vue";
5 |
6 | const track = useTrack();
7 | </script>
8 |
9 | <template>
10 | <Section title="Custom track event">
11 | <button @click="track('Huddles Started', { huddlesType: 'voice' })">
12 | Send custom track event
13 | </button>
14 | </Section>
15 | </template>
16 |
```
--------------------------------------------------------------------------------
/packages/node-sdk/test/testConfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "flagOverrides": {
3 | "myFlag": { "isEnabled": true },
4 | "myFlagFalse": false,
5 | "myFlagWithConfig": {
6 | "isEnabled": true,
7 | "config": {
8 | "key": "config-1",
9 | "payload": { "something": "else" }
10 | }
11 | }
12 | },
13 | "secretKey": "mySecretKey",
14 | "offline": true,
15 | "apiBaseUrl": "http://localhost:3000"
16 | }
17 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/feedback/ui/Plug.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import { FunctionComponent, h } from "preact";
2 |
3 | import { Logo } from "../../ui/icons/Logo";
4 |
5 | export const Plug: FunctionComponent = () => {
6 | return (
7 | <footer class="plug">
8 | <a href="https://reflag.com" rel="noreferrer" target="_blank">
9 | Powered by <Logo height="10px" width="10px" /> Reflag
10 | </a>
11 | </footer>
12 | );
13 | };
14 |
```
--------------------------------------------------------------------------------
/packages/cli/vite.config.js:
--------------------------------------------------------------------------------
```javascript
1 | import { defineConfig } from "vite";
2 |
3 | export default defineConfig({
4 | test: {
5 | environment: "node",
6 | exclude: [
7 | "**/node_modules/**",
8 | "**/dist/**",
9 | "**/.{idea,git,cache,output,temp}/**",
10 | "**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*",
11 | "**/example/**",
12 | ],
13 | },
14 | });
15 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/vitest.setup.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { afterAll, afterEach, beforeAll } from "vitest";
2 |
3 | import { server } from "./test/mocks/server.js";
4 |
5 | beforeAll(() => {
6 | server.listen({
7 | onUnhandledRequest(request) {
8 | console.error("Unhandled %s %s", request.method, request.url);
9 | },
10 | });
11 | });
12 |
13 | afterEach(() => {
14 | server.resetHandlers();
15 | });
16 |
17 | afterAll(() => {
18 | server.close();
19 | });
20 |
```
--------------------------------------------------------------------------------
/packages/node-sdk/examples/express/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES6",
4 | "module": "CommonJS",
5 | "moduleResolution": "Node",
6 | "allowImportingTsExtensions": true,
7 | "allowArbitraryExtensions": true,
8 | "noEmit": true,
9 | "verbatimModuleSyntax": false,
10 | "esModuleInterop": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "strict": true,
13 | "skipLibCheck": true
14 | }
15 | }
16 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "extends": "@reflag/tsconfig/library",
3 | "compilerOptions": {
4 | "lib": [],
5 | "outDir": "./dist/",
6 | "jsx": "react",
7 | "jsxFactory": "h",
8 | "jsxFragmentFactory": "Fragment",
9 | "declarationDir": "./dist/types",
10 | "skipLibCheck": true,
11 | "declarationMap": true
12 | },
13 | "include": ["src", "test", "dev"],
14 | "typeRoots": ["./node_modules/@types", "./types"]
15 | }
16 |
```
--------------------------------------------------------------------------------
/packages/vue-sdk/dev/plain/components/Section.vue:
--------------------------------------------------------------------------------
```vue
1 | <script setup lang="ts">
2 | defineProps<{
3 | title: string;
4 | }>();
5 | </script>
6 | <template>
7 | <div
8 | style="
9 | padding: 20px;
10 | margin: 10px;
11 | border: 2px solid;
12 | border-radius: 8px;
13 |
14 | font-family: Arial, sans-serif;
15 | "
16 | >
17 | <h3 style="margin: 0 0 10px 0">{{ title }}</h3>
18 | <div style="margin: 10px 0 0 0">
19 | <slot />
20 | </div>
21 | </div>
22 | </template>
23 |
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/example/components/OpenFeatureProvider.tsx:
--------------------------------------------------------------------------------
```typescript
1 | "use client";
2 |
3 | import { initOpenFeature } from "@/app/featureManagement";
4 | import { OpenFeatureProvider as OFProvider } from "@openfeature/react-sdk";
5 | import { useEffect } from "react";
6 |
7 | export const OpenFeatureProvider = ({
8 | children,
9 | }: {
10 | children: React.ReactNode;
11 | }) => {
12 | useEffect(() => {
13 | initOpenFeature();
14 | }, []);
15 |
16 | return <OFProvider>{children}</OFProvider>;
17 | };
18 |
```
--------------------------------------------------------------------------------
/packages/node-sdk/examples/cloudflare-worker/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "cloudflare-worker",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "deploy": "wrangler deploy",
7 | "dev": "wrangler dev",
8 | "start": "wrangler dev",
9 | "test": "vitest",
10 | "cf-typegen": "wrangler types"
11 | },
12 | "devDependencies": {
13 | "@cloudflare/vitest-pool-workers": "^0.8.19",
14 | "typescript": "^5.5.2",
15 | "vitest": "~3.2.0",
16 | "wrangler": "^4.20.5"
17 | }
18 | }
19 |
```
--------------------------------------------------------------------------------
/packages/openfeature-node-provider/example/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES6",
4 | "module": "CommonJS",
5 | "moduleResolution": "Node",
6 | "allowImportingTsExtensions": true,
7 | "customConditions": ["module"],
8 | "allowArbitraryExtensions": true,
9 | "noEmit": true,
10 | "verbatimModuleSyntax": false,
11 | "esModuleInterop": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "strict": true,
14 | "skipLibCheck": true
15 | }
16 | }
17 |
```
--------------------------------------------------------------------------------
/vitest.workspace.js:
--------------------------------------------------------------------------------
```javascript
1 | import { defineWorkspace } from "vitest/config";
2 |
3 | export default defineWorkspace([
4 | "./packages/openfeature-browser-provider/vite.config.js",
5 | "./packages/browser-sdk/vitest.config.ts",
6 | "./packages/openfeature-node-provider/vite.config.js",
7 | "./packages/node-sdk/vite.config.js",
8 | "./packages/react-sdk/vite.config.mjs",
9 | "./packages/vue-sdk/vite.config.mjs",
10 | "./packages/cli/vite.config.js",
11 | ]);
12 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/feedback/ui/Button.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import { FunctionComponent, h } from "preact";
2 |
3 | export type ButtonProps = h.JSX.HTMLAttributes<HTMLButtonElement> & {
4 | variant?: "primary" | "secondary";
5 | };
6 |
7 | export const Button: FunctionComponent<ButtonProps> = ({
8 | variant = "primary",
9 | children,
10 | ...rest
11 | }) => {
12 | const classes = ["button", variant].join(" ");
13 |
14 | return (
15 | <button class={classes} {...rest}>
16 | {children}
17 | </button>
18 | );
19 | };
20 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/ui/icons/Check.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import { FunctionComponent, h } from "preact";
2 |
3 | export const Check: FunctionComponent<h.JSX.SVGAttributes<SVGSVGElement>> = (
4 | props,
5 | ) => (
6 | <svg
7 | height="24px"
8 | viewBox="0 0 24 24"
9 | width="24px"
10 | xmlns="http://www.w3.org/2000/svg"
11 | {...props}
12 | >
13 | <path
14 | d="m10 15.17l9.193-9.191l1.414 1.414l-10.606 10.606l-6.364-6.364l1.414-1.414l4.95 4.95Z"
15 | fill="currentColor"
16 | />
17 | </svg>
18 | );
19 |
```
--------------------------------------------------------------------------------
/packages/openfeature-node-provider/example/serve.ts:
--------------------------------------------------------------------------------
```typescript
1 | import reflag from "./reflag";
2 | import app from "./app";
3 |
4 | // Initialize Reflag SDK before starting the server,
5 | // so that features are available when the server starts.
6 | reflag.initialize().then(() => {
7 | // Start listening for requests only after Reflag is initialized,
8 | // which guarantees that features are available.
9 | app.listen(process.env.PORT ?? 3000, () => {
10 | console.log("Server is running on port 3000");
11 | });
12 | });
13 |
```
--------------------------------------------------------------------------------
/packages/node-sdk/examples/express/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "example",
3 | "packageManager": "[email protected]",
4 | "scripts": {
5 | "start": "tsx serve.ts",
6 | "test": "vitest"
7 | },
8 | "type": "commonjs",
9 | "main": "serve.ts",
10 | "dependencies": {
11 | "express": "^4.20.0",
12 | "tsx": "^4.16.2",
13 | "typescript": "^5.5.3",
14 | "wrangler": "^4.20.5"
15 | },
16 | "devDependencies": {
17 | "@types/express": "^4.17.21",
18 | "@types/supertest": "^6.0.2",
19 | "supertest": "^7.0.0",
20 | "vitest": "^2.1.9"
21 | }
22 | }
23 |
```
--------------------------------------------------------------------------------
/packages/node-sdk/examples/express/serve.ts:
--------------------------------------------------------------------------------
```typescript
1 | import "dotenv/config";
2 |
3 | import reflag from "./reflag";
4 | import app from "./app";
5 |
6 | // Initialize Reflag SDK before starting the server,
7 | // so that features are available when the server starts.
8 | reflag.initialize().then(() => {
9 | // Start listening for requests only after Reflag is initialized,
10 | // which guarantees that features are available.
11 | app.listen(process.env.PORT ?? 3000, () => {
12 | console.log("Server is running on port 3000");
13 | });
14 | });
15 |
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-bootstrap-demo/components/Flags.tsx:
--------------------------------------------------------------------------------
```typescript
1 | "use client";
2 |
3 | import React from "react";
4 | import { useFlag } from "@reflag/react-sdk";
5 |
6 | export const Flags = () => {
7 | const { isEnabled } = useFlag("huddles");
8 | return (
9 | <div className="border border-gray-300 p-6 rounded-xl dark:border-neutral-800 dark:bg-zinc-800/30">
10 | <h3 className="text-xl mb-4">Huddles feature enabled:</h3>
11 | <pre>
12 | <code className="font-mono font-bold">{JSON.stringify(isEnabled)}</code>
13 | </pre>
14 | </div>
15 | );
16 | };
17 |
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-flag-demo/components/Flags.tsx:
--------------------------------------------------------------------------------
```typescript
1 | "use client";
2 |
3 | import React from "react";
4 | import { useFlag } from "@reflag/react-sdk";
5 |
6 | export const Flags = () => {
7 | const { isEnabled } = useFlag("huddles");
8 | return (
9 | <div className="border border-gray-300 p-6 rounded-xl dark:border-neutral-800 dark:bg-zinc-800/30">
10 | <h3 className="text-xl mb-4">Huddles feature enabled:</h3>
11 | <pre>
12 | <code className="font-mono font-bold">{JSON.stringify(isEnabled)}</code>
13 | </pre>
14 | </div>
15 | );
16 | };
17 |
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/example/tailwind.config.ts:
--------------------------------------------------------------------------------
```typescript
1 | import type { Config } from "tailwindcss";
2 |
3 | const config: Config = {
4 | content: [
5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}",
6 | "./components/**/*.{js,ts,jsx,tsx,mdx}",
7 | "./app/**/*.{js,ts,jsx,tsx,mdx}",
8 | ],
9 | theme: {
10 | extend: {
11 | backgroundImage: {
12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
13 | "gradient-conic":
14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
15 | },
16 | },
17 | },
18 | plugins: [],
19 | };
20 | export default config;
21 |
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-bootstrap-demo/tailwind.config.ts:
--------------------------------------------------------------------------------
```typescript
1 | import type { Config } from "tailwindcss";
2 |
3 | const config: Config = {
4 | content: [
5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}",
6 | "./components/**/*.{js,ts,jsx,tsx,mdx}",
7 | "./app/**/*.{js,ts,jsx,tsx,mdx}",
8 | ],
9 | theme: {
10 | extend: {
11 | backgroundImage: {
12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
13 | "gradient-conic":
14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
15 | },
16 | },
17 | },
18 | plugins: [],
19 | };
20 | export default config;
21 |
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-flag-demo/tailwind.config.ts:
--------------------------------------------------------------------------------
```typescript
1 | import type { Config } from "tailwindcss";
2 |
3 | const config: Config = {
4 | content: [
5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}",
6 | "./components/**/*.{js,ts,jsx,tsx,mdx}",
7 | "./app/**/*.{js,ts,jsx,tsx,mdx}",
8 | ],
9 | theme: {
10 | extend: {
11 | backgroundImage: {
12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
13 | "gradient-conic":
14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
15 | },
16 | },
17 | },
18 | plugins: [],
19 | };
20 | export default config;
21 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/config.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { version } from "../package.json";
2 |
3 | export const API_BASE_URL = "https://front.reflag.com";
4 | export const APP_BASE_URL = "https://app.reflag.com";
5 | export const SSE_REALTIME_BASE_URL = "https://livemessaging.bucket.co";
6 |
7 | export const SDK_VERSION_HEADER_NAME = "reflag-sdk-version";
8 |
9 | export const SDK_VERSION = `browser-sdk/${version}`;
10 | export const FLAG_EVENTS_PER_MIN = 1;
11 | export const FLAGS_EXPIRE_MS = 30 * 24 * 60 * 60 * 1000; // expire entirely after 30 days
12 |
13 | export const IS_SERVER = typeof window === "undefined";
14 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/ui/icons/CheckCircle.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import { FunctionComponent, h } from "preact";
2 |
3 | export const CheckCircle: FunctionComponent<
4 | h.JSX.SVGAttributes<SVGSVGElement>
5 | > = (props) => (
6 | <svg
7 | height="24px"
8 | viewBox="0 0 24 24"
9 | width="24px"
10 | xmlns="http://www.w3.org/2000/svg"
11 | {...props}
12 | >
13 | <path
14 | 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"
15 | fill="currentColor"
16 | />
17 | </svg>
18 | );
19 |
```
--------------------------------------------------------------------------------
/packages/cli/utils/types.ts:
--------------------------------------------------------------------------------
```typescript
1 | export const FunnelStepList = [
2 | "company",
3 | "segment",
4 | "tried",
5 | "adopted",
6 | "retained",
7 | ] as const;
8 |
9 | export type FunnelStep = (typeof FunnelStepList)[number];
10 |
11 | export type FeedbackSource = "api" | "manual" | "prompt" | "sdk" | "widget";
12 |
13 | export type SatisfactionScore = 0 | 1 | 2 | 3 | 4 | 5;
14 |
15 | export type ParamType = string | number | boolean | Date;
16 |
17 | export type PaginatedResponse<T> = {
18 | data: T[];
19 | metadata: Record<string, any>;
20 | totalCount: number;
21 | pageIndex: number;
22 | pageSize: number;
23 | sortBy: string;
24 | sortOrder: string;
25 | };
26 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/ui/icons/Close.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import { FunctionComponent, h } from "preact";
2 |
3 | export const Close: FunctionComponent<h.JSX.SVGAttributes<SVGSVGElement>> = (
4 | props,
5 | ) => (
6 | <svg
7 | fill="none"
8 | height="22"
9 | viewBox="0 0 24 24"
10 | width="22"
11 | xmlns="http://www.w3.org/2000/svg"
12 | {...props}
13 | >
14 | <path
15 | 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"
16 | fill="currentColor"
17 | />
18 | </svg>
19 | );
20 |
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/example/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "strict": true,
7 | "noEmit": true,
8 | "esModuleInterop": true,
9 | "module": "esnext",
10 | "moduleResolution": "bundler",
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "jsx": "preserve",
14 | "incremental": true,
15 | "plugins": [
16 | {
17 | "name": "next"
18 | }
19 | ],
20 | "paths": {
21 | "@/*": ["./*"]
22 | }
23 | },
24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25 | "exclude": ["node_modules"]
26 | }
27 |
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-bootstrap-demo/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "strict": true,
7 | "noEmit": true,
8 | "esModuleInterop": true,
9 | "module": "esnext",
10 | "moduleResolution": "bundler",
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "jsx": "preserve",
14 | "incremental": true,
15 | "plugins": [
16 | {
17 | "name": "next"
18 | }
19 | ],
20 | "paths": {
21 | "@/*": ["./*"]
22 | }
23 | },
24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25 | "exclude": ["node_modules"]
26 | }
27 |
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-flag-demo/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "strict": true,
7 | "noEmit": true,
8 | "esModuleInterop": true,
9 | "module": "esnext",
10 | "moduleResolution": "bundler",
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "jsx": "preserve",
14 | "incremental": true,
15 | "plugins": [
16 | {
17 | "name": "next"
18 | }
19 | ],
20 | "paths": {
21 | "@/*": ["./*"]
22 | }
23 | },
24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25 | "exclude": ["node_modules"]
26 | }
27 |
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-flag-demo/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "nextjs-flag-demo",
3 | "version": "0.2.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@reflag/react-sdk": "workspace:^",
13 | "next": "14.2.21",
14 | "react": "^18",
15 | "react-dom": "^18"
16 | },
17 | "devDependencies": {
18 | "@types/node": "^22.12.0",
19 | "@types/react": "^18",
20 | "@types/react-dom": "^18",
21 | "eslint": "^8",
22 | "eslint-config-next": "14.2.5",
23 | "postcss": "^8",
24 | "tailwindcss": "^3.4.1",
25 | "typescript": "^5.7.3"
26 | }
27 | }
28 |
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/example/app/layout.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import type { Metadata } from "next";
2 | import { Inter } from "next/font/google";
3 | import "./globals.css";
4 | import { OpenFeatureProvider } from "@/components/OpenFeatureProvider";
5 |
6 | const inter = Inter({ subsets: ["latin"] });
7 |
8 | export const metadata: Metadata = {
9 | title: "Create Next App",
10 | description: "Generated by create next app",
11 | };
12 |
13 | export default function RootLayout({
14 | children,
15 | }: Readonly<{
16 | children: React.ReactNode;
17 | }>) {
18 | return (
19 | <html lang="en">
20 | <body className={inter.className}>
21 | <OpenFeatureProvider>{children}</OpenFeatureProvider>
22 | </body>
23 | </html>
24 | );
25 | }
26 |
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/example/app/globals.css:
--------------------------------------------------------------------------------
```css
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --foreground-rgb: 0, 0, 0;
7 | --background-start-rgb: 214, 219, 220;
8 | --background-end-rgb: 255, 255, 255;
9 | }
10 |
11 | @media (prefers-color-scheme: dark) {
12 | :root {
13 | --foreground-rgb: 255, 255, 255;
14 | --background-start-rgb: 0, 0, 0;
15 | --background-end-rgb: 0, 0, 0;
16 | }
17 | }
18 |
19 | body {
20 | color: rgb(var(--foreground-rgb));
21 | background: linear-gradient(
22 | to bottom,
23 | transparent,
24 | rgb(var(--background-end-rgb))
25 | )
26 | rgb(var(--background-start-rgb));
27 | }
28 |
29 | @layer utilities {
30 | .text-balance {
31 | text-wrap: balance;
32 | }
33 | }
34 |
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-bootstrap-demo/app/globals.css:
--------------------------------------------------------------------------------
```css
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --foreground-rgb: 0, 0, 0;
7 | --background-start-rgb: 214, 219, 220;
8 | --background-end-rgb: 255, 255, 255;
9 | }
10 |
11 | @media (prefers-color-scheme: dark) {
12 | :root {
13 | --foreground-rgb: 255, 255, 255;
14 | --background-start-rgb: 0, 0, 0;
15 | --background-end-rgb: 0, 0, 0;
16 | }
17 | }
18 |
19 | body {
20 | color: rgb(var(--foreground-rgb));
21 | background: linear-gradient(
22 | to bottom,
23 | transparent,
24 | rgb(var(--background-end-rgb))
25 | )
26 | rgb(var(--background-start-rgb));
27 | }
28 |
29 | @layer utilities {
30 | .text-balance {
31 | text-wrap: balance;
32 | }
33 | }
34 |
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-flag-demo/app/globals.css:
--------------------------------------------------------------------------------
```css
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --foreground-rgb: 0, 0, 0;
7 | --background-start-rgb: 214, 219, 220;
8 | --background-end-rgb: 255, 255, 255;
9 | }
10 |
11 | @media (prefers-color-scheme: dark) {
12 | :root {
13 | --foreground-rgb: 255, 255, 255;
14 | --background-start-rgb: 0, 0, 0;
15 | --background-end-rgb: 0, 0, 0;
16 | }
17 | }
18 |
19 | body {
20 | color: rgb(var(--foreground-rgb));
21 | background: linear-gradient(
22 | to bottom,
23 | transparent,
24 | rgb(var(--background-end-rgb))
25 | )
26 | rgb(var(--background-start-rgb));
27 | }
28 |
29 | @layer utilities {
30 | .text-balance {
31 | text-wrap: balance;
32 | }
33 | }
34 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/example/typescript/index.html:
--------------------------------------------------------------------------------
```html
1 | <!doctype html>
2 | <html lang="en">
3 | <head>
4 | <meta charset="UTF-8" />
5 | <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6 | <title>Reflag flag management</title>
7 | </head>
8 | <body>
9 | <span id="loading">Loading...</span>
10 | <style>
11 | #start-huddle {
12 | border: 1px solid black;
13 | padding: 10px;
14 | }
15 | </style>
16 | <div id="start-huddle" style="display: none">
17 | Start huddle!<br />
18 | <button id="startHuddle">Click me</button>
19 | <button id="giveFeedback">Give feedback!</button>
20 | </div>
21 | <script src="app.ts" type="module"></script>
22 | </body>
23 | </html>
24 |
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-bootstrap-demo/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "nextjs-bootstrap-demo",
3 | "version": "0.2.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@reflag/node-sdk": "workspace:^",
13 | "@reflag/react-sdk": "workspace:^",
14 | "next": "14.2.21",
15 | "react": "^18",
16 | "react-dom": "^18"
17 | },
18 | "devDependencies": {
19 | "@types/node": "^22.12.0",
20 | "@types/react": "^18",
21 | "@types/react-dom": "^18",
22 | "eslint": "^8",
23 | "eslint-config-next": "14.2.5",
24 | "postcss": "^8",
25 | "tailwindcss": "^3.4.1",
26 | "typescript": "^5.7.3"
27 | }
28 | }
29 |
```
--------------------------------------------------------------------------------
/packages/vue-sdk/src/ReflagClientProvider.vue:
--------------------------------------------------------------------------------
```vue
1 | <script setup lang="ts">
2 | import { provide, ref } from "vue";
3 |
4 | import { ProviderSymbol, useOnEvent } from "./hooks";
5 | import { ReflagClientProviderProps } from "./types";
6 |
7 | const { client, initialLoading = true } =
8 | defineProps<ReflagClientProviderProps>();
9 |
10 | const isLoading = ref(
11 | client.getState() !== "initialized" ? initialLoading : false,
12 | );
13 | useOnEvent(
14 | "stateUpdated",
15 | (state) => {
16 | isLoading.value = state === "initializing";
17 | },
18 | client,
19 | );
20 |
21 | provide(ProviderSymbol, {
22 | isLoading,
23 | client,
24 | });
25 | </script>
26 |
27 | <template>
28 | <slot v-if="isLoading && $slots.loading" name="loading" />
29 | <slot v-else />
30 | </template>
31 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/ui/icons/Logo.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import { FunctionComponent, h } from "preact";
2 |
3 | export const Logo: FunctionComponent<h.JSX.SVGAttributes<SVGSVGElement>> = (
4 | props = { height: "10px", width: "10px" },
5 | ) => (
6 | <svg
7 | width="128"
8 | height="128"
9 | viewBox="0 0 128 128"
10 | fill="none"
11 | xmlns="http://www.w3.org/2000/svg"
12 | {...props}
13 | >
14 | <path
15 | 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"
16 | fill="currentColor"
17 | />
18 | </svg>
19 | );
20 |
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-bootstrap-demo/public/vercel.svg:
--------------------------------------------------------------------------------
```
1 | <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:
--------------------------------------------------------------------------------
```
1 | <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
1 | "use client";
2 |
3 | import React, { ReactNode } from "react";
4 | import { ReflagProvider } from "@reflag/react-sdk";
5 |
6 | type Props = {
7 | publishableKey: string;
8 | children: ReactNode;
9 | };
10 |
11 | export const Providers = ({ publishableKey, children }: Props) => {
12 | return (
13 | <ReflagProvider
14 | publishableKey={publishableKey}
15 | context={{
16 | company: { id: "demo-company", name: "Demo Company" },
17 | user: {
18 | id: "demo-user",
19 | email: "[email protected]",
20 | "optin-huddles": "true",
21 | },
22 | }}
23 | fallbackFlags={["fallback-feature"]}
24 | >
25 | {children}
26 | </ReflagProvider>
27 | );
28 | };
29 |
```
--------------------------------------------------------------------------------
/packages/node-sdk/src/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | export { BoundReflagClient, ReflagClient } from "./client";
2 | export { EdgeClient, EdgeClientOptions } from "./edgeClient";
3 | export type {
4 | Attributes,
5 | BatchBufferOptions,
6 | BootstrappedFlags,
7 | CacheStrategy,
8 | ClientOptions,
9 | Context,
10 | ContextWithTracking,
11 | EmptyFlagRemoteConfig,
12 | Flag,
13 | FlagConfigVariant,
14 | FlagDefinition,
15 | FlagOverride,
16 | FlagOverrides,
17 | FlagOverridesFn,
18 | FlagRemoteConfig,
19 | Flags,
20 | FlagType,
21 | HttpClient,
22 | HttpClientResponse,
23 | IdType,
24 | LOG_LEVELS,
25 | Logger,
26 | LogLevel,
27 | RawFlag,
28 | RawFlagRemoteConfig,
29 | RawFlags,
30 | TrackingMeta,
31 | TrackOptions,
32 | TypedFlagKey,
33 | TypedFlags,
34 | } from "./types";
35 |
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-flag-demo/app/layout.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import type { Metadata } from "next";
2 | import { Inter } from "next/font/google";
3 | import "./globals.css";
4 | import { Providers } from "@/components/Providers";
5 |
6 | const inter = Inter({ subsets: ["latin"] });
7 |
8 | export const metadata: Metadata = {
9 | title: "Create Next App",
10 | description: "Generated by create next app",
11 | };
12 |
13 | const publishableKey = process.env.PUBLISHABLE_KEY || "";
14 |
15 | export default function RootLayout({
16 | children,
17 | }: Readonly<{
18 | children: React.ReactNode;
19 | }>) {
20 | return (
21 | <html lang="en">
22 | <body className={inter.className}>
23 | <Providers publishableKey={publishableKey}>{children}</Providers>
24 | </body>
25 | </html>
26 | );
27 | }
28 |
```
--------------------------------------------------------------------------------
/packages/openfeature-node-provider/example/reflag.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { OpenFeature } from "@openfeature/server-sdk";
2 | import { ReflagNodeProvider } from "../src";
3 |
4 | if (!process.env.REFLAG_SECRET_KEY) {
5 | throw new Error("REFLAG_SECRET_KEY is required");
6 | }
7 |
8 | export type CreateTodosConfig = {
9 | maxLength: number;
10 | };
11 |
12 | const provider = new ReflagNodeProvider({
13 | secretKey: process.env.REFLAG_SECRET_KEY!,
14 | fallbackFlags: {
15 | "show-todos": {
16 | isEnabled: true,
17 | },
18 | "create-todos": {
19 | isEnabled: true,
20 | config: {
21 | key: "default",
22 | payload: {
23 | maxLength: 100,
24 | },
25 | },
26 | },
27 | },
28 | logger: console,
29 | });
30 |
31 | OpenFeature.setProvider(provider);
32 |
33 | export default provider;
34 |
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-bootstrap-demo/app/client.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ReflagClient as ReflagNodeClient } from "@reflag/node-sdk";
2 |
3 | const secretKey = process.env.REFLAG_SECRET_KEY || "";
4 | const offline = process.env.CI === "true";
5 |
6 | declare global {
7 | var serverClient: ReflagNodeClient;
8 | }
9 |
10 | /**
11 | * Create a singleton server client and store it in globalThis.
12 | * This avoids creating multiple instances of the client in each loaded chunk.
13 | * @returns The server client.
14 | */
15 | export async function getServerClient() {
16 | if (!globalThis.serverClient) {
17 | globalThis.serverClient = new ReflagNodeClient({
18 | secretKey,
19 | offline,
20 | });
21 | }
22 | await globalThis.serverClient.initialize();
23 | return globalThis.serverClient;
24 | }
25 |
```
--------------------------------------------------------------------------------
/packages/eslint-config/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "@reflag/eslint-config",
3 | "version": "0.0.2",
4 | "license": "MIT",
5 | "private": true,
6 | "main": "./base.js",
7 | "dependencies": {
8 | "@eslint/js": "^9.21.0",
9 | "@typescript-eslint/eslint-plugin": "^8.25.0",
10 | "@typescript-eslint/parser": "^8.25.0",
11 | "eslint": "^9.21.0",
12 | "eslint-config-prettier": "^10.0.1",
13 | "eslint-import-resolver-typescript": "^3.8.3",
14 | "eslint-plugin-import": "^2.31.0",
15 | "eslint-plugin-react": "^7.37.4",
16 | "eslint-plugin-react-hooks": "^5.1.0",
17 | "eslint-plugin-simple-import-sort": "^12.1.1",
18 | "eslint-plugin-unused-imports": "^4.1.4",
19 | "globals": "^16.2.0",
20 | "prettier": "^3.5.2",
21 | "typescript": "^5.7.3"
22 | }
23 | }
24 |
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/example/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "nextjs-openfeature-example",
3 | "version": "0.2.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@openfeature/core": "1.3.0",
13 | "@openfeature/react-sdk": "^0.4.5",
14 | "@openfeature/web-sdk": "^1.2.3",
15 | "@reflag/react-sdk": "workspace:^",
16 | "next": "14.2.26",
17 | "react": "^18",
18 | "react-dom": "^18"
19 | },
20 | "devDependencies": {
21 | "@types/node": "^22.12.0",
22 | "@types/react": "^18",
23 | "@types/react-dom": "^18",
24 | "eslint": "^8",
25 | "eslint-config-next": "14.2.5",
26 | "postcss": "^8",
27 | "tailwindcss": "^3.4.1",
28 | "typescript": "^5.7.3"
29 | }
30 | }
31 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/toolbar/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { h, render } from "preact";
2 |
3 | import { ReflagClient } from "../client";
4 | import { toolbarContainerId } from "../ui/constants";
5 | import { ToolbarPosition } from "../ui/types";
6 | import { attachContainer } from "../ui/utils";
7 |
8 | import Toolbar from "./Toolbar";
9 |
10 | type showToolbarToggleOptions = {
11 | reflagClient: ReflagClient;
12 | position?: ToolbarPosition;
13 | };
14 |
15 | export const DEFAULT_PLACEMENT = "bottom-right" as const;
16 |
17 | export function showToolbarToggle(options: showToolbarToggleOptions) {
18 | const shadowRoot = attachContainer(toolbarContainerId);
19 | const position: ToolbarPosition = options.position ?? {
20 | placement: DEFAULT_PLACEMENT,
21 | };
22 |
23 | render(h(Toolbar, { ...options, position }), shadowRoot);
24 | }
25 |
```
--------------------------------------------------------------------------------
/packages/vue-sdk/dev/plain/components/MissingKeyMessage.vue:
--------------------------------------------------------------------------------
```vue
1 | <template>
2 | <div
3 | style="
4 | padding: 20px;
5 | border: 2px solid #ff6b6b;
6 | border-radius: 8px;
7 | background-color: #fff5f5;
8 | color: #d63031;
9 | font-family: Arial, sans-serif;
10 | "
11 | >
12 | <h3 style="margin: 0 0 10px 0; color: #d63031">Missing Publishable Key</h3>
13 | <p style="margin: 0; line-height: 1.5">
14 | The <code>VITE_PUBLISHABLE_KEY</code> environment variable is not set.
15 | Please set this variable in your <code>.env</code> file to use the Reflag
16 | SDK.
17 | </p>
18 | <p style="margin: 10px 0 0 0; font-size: 14px; opacity: 0.8">
19 | Example: <code>VITE_PUBLISHABLE_KEY=your_key_here</code>
20 | </p>
21 | </div>
22 | </template>
23 |
24 | <script setup lang="ts"></script>
25 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/feedback/ui/RadialProgress.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import { FunctionComponent, h } from "preact";
2 |
3 | export const RadialProgress: FunctionComponent<{
4 | diameter: number;
5 | progress: number;
6 | }> = ({ diameter, progress }) => {
7 | const stroke = 2;
8 | const radius = diameter / 2 - stroke;
9 | const circumference = 2 * Math.PI * radius;
10 | const filled = circumference * progress;
11 |
12 | return (
13 | <svg class="radial-progress" height={diameter} width={diameter}>
14 | <circle
15 | cx={radius + stroke}
16 | cy={radius + stroke}
17 | fill="transparent"
18 | r={radius}
19 | stroke-dasharray={circumference}
20 | stroke-dashoffset={filled}
21 | stroke-width={stroke}
22 | transform={`rotate(-90) translate(-${radius * 2 + stroke * 2} 0)`}
23 | />
24 | </svg>
25 | );
26 | };
27 |
```
--------------------------------------------------------------------------------
/packages/cli/utils/file.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { access, constants } from "node:fs/promises";
2 | import os from "node:os";
3 | import { join } from "node:path";
4 | /**
5 | * Checks if a file exists at the given path.
6 | * @param path The path to the file.
7 | * @returns True if the file exists, false otherwise.
8 | */
9 | export async function fileExists(path: string): Promise<boolean> {
10 | try {
11 | await access(path, constants.F_OK);
12 | return true;
13 | } catch {
14 | return false;
15 | }
16 | }
17 |
18 | // Helper to resolve home directory
19 | export const resolvePath = (p: string) => {
20 | return join(
21 | ...p.split("/").map((part) => {
22 | if (part === "~") {
23 | return os.homedir();
24 | } else if (part === "@") {
25 | return process.env.APPDATA ?? "";
26 | } else {
27 | return part;
28 | }
29 | }),
30 | );
31 | };
32 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/feedback/ui/config/defaultTranslations.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import { FeedbackTranslations } from "../types";
2 | /**
3 | * {@includeCode ./defaultTranslations.tsx}
4 | */
5 | export const DEFAULT_TRANSLATIONS: FeedbackTranslations = {
6 | DefaultQuestionLabel: "How satisfied are you with this feature?",
7 | QuestionPlaceholder: "Write a comment",
8 | ScoreStatusDescription: "Pick a score and leave a comment",
9 | ScoreStatusLoading: "Saving score, please wait...",
10 | ScoreStatusReceived: "Score has been received!",
11 | ScoreVeryDissatisfiedLabel: "Very dissatisfied (1/5)",
12 | ScoreDissatisfiedLabel: "Dissatisfied (2/5)",
13 | ScoreNeutralLabel: "Neutral (3/5)",
14 | ScoreSatisfiedLabel: "Satisfied (4/5)",
15 | ScoreVerySatisfiedLabel: "Very satisfied (5/5)",
16 | SuccessMessage: "Feedback received, thank you!",
17 | SendButton: "Send feedback",
18 | };
19 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/rateLimiter.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Logger } from "./logger";
2 |
3 | const oneMinute = 60 * 1000;
4 |
5 | export default class RateLimiter {
6 | private eventsByKey: Record<string, number[]> = {};
7 |
8 | constructor(
9 | private eventsPerMinute: number,
10 | private logger: Logger,
11 | ) {}
12 |
13 | public rateLimited<R>(key: string, func: () => R): R | undefined {
14 | const now = Date.now();
15 |
16 | if (!this.eventsByKey[key]) {
17 | this.eventsByKey[key] = [];
18 | }
19 |
20 | const events = this.eventsByKey[key];
21 |
22 | while (events.length && now - events[0] > oneMinute) {
23 | events.shift();
24 | }
25 |
26 | const limitExceeded = events.length >= this.eventsPerMinute;
27 | if (limitExceeded) {
28 | this.logger.debug("Rate limit exceeded", { key });
29 | return;
30 | }
31 |
32 | events.push(now);
33 | return func();
34 | }
35 | }
36 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/ui/types.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Placement } from "./packages/floating-ui-preact-dom/types";
2 |
3 | export type DialogPlacement =
4 | | "bottom-right"
5 | | "bottom-left"
6 | | "top-right"
7 | | "top-left";
8 |
9 | export type PopoverPlacement = Placement;
10 |
11 | export type Offset = {
12 | /**
13 | * Offset from the nearest horizontal screen edge after placement is resolved
14 | */
15 | x?: string | number;
16 | /**
17 | * Offset from the nearest vertical screen edge after placement is resolved
18 | */
19 | y?: string | number;
20 | };
21 |
22 | export type Position =
23 | | { type: "MODAL" }
24 | | {
25 | type: "DIALOG";
26 | placement: DialogPlacement;
27 | offset?: Offset;
28 | }
29 | | {
30 | type: "POPOVER";
31 | anchor: HTMLElement | null;
32 | placement?: PopoverPlacement;
33 | };
34 |
35 | export interface ToolbarPosition {
36 | placement: DialogPlacement;
37 | offset?: Offset;
38 | }
39 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/playwright.config.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { defineConfig, devices } from "@playwright/test";
2 |
3 | /**
4 | * See https://playwright.dev/docs/test-configuration.
5 | */
6 | export default defineConfig({
7 | testDir: "./test/e2e",
8 | testMatch: "**/*.spec.?(c|m)[jt]s?(x)",
9 | fullyParallel: true,
10 | forbidOnly: !!process.env.CI,
11 | retries: process.env.CI ? 2 : 0,
12 | reporter: "list",
13 |
14 | use: {
15 | trace: "on-first-retry",
16 | },
17 |
18 | projects: [
19 | {
20 | name: "chromium",
21 | use: { ...devices["Desktop Chrome"] },
22 | },
23 | {
24 | name: "firefox",
25 | use: { ...devices["Desktop Firefox"] },
26 | },
27 | {
28 | name: "webkit",
29 | use: { ...devices["Desktop Safari"] },
30 | },
31 | ],
32 |
33 | webServer: {
34 | // separate port to let the app run alongside the tracking sdk tests
35 | command: "npx http-server . -p 8001",
36 | timeout: 120 * 1000,
37 | port: 8001,
38 | },
39 | });
40 |
```
--------------------------------------------------------------------------------
/packages/cli/utils/constants.ts:
--------------------------------------------------------------------------------
```typescript
1 | import os from "node:os";
2 | import { join } from "node:path";
3 | import { fileURLToPath } from "node:url";
4 |
5 | export const CLIENT_VERSION_HEADER_NAME = "reflag-sdk-version";
6 | export const CLIENT_VERSION_HEADER_VALUE = (version: string) =>
7 | `cli/${version}`;
8 |
9 | export const CONFIG_FILE_NAME = "reflag.config.json";
10 | export const AUTH_FILE = join(os.homedir(), ".reflag-auth");
11 | export const SCHEMA_URL = `https://unpkg.com/@reflag/cli@latest/schema.json`;
12 |
13 | export const DEFAULT_BASE_URL = "https://app.reflag.com";
14 | export const DEFAULT_API_URL = `${DEFAULT_BASE_URL}/api`;
15 | export const DEFAULT_TYPES_OUTPUT = join("gen", "flags.d.ts");
16 |
17 | export const DEFAULT_AUTH_TIMEOUT = 60000; // 60 seconds
18 |
19 | export const MODULE_ROOT = fileURLToPath(import.meta.url).substring(
20 | 0,
21 | fileURLToPath(import.meta.url).lastIndexOf("cli") + 3,
22 | );
23 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/toolbar/Switch.css:
--------------------------------------------------------------------------------
```css
1 | .switch {
2 | cursor: pointer;
3 | position: relative;
4 | }
5 |
6 | .switch-input {
7 | border: 0px;
8 | clip: rect(0px, 0px, 0px, 0px);
9 | height: 1px;
10 | width: 1px;
11 | margin: -1px;
12 | padding: 0px;
13 | overflow: hidden;
14 | white-space: nowrap;
15 | position: absolute;
16 | }
17 |
18 | .switch-track {
19 | position: relative;
20 | transition: background 0.1s ease;
21 | background: var(--gray600);
22 | border-radius: 999px;
23 | transition: transform 0.1s ease-out;
24 | }
25 |
26 | .switch-input:focus-visible + .switch-track {
27 | outline: 1px solid #fff;
28 | outline-offset: 1px;
29 | }
30 |
31 | .switch[data-enabled="true"] .switch-track {
32 | background: white;
33 | }
34 |
35 | .switch-dot {
36 | background: white;
37 | border-radius: 50%;
38 | position: absolute;
39 | top: 1px;
40 | transition: transform 0.1s ease-out;
41 | box-shadow: 0 0px 5px rgba(0, 0, 0, 0.2);
42 | }
43 |
44 | .switch[data-enabled="true"] .switch-dot {
45 | background: var(--black);
46 | }
47 |
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/vite.config.js:
--------------------------------------------------------------------------------
```javascript
1 | import { resolve } from "path";
2 | import { defineConfig } from "vite";
3 | import dts from "vite-plugin-dts";
4 |
5 | export default defineConfig({
6 | test: {
7 | environment: "jsdom",
8 | exclude: [
9 | "test/e2e/**",
10 | "**/node_modules/**",
11 | "**/dist/**",
12 | "**/cypress/**",
13 | "**/.{idea,git,cache,output,temp}/**",
14 | "**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*",
15 | ],
16 | },
17 | plugins: [dts({ insertTypesEntry: true })],
18 | build: {
19 | exclude: ["**/node_modules/**", "test/e2e/**", "**/*.test.ts"],
20 | sourcemap: true,
21 | lib: {
22 | // Could also be a dictionary or array of multiple entry points
23 | entry: resolve(__dirname, "src/index.ts"),
24 | name: "ReflagOpenFeatureBrowserProvider",
25 | // the proper extensions will be added
26 | fileName: "reflag-openfeature-browser-provider",
27 | },
28 | },
29 | });
30 |
```
--------------------------------------------------------------------------------
/packages/react-sdk/vite.config.mjs:
--------------------------------------------------------------------------------
```
1 | import { resolve } from "path";
2 | import preserveDirectives from "rollup-preserve-directives";
3 | import { defineConfig } from "vite";
4 | import dts from "vite-plugin-dts";
5 |
6 | export default defineConfig({
7 | test: {
8 | root: __dirname,
9 | environment: "jsdom",
10 | },
11 | optimizeDeps: {
12 | include: ["@reflag/browser-sdk"],
13 | },
14 | plugins: [
15 | dts({ insertTypesEntry: true, exclude: ["dev"] }),
16 | preserveDirectives(),
17 | ],
18 | build: {
19 | exclude: ["**/node_modules/**", "test/e2e/**", "dev"],
20 | sourcemap: true,
21 | lib: {
22 | entry: resolve(__dirname, "src/index.tsx"),
23 | name: "ReflagReactSDK",
24 | fileName: "reflag-react-sdk",
25 | formats: ["es", "umd"],
26 | },
27 | rollupOptions: {
28 | external: ["react", "react-dom"],
29 | output: {
30 | globals: {
31 | react: "React",
32 | },
33 | },
34 | },
35 | },
36 | server: {
37 | open: "/dev/plain/index.html",
38 | },
39 | });
40 |
```
--------------------------------------------------------------------------------
/packages/node-sdk/src/edgeClient.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ReflagClient } from "./client";
2 | import { ClientOptions } from "./types";
3 |
4 | export type EdgeClientOptions = Omit<
5 | ClientOptions,
6 | "cacheStrategy" | "flushIntervalMs" | "batchOptions"
7 | >;
8 |
9 | /**
10 | * The EdgeClient is ReflagClient pre-configured to be used in edge runtimes, like
11 | * Cloudflare Workers.
12 | *
13 | * @example
14 | * ```ts
15 | * // set the REFLAG_SECRET_KEY environment variable or pass the secret key in the constructor
16 | * const client = new EdgeClient();
17 | *
18 | * // evaluate a flag
19 | * const context = {
20 | * user: { id: "user-id" },
21 | * company: { id: "company-id" },
22 | * }
23 | * const { isEnabled } = client.getFlag(context, "flag-key");
24 | *
25 | * ```
26 | */
27 | export class EdgeClient extends ReflagClient {
28 | constructor(options: EdgeClientOptions = {}) {
29 | const opts = {
30 | ...options,
31 | cacheStrategy: "in-request" as const,
32 | batchOptions: {
33 | intervalMs: 0,
34 | },
35 | };
36 | super(opts);
37 | }
38 | }
39 |
```
--------------------------------------------------------------------------------
/packages/vue-sdk/dev/plain/components/StartHuddlesButton.vue:
--------------------------------------------------------------------------------
```vue
1 | <script setup lang="ts">
2 | import { useFlag } from "../../../src";
3 |
4 | import Section from "./Section.vue";
5 |
6 | const { isLoading, isEnabled, config, requestFeedback, track } =
7 | useFlag("huddles");
8 | </script>
9 | <template>
10 | <Section title="Huddles">
11 | <div style="display: flex; gap: 10px; flex-wrap: wrap">
12 | <div>huddles enabled: {{ isEnabled }}</div>
13 | <div v-if="isLoading">Loading...</div>
14 | <div v-else style="display: flex; gap: 10px; flex-wrap: wrap">
15 | <div>
16 | <button @click="track()">
17 | {{ config?.payload?.buttonTitle ?? "Start Huddles (track event)" }}
18 | </button>
19 | </div>
20 | <div>
21 | <button
22 | @click="
23 | (e) =>
24 | requestFeedback({
25 | title: 'Do you like huddles?',
26 | })
27 | "
28 | >
29 | Trigger survey
30 | </button>
31 | </div>
32 | </div>
33 | </div>
34 | </Section>
35 | </template>
36 |
```
--------------------------------------------------------------------------------
/packages/vue-sdk/vite.config.mjs:
--------------------------------------------------------------------------------
```
1 | import { resolve } from "path";
2 | import vue from "@vitejs/plugin-vue";
3 | import preserveDirectives from "rollup-preserve-directives";
4 | import { defineConfig } from "vite";
5 | import dts from "vite-plugin-dts";
6 |
7 | export default defineConfig({
8 | test: {
9 | environment: "jsdom",
10 | },
11 | optimizeDeps: {
12 | include: ["@reflag/browser-sdk"],
13 | },
14 | resolve: {
15 | alias: {
16 | vue: "vue/dist/vue.esm-bundler.js",
17 | },
18 | },
19 | plugins: [
20 | vue(),
21 | dts({ insertTypesEntry: true, exclude: ["dev"] }),
22 | preserveDirectives(),
23 | ],
24 | build: {
25 | exclude: ["**/node_modules/**", "test/e2e/**", "dev"],
26 | sourcemap: true,
27 | lib: {
28 | entry: resolve(__dirname, "src/index.ts"),
29 | name: "ReflagVueSDK",
30 | fileName: "reflag-vue-sdk",
31 | formats: ["es", "umd"],
32 | },
33 | rollupOptions: {
34 | external: ["vue"],
35 | output: {
36 | globals: {
37 | vue: "Vue",
38 | },
39 | },
40 | },
41 | },
42 | server: {
43 | open: "/dev/plain/index.html",
44 | },
45 | });
46 |
```
--------------------------------------------------------------------------------
/packages/cli/commands/apps.ts:
--------------------------------------------------------------------------------
```typescript
1 | import chalk from "chalk";
2 | import { Command } from "commander";
3 | import ora from "ora";
4 |
5 | import { listApps } from "../services/bootstrap.js";
6 | import { configStore } from "../stores/config.js";
7 | import { handleError } from "../utils/errors.js";
8 |
9 | export const listAppsAction = async () => {
10 | const baseUrl = configStore.getConfig("baseUrl");
11 | const spinner = ora(`Loading apps from ${chalk.cyan(baseUrl)}...`).start();
12 |
13 | try {
14 | const apps = listApps();
15 | spinner.succeed(`Loaded apps from ${chalk.cyan(baseUrl)}.`);
16 | console.table(apps.map(({ name, id, demo }) => ({ name, id, demo })));
17 | } catch (error) {
18 | spinner.fail("Failed to list apps.");
19 | handleError(error, "Apps List");
20 | }
21 | };
22 |
23 | export function registerAppCommands(cli: Command) {
24 | const appsCommand = new Command("apps").description("Manage apps.");
25 |
26 | appsCommand
27 | .command("list")
28 | .alias("ls")
29 | .description("List all available apps.")
30 | .action(listAppsAction);
31 |
32 | cli.addCommand(appsCommand);
33 | }
34 |
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "workspaces",
3 | "version": "0.0.1",
4 | "private": true,
5 | "license": "MIT",
6 | "workspaces": [
7 | "packages/*",
8 | "packages/react-sdk/dev/*",
9 | "packages/openfeature-browser-provider/example"
10 | ],
11 | "scripts": {
12 | "dev": "lerna run dev --parallel",
13 | "build": "lerna run build --stream",
14 | "test:ci": "lerna run test:ci --stream",
15 | "test": "lerna run test --stream",
16 | "format": "lerna run format --stream",
17 | "prettier": "lerna run prettier --stream",
18 | "prettier:fix": "lerna run prettier -- --write",
19 | "lint": "lerna run lint --stream",
20 | "lint:ci": "lerna run lint:ci --stream",
21 | "version": "lerna version --exact --no-push",
22 | "docs": "./docs.sh"
23 | },
24 | "packageManager": "[email protected]",
25 | "devDependencies": {
26 | "lerna": "^8.1.3",
27 | "prettier": "^3.5.2",
28 | "typedoc": "0.27.6",
29 | "typedoc-plugin-frontmatter": "^1.1.2",
30 | "typedoc-plugin-markdown": "^4.4.2",
31 | "typedoc-plugin-mdn-links": "^4.0.7",
32 | "typescript": "^5.7.3"
33 | }
34 | }
35 |
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/example/app/featureManagement.ts:
--------------------------------------------------------------------------------
```typescript
1 | "use client";
2 |
3 | import { ReflagBrowserSDKProvider } from "@reflag/openfeature-browser-provider";
4 | import { OpenFeature } from "@openfeature/react-sdk";
5 |
6 | const publishableKey = process.env.NEXT_PUBLIC_REFLAG_PUBLISHABLE_KEY;
7 |
8 | let reflagProvider: ReflagBrowserSDKProvider | null = null;
9 | let initialized = false;
10 |
11 | export async function initOpenFeature() {
12 | if (initialized) {
13 | return;
14 | }
15 | initialized = true;
16 |
17 | if (!publishableKey) {
18 | console.error("No publishable key set for Reflag");
19 | return;
20 | }
21 | reflagProvider = new ReflagBrowserSDKProvider({
22 | publishableKey,
23 | fallbackFlags: {
24 | huddle: {
25 | key: "zoom", // huddleMeetingProvider
26 | payload: {
27 | joinUrl: "https://zoom.us/join",
28 | },
29 | },
30 | },
31 | });
32 | return OpenFeature.setProviderAndWait(reflagProvider);
33 | }
34 |
35 | export function track(event: string, attributes?: { [key: string]: any }) {
36 | console.log("Tracking event", event, attributes);
37 | reflagProvider?.client?.track(event, attributes);
38 | }
39 |
```
--------------------------------------------------------------------------------
/packages/cli/utils/urls.ts:
--------------------------------------------------------------------------------
```typescript
1 | import chalk from "chalk";
2 | import slugMod from "slug";
3 |
4 | import { DEFAULT_BASE_URL } from "./constants.js";
5 |
6 | export type UrlArgs = { id: string; name: string };
7 |
8 | export function slug({ id, name }: UrlArgs) {
9 | return `${slugMod(name).substring(0, 15)}-${id}`;
10 | }
11 |
12 | export function stripTrailingSlash<T extends string | undefined>(str: T): T {
13 | return str?.endsWith("/") ? (str.slice(0, -1) as T) : str;
14 | }
15 |
16 | export const successUrl = (baseUrl: string) => `${baseUrl}/cli-login/success`;
17 | export const errorUrl = (baseUrl: string, error: string) =>
18 | `${baseUrl}/cli-login/error?error=${error}`;
19 |
20 | export const baseUrlSuffix = (baseUrl: string) => {
21 | return baseUrl !== DEFAULT_BASE_URL ? ` at ${chalk.cyan(baseUrl)}` : "";
22 | };
23 |
24 | export function environmentUrl(baseUrl: string, environment: UrlArgs): string {
25 | return `${baseUrl}/envs/${slug(environment)}`;
26 | }
27 |
28 | export function featureUrl(
29 | baseUrl: string,
30 | env: UrlArgs,
31 | feature: UrlArgs,
32 | ): string {
33 | return `${environmentUrl(baseUrl, env)}/features/${slug(feature)}`;
34 | }
35 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/logger.ts:
--------------------------------------------------------------------------------
```typescript
1 | export interface Logger {
2 | debug(message: string, ...args: any[]): void;
3 | info(message: string, ...args: any[]): void;
4 | warn(message: string, ...args: any[]): void;
5 | error(message: string, ...args: any[]): void;
6 | }
7 |
8 | export const quietConsoleLogger = {
9 | debug(_: string) {
10 | // do nothing
11 | },
12 | info(_: string) {
13 | // do nothing
14 | },
15 | warn(message: string, ...args: any[]) {
16 | console.warn(message, ...args);
17 | },
18 | error(message: string, ...args: any[]) {
19 | console.error(message, ...args);
20 | },
21 | };
22 |
23 | export function loggerWithPrefix(logger: Logger, prefix: string): Logger {
24 | return {
25 | debug(message: string, ...args: any[]) {
26 | logger.debug(`${prefix} ${message}`, ...args);
27 | },
28 | info(message: string, ...args: any[]) {
29 | logger.info(`${prefix} ${message}`, ...args);
30 | },
31 | warn(message: string, ...args: any[]) {
32 | logger.warn(`${prefix} ${message}`, ...args);
33 | },
34 | error(message: string, ...args: any[]) {
35 | logger.error(`${prefix} ${message}`, ...args);
36 | },
37 | };
38 | }
39 |
```
--------------------------------------------------------------------------------
/packages/node-sdk/examples/express/app.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import request from "supertest";
2 | import app, { todos } from "./app";
3 | import { beforeEach, describe, it, expect, beforeAll } from "vitest";
4 |
5 | import reflag from "./reflag";
6 |
7 | beforeAll(async () => await reflag.initialize());
8 | beforeEach(() => {
9 | reflag.featureOverrides = {
10 | "show-todos": true,
11 | };
12 | });
13 |
14 | describe("API Tests", () => {
15 | it("should return 200 for the root endpoint", async () => {
16 | const response = await request(app).get("/");
17 | expect(response.status).toBe(200);
18 | expect(response.body).toEqual({ message: "Ready to manage some TODOs!" });
19 | });
20 |
21 | it("should return todos", async () => {
22 | const response = await request(app).get("/todos");
23 | expect(response.status).toBe(200);
24 | expect(response.body).toEqual({ todos });
25 | });
26 |
27 | it("should return no todos when list is disabled", async () => {
28 | reflag.featureOverrides = () => ({
29 | "show-todos": false,
30 | });
31 | const response = await request(app).get("/todos");
32 | expect(response.status).toBe(200);
33 | expect(response.body).toEqual({ todos: [] });
34 | });
35 | });
36 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/eslint.config.js:
--------------------------------------------------------------------------------
```javascript
1 | const base = require("@reflag/eslint-config");
2 | const preactConfig = require("eslint-config-preact");
3 |
4 | const compatPlugin = require("eslint-plugin-compat");
5 | const reactPlugin = require("eslint-plugin-react");
6 | const reactHooksPlugin = require("eslint-plugin-react-hooks");
7 |
8 | module.exports = [
9 | ...base,
10 | {
11 | // Preact projects
12 | files: ["**/*.tsx"],
13 |
14 | settings: {
15 | react: {
16 | // We only care about marking h() as being a used variable.
17 | pragma: "h",
18 | // We use "react 16.0" to avoid pushing folks to UNSAFE_ methods.
19 | version: "16.0",
20 | },
21 | },
22 | plugins: {
23 | compat: compatPlugin,
24 | react: reactPlugin,
25 | "react-hooks": reactHooksPlugin,
26 | },
27 | rules: {
28 | ...preactConfig.rules,
29 | // Ignore React attributes that are not valid in Preact.
30 | // Alternatively, we could use the preact/compat alias or turn off the rule.
31 | "react/no-unknown-property": ["off"],
32 | "no-unused-vars": ["off"],
33 | "react/no-danger": ["off"],
34 | },
35 | },
36 | { ignores: ["dist/", "example/"] },
37 | ];
38 |
```
--------------------------------------------------------------------------------
/packages/cli/schema.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "title": "Reflag cli schema",
4 | "type": "object",
5 | "properties": {
6 | "baseUrl": {
7 | "type": "string",
8 | "pattern": "^https?://.*",
9 | "description": "Base URL for the API. Defaults to https://app.reflag.com."
10 | },
11 | "apiUrl": {
12 | "type": "string",
13 | "pattern": "^https?://.*",
14 | "description": "API URL for the API. Defaults to https://app.reflag.com/api."
15 | },
16 | "appId": {
17 | "type": "string",
18 | "minLength": 14,
19 | "maxLength": 14,
20 | "description": "Mandatory ID for the Reflag app. You can find it by calling reflag apps list."
21 | },
22 | "typesOutput": {
23 | "type": "array",
24 | "description": "List of paths to output the types. The path is relative to the current working directory.",
25 | "items": {
26 | "type": "object",
27 | "properties": {
28 | "path": { "type": "string" },
29 | "format": { "type": "string", "enum": ["react", "node"] }
30 | },
31 | "required": ["path"]
32 | }
33 | }
34 | },
35 | "required": ["appId"]
36 | }
37 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/vite.config.mjs:
--------------------------------------------------------------------------------
```
1 | import { resolve } from "path";
2 | import { defineConfig } from "vite";
3 | import dts from "vite-plugin-dts";
4 | import { defaultExclude } from "vitest/config";
5 |
6 | export default defineConfig({
7 | test: {
8 | root: __dirname,
9 | environment: "jsdom",
10 | globals: true,
11 | setupFiles: ["./vitest.setup.ts"],
12 | exclude: [...defaultExclude, "test/e2e/**"],
13 | },
14 | plugins: [dts({ insertTypesEntry: true })],
15 | build: {
16 | exclude: ["**/node_modules/**", "test/e2e/**"],
17 | sourcemap: true,
18 | lib: {
19 | // Could also be a dictionary or array of multiple entry points
20 | entry: resolve(__dirname, "src/index.ts"),
21 | name: "ReflagBrowserSDK",
22 | // the proper extensions will be added
23 | fileName: "reflag-browser-sdk",
24 | },
25 | rollupOptions: {
26 | // make sure to externalize deps that shouldn't be bundled
27 | // into your library
28 | // external: ["vue"],
29 | output: {
30 | // Provide global variables to use in the UMD build
31 | // for externalized deps
32 | globals: {
33 | ReflagClient: "ReflagClient",
34 | },
35 | },
36 | },
37 | },
38 | });
39 |
```
--------------------------------------------------------------------------------
/packages/flag-evaluation/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "@reflag/flag-evaluation",
3 | "version": "1.0.0",
4 | "license": "MIT",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/reflagcom/javascript.git"
8 | },
9 | "publishConfig": {
10 | "access": "public"
11 | },
12 | "scripts": {
13 | "build": "tsc --project tsconfig.build.json",
14 | "test": "vitest",
15 | "test:ci": "vitest --reporter=default --reporter=junit --outputFile=junit.xml",
16 | "lint": "eslint .",
17 | "lint:ci": "eslint --output-file eslint-report.json --format json .",
18 | "prettier": "prettier --check .",
19 | "format": "yarn lint --fix && yarn prettier --write",
20 | "preversion": "yarn lint && yarn prettier && yarn vitest run -c vite.config.js && yarn build"
21 | },
22 | "files": [
23 | "dist"
24 | ],
25 | "main": "./dist/index.js",
26 | "types": "./dist/index.d.ts",
27 | "devDependencies": {
28 | "@reflag/eslint-config": "^0.0.2",
29 | "@reflag/tsconfig": "^0.0.2",
30 | "@types/node": "^22.12.0",
31 | "eslint": "^9.21.0",
32 | "prettier": "^3.5.2",
33 | "ts-node": "^10.9.2",
34 | "typescript": "^5.7.3",
35 | "vitest": "^2.0.5"
36 | },
37 | "dependencies": {
38 | "js-sha256": "0.11.0"
39 | }
40 | }
41 |
```
--------------------------------------------------------------------------------
/packages/vue-sdk/src/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { App } from "vue";
2 |
3 | import ReflagBootstrappedProvider from "./ReflagBootstrappedProvider.vue";
4 | import ReflagClientProvider from "./ReflagClientProvider.vue";
5 | import ReflagProvider from "./ReflagProvider.vue";
6 |
7 | export {
8 | useClient,
9 | useFlag,
10 | useIsLoading,
11 | useOnEvent,
12 | useRequestFeedback,
13 | useSendFeedback,
14 | useTrack,
15 | useUpdateCompany,
16 | useUpdateOtherContext,
17 | useUpdateUser,
18 | } from "./hooks";
19 | export type {
20 | BootstrappedFlags,
21 | EmptyFlagRemoteConfig,
22 | Flag,
23 | Flags,
24 | FlagType,
25 | ReflagBaseProps,
26 | ReflagBootstrappedProps,
27 | ReflagClientProviderProps,
28 | ReflagInitOptionsBase,
29 | ReflagProps,
30 | RequestFlagFeedbackOptions,
31 | TypedFlags,
32 | } from "./types";
33 | export type {
34 | CheckEvent,
35 | CompanyContext,
36 | TrackEvent,
37 | UserContext,
38 | } from "@reflag/browser-sdk";
39 |
40 | export { ReflagBootstrappedProvider, ReflagClientProvider, ReflagProvider };
41 |
42 | export default {
43 | install(app: App) {
44 | app.component("ReflagProvider", ReflagProvider);
45 | app.component("ReflagBootstrappedProvider", ReflagBootstrappedProvider);
46 | app.component("ReflagClientProvider", ReflagClientProvider);
47 | },
48 | };
49 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/toolbar/Switch.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import { Fragment, h } from "preact";
2 |
3 | interface SwitchProps extends h.JSX.HTMLAttributes<HTMLInputElement> {
4 | checked: boolean;
5 | width?: number;
6 | height?: number;
7 | }
8 |
9 | const gutter = 1;
10 |
11 | export function Switch({
12 | checked,
13 | width = 24,
14 | height = 14,
15 | ...props
16 | }: SwitchProps) {
17 | return (
18 | <>
19 | <label class="switch" data-enabled={checked}>
20 | <input
21 | checked={checked}
22 | class="switch-input"
23 | name="enabled"
24 | type="checkbox"
25 | {...props}
26 | />
27 | <div
28 | class="switch-track"
29 | style={{
30 | width: `${width}px`,
31 | height: `${height}px`,
32 | borderRadius: `${height}px`,
33 | }}
34 | >
35 | <div
36 | class="switch-dot"
37 | style={{
38 | width: `${height - gutter * 2}px`,
39 | height: `${height - gutter * 2}px`,
40 | transform: checked
41 | ? `translateX(${width - (height - gutter * 2) - gutter}px)`
42 | : `translateX(${gutter}px)`,
43 | top: `${gutter}px`,
44 | }}
45 | />
46 | </div>
47 | </label>
48 | </>
49 | );
50 | }
51 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/ui/packages/floating-ui-preact-dom/utils/deepEqual.ts:
--------------------------------------------------------------------------------
```typescript
1 | // Fork of `fast-deep-equal` that only does the comparisons we need and compares
2 | // functions
3 | export function deepEqual(a: any, b: any) {
4 | if (a === b) {
5 | return true;
6 | }
7 |
8 | if (typeof a !== typeof b) {
9 | return false;
10 | }
11 |
12 | if (typeof a === "function" && a.toString() === b.toString()) {
13 | return true;
14 | }
15 |
16 | let length, i, keys;
17 |
18 | if (a && b && typeof a == "object") {
19 | if (Array.isArray(a)) {
20 | length = a.length;
21 | if (length != b.length) return false;
22 | for (i = length; i-- !== 0; ) {
23 | if (!deepEqual(a[i], b[i])) {
24 | return false;
25 | }
26 | }
27 |
28 | return true;
29 | }
30 |
31 | keys = Object.keys(a);
32 | length = keys.length;
33 | if (length !== Object.keys(b).length) {
34 | return false;
35 | }
36 |
37 | for (i = length; i-- !== 0; ) {
38 | if (!{}.hasOwnProperty.call(b, keys[i])) {
39 | return false;
40 | }
41 | }
42 |
43 | for (i = length; i-- !== 0; ) {
44 | const key = keys[i];
45 | if (key === "_owner" && a.$$typeof) {
46 | continue;
47 | }
48 |
49 | if (!deepEqual(a[key], b[key])) {
50 | return false;
51 | }
52 | }
53 |
54 | return true;
55 | }
56 |
57 | return a !== a && b !== b;
58 | }
59 |
```
--------------------------------------------------------------------------------
/packages/vue-sdk/dev/plain/components/Events.vue:
--------------------------------------------------------------------------------
```vue
1 | <script setup lang="ts">
2 | import { onMounted, onUnmounted, ref } from "vue";
3 |
4 | import { CheckEvent, TrackEvent, useClient } from "../../../src";
5 |
6 | import Section from "./Section.vue";
7 |
8 | const client = useClient();
9 |
10 | const events = ref<string[]>([]);
11 |
12 | function checkEvent(evt: CheckEvent) {
13 | events.value = [
14 | ...events.value,
15 | `Check event: The feature ${evt.key} is ${evt.value} for user.`,
16 | ];
17 | }
18 |
19 | function featuresUpdatedEvent() {
20 | events.value = [...events.value, `Flags Updated!`];
21 | }
22 |
23 | function trackEvent(evt: TrackEvent) {
24 | events.value = [...events.value, `Track event: ${evt.eventName}`];
25 | }
26 |
27 | onMounted(() => {
28 | client.on("check", checkEvent);
29 | client.on("flagsUpdated", featuresUpdatedEvent);
30 | client.on("track", trackEvent);
31 | });
32 | onUnmounted(() => {
33 | client.off("check", checkEvent);
34 | client.off("flagsUpdated", featuresUpdatedEvent);
35 | client.off("track", trackEvent);
36 | });
37 | </script>
38 |
39 | <template>
40 | <Section title="Events">
41 | <div
42 | style="display: flex; gap: 10px; flex-wrap: wrap; flex-direction: column"
43 | >
44 | <div v-for="event in events" :key="event">
45 | {{ event }}
46 | </div>
47 | </div>
48 | </Section>
49 | </template>
50 |
```
--------------------------------------------------------------------------------
/packages/node-sdk/examples/express/bucket.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ReflagClient, Context, FlagOverrides } from "../../";
2 |
3 | type CreateConfigPayload = {
4 | minimumLength: number;
5 | };
6 |
7 | // Extending the Flags interface to define the available features
8 | declare module "../../types" {
9 | interface Flags {
10 | "show-todos": boolean;
11 | "create-todos": {
12 | config: {
13 | payload: CreateConfigPayload;
14 | };
15 | };
16 | "delete-todos": boolean;
17 | "some-else": {};
18 | }
19 | }
20 |
21 | let featureOverrides = (_: Context): FlagOverrides => {
22 | return {
23 | "create-todos": {
24 | isEnabled: true,
25 | config: {
26 | key: "short",
27 | payload: {
28 | minimumLength: 10,
29 | },
30 | },
31 | },
32 | }; // feature keys checked at compile time
33 | };
34 |
35 | // Create a new ReflagClient instance with the secret key and default features
36 | // The default features will be used if the user does not have any features set
37 | // Create a reflag.config.json file to configure the client or set environment variables
38 | // like REFLAG_SECRET_KEY, REFLAG_FLAGS_ENABLED, REFLAG_FLAGS_DISABLED, etc.
39 | export default new ReflagClient({
40 | // Optional: Set a logger to log debug information, errors, etc.
41 | logger: console,
42 | featureOverrides, // Optional: Set feature overrides
43 | });
44 |
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-bootstrap-demo/app/layout.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import type { Metadata } from "next";
2 | import { Inter } from "next/font/google";
3 | import "./globals.css";
4 | import { getServerClient } from "./client";
5 | import { ReflagBootstrappedProvider } from "@reflag/react-sdk";
6 |
7 | const inter = Inter({ subsets: ["latin"] });
8 |
9 | export const metadata: Metadata = {
10 | title: "Create Next App",
11 | description: "Generated by create next app",
12 | };
13 |
14 | const publishableKey = process.env.REFLAG_PUBLISHABLE_KEY || "";
15 |
16 | export default async function RootLayout({
17 | children,
18 | }: Readonly<{
19 | children: React.ReactNode;
20 | }>) {
21 | // Get the singleton server client
22 | const serverClient = await getServerClient();
23 |
24 | // In a real app, you'd get user/company from your auth system
25 | const flags = serverClient.getFlagsForBootstrap({
26 | user: {
27 | id: "demo-user",
28 | email: "[email protected]",
29 | "optin-huddles": true,
30 | },
31 | company: { id: "demo-company", name: "Demo Company" },
32 | other: { source: "web" },
33 | });
34 |
35 | return (
36 | <html lang="en">
37 | <body className={inter.className}>
38 | <ReflagBootstrappedProvider
39 | publishableKey={publishableKey}
40 | flags={flags}
41 | >
42 | {children}
43 | </ReflagBootstrappedProvider>
44 | </body>
45 | </html>
46 | );
47 | }
48 |
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/example/components/HuddleFeature.tsx:
--------------------------------------------------------------------------------
```typescript
1 | "use client";
2 |
3 | import React from "react";
4 | import {
5 | useBooleanFlagValue,
6 | useObjectFlagDetails,
7 | } from "@openfeature/react-sdk";
8 | import { track } from "@/app/featureManagement";
9 |
10 | const flagKey = "huddle";
11 |
12 | export const HuddleFeature = () => {
13 | const isEnabled = useBooleanFlagValue(flagKey, false);
14 | const { variant: huddleMeetingProvider, value: config } =
15 | useObjectFlagDetails(flagKey, {
16 | joinUrl: "https://zoom.us/join",
17 | });
18 |
19 | return (
20 | <div className="border border-gray-300 p-6 rounded-xl dark:border-neutral-800 dark:bg-zinc-800/30">
21 | <h3 className="text-xl mb-4">Huddle feature enabled:</h3>
22 | <pre>
23 | <code className="font-mono font-bold">{JSON.stringify(isEnabled)}</code>
24 | </pre>
25 | <h3 className="text-xl mb-4">
26 | Huddle using <strong>{huddleMeetingProvider}</strong>:
27 | </h3>
28 | <pre>
29 | <code className="font-mono font-bold">
30 | Join the huddle at <a href={config.joinUrl}>{config.joinUrl}</a>
31 | </code>
32 | </pre>
33 | <button
34 | className="border-solid m-auto max-w-60 border-2 border-indigo-600 rounded-lg p-2 mt-4 disabled:opacity-50"
35 | onClick={() => track(flagKey)}
36 | >
37 | Track usage
38 | </button>
39 | </div>
40 | );
41 | };
42 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | export type {
2 | Config,
3 | Flag,
4 | FlagRemoteConfig,
5 | InitOptions,
6 | ToolbarOptions,
7 | } from "./client";
8 | export { ReflagClient } from "./client";
9 | export type {
10 | CompanyContext,
11 | ReflagContext,
12 | ReflagDeprecatedContext,
13 | UserContext,
14 | } from "./context";
15 | export type {
16 | Feedback,
17 | FeedbackOptions,
18 | FeedbackPrompt,
19 | FeedbackPromptHandler,
20 | FeedbackPromptHandlerCallbacks,
21 | FeedbackPromptHandlerOpenFeedbackFormOptions,
22 | FeedbackPromptReply,
23 | FeedbackPromptReplyHandler,
24 | RequestFeedbackData,
25 | RequestFeedbackOptions,
26 | UnassignedFeedback,
27 | } from "./feedback/feedback";
28 | export type { DEFAULT_TRANSLATIONS } from "./feedback/ui/config/defaultTranslations";
29 | export type {
30 | FeedbackScoreSubmission,
31 | FeedbackSubmission,
32 | FeedbackTranslations,
33 | OnScoreSubmitResult,
34 | OpenFeedbackFormOptions,
35 | } from "./feedback/ui/types";
36 | export type {
37 | CheckEvent,
38 | FallbackFlagOverride,
39 | FlagOverrides,
40 | RawFlag,
41 | RawFlags,
42 | } from "./flag/flags";
43 | export type { HookArgs, State, TrackEvent } from "./hooksManager";
44 | export type { Logger } from "./logger";
45 | export { feedbackContainerId, propagatedEvents } from "./ui/constants";
46 | export type {
47 | DialogPlacement,
48 | Offset,
49 | PopoverPlacement,
50 | Position,
51 | ToolbarPosition,
52 | } from "./ui/types";
53 |
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/example/components/Context.tsx:
--------------------------------------------------------------------------------
```typescript
1 | "use client";
2 |
3 | import { OpenFeature } from "@openfeature/react-sdk";
4 | import React from "react";
5 |
6 | const initialContext = {
7 | trackingKey: "user42",
8 | companyName: "Acme Inc.",
9 | companyPlan: "enterprise",
10 | companyId: "company42",
11 | };
12 |
13 | export const Context = () => {
14 | const [context, setContext] = React.useState<any>(
15 | JSON.stringify(initialContext, null, 2),
16 | );
17 | let validJson = true;
18 | try {
19 | validJson = JSON.parse(context);
20 | } catch (e) {
21 | validJson = false;
22 | }
23 |
24 | return (
25 | <div className="flex flex-col border border-gray-300 p-6 rounded-xl size-full dark:border-neutral-800 dark:bg-zinc-800/30">
26 | <h3 className="text-xl mb-4">Context:</h3>
27 | <textarea
28 | className="min-h-[200px]"
29 | value={context}
30 | onChange={(e) => setContext(e.target.value)}
31 | ></textarea>
32 | <button
33 | disabled={!validJson}
34 | className="border-solid m-auto max-w-60 border-2 border-indigo-600 rounded-lg p-2 mt-4 disabled:opacity-50"
35 | onClick={() => OpenFeature.setContext(JSON.parse(context))}
36 | >
37 | Update Context
38 | </button>
39 | <span className="text-gray-600">
40 | Open the developer console to see what happens when you update the
41 | context.
42 | </span>
43 | </div>
44 | );
45 | };
46 |
```
--------------------------------------------------------------------------------
/packages/react-sdk/eslint.config.js:
--------------------------------------------------------------------------------
```javascript
1 | const base = require("@reflag/eslint-config");
2 | const reactPlugin = require("eslint-plugin-react");
3 | const hooksPlugin = require("eslint-plugin-react-hooks");
4 |
5 | module.exports = [
6 | ...base,
7 | { ignores: ["dist/", "dev/"] },
8 | {
9 | files: ["**/*.ts", "**/*.tsx"],
10 |
11 | rules: {
12 | ...reactPlugin.configs.recommended.rules,
13 | ...hooksPlugin.configs.recommended.rules,
14 |
15 | "react/jsx-key": [
16 | "error",
17 | {
18 | checkFragmentShorthand: true,
19 | },
20 | ],
21 | "react/self-closing-comp": ["error"],
22 | "react/prefer-es6-class": ["error"],
23 | "react/prefer-stateless-function": ["warn"],
24 | "react/no-did-mount-set-state": ["error"],
25 | "react/no-did-update-set-state": ["error"],
26 | "react/jsx-filename-extension": [
27 | "warn",
28 | {
29 | extensions: [".mdx", ".jsx", ".tsx"],
30 | },
31 | ],
32 | "react/react-in-jsx-scope": ["off"],
33 | "react/jsx-sort-props": [
34 | "error",
35 | {
36 | callbacksLast: true,
37 | shorthandFirst: false,
38 | shorthandLast: true,
39 | ignoreCase: true,
40 | noSortAlphabetically: false,
41 | reservedFirst: true,
42 | },
43 | ],
44 | },
45 | plugins: {
46 | react: reactPlugin,
47 | "react-hooks": hooksPlugin,
48 | },
49 | },
50 | ];
51 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/feedback/promptStorage.ts:
--------------------------------------------------------------------------------
```typescript
1 | import Cookies from "js-cookie";
2 |
3 | export const markPromptMessageCompleted = (
4 | userId: string,
5 | promptId: string,
6 | expiresAt: Date,
7 | ) => {
8 | Cookies.set(`reflag-prompt-${userId}`, promptId, {
9 | expires: expiresAt,
10 | sameSite: "strict",
11 | secure: true,
12 | });
13 | };
14 |
15 | export const checkPromptMessageCompleted = (
16 | userId: string,
17 | promptId: string,
18 | ) => {
19 | const id =
20 | Cookies.get(`reflag-prompt-${userId}`) ||
21 | Cookies.get(`bucket-prompt-${userId}`); // Legacy cookie name
22 | return id === promptId;
23 | };
24 |
25 | export const rememberAuthToken = (
26 | userId: string,
27 | channel: string,
28 | token: string,
29 | expiresAt: Date,
30 | ) => {
31 | Cookies.set(`reflag-token-${userId}`, JSON.stringify({ channel, token }), {
32 | expires: expiresAt,
33 | sameSite: "strict",
34 | secure: true,
35 | });
36 | };
37 |
38 | export const getAuthToken = (userId: string) => {
39 | const val = Cookies.get(`reflag-token-${userId}`);
40 | if (!val) {
41 | return undefined;
42 | }
43 |
44 | try {
45 | const { channel, token } = JSON.parse(val) as {
46 | channel: string;
47 | token: string;
48 | };
49 | if (!channel?.length || !token?.length) {
50 | return undefined;
51 | }
52 | return {
53 | channel,
54 | token,
55 | };
56 | } catch {
57 | return undefined;
58 | }
59 | };
60 |
61 | export const forgetAuthToken = (userId: string) => {
62 | Cookies.remove(`reflag-token-${userId}`);
63 | };
64 |
```
--------------------------------------------------------------------------------
/packages/vue-sdk/eslint.config.js:
--------------------------------------------------------------------------------
```javascript
1 | const base = require("@reflag/eslint-config");
2 | const importsPlugin = require("eslint-plugin-import");
3 | const vuePlugin = require("eslint-plugin-vue");
4 | const vueParser = require("vue-eslint-parser");
5 |
6 | module.exports = [
7 | ...base,
8 | {
9 | ignores: ["dist/"],
10 | },
11 | {
12 | // Vue files
13 | files: ["**/*.vue"],
14 | plugins: {
15 | vue: vuePlugin,
16 | import: importsPlugin,
17 | },
18 | languageOptions: {
19 | parser: vueParser,
20 | parserOptions: {
21 | ecmaVersion: "latest",
22 | sourceType: "module",
23 | ecmaFeatures: {
24 | jsx: true,
25 | },
26 | parser: {
27 | ts: require("@typescript-eslint/parser"),
28 | },
29 | },
30 | },
31 | settings: {
32 | "import/resolver": {
33 | typescript: {
34 | alwaysTryTypes: true,
35 | project: "./tsconfig.eslint.json",
36 | },
37 | },
38 | },
39 | rules: {
40 | ...vuePlugin.configs.recommended.rules,
41 | ...vuePlugin.configs["vue3-recommended"].rules,
42 |
43 | // Vue specific rules
44 | "vue/multi-word-component-names": "off",
45 | "vue/no-unused-vars": "warn",
46 | "vue/require-default-prop": "off",
47 | "vue/require-explicit-emits": "off",
48 | "vue/no-v-html": "off",
49 |
50 | // Import rules for Vue files
51 | "import/no-unresolved": "off", // Disable for now since we're using TypeScript resolver
52 | },
53 | },
54 | ];
55 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/feedback/ui/StarRating.css:
--------------------------------------------------------------------------------
```css
1 | .star-rating {
2 | display: flex;
3 | flex-direction: column;
4 | }
5 |
6 | .star-rating-icons {
7 | display: flex;
8 | width: 0;
9 |
10 | > input {
11 | border: 0px;
12 | clip: rect(0px, 0px, 0px, 0px);
13 | height: 1px;
14 | width: 1px;
15 | margin: -1px;
16 | padding: 0px;
17 | overflow: hidden;
18 | white-space: nowrap;
19 | position: absolute;
20 | }
21 |
22 | > .button {
23 | border: 1px solid;
24 | border-color: var(--reflag-feedback-dialog-input-border-color, #d8d9df);
25 |
26 | padding: 0px 7px;
27 |
28 | &:not(:first-of-type) {
29 | border-top-left-radius: 0;
30 | border-bottom-left-radius: 0;
31 | }
32 |
33 | &:not(:last-of-type) {
34 | border-top-right-radius: 0;
35 | border-bottom-right-radius: 0;
36 | margin-inline-end: -1px;
37 | }
38 |
39 | + .button-tooltip {
40 | pointer-events: none;
41 | opacity: 0;
42 |
43 | background: var(--reflag-feedback-dialog-tooltip-background-color, #000);
44 | color: var(--reflag-feedback-dialog-tooltip-color, #fff);
45 | padding: 6px 8px;
46 | border-radius: 4px;
47 | font-size: 13px;
48 | }
49 |
50 | &:hover + .button-tooltip {
51 | opacity: 1;
52 | }
53 |
54 | > svg {
55 | transition: transform 200ms ease-in-out;
56 | }
57 | }
58 | }
59 |
60 | .button-tooltip-arrow {
61 | position: absolute;
62 | width: 10px;
63 | height: 10px;
64 | background-color: var(
65 | --reflag-feedback-dialog-tooltip-background-color,
66 | #000
67 | );
68 | transform: rotate(45deg);
69 | }
70 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/ui/icons/VerySatisfied.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import { FunctionComponent, h } from "preact";
2 |
3 | export const VerySatisfied: FunctionComponent<
4 | h.JSX.SVGAttributes<SVGSVGElement>
5 | > = (props) => (
6 | <svg
7 | fill="none"
8 | height="22"
9 | viewBox="0 0 24 24"
10 | width="22"
11 | xmlns="http://www.w3.org/2000/svg"
12 | {...props}
13 | >
14 | <path
15 | d="M12 2C17.523 2 22 6.477 22 12C22 17.523 17.523 22 12 22C6.477 22 2 17.523 2 12C2 6.477 6.477 2 12 2ZM12 4C9.87827 4 7.84344 4.84285 6.34315 6.34315C4.84285 7.84344 4 9.87827 4 12C4 14.1217 4.84285 16.1566 6.34315 17.6569C7.84344 19.1571 9.87827 20 12 20C14.1217 20 16.1566 19.1571 17.6569 17.6569C19.1571 16.1566 20 14.1217 20 12C20 9.87827 19.1571 7.84344 17.6569 6.34315C16.1566 4.84285 14.1217 4 12 4ZM12 12C14 12 15.667 12.333 17 13C17 14.3261 16.4732 15.5979 15.5355 16.5355C14.5979 17.4732 13.3261 18 12 18C10.6739 18 9.40215 17.4732 8.46447 16.5355C7.52678 15.5979 7 14.3261 7 13C8.333 12.333 10 12 12 12ZM8.5 7C9.07633 6.99988 9.63499 7.19889 10.0815 7.56335C10.5279 7.9278 10.8347 8.43532 10.95 9H6.05C6.16526 8.43532 6.47209 7.9278 6.91855 7.56335C7.36501 7.19889 7.92367 6.99988 8.5 7ZM15.5 7C16.0763 6.99988 16.635 7.19889 17.0814 7.56335C17.5279 7.9278 17.8347 8.43532 17.95 9H13.05C13.1653 8.43532 13.4721 7.9278 13.9185 7.56335C14.365 7.19889 14.9237 6.99988 15.5 7Z"
16 | fill="currentColor"
17 | />
18 | </svg>
19 | );
20 |
```
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "[html]": {
3 | "editor.defaultFormatter": "esbenp.prettier-vscode"
4 | },
5 | "[json]": {
6 | "editor.defaultFormatter": "esbenp.prettier-vscode"
7 | },
8 | "[jsonc]": {
9 | "editor.defaultFormatter": "esbenp.prettier-vscode"
10 | },
11 | "[javascript]": {
12 | "editor.defaultFormatter": "esbenp.prettier-vscode"
13 | },
14 | "[javascriptreact]": {
15 | "editor.defaultFormatter": "esbenp.prettier-vscode"
16 | },
17 | "[typescript]": {
18 | "editor.defaultFormatter": "esbenp.prettier-vscode"
19 | },
20 | "[typescriptreact]": {
21 | "editor.defaultFormatter": "esbenp.prettier-vscode"
22 | },
23 | "[vue]": {
24 | "editor.defaultFormatter": "esbenp.prettier-vscode"
25 | },
26 | "[yaml]": {
27 | "editor.defaultFormatter": "esbenp.prettier-vscode"
28 | },
29 | "editor.codeActionsOnSave": ["source.formatDocument", "source.fixAll.eslint"],
30 | "editor.formatOnSave": false,
31 | "editor.insertSpaces": true,
32 | "editor.tabSize": 2,
33 | "eslint.workingDirectories": [{ "mode": "location" }],
34 | "files.exclude": {
35 | "**/node_modules": true
36 | },
37 | "search.exclude": {
38 | "**/.next": true,
39 | "**/build": true,
40 | "**/dist": true,
41 | "**/coverage": true,
42 | "**/node_modules": true,
43 | "**/*.lock": true
44 | },
45 | "typescript.tsdk": "node_modules/typescript/lib",
46 | "cSpell.words": [
47 | "booleanish",
48 | "bucketco",
49 | "npmjs",
50 | "nvmrc",
51 | "openfeature",
52 | "PKCE",
53 | "Reflag",
54 | "reflagcom"
55 | ]
56 | }
57 |
```
--------------------------------------------------------------------------------
/packages/cli/commands/new.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Command } from "commander";
2 | import { findUp } from "find-up";
3 |
4 | import { configStore } from "../stores/config.js";
5 | import { CONFIG_FILE_NAME } from "../utils/constants.js";
6 | import {
7 | appIdOption,
8 | flagKeyOption,
9 | flagNameArgument,
10 | typesFormatOption,
11 | typesOutOption,
12 | } from "../utils/options.js";
13 |
14 | import { createFlagAction, generateTypesAction } from "./flags.js";
15 | import { initAction } from "./init.js";
16 |
17 | type NewArgs = {
18 | appId?: string;
19 | out: string;
20 | key?: string;
21 | };
22 |
23 | export const newAction = async (name: string | undefined, { key }: NewArgs) => {
24 | if (!(await findUp(CONFIG_FILE_NAME))) {
25 | await initAction();
26 | }
27 | await createFlagAction(name, {
28 | key,
29 | });
30 | await generateTypesAction();
31 | };
32 |
33 | export function registerNewCommand(cli: Command) {
34 | cli
35 | .command("new")
36 | .description(
37 | "Initialize the Reflag CLI, authenticates, and creates a new flag.",
38 | )
39 | .addOption(appIdOption)
40 | .addOption(typesOutOption)
41 | .addOption(typesFormatOption)
42 | .addOption(flagKeyOption)
43 | .addArgument(flagNameArgument)
44 | .action(newAction);
45 |
46 | // Update the config with the cli override values
47 | cli.hook("preAction", (command) => {
48 | const { appId, out, format } = command.opts();
49 | configStore.setConfig({
50 | appId,
51 | typesOutput: out ? [{ path: out, format: format || "react" }] : undefined,
52 | });
53 | });
54 | }
55 |
```
--------------------------------------------------------------------------------
/packages/vue-sdk/dev/plain/components/RequestFeedback.vue:
--------------------------------------------------------------------------------
```vue
1 | <script setup lang="ts">
2 | import { useRequestFeedback } from "../../../src";
3 |
4 | import Section from "./Section.vue";
5 |
6 | const requestFeedback = useRequestFeedback();
7 | </script>
8 |
9 | <template>
10 | <Section title="Request Feedback">
11 | <div style="display: flex; gap: 10px; flex-wrap: wrap">
12 | <button
13 | @click="
14 | (e) =>
15 | requestFeedback({
16 | flagKey: 'demo-feature',
17 | title: 'How satisfied are you with this feature?',
18 | position: {
19 | type: 'POPOVER',
20 | anchor: e.currentTarget as HTMLElement,
21 | },
22 | })
23 | "
24 | >
25 | Request Feedback (Popover)
26 | </button>
27 |
28 | <button
29 | @click="
30 | requestFeedback({
31 | flagKey: 'demo-feature',
32 | title: 'How was your experience?',
33 | position: {
34 | type: 'MODAL',
35 | },
36 | })
37 | "
38 | >
39 | Request Feedback (Modal)
40 | </button>
41 |
42 | <button
43 | @click="
44 | requestFeedback({
45 | flagKey: 'demo-feature',
46 | title: 'What do you think about our product?',
47 | position: {
48 | type: 'MODAL',
49 | },
50 | openWithCommentVisible: true,
51 | })
52 | "
53 | >
54 | Request Feedback (Modal with Comment)
55 | </button>
56 | </div>
57 | </Section>
58 | </template>
59 |
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-bootstrap-demo/public/next.svg:
--------------------------------------------------------------------------------
```
1 | <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
```
--------------------------------------------------------------------------------
/packages/react-sdk/dev/nextjs-flag-demo/public/next.svg:
--------------------------------------------------------------------------------
```
1 | <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
```
--------------------------------------------------------------------------------
/packages/browser-sdk/example/typescript/app.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ReflagClient, RawFlags } from "../../src";
2 |
3 | const urlParams = new URLSearchParams(window?.location?.search);
4 | const publishableKey = urlParams.get("publishableKey");
5 | const flagKey = urlParams.get("flagKey") ?? "huddles";
6 |
7 | if (!publishableKey) {
8 | throw Error("publishableKey is missing");
9 | }
10 |
11 | const reflag = new ReflagClient({
12 | publishableKey,
13 | user: { id: "42" },
14 | company: { id: "1" },
15 | toolbar: {
16 | show: true,
17 | position: { placement: "bottom-right" },
18 | },
19 | });
20 |
21 | document
22 | .getElementById("startHuddle")
23 | ?.addEventListener("click", () => reflag.track(flagKey));
24 | document.getElementById("giveFeedback")?.addEventListener("click", (event) =>
25 | reflag.requestFeedback({
26 | flagKey,
27 | position: { type: "POPOVER", anchor: event.currentTarget as HTMLElement },
28 | }),
29 | );
30 |
31 | reflag.initialize().then(() => {
32 | console.log("Reflag initialized");
33 | const loadingElem = document.getElementById("loading");
34 | if (loadingElem) loadingElem.style.display = "none";
35 | });
36 |
37 | reflag.on("flagsUpdated", (flags: RawFlags) => {
38 | const { isEnabled } = flags[flagKey];
39 |
40 | const startHuddleElem = document.getElementById("start-huddle");
41 | if (isEnabled) {
42 | // show the start-huddle button
43 | if (startHuddleElem) startHuddleElem.style.display = "block";
44 | } else {
45 | // hide the start-huddle button
46 | if (startHuddleElem) startHuddleElem.style.display = "none";
47 | }
48 | });
49 |
```
--------------------------------------------------------------------------------
/packages/node-sdk/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "@reflag/node-sdk",
3 | "version": "1.1.0",
4 | "license": "MIT",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/reflagcom/javascript.git"
8 | },
9 | "scripts": {
10 | "dev": "vite",
11 | "start": "vite",
12 | "build": "tsc --project tsconfig.build.json",
13 | "test": "vitest run",
14 | "test:watch": "vitest",
15 | "test:ci": "yarn test --reporter=default --reporter=junit --outputFile=junit.xml",
16 | "coverage": "yarn test --coverage",
17 | "lint": "eslint .",
18 | "lint:ci": "eslint --output-file eslint-report.json --format json .",
19 | "prettier": "prettier --check .",
20 | "format": "yarn lint --fix && yarn prettier --write",
21 | "preversion": "yarn lint && yarn prettier && yarn test && yarn build"
22 | },
23 | "files": [
24 | "dist"
25 | ],
26 | "publishConfig": {
27 | "access": "public"
28 | },
29 | "main": "./dist/src/index.js",
30 | "types": "./dist/types/src/index.d.ts",
31 | "devDependencies": {
32 | "@babel/core": "~7.24.7",
33 | "@reflag/eslint-config": "~0.0.2",
34 | "@reflag/tsconfig": "~0.0.2",
35 | "@types/node": "^22.12.0",
36 | "@vitest/coverage-v8": "~1.6.0",
37 | "c8": "~10.1.0",
38 | "eslint": "^9.21.0",
39 | "flush-promises": "~1.0.2",
40 | "prettier": "^3.5.2",
41 | "ts-node": "~10.9.2",
42 | "typescript": "^5.7.3",
43 | "vite": "~5.4.18",
44 | "vite-plugin-dts": "~3.9.1",
45 | "vitest": "~1.6.0"
46 | },
47 | "dependencies": {
48 | "@reflag/flag-evaluation": "1.0.0"
49 | }
50 | }
51 |
```
--------------------------------------------------------------------------------
/packages/vue-sdk/src/ReflagBootstrappedProvider.vue:
--------------------------------------------------------------------------------
```vue
1 | <script setup lang="ts">
2 | import { onMounted, provide, ref, watch } from "vue";
3 |
4 | import { ProviderSymbol, useOnEvent, useReflagClient } from "./hooks";
5 | import type { ReflagBootstrappedProps } from "./types";
6 |
7 | const {
8 | flags,
9 | initialLoading = false,
10 | enableTracking = true,
11 | debug,
12 | ...config
13 | } = defineProps<ReflagBootstrappedProps>();
14 |
15 | const client = useReflagClient(
16 | {
17 | ...config,
18 | ...flags?.context,
19 | enableTracking,
20 | bootstrappedFlags: flags?.flags,
21 | },
22 | debug,
23 | );
24 |
25 | const isLoading = ref(
26 | client.getState() !== "initialized" ? initialLoading : false,
27 | );
28 | useOnEvent(
29 | "stateUpdated",
30 | (state) => {
31 | isLoading.value = state === "initializing";
32 | },
33 | client,
34 | );
35 |
36 | // Initialize the client if it is not already initialized
37 | onMounted(() => {
38 | if (client.getState() !== "idle") return;
39 | void client.initialize().catch((e) => {
40 | client.logger.error("failed to initialize client", e);
41 | });
42 | });
43 |
44 | // Update the context if it changes
45 | watch(
46 | () => flags.context,
47 | (newContext) => {
48 | void client.setContext(newContext);
49 | },
50 | { deep: true },
51 | );
52 |
53 | // Update the flags if they change
54 | watch(
55 | () => flags.flags,
56 | (newFlags) => {
57 | client.updateFlags(newFlags);
58 | },
59 | { deep: true },
60 | );
61 |
62 | provide(ProviderSymbol, {
63 | isLoading,
64 | client,
65 | });
66 | </script>
67 |
68 | <template>
69 | <slot v-if="isLoading && $slots.loading" name="loading" />
70 | <slot v-else />
71 | </template>
72 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/feedback/ui/Button.css:
--------------------------------------------------------------------------------
```css
1 | .button {
2 | appearance: none;
3 | display: inline-flex;
4 | justify-content: center;
5 | align-items: center;
6 | user-select: none;
7 | position: relative;
8 | white-space: nowrap;
9 | height: 2rem;
10 | padding-inline-start: 0.75rem;
11 | padding-inline-end: 0.75rem;
12 | gap: 0.5em;
13 | justify-content: center;
14 | border: none;
15 | cursor: pointer;
16 | font-family: var(--reflag-feedback-dialog-font-family);
17 | font-size: 12px;
18 | font-weight: 500;
19 | box-shadow:
20 | 0 1px 2px 0 rgba(0, 0, 0, 0.06),
21 | 0 1px 1px 0 rgba(0, 0, 0, 0.01);
22 | border-radius: var(--reflag-feedback-dialog-border-radius, 6px);
23 | transition-duration: 200ms;
24 | transition-property:
25 | background-color, border-color, color, opacity, box-shadow, transform;
26 |
27 | &.primary {
28 | background-color: var(
29 | --reflag-feedback-dialog-primary-button-background-color,
30 | white
31 | );
32 | color: var(--reflag-feedback-dialog-primary-button-color, #1e1f24);
33 | border: 1px solid
34 | var(--reflag-feedback-dialog-primary-border-color, #d8d9df);
35 | }
36 |
37 | &:disabled {
38 | opacity: 0.5;
39 | cursor: not-allowed;
40 | border-color: var(--reflag-feedback-dialog-primary-border-color, #d8d9df);
41 |
42 | transition-duration: 200ms;
43 | transition-property:
44 | background-color, border-color, color, opacity, box-shadow, transform;
45 | }
46 |
47 | &:focus {
48 | outline: none;
49 | border-color: var(
50 | --reflag-feedback-dialog-input-focus-border-color,
51 | #787c91
52 | );
53 | }
54 | }
55 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/ui/constants.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * ID of HTML DIV element which contains the feedback dialog
3 | */
4 | export const feedbackContainerId = "reflag-feedback-dialog-container";
5 | export const toolbarContainerId = "reflag-toolbar-dialog-container";
6 |
7 | /**
8 | * These events will be propagated to the feedback dialog
9 | *
10 | * @see [https://developer.mozilla.org/en-US/docs/Web/API/Element#events](https://developer.mozilla.org/en-US/docs/Web/API/Element#events)
11 | */
12 | export const propagatedEvents = [
13 | "animationcancel",
14 | "animationend",
15 | "animationiteration",
16 | "animationstart",
17 | "afterscriptexecute",
18 | "auxclick",
19 | "beforescriptexecute",
20 | "blur",
21 | "click",
22 | "compositionend",
23 | "compositionstart",
24 | "compositionupdate",
25 | "contextmenu",
26 | "copy",
27 | "cut",
28 | "dblclick",
29 | "DOMActivate",
30 | "DOMMouseScroll",
31 | "error",
32 | "focusin",
33 | "focusout",
34 | "focus",
35 | "fullscreenchange",
36 | "fullscreenerror",
37 | "gesturechange",
38 | "gestureend",
39 | "gesturestart",
40 | "gotpointercapture",
41 | "keydown",
42 | "keypress",
43 | "keyup",
44 | "lostpointercapture",
45 | "mousedown",
46 | "mouseenter",
47 | "mouseleave",
48 | "mousemove",
49 | "mouseout",
50 | "mouseover",
51 | "mouseup",
52 | "mousewheel",
53 | "paste",
54 | "pointercancel",
55 | "pointerdown",
56 | "pointerenter",
57 | "pointerleave",
58 | "pointermove",
59 | "pointerout",
60 | "pointerover",
61 | "pointerup",
62 | "scroll",
63 | "select",
64 | "touchcancel",
65 | "touchend",
66 | "touchmove",
67 | "touchstart",
68 | "transitioncancel",
69 | "transitionend",
70 | "transitionrun",
71 | "transitionstart",
72 | "wheel",
73 | ];
74 |
```
--------------------------------------------------------------------------------
/packages/node-sdk/src/flusher.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { constants } from "os";
2 |
3 | import { END_FLUSH_TIMEOUT_MS } from "./config";
4 | import { TimeoutError, withTimeout } from "./utils";
5 |
6 | type Callback = () => Promise<void>;
7 |
8 | const killSignals = ["SIGINT", "SIGTERM", "SIGHUP", "SIGBREAK"] as const;
9 |
10 | export function subscribe(
11 | callback: Callback,
12 | timeout: number = END_FLUSH_TIMEOUT_MS,
13 | ) {
14 | let state: boolean | undefined;
15 |
16 | const wrappedCallback = async () => {
17 | if (state !== undefined) {
18 | return;
19 | }
20 |
21 | state = false;
22 |
23 | try {
24 | await withTimeout(callback(), timeout);
25 | } catch (error) {
26 | if (error instanceof TimeoutError) {
27 | console.error(
28 | "[Reflag SDK] Timeout while flushing events on process exit.",
29 | );
30 | } else {
31 | console.error(
32 | "[Reflag SDK] An error occurred while flushing events on process exit.",
33 | error,
34 | );
35 | }
36 | }
37 |
38 | state = true;
39 | };
40 |
41 | killSignals.forEach((signal) => {
42 | const hasListeners = process.listenerCount(signal) > 0;
43 |
44 | if (hasListeners) {
45 | process.prependListener(signal, wrappedCallback);
46 | } else {
47 | process.on(signal, async () => {
48 | await wrappedCallback();
49 | process.exit(0x80 + constants.signals[signal]);
50 | });
51 | }
52 | });
53 |
54 | process.on("beforeExit", wrappedCallback);
55 | process.on("exit", () => {
56 | if (!state) {
57 | console.error(
58 | "[Reflag SDK] Failed to finalize the flushing of events on process exit.",
59 | );
60 | }
61 | });
62 | }
63 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/ui/packages/floating-ui-preact-dom/arrow.ts:
--------------------------------------------------------------------------------
```typescript
1 | import type { Middleware, Padding } from "@floating-ui/core";
2 | import { arrow as arrowCore, MiddlewareState } from "@floating-ui/dom";
3 | import { RefObject } from "preact";
4 |
5 | export interface Options {
6 | /**
7 | * The arrow element to be positioned.
8 | * @default undefined
9 | */
10 | element: RefObject<Element | null> | Element | null;
11 | /**
12 | * The padding between the arrow element and the floating element edges.
13 | * Useful when the floating element has rounded corners.
14 | * @default 0
15 | */
16 | padding?: Padding;
17 | }
18 |
19 | /**
20 | * Provides data to position an inner element of the floating element so that it
21 | * appears centered to the reference element.
22 | * This wraps the core `arrow` middleware to allow React refs as the element.
23 | * @see https://floating-ui.com/docs/arrow
24 | */
25 | export const arrow = (
26 | options: Options | ((state: MiddlewareState) => Options),
27 | ): Middleware => {
28 | function isRef(value: unknown): value is RefObject<unknown> {
29 | return {}.hasOwnProperty.call(value, "current");
30 | }
31 |
32 | return {
33 | name: "arrow",
34 | options,
35 | fn(state) {
36 | const { element, padding } =
37 | typeof options === "function" ? options(state) : options;
38 |
39 | if (element && isRef(element)) {
40 | if (element.current != null) {
41 | return arrowCore({ element: element.current, padding }).fn(state);
42 | }
43 |
44 | return {};
45 | } else if (element) {
46 | return arrowCore({ element, padding }).fn(state);
47 | }
48 |
49 | return {};
50 | },
51 | };
52 | };
53 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/feedback/ui/hooks/useTimer.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { useCallback, useEffect, useState } from "preact/hooks";
2 |
3 | export const useTimer = ({
4 | initialDuration,
5 | enabled,
6 | onEnd,
7 | }: {
8 | initialDuration: number;
9 | enabled: boolean;
10 | onEnd: () => void;
11 | }): {
12 | duration: number;
13 | elapsedFraction: number;
14 | startTime: number;
15 | endTime: number;
16 | stopped: boolean;
17 | startWithDuration: (duration: number) => void;
18 | stop: () => void;
19 | } => {
20 | const [stopped, setStopped] = useState(!enabled);
21 | const [duration, setDuration] = useState(initialDuration);
22 | const [startTime, setStartTime] = useState(Date.now());
23 | const [currentTime, setCurrentTime] = useState(Date.now());
24 |
25 | useEffect(() => {
26 | if (stopped) return;
27 |
28 | const t = setInterval(() => {
29 | setCurrentTime(Date.now());
30 |
31 | if (Date.now() >= startTime + duration) {
32 | clearTimeout(t);
33 | setStopped(true);
34 | onEnd();
35 | }
36 | }, 25);
37 |
38 | return () => {
39 | clearTimeout(t);
40 | };
41 | }, [duration, onEnd, startTime, stopped]);
42 |
43 | const stop = useCallback(() => {
44 | setStopped(true);
45 | }, []);
46 |
47 | const startWithDuration = useCallback((nextDuration: number) => {
48 | setStartTime(Date.now());
49 | setDuration(nextDuration);
50 | setStopped(false);
51 | }, []);
52 |
53 | const endTime = startTime + duration;
54 | const elapsedMs = stopped ? 0 : currentTime - startTime;
55 | const elapsedFraction = elapsedMs / duration;
56 |
57 | return {
58 | duration,
59 | elapsedFraction,
60 | startTime,
61 | endTime,
62 | stopped,
63 | startWithDuration,
64 | stop,
65 | };
66 | };
67 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/ui/icons/Neutral.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import { FunctionComponent, h } from "preact";
2 |
3 | export const Neutral: FunctionComponent<h.JSX.SVGAttributes<SVGSVGElement>> = (
4 | props,
5 | ) => (
6 | <svg
7 | fill="none"
8 | height="22"
9 | viewBox="0 0 24 24"
10 | width="22"
11 | xmlns="http://www.w3.org/2000/svg"
12 | {...props}
13 | >
14 | <path
15 | d="M12 22C6.477 22 2 17.523 2 12C2 6.477 6.477 2 12 2C17.523 2 22 6.477 22 12C22 17.523 17.523 22 12 22ZM12 20C14.1217 20 16.1566 19.1571 17.6569 17.6569C19.1571 16.1566 20 14.1217 20 12C20 9.87827 19.1571 7.84344 17.6569 6.34315C16.1566 4.84285 14.1217 4 12 4C9.87827 4 7.84344 4.84285 6.34315 6.34315C4.84285 7.84344 4 9.87827 4 12C4 14.1217 4.84285 16.1566 6.34315 17.6569C7.84344 19.1571 9.87827 20 12 20ZM8 15C8 14.4477 8.44771 14 9 14H15C15.5523 14 16 14.4477 16 15V15C16 15.5523 15.5523 16 15 16H9C8.44771 16 8 15.5523 8 15V15ZM8 11C7.60217 11 7.22064 10.842 6.93934 10.5607C6.65803 10.2794 6.5 9.89782 6.5 9.5C6.5 9.10217 6.65803 8.72064 6.93934 8.43934C7.22064 8.15803 7.60217 8 8 8C8.39782 8 8.77935 8.15803 9.06066 8.43934C9.34196 8.72064 9.5 9.10217 9.5 9.5C9.5 9.89782 9.34196 10.2794 9.06066 10.5607C8.77935 10.842 8.39782 11 8 11ZM16 11C15.6022 11 15.2206 10.842 14.9393 10.5607C14.658 10.2794 14.5 9.89782 14.5 9.5C14.5 9.10217 14.658 8.72064 14.9393 8.43934C15.2206 8.15803 15.6022 8 16 8C16.3978 8 16.7794 8.15803 17.0607 8.43934C17.342 8.72064 17.5 9.10217 17.5 9.5C17.5 9.89782 17.342 10.2794 17.0607 10.5607C16.7794 10.842 16.3978 11 16 11Z"
16 | fill="currentColor"
17 | />
18 | </svg>
19 | );
20 |
```
--------------------------------------------------------------------------------
/packages/node-sdk/test/config.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { describe, expect, it } from "vitest";
2 |
3 | import { loadConfig } from "../src/config";
4 |
5 | describe("config tests", () => {
6 | it("should load config file", () => {
7 | const config = loadConfig("test/testConfig.json");
8 |
9 | expect(config).toEqual({
10 | flagOverrides: {
11 | myFlag: {
12 | isEnabled: true,
13 | },
14 | myFlagFalse: false,
15 | myFlagWithConfig: {
16 | isEnabled: true,
17 | config: {
18 | key: "config-1",
19 | payload: { something: "else" },
20 | },
21 | },
22 | },
23 | secretKey: "mySecretKey",
24 | offline: true,
25 | apiBaseUrl: "http://localhost:3000",
26 | });
27 | });
28 |
29 | it("should load ENV VARS", () => {
30 | process.env.REFLAG_SECRET_KEY = "mySecretKeyFromEnv";
31 | process.env.REFLAG_OFFLINE = "true";
32 | process.env.REFLAG_API_BASE_URL = "http://localhost:4999";
33 | process.env.REFLAG_FLAGS_ENABLED = "myNewFlag";
34 | process.env.REFLAG_FLAGS_DISABLED = "myNewFlagFalse";
35 |
36 | const config = loadConfig("test/testConfig.json");
37 | expect(config).toEqual({
38 | flagOverrides: {
39 | myFlag: {
40 | isEnabled: true,
41 | },
42 | myFlagFalse: false,
43 | myNewFlag: true,
44 | myNewFlagFalse: false,
45 | myFlagWithConfig: {
46 | isEnabled: true,
47 | config: {
48 | key: "config-1",
49 | payload: { something: "else" },
50 | },
51 | },
52 | },
53 | secretKey: "mySecretKeyFromEnv",
54 | offline: true,
55 | apiBaseUrl: "http://localhost:4999",
56 | });
57 | });
58 | });
59 |
```
--------------------------------------------------------------------------------
/packages/cli/stores/auth.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { mkdir, readFile, writeFile } from "node:fs/promises";
2 | import { dirname } from "node:path";
3 |
4 | import { AUTH_FILE } from "../utils/constants.js";
5 |
6 | class AuthStore {
7 | protected tokens: Map<string, string> = new Map();
8 | protected apiKey: string | undefined;
9 |
10 | async initialize() {
11 | await this.loadTokenFile();
12 | }
13 |
14 | protected async loadTokenFile() {
15 | try {
16 | const content = await readFile(AUTH_FILE, "utf-8");
17 | this.tokens = new Map(
18 | content
19 | .split("\n")
20 | .filter(Boolean)
21 | .map((line) => {
22 | const [baseUrl, token] = line.split("|");
23 | return [baseUrl, token];
24 | }),
25 | );
26 | } catch {
27 | // No tokens file found
28 | }
29 | }
30 |
31 | protected async saveTokenFile(newTokens: Map<string, string>) {
32 | const content = Array.from(newTokens.entries())
33 | .map(([baseUrl, token]) => `${baseUrl}|${token}`)
34 | .join("\n");
35 |
36 | await mkdir(dirname(AUTH_FILE), { recursive: true });
37 | await writeFile(AUTH_FILE, content);
38 |
39 | this.tokens = newTokens;
40 | }
41 |
42 | getToken(baseUrl: string) {
43 | return {
44 | token: this.apiKey || this.tokens.get(baseUrl),
45 | isApiKey: !!this.apiKey,
46 | };
47 | }
48 |
49 | async setToken(baseUrl: string, newToken: string | null) {
50 | if (newToken) {
51 | this.tokens.set(baseUrl, newToken);
52 | } else {
53 | this.tokens.delete(baseUrl);
54 | }
55 |
56 | await this.saveTokenFile(this.tokens);
57 | }
58 |
59 | useApiKey(key: string) {
60 | this.apiKey = key;
61 | }
62 | }
63 |
64 | export const authStore = new AuthStore();
65 |
```
--------------------------------------------------------------------------------
/packages/vue-sdk/src/ReflagProvider.vue:
--------------------------------------------------------------------------------
```vue
1 | <script setup lang="ts">
2 | import { computed, onMounted, provide, ref, watch } from "vue";
3 |
4 | import { ProviderSymbol, useOnEvent, useReflagClient } from "./hooks";
5 | import type { ReflagProps } from "./types";
6 |
7 | // any optional prop which has boolean as part of the type, will default to false
8 | // instead of `undefined`, so we use `withDefaults` here to pass the undefined
9 | // down into the client.
10 | const {
11 | context,
12 | user,
13 | company,
14 | otherContext,
15 | initialLoading = true,
16 | enableTracking = true,
17 | debug,
18 | ...config
19 | } = defineProps<ReflagProps>();
20 |
21 | const resolvedContext = computed(() => ({
22 | user,
23 | company,
24 | other: otherContext,
25 | ...context,
26 | }));
27 |
28 | const client = useReflagClient(
29 | {
30 | ...config,
31 | ...resolvedContext.value,
32 | enableTracking,
33 | },
34 | debug,
35 | );
36 |
37 | const isLoading = ref(
38 | client.getState() !== "initialized" ? initialLoading : false,
39 | );
40 | useOnEvent(
41 | "stateUpdated",
42 | (state) => {
43 | isLoading.value = state === "initializing";
44 | },
45 | client,
46 | );
47 |
48 | // Initialize the client if it is not already initialized
49 | onMounted(() => {
50 | if (client.getState() !== "idle") return;
51 | void client.initialize().catch((e) => {
52 | client.logger.error("failed to initialize client", e);
53 | });
54 | });
55 |
56 | // Update the context if it changes
57 | watch(
58 | () => resolvedContext,
59 | () => {
60 | void client.setContext(resolvedContext.value);
61 | },
62 | { deep: true },
63 | );
64 |
65 | provide(ProviderSymbol, {
66 | isLoading,
67 | client,
68 | });
69 | </script>
70 |
71 | <template>
72 | <slot v-if="isLoading && $slots.loading" name="loading" />
73 | <slot v-else />
74 | </template>
75 |
```
--------------------------------------------------------------------------------
/packages/cli/utils/schemas.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { z } from "zod";
2 |
3 | export const sortTypeSchema = z
4 | .enum(["flat", "hierarchical"])
5 | .describe("Type of sorting to apply");
6 |
7 | export const booleanish = z.preprocess((value) => {
8 | if (typeof value === "string") {
9 | return value === "true" || value === "1";
10 | }
11 | return Boolean(value);
12 | }, z.boolean().describe("Boolean value that can be parsed from strings like 'true' or '1'")) as z.ZodEffects<
13 | z.ZodBoolean,
14 | boolean,
15 | boolean
16 | >;
17 |
18 | export const PaginationQueryBaseSchema = (
19 | {
20 | sortOrder = "asc",
21 | pageIndex = 0,
22 | pageSize = 20,
23 | }: {
24 | sortOrder?: "asc" | "desc";
25 | pageIndex?: number;
26 | pageSize?: number;
27 | } = {
28 | sortOrder: "asc",
29 | pageIndex: 0,
30 | pageSize: 20,
31 | },
32 | ) =>
33 | z.object({
34 | sortOrder: z
35 | .enum(["asc", "desc"])
36 | .default(sortOrder)
37 | .describe("Sort direction (ascending or descending)"),
38 | pageIndex: z.coerce
39 | .number()
40 | .int()
41 | .nonnegative()
42 | .default(pageIndex)
43 | .describe("Zero-based page index"),
44 | pageSize: z.coerce
45 | .number()
46 | .int()
47 | .nonnegative()
48 | .min(1)
49 | .max(100)
50 | .default(pageSize)
51 | .describe("Number of items per page (1-100)"),
52 | });
53 |
54 | export const EnvironmentQuerySchema = z
55 | .object({
56 | envId: z.string().min(1).describe("Environment identifier"),
57 | })
58 | .strict();
59 |
60 | export type EnvironmentQuery = z.infer<typeof EnvironmentQuerySchema>;
61 |
62 | export const ExternalIdSchema = z
63 | .string()
64 | .nonempty()
65 | .max(256)
66 | .describe("External identifier, non-empty string up to 256 characters");
67 |
```
--------------------------------------------------------------------------------
/packages/node-sdk/examples/cloudflare-worker/src/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * This is a simple example of how to use the Reflag SDK in a Cloudflare Worker.
3 | * It demonstrates how to initialize the client and evaluate flags.
4 | * It also shows how to flush the client and wait for any in-flight requests to complete.
5 | *
6 | * Set the REFLAG_SECRET_KEY environment variable in wrangler.jsonc to get started.
7 | *
8 | * - Run `yarn run dev` in your terminal to start a development server
9 | * - Open a browser tab at http://localhost:8787/ to see your worker in action
10 | * - Run `yarn run deploy` to publish your worker
11 | *
12 | */
13 |
14 | import { EdgeClient } from "../../../";
15 |
16 | // set the REFLAG_SECRET_KEY environment variable or pass the secret key in the constructor
17 | const reflag = new EdgeClient();
18 |
19 | export default {
20 | async fetch(request, _env, ctx): Promise<Response> {
21 | // initialize the client and wait for it to complete
22 | // this is not required for the edge client, but is included for completeness
23 | await reflag.initialize();
24 |
25 | const url = new URL(request.url);
26 | const userId = url.searchParams.get("user.id");
27 | const companyId = url.searchParams.get("company.id");
28 |
29 | const f = reflag.getFlags({
30 | user: { id: userId ?? undefined },
31 | company: { id: companyId ?? undefined },
32 | });
33 |
34 | // ensure all events are flushed and any requests to refresh the feature cache
35 | // have completed after the response is sent
36 | ctx.waitUntil(reflag.flush());
37 |
38 | return new Response(
39 | `Flags for user ${userId} and company ${companyId}: ${JSON.stringify(f, null, 2)}`,
40 | );
41 | },
42 | } satisfies ExportedHandler<Env>;
43 |
```
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
```yaml
1 | name: Publish updated packages
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | release:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v4
13 | # Setup .npmrc file to publish to npm
14 | - name: Enable corepack
15 | run: corepack enable
16 | - uses: actions/setup-node@v4
17 | with:
18 | node-version-file: ".nvmrc"
19 | cache: "yarn"
20 | cache-dependency-path: "**/yarn.lock"
21 | registry-url: "https://registry.npmjs.org"
22 | scope: "@reflag"
23 | - name: Install dependencies
24 | run: yarn install --immutable
25 | - name: Build packages
26 | run: yarn build
27 | - name: npm login
28 | run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc
29 | env:
30 | NPM_TOKEN: ${{ secrets.REFLAG_NPM_TOKEN }}
31 | - name: Publish
32 | run: yarn lerna publish from-package --no-private --yes
33 | - name: Build docs
34 | run: yarn docs
35 | - name: Checkout docs with SSH
36 | uses: actions/checkout@v3
37 | with:
38 | repository: reflagcom/docs
39 | ssh-key: ${{ secrets.DOCS_DEPLOY_KEY }}
40 | path: reflag-docs
41 | - name: Copy generated docs to docs repo
42 | run: |
43 | rm -rf reflag-docs/sdk
44 | cp -R dist/docs reflag-docs/sdk
45 | - name: Commit and push changes
46 | run: |
47 | cd reflag-docs
48 | git config user.name "github-actions[bot]"
49 | git config user.email "github-actions[bot]@reflag.com"
50 | git add sdk
51 | git commit -m "Update documentation" && git push || echo "No docs changes to commit"
52 |
```
--------------------------------------------------------------------------------
/packages/node-sdk/examples/cloudflare-worker/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
6 | "target": "es2021",
7 | /* Specify a set of bundled library declaration files that describe the target runtime environment. */
8 | "lib": ["es2021"],
9 | /* Specify what JSX code is generated. */
10 | "jsx": "react-jsx",
11 |
12 | /* Specify what module code is generated. */
13 | "module": "es2022",
14 | /* Specify how TypeScript looks up a file from a given module specifier. */
15 | "moduleResolution": "Bundler",
16 | /* Enable importing .json files */
17 | "resolveJsonModule": true,
18 |
19 | /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
20 | "allowJs": true,
21 | /* Enable error reporting in type-checked JavaScript files. */
22 | "checkJs": false,
23 |
24 | /* Disable emitting files from a compilation. */
25 | "noEmit": true,
26 |
27 | /* Ensure that each file can be safely transpiled without relying on other imports. */
28 | "isolatedModules": true,
29 | /* Allow 'import x from y' when a module doesn't have a default export. */
30 | "allowSyntheticDefaultImports": true,
31 | /* Ensure that casing is correct in imports. */
32 | "forceConsistentCasingInFileNames": true,
33 |
34 | /* Enable all strict type-checking options. */
35 | "strict": true,
36 |
37 | /* Skip type checking all .d.ts files. */
38 | "skipLibCheck": true,
39 | "types": ["./worker-configuration.d.ts"]
40 | },
41 | "exclude": ["test"],
42 | "include": ["worker-configuration.d.ts", "src/**/*.ts"]
43 | }
44 |
```
--------------------------------------------------------------------------------
/packages/openfeature-node-provider/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "@reflag/openfeature-node-provider",
3 | "version": "1.0.1",
4 | "license": "MIT",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/reflagcom/javascript.git"
8 | },
9 | "scripts": {
10 | "dev": "vite",
11 | "start": "vite",
12 | "build": "tsc --project tsconfig.build.json",
13 | "test": "vitest -c vite.config.js",
14 | "test:ci": "vitest run -c vite.config.js --reporter=default --reporter=junit --outputFile=junit.xml",
15 | "coverage": "vitest run --coverage",
16 | "lint": "eslint .",
17 | "lint:ci": "eslint --output-file eslint-report.json --format json .",
18 | "prettier": "prettier --check .",
19 | "format": "yarn lint --fix && yarn prettier --write",
20 | "preversion": "yarn lint && yarn prettier && yarn vitest run -c vite.config.js && yarn build"
21 | },
22 | "files": [
23 | "dist"
24 | ],
25 | "publishConfig": {
26 | "access": "public"
27 | },
28 | "main": "./dist/index.js",
29 | "types": "./dist/types/index.d.ts",
30 | "exports": {
31 | ".": {
32 | "types": "./dist/types/index.d.ts",
33 | "require": "./dist/index.js"
34 | }
35 | },
36 | "devDependencies": {
37 | "@babel/core": "~7.24.7",
38 | "@openfeature/core": "^1.5.0",
39 | "@openfeature/server-sdk": ">=1.16.1",
40 | "@reflag/eslint-config": "~0.0.2",
41 | "@reflag/tsconfig": "~0.0.2",
42 | "@types/node": "^22.12.0",
43 | "eslint": "^9.21.0",
44 | "flush-promises": "~1.0.2",
45 | "prettier": "^3.5.2",
46 | "ts-node": "~10.9.2",
47 | "typescript": "^5.7.3",
48 | "vite": "~5.4.18",
49 | "vite-plugin-dts": "~3.9.1",
50 | "vitest": "~1.6.0"
51 | },
52 | "dependencies": {
53 | "@reflag/node-sdk": "1.1.0"
54 | },
55 | "peerDependencies": {
56 | "@openfeature/server-sdk": ">=1.16.1"
57 | }
58 | }
59 |
```
--------------------------------------------------------------------------------
/packages/node-sdk/src/rate-limiter.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ok } from "./utils";
2 |
3 | /**
4 | * Creates a new rate limiter.
5 | *
6 | * @typeparam TKey - The type of the key.
7 | * @param windowSizeMs - The length of the time window in milliseconds.
8 | *
9 | * @returns The rate limiter.
10 | **/
11 | export function newRateLimiter(windowSizeMs: number) {
12 | ok(
13 | typeof windowSizeMs == "number" && windowSizeMs > 0,
14 | "windowSizeMs must be greater than 0",
15 | );
16 |
17 | let lastAllowedTimestampsByKey: { [key: string]: number } = {};
18 |
19 | function clearStale(all: boolean = false): void {
20 | if (all) {
21 | lastAllowedTimestampsByKey = {};
22 | }
23 |
24 | const expireBeforeTimestamp = Date.now() - windowSizeMs;
25 | const keys = Object.keys(lastAllowedTimestampsByKey);
26 |
27 | for (const key of keys) {
28 | const lastAllowedTimestamp = lastAllowedTimestampsByKey[key];
29 |
30 | if (
31 | lastAllowedTimestamp &&
32 | lastAllowedTimestamp < expireBeforeTimestamp
33 | ) {
34 | delete lastAllowedTimestampsByKey[key];
35 | }
36 | }
37 | }
38 |
39 | function isAllowed(key: string): boolean {
40 | const now = Date.now();
41 |
42 | // every ~100 calls, remove all stale items from the cache.
43 | //
44 | // we previously used a fixed time interval here, but setTimeout
45 | // is not available in serverless runtimes.
46 | if (Math.random() < 0.01) {
47 | clearStale();
48 | }
49 |
50 | const lastAllowedTimestamp = lastAllowedTimestampsByKey[key];
51 | if (lastAllowedTimestamp && lastAllowedTimestamp >= now - windowSizeMs) {
52 | return false;
53 | }
54 |
55 | lastAllowedTimestampsByKey[key] = now;
56 | return true;
57 | }
58 |
59 | return {
60 | clearStale,
61 | isAllowed,
62 | cacheSize: () => Object.keys(lastAllowedTimestampsByKey).length,
63 | };
64 | }
65 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/ui/utils.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { propagatedEvents } from "./constants";
2 | import { Offset } from "./types";
3 |
4 | function stopPropagation(e: Event) {
5 | e.stopPropagation();
6 | }
7 |
8 | export function attachContainer(containerId: string) {
9 | let container = document.querySelector(`#${containerId}`);
10 |
11 | if (!container) {
12 | container = document.createElement("div");
13 | container.attachShadow({ mode: "open" });
14 | (container as HTMLElement).style.all = "initial";
15 | container.id = containerId;
16 | document.body.appendChild(container);
17 |
18 | for (const event of propagatedEvents) {
19 | container.addEventListener(event, stopPropagation, { passive: true });
20 | }
21 | }
22 |
23 | return container.shadowRoot!;
24 | }
25 |
26 | function parseOffset(offsetInput?: Offset["x"] | Offset["y"]) {
27 | if (offsetInput === undefined) return "1rem";
28 | if (typeof offsetInput === "number") return offsetInput + "px";
29 |
30 | return offsetInput;
31 | }
32 |
33 | export function parseUnanchoredPosition(position: {
34 | offset?: Offset;
35 | placement: string;
36 | }) {
37 | const offsetY = parseOffset(position.offset?.y);
38 | const offsetX = parseOffset(position.offset?.x);
39 |
40 | switch (position.placement) {
41 | case "top-left":
42 | return {
43 | top: offsetY,
44 | left: offsetX,
45 | };
46 | case "top-right":
47 | return {
48 | top: offsetY,
49 | right: offsetX,
50 | };
51 | case "bottom-left":
52 | return {
53 | bottom: offsetY,
54 | left: offsetX,
55 | };
56 | case "bottom-right":
57 | return {
58 | bottom: offsetY,
59 | right: offsetX,
60 | };
61 | default:
62 | console.error("[Reflag]", "Invalid placement", position.placement);
63 | return parseUnanchoredPosition({ placement: "bottom-right" });
64 | }
65 | }
66 |
```
--------------------------------------------------------------------------------
/packages/openfeature-browser-provider/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "@reflag/openfeature-browser-provider",
3 | "version": "1.1.0",
4 | "packageManager": "[email protected]",
5 | "license": "MIT",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/reflagcom/javascript.git"
9 | },
10 | "publishConfig": {
11 | "access": "public"
12 | },
13 | "scripts": {
14 | "dev": "vite",
15 | "build": "tsc --project tsconfig.build.json && vite build",
16 | "test": "vitest",
17 | "test:ci": "vitest run --reporter=default --reporter=junit --outputFile=junit.xml",
18 | "coverage": "vitest run --coverage",
19 | "lint": "eslint .",
20 | "lint:ci": "eslint --output-file eslint-report.json --format json .",
21 | "prettier": "prettier --check .",
22 | "format": "yarn lint --fix && yarn prettier --write",
23 | "preversion": "yarn lint && yarn prettier && yarn vitest run && yarn build"
24 | },
25 | "files": [
26 | "dist"
27 | ],
28 | "main": "./dist/reflag-openfeature-browser-provider.umd.js",
29 | "types": "./dist/index.d.ts",
30 | "exports": {
31 | ".": {
32 | "types": "./dist/index.d.ts",
33 | "import": "./dist/reflag-openfeature-browser-provider.mjs",
34 | "require": "./dist/reflag-openfeature-browser-provider.umd.js"
35 | }
36 | },
37 | "dependencies": {
38 | "@reflag/browser-sdk": "1.2.0"
39 | },
40 | "devDependencies": {
41 | "@openfeature/core": "1.5.0",
42 | "@openfeature/web-sdk": "^1.3.0",
43 | "@reflag/eslint-config": "0.0.2",
44 | "@reflag/tsconfig": "0.0.2",
45 | "@types/node": "^22.12.0",
46 | "eslint": "^9.21.0",
47 | "jsdom": "^24.1.0",
48 | "prettier": "^3.5.2",
49 | "typescript": "^5.7.3",
50 | "vite": "^5.3.5",
51 | "vite-plugin-dts": "^4.0.0-beta.1",
52 | "vitest": "^2.0.4"
53 | },
54 | "peerDependencies": {
55 | "@openfeature/web-sdk": ">=1.3"
56 | }
57 | }
58 |
```
--------------------------------------------------------------------------------
/packages/browser-sdk/src/feedback/ui/types.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Position } from "../../ui/types";
2 |
3 | export type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
4 |
5 | export interface FeedbackSubmission {
6 | question: string;
7 | feedbackId?: string;
8 | score: number;
9 | comment: string;
10 | }
11 |
12 | export interface FeedbackScoreSubmission {
13 | feedbackId?: string;
14 | question: string;
15 | score: number;
16 | }
17 |
18 | export interface OnScoreSubmitResult {
19 | feedbackId: string;
20 | }
21 |
22 | export interface OpenFeedbackFormOptions {
23 | key: string;
24 | title?: string;
25 |
26 | /**
27 | * Control the placement and behavior of the feedback form.
28 | */
29 | position?: Position;
30 |
31 | /**
32 | * Add your own custom translations for the feedback form.
33 | * Undefined translation keys fall back to english defaults.
34 | */
35 | translations?: Partial<FeedbackTranslations>;
36 |
37 | /**
38 | * Open the form with both the score and comment fields visible.
39 | * Defaults to `false`
40 | */
41 | openWithCommentVisible?: boolean;
42 |
43 | onSubmit: (data: FeedbackSubmission) => Promise<void> | void;
44 | onScoreSubmit?: (
45 | data: FeedbackScoreSubmission,
46 | ) => Promise<OnScoreSubmitResult>;
47 | onClose?: () => void;
48 | onDismiss?: () => void;
49 | }
50 | /**
51 | * You can use this to override text values in the feedback form
52 | * with desired language translation
53 | */
54 | export type FeedbackTranslations = {
55 | /**
56 | *
57 | */
58 | DefaultQuestionLabel: string;
59 | QuestionPlaceholder: string;
60 | ScoreStatusDescription: string;
61 | ScoreStatusLoading: string;
62 | ScoreStatusReceived: string;
63 | ScoreVeryDissatisfiedLabel: string;
64 | ScoreDissatisfiedLabel: string;
65 | ScoreNeutralLabel: string;
66 | ScoreSatisfiedLabel: string;
67 | ScoreVerySatisfiedLabel: string;
68 | SuccessMessage: string;
69 | SendButton: string;
70 | };
71 |
```
--------------------------------------------------------------------------------
/packages/cli/utils/version.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { readFile } from "fs/promises";
2 | import { join } from "path";
3 | import { gt } from "semver";
4 |
5 | import { MODULE_ROOT } from "./constants.js";
6 |
7 | export async function current() {
8 | try {
9 | const packageJsonPath = join(MODULE_ROOT, "package.json");
10 | const packageJsonContent = await readFile(packageJsonPath, "utf-8");
11 | const packageInfo: {
12 | version: string;
13 | name: string;
14 | } = JSON.parse(packageJsonContent);
15 |
16 | return {
17 | version: packageInfo.version,
18 | name: packageInfo.name,
19 | };
20 | } catch (error) {
21 | throw new Error(
22 | `Failed to read current version: ${error instanceof Error ? error.message : "Unknown error"}`,
23 | );
24 | }
25 | }
26 |
27 | async function getLatestVersionFromNpm(packageName: string): Promise<string> {
28 | try {
29 | const response = await fetch(`https://registry.npmjs.org/${packageName}`, {
30 | signal: AbortSignal.timeout(5000),
31 | });
32 |
33 | if (!response.ok) {
34 | throw new Error(
35 | `Failed to fetch package info: ${response.status} ${response.statusText}`,
36 | );
37 | }
38 |
39 | const data: {
40 | "dist-tags": {
41 | latest: string;
42 | };
43 | } = await response.json();
44 |
45 | return data["dist-tags"].latest;
46 | } catch (error) {
47 | throw new Error(
48 | `Failed to fetch latest version from npm: ${error instanceof Error ? error.message : "Unknown error"}`,
49 | );
50 | }
51 | }
52 |
53 | export async function checkLatest() {
54 | const { version: currentVersion, name: packageName } = await current();
55 |
56 | const latestVersion = await getLatestVersionFromNpm(packageName);
57 | const isNewerAvailable = gt(latestVersion, currentVersion);
58 | return {
59 | currentVersion,
60 | latestVersion,
61 | isNewerAvailable,
62 | };
63 | }
64 |
```