diff --git a/.env.example b/.env.example index f12e435f..bb8f19fd 100644 --- a/.env.example +++ b/.env.example @@ -28,6 +28,9 @@ FLAGSMITH_ENV_KEY= # Harness SDK Key HARNESS_KEY= +# ConfigCat SDK Key +CONFIGCAT_SDK_KEY= + ############################################### ## ## Feature Flag SDK keys (web) @@ -59,4 +62,7 @@ FLAGD_PORT_WEB= # Determines if TLS (https) should be used # @default false -FLAGD_TLS_WEB= \ No newline at end of file +FLAGD_TLS_WEB= + +# ConfigCat SDK Key +CONFIGCAT_SDK_KEY_WEB= \ No newline at end of file diff --git a/README.md b/README.md index f9dd1d69..317ffd92 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ If you're brand new to feature flagging, consider reviewing the [What are featur - [LaunchDarkly](#launchdarkly) - [Flagsmith](#flagsmith) - [Flipt](#flipt) + - [ConfigCat](#configcat) - [Experimenting beyond the demo](#experimenting-beyond-the-demo) - [Evaluation context](#evaluation-context) - [Troubleshooting](#troubleshooting) @@ -428,6 +429,38 @@ It's easy to set up, has no seat limits, and is built for developers from scale- After [starting the demo](#how-to-run-the-demo), the Flipt UI is available at [http://localhost:8080](http://localhost:8080). +### ConfigCat + +[ConfigCat](https://configcat.com/) is a user-friendly, scalable and secure feature flagging solution with clear [pricing](https://configcat.com/#pricing). ConfigCat allows for unlimited team members and MAUs across all plans, including the free tier. All ConfigCat plans come with all security measures, including audit logs, two-factor authentication, SSO, SAML and SCIM for secure feature management. ConfigCat offers users the choice to keep data within the EU to comply with GDPR more easily. ConfigCat provides SDKs for all major programming languages and platforms. + +
+ Follow these steps to set up ConfigCat for the demo: + +1. Sign in to your ConfigCat account. If you don't already have an account, you can [sign up](https://app.configcat.com/auth/signup) for free. +1. Create a new feature flag with the key `new-welcome-message`. + + + +1. Create a new text setting with the key `hex-color`. Add three percentage options (`+ %` button) with the following values: `c05543`, `2f5230`, and `0d507b`. Set `c05543` to 100%. Set the `To unindentified` value to `c05543`. + + + +1. Create a new text setting with the key `fib-algo`. + - Add a targeting rule (`+ IF` button) that looks for the `Email` user attribute to end with `@faas.com` and serves `binet`. + - Add five percentage options (`+ %` button) with the following values: `recursive`, `memo`, `loop`, `binet`, and `default`. Set `recursive` to 100%. + - Set the `To unindentified` value to `default`. + + + +1. Click on `VIEW SDK KEY` and copy the `SDK Key`. + + + +1. Open the `.env` file and set the values of `CONFIGCAT_SDK_KEY` and `CONFIGCAT_SDK_KEY_WEB` to the key copied above. + +Now that everything is configured, you should be able to [start the demo](#how-to-run-the-demo). Once it's started, select `configcat` from the provider list located at the bottom right of your screen. You should now be able to control the demo app via ConfigCat! +
+ ## Experimenting beyond the demo ### Evaluation context diff --git a/docker-compose.yaml b/docker-compose.yaml index 4853df1d..4e103e9f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -38,6 +38,7 @@ services: - LD_KEY - FLAGSMITH_ENV_KEY - CLOUDBEES_APP_KEY + - CONFIGCAT_SDK_KEY ## Web - HARNESS_KEY_WEB - SPLIT_KEY_WEB @@ -46,7 +47,8 @@ services: - CLOUDBEES_APP_KEY_WEB - FLAGD_HOST_WEB - FLAGD_PORT_WEB - - FLAGD_TLS_WEB + - FLAGD_TLS_WEB + - CONFIGCAT_SDK_KEY_WEB fib-service: image: ghcr.io/open-feature/playground-fib-service:v0.16.0 # x-release-please-version @@ -74,6 +76,7 @@ services: - LD_KEY - FLAGSMITH_ENV_KEY - CLOUDBEES_APP_KEY + - CONFIGCAT_SDK_KEY jaeger: image: jaegertracing/all-in-one:1.60 diff --git a/images/configcat/fib-algo.png b/images/configcat/fib-algo.png new file mode 100644 index 00000000..681e2c61 Binary files /dev/null and b/images/configcat/fib-algo.png differ diff --git a/images/configcat/hex-color.png b/images/configcat/hex-color.png new file mode 100644 index 00000000..5baebb2f Binary files /dev/null and b/images/configcat/hex-color.png differ diff --git a/images/configcat/new-welcome-message.png b/images/configcat/new-welcome-message.png new file mode 100644 index 00000000..d73a9d3e Binary files /dev/null and b/images/configcat/new-welcome-message.png differ diff --git a/images/configcat/sdk-key.png b/images/configcat/sdk-key.png new file mode 100644 index 00000000..cb166673 Binary files /dev/null and b/images/configcat/sdk-key.png differ diff --git a/package-lock.json b/package-lock.json index dc8df93c..78cbed7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,8 @@ "@nestjs/core": "10.4.1", "@nestjs/platform-express": "10.4.1", "@nestjs/serve-static": "^4.0.2", + "@openfeature/config-cat-provider": "^0.7.2", + "@openfeature/config-cat-web-provider": "^0.1.3", "@openfeature/env-var-provider": "^0.3.0", "@openfeature/flagd-provider": "^0.13.0", "@openfeature/flagd-web-provider": "^0.7.0", @@ -6344,6 +6346,106 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/@openfeature/config-cat-provider": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@openfeature/config-cat-provider/-/config-cat-provider-0.7.2.tgz", + "integrity": "sha512-H2caGgKrdv71KwNKLTyrotLD4rofdYtu5IiuOKiODa/le2P4xfBD2BkGfP2xgVGl0ZshG2+DAXFUnjp6i8Ak3w==", + "dependencies": { + "@openfeature/config-cat-core": "0.1.0", + "configcat-common": "9.3.1", + "configcat-js-ssr": "8.4.3", + "events": "3.3.0" + }, + "peerDependencies": { + "@openfeature/server-sdk": "^1.13.5", + "configcat-node": "^11.3.1" + } + }, + "node_modules/@openfeature/config-cat-provider/node_modules/@openfeature/config-cat-core": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@openfeature/config-cat-core/-/config-cat-core-0.1.0.tgz", + "integrity": "sha512-Za1dq570QzEHZc9m4tDGq8Kgw5CsEUZ9FeuW4ByJgFwCCsZTrygPQcts2bHcxrg3ZkMOfGAf8gmwsCzmeP3Adg==", + "dependencies": { + "configcat-common": "9.3.0", + "configcat-js-ssr": "8.4.1" + }, + "peerDependencies": { + "@openfeature/core": "<=1.0.0" + } + }, + "node_modules/@openfeature/config-cat-provider/node_modules/@openfeature/config-cat-core/node_modules/configcat-common": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/configcat-common/-/configcat-common-9.3.0.tgz", + "integrity": "sha512-WgpCanLPsT0ig4eLEo2BCZvo0sqtGIkRREnxNAX3Hubw0FzyQ7JUbiliw7ZlBNgda5jaO2nvcs3man+PDdfyLQ==", + "dependencies": { + "tslib": "^2.4.1" + } + }, + "node_modules/@openfeature/config-cat-provider/node_modules/@openfeature/config-cat-core/node_modules/configcat-js-ssr": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/configcat-js-ssr/-/configcat-js-ssr-8.4.1.tgz", + "integrity": "sha512-MWYgtaBkWzAbIPy0hA0M1UV1JSqzXhk1m25f1HFbQMoP/ybX/lDrGUiCMyDosPjcV82qjul8MTiDWIIgOfabPw==", + "dependencies": { + "axios": "^1.6.8", + "configcat-common": "9.3.0", + "tslib": "^2.4.1" + } + }, + "node_modules/@openfeature/config-cat-provider/node_modules/@openfeature/core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@openfeature/core/-/core-1.0.0.tgz", + "integrity": "sha512-xbtVVfZZQu0CZKql9KY8RWfM6Pm1dcoev0z15UL+MUxRnzNuAIM1gGX6FXztRousUvzq8D/y77Qs2hUppDAdcg==", + "peer": true + }, + "node_modules/@openfeature/config-cat-web-provider": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@openfeature/config-cat-web-provider/-/config-cat-web-provider-0.1.3.tgz", + "integrity": "sha512-N4QJFoXbZv7zYzHoRAzRkP7PEY0YGs7XwIiMvLbP4DA3AnGeWw10bPUdrdVqcW7b2aLDf8eS/86Z1Gf6kOFQOQ==", + "dependencies": { + "@openfeature/config-cat-core": "0.1.0", + "events": "3.3.0" + }, + "peerDependencies": { + "@openfeature/web-sdk": "^1.0.0", + "configcat-js-ssr": "^8.4.3" + } + }, + "node_modules/@openfeature/config-cat-web-provider/node_modules/@openfeature/config-cat-core": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@openfeature/config-cat-core/-/config-cat-core-0.1.0.tgz", + "integrity": "sha512-Za1dq570QzEHZc9m4tDGq8Kgw5CsEUZ9FeuW4ByJgFwCCsZTrygPQcts2bHcxrg3ZkMOfGAf8gmwsCzmeP3Adg==", + "dependencies": { + "configcat-common": "9.3.0", + "configcat-js-ssr": "8.4.1" + }, + "peerDependencies": { + "@openfeature/core": "<=1.0.0" + } + }, + "node_modules/@openfeature/config-cat-web-provider/node_modules/@openfeature/config-cat-core/node_modules/configcat-js-ssr": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/configcat-js-ssr/-/configcat-js-ssr-8.4.1.tgz", + "integrity": "sha512-MWYgtaBkWzAbIPy0hA0M1UV1JSqzXhk1m25f1HFbQMoP/ybX/lDrGUiCMyDosPjcV82qjul8MTiDWIIgOfabPw==", + "dependencies": { + "axios": "^1.6.8", + "configcat-common": "9.3.0", + "tslib": "^2.4.1" + } + }, + "node_modules/@openfeature/config-cat-web-provider/node_modules/@openfeature/core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@openfeature/core/-/core-1.0.0.tgz", + "integrity": "sha512-xbtVVfZZQu0CZKql9KY8RWfM6Pm1dcoev0z15UL+MUxRnzNuAIM1gGX6FXztRousUvzq8D/y77Qs2hUppDAdcg==", + "peer": true + }, + "node_modules/@openfeature/config-cat-web-provider/node_modules/configcat-common": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/configcat-common/-/configcat-common-9.3.0.tgz", + "integrity": "sha512-WgpCanLPsT0ig4eLEo2BCZvo0sqtGIkRREnxNAX3Hubw0FzyQ7JUbiliw7ZlBNgda5jaO2nvcs3man+PDdfyLQ==", + "dependencies": { + "tslib": "^2.4.1" + } + }, "node_modules/@openfeature/core": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@openfeature/core/-/core-1.1.0.tgz", @@ -11711,6 +11813,38 @@ "node": ">=0.10.0" } }, + "node_modules/configcat-common": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/configcat-common/-/configcat-common-9.3.1.tgz", + "integrity": "sha512-yVkIbluksD/kZfVyKjLIOpwLrq3/ZRM7Lwrsz89JmbpQ6VtbnelrTQynSPElTtKjrPRZx56v3IZYk3nWTnnM6A==", + "dependencies": { + "tslib": "^2.4.1" + } + }, + "node_modules/configcat-js-ssr": { + "version": "8.4.3", + "resolved": "https://registry.npmjs.org/configcat-js-ssr/-/configcat-js-ssr-8.4.3.tgz", + "integrity": "sha512-9tNM61cgJOE9C1MO8wBK1QglrnlT8VpiAW/KgGdFdOuIPs3ky62EThgAE+HYSRYEv4JrRNB4i7G0v1Qgbf18Hw==", + "dependencies": { + "axios": "^1.7.4", + "configcat-common": "9.3.1", + "tslib": "^2.4.1" + } + }, + "node_modules/configcat-node": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/configcat-node/-/configcat-node-11.3.1.tgz", + "integrity": "sha512-7XJbgBpcxlwzlRLmvCtHTkO247Ban2ZkBqlmk+T0wVEt5tXfltgd53SYLYpw7RBWX0ma/QyP5E+/k/UDdMrOCw==", + "peer": true, + "dependencies": { + "configcat-common": "9.3.1", + "tslib": "^2.4.1", + "tunnel": "0.0.6" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/confusing-browser-globals": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", diff --git a/package.json b/package.json index 0d3762b1..2d3e874f 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,8 @@ "@nestjs/core": "10.4.1", "@nestjs/platform-express": "10.4.1", "@nestjs/serve-static": "^4.0.2", + "@openfeature/config-cat-web-provider": "^0.1.3", + "@openfeature/config-cat-provider": "^0.7.2", "@openfeature/env-var-provider": "^0.3.0", "@openfeature/flagd-provider": "^0.13.0", "@openfeature/flagd-web-provider": "^0.7.0", @@ -142,6 +144,9 @@ "overrides": { "braces": ">=3.0.3", "ws": ">=8.17.1", - "@grpc/grpc-js": ">=1.10.9" + "@grpc/grpc-js": ">=1.10.9", + "@openfeature/config-cat-provider": { + "@openfeature/server-sdk": "1.13.4" + } } } diff --git a/packages/provider/src/lib/provider.service.ts b/packages/provider/src/lib/provider.service.ts index 4485954d..5f147923 100644 --- a/packages/provider/src/lib/provider.service.ts +++ b/packages/provider/src/lib/provider.service.ts @@ -15,6 +15,7 @@ import { OpenFeatureLogger } from '@openfeature/extra'; import { AvailableProvider, CB_PROVIDER_ID, + CONFIGCAT_PROVIDER_ID, ENV_PROVIDER_ID, FLAGD_OFREP_PROVIDER_ID, FLAGD_PROVIDER_ID, @@ -28,6 +29,8 @@ import { } from '@openfeature/utils'; import { OFREPProvider } from '@openfeature/ofrep-provider'; import { FliptProvider } from '@openfeature/flipt-provider'; +import { ConfigCatProvider } from '@openfeature/config-cat-provider'; +import { PollingMode } from 'configcat-node'; type ProviderMap = Record< ProviderId, @@ -171,6 +174,18 @@ export class ProviderService { available: () => !!process.env.FLIPT_URL, url: process.env.FLIPT_WEB_URL, }, + [CONFIGCAT_PROVIDER_ID]: { + factory: () => { + const sdkKey = process.env.CONFIGCAT_SDK_KEY; + if (!sdkKey) { + throw new Error('"CONFIGCAT_SDK_KEY" must be defined.'); + } else { + return ConfigCatProvider.create(sdkKey, PollingMode.AutoPoll, { pollIntervalSeconds: 5 }); + } + }, + available: () => !!process.env.CONFIGCAT_SDK_KEY && !!process.env.CONFIGCAT_SDK_KEY_WEB, + webCredential: process.env.CONFIGCAT_SDK_KEY_WEB, + }, }; constructor() { diff --git a/packages/ui/src/app/demos.tsx b/packages/ui/src/app/demos.tsx index f44a5292..243f37f7 100644 --- a/packages/ui/src/app/demos.tsx +++ b/packages/ui/src/app/demos.tsx @@ -3,6 +3,7 @@ import { FlagdWebProvider } from '@openfeature/flagd-web-provider'; import { AvailableProvider, CB_PROVIDER_ID, + CONFIGCAT_PROVIDER_ID, FLAGD_OFREP_PROVIDER_ID, FLAGD_PROVIDER_ID, FLAGSMITH_PROVIDER_ID, @@ -35,6 +36,7 @@ import { JSON_UPDATED } from './types'; import { getData } from './utils'; import { GoFeatureFlagWebProvider } from '@openfeature/go-feature-flag-web-provider'; import { FliptWebProvider } from '@openfeature/flipt-web-provider'; +import { ConfigCatWebProvider } from '@openfeature/config-cat-web-provider'; type ProviderMap = Record< string, @@ -138,6 +140,11 @@ export class Demos extends Component< return new FliptWebProvider('default', { url: fliptConfig?.url }); }, }, + [CONFIGCAT_PROVIDER_ID]: { + factory: () => { + return ConfigCatWebProvider.create(this.getProviderCredential(CONFIGCAT_PROVIDER_ID), { pollIntervalSeconds: 5 }); + }, + }, }; constructor(props: Record) { diff --git a/packages/utils/src/lib/types.ts b/packages/utils/src/lib/types.ts index 05630702..cca5da20 100644 --- a/packages/utils/src/lib/types.ts +++ b/packages/utils/src/lib/types.ts @@ -9,6 +9,7 @@ export const CB_PROVIDER_ID = 'cloudbees'; export const FLAGSMITH_PROVIDER_ID = 'flagsmith'; export const HARNESS_PROVIDER_ID = 'harness'; export const FLIPT_PROVIDER_ID = 'flipt'; +export const CONFIGCAT_PROVIDER_ID = 'configcat'; export type ProviderId = | typeof ENV_PROVIDER_ID @@ -21,7 +22,8 @@ export type ProviderId = | typeof CB_PROVIDER_ID | typeof FLAGSMITH_PROVIDER_ID | typeof HARNESS_PROVIDER_ID - | typeof FLIPT_PROVIDER_ID; + | typeof FLIPT_PROVIDER_ID + | typeof CONFIGCAT_PROVIDER_ID; export interface AvailableProvider { id: ProviderId;