{"id":41753025,"url":"https://github.com/featurehub-io/featurehub-javascript-sdk","last_synced_at":"2026-01-25T01:30:51.236Z","repository":{"id":38015831,"uuid":"453263222","full_name":"featurehub-io/featurehub-javascript-sdk","owner":"featurehub-io","description":"Official FeatureHub Javascript, Typescript, React, SolidJS, Node SDKs","archived":false,"fork":false,"pushed_at":"2026-01-04T08:27:01.000Z","size":3682,"stargazers_count":7,"open_issues_count":9,"forks_count":4,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-01-07T15:11:36.199Z","etag":null,"topics":["feature-flags","featurehub-sdk","node-feature-flags","node-feature-toggles","react-feature-flags","react-feature-toggles","solid-feature-flags","solidjs-feature-toggles"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/featurehub-io.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-01-29T01:07:49.000Z","updated_at":"2026-01-04T08:27:04.000Z","dependencies_parsed_at":"2024-01-31T09:40:04.015Z","dependency_job_id":"9dfa0647-6dd4-4005-b716-6559b4fbe918","html_url":"https://github.com/featurehub-io/featurehub-javascript-sdk","commit_stats":{"total_commits":101,"total_committers":5,"mean_commits":20.2,"dds":0.6138613861386139,"last_synced_commit":"d8bb381b4347bfbcb0522a72b8d8ec7000d60948"},"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"purl":"pkg:github/featurehub-io/featurehub-javascript-sdk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/featurehub-io%2Ffeaturehub-javascript-sdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/featurehub-io%2Ffeaturehub-javascript-sdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/featurehub-io%2Ffeaturehub-javascript-sdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/featurehub-io%2Ffeaturehub-javascript-sdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/featurehub-io","download_url":"https://codeload.github.com/featurehub-io/featurehub-javascript-sdk/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/featurehub-io%2Ffeaturehub-javascript-sdk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28740871,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-25T01:25:41.653Z","status":"ssl_error","status_checked_at":"2026-01-25T01:25:34.364Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["feature-flags","featurehub-sdk","node-feature-flags","node-feature-toggles","react-feature-flags","react-feature-toggles","solid-feature-flags","solidjs-feature-toggles"],"created_at":"2026-01-25T01:30:51.161Z","updated_at":"2026-01-25T01:30:51.221Z","avatar_url":"https://github.com/featurehub-io.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"**Client SDK**\n\n[![Build featurehub-javascript-client-sdk](https://github.com/featurehub-io/featurehub-javascript-sdk/actions/workflows/typescript-client-build.yml/badge.svg)](https://github.com/featurehub-io/featurehub-javascript-sdk/actions/workflows/typescript-client-build.yml)\n[![npm version](https://badge.fury.io/js/featurehub-javascript-client-sdk.svg)](https://badge.fury.io/js/featurehub-javascript-client-sdk)\n\n**Node SDK**\n\n[![Build featurehub-javascript-node-sdk](https://github.com/featurehub-io/featurehub-javascript-sdk/actions/workflows/typescript-node-build.yml/badge.svg)](https://github.com/featurehub-io/featurehub-javascript-sdk/actions/workflows/typescript-node-build.yml)\n[![npm version](https://badge.fury.io/js/featurehub-javascript-node-sdk.svg)](https://badge.fury.io/js/featurehub-javascript-node-sdk)\n\n| Documentation                                          | Changelog                                                                                                                                                                                                                                                                              | Example Quick links                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |\n| ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| [FeatureHub platform docs](https://docs.featurehub.io) | [Changelog Client SDK](https://github.com/featurehub-io/featurehub-javascript-sdk/blob/main/featurehub-javascript-client-sdk/CHANGELOG.md) \u003cbr\u003e [Changelog Node SDK](https://github.com/featurehub-io/featurehub-javascript-sdk/blob/main/featurehub-javascript-node-sdk/CHANGELOG.md) | [React example](https://github.com/featurehub-io/featurehub-javascript-sdk/tree/main/examples/todo-frontend-react-typescript) \u003cbr\u003e [Node example](https://github.com/featurehub-io/featurehub-javascript-sdk/tree/main/examples/todo-backend-typescript) \u003cbr\u003e [Test automation example](https://github.com/featurehub-io/featurehub-javascript-sdk/tree/main/examples/todo-server-tests) \u003cbr\u003e [React Catch \u0026 Release mode example](https://github.com/featurehub-io/featurehub-javascript-sdk/tree/main/examples/todo-frontend-react-typescript-catch-and-release) \u003cbr\u003e [React Feature Overrides example](https://github.com/featurehub-io/featurehub-javascript-sdk/tree/main/examples/todo-frontend-react-typescript-feature-override) |\n\n# Javascript/Typescript SDK for FeatureHub\n\n## Overview\n\nWelcome to the Javascript/Typescript SDK implementation for [FeatureHub.io](https://featurehub.io) - Open source Feature flags management, A/B testing and remote configuration platform.\n\nThis documentation covers both [featurehub-javascript-node-sdk](https://www.npmjs.com/featurehub-javascript-node-sdk) and [featurehub-javascript-client-sdk](https://www.npmjs.com/featurehub-javascript-client-sdk) and explains how you can use the FeatureHub SDK in Javascript or Typescript for applications like Node.js\nbackend servers, Web front-ends (e.g. Vanilla, React, Angular) or Mobile apps (React Native, Ionic, etc.).\n\nTo control the feature flags from the FeatureHub Admin console, either use FeatureHub SaaS cloud version [demo](https://app.featurehub.io) or install the app using our guide [here](https://docs.featurehub.io/featurehub/latest/installation.html)\n\n## Contributing\n\nInterested in contributing to the FeatureHub JavaScript SDK? Please see our [Contributing Guide](CONTRIBUTING.md) for development setup, testing, and contribution guidelines.\n\n## SDK installation\n\nRun to install the dependency:\n\nif you are intending to use this SDK with React, Angular and other browser frameworks:\n\n`pnpm install featurehub-javascript-client-sdk`\n\nif you are using NodeJS use\n\n`pnpm install featurehub-javascript-node-sdk`\n\n(and further imports you see below should refer to this node library instead of the client library)\n\n## Options to get feature updates\n\nThere are 2 ways to request for feature updates via this SDK:\n\n- **FeatureHub polling client (GET request updates)**\n\n  In this mode, you make a GET request, which you can choose to either do once, when specific things happen in your application,\n  (such as navigation change) or on a regular basis (say every 5 minutes) and the changes will be passed into the FeatureHub repository for processing. This mode is recommended for browser type applications (React, Angular, Vue) and Mobile applications. The `featurehub-javascript-client-sdk` defaults to this behaviour as of 1.2.0, and we have updated and streamlined the browser API to reflect this.\n\n- **SSE (Server Sent Events) realtime updates mechanism**\n\n  In this mode, you will make a connection to the FeatureHub Edge server using the EventSource, and any updates to any features will come through to you in _near realtime_, automatically updating the feature values in the repository. This method is recommended for server (Node) applications. `featurehub-javascript-node-sdk` is configured to use SSE by default. If you decide to use SSE in the browser applications, there is a known issues in the browsers with Kaspersky antivirus potentially blocking SSE events. [GitHub issue](https://github.com/featurehub-io/featurehub/issues/296)\n\n## Browser Quick Start\n\n### Connecting to FeatureHub\n\nThere are 3 steps to connecting:\n\n1. Copy FeatureHub API Key from the FeatureHub Admin Console\n2. Create FeatureHub config\n3. Request feature state\n\n#### 1. API Key from the FeatureHub Admin Console\n\nFind and copy your Server Eval API Key from the FeatureHub Admin Console on the API Keys page -\nyou will use this in your code to configure feature updates for your environments. _Server Side evaluation_ is more suitable when you are using an _insecure client_. (e.g. Browser or Mobile). This means your application is reflecting the actions of a single person.\n\nIt should look similar to this: `5e61fd62-d4ed-40e0-9cc1-cb3d809f6149/YDr1E4uQGA2Li54fQ0HpmSr2LMv9yHhwzxut2DRO`.\n\nThere are other variations of applications where you might want to use a key in a different way from above, we cover more on\nthis [in the main FeatureHub documentation](https://docs.featurehub.io/featurehub/latest/sdks.html#_client_and_server_api_keys)\n\n#### 2. Create FeatureHub config:\n\nIn your page's HTML, add the following (replacing the urls and keys with your own server details):\n\n```html\n\u003cmeta name=\"featurehub-url\" content=\"http://localhost:8085\" /\u003e\n\u003cmeta\n  name=\"featurehub-apiKey\"\n  content=\"c320b6aa-3054-4505-92a5-c01682d47ec2/So1qQ4FOX2UM0Bpxs3r6TqjuDo0WjEIAeYO01dwa\"\n/\u003e\n\u003cmeta name=\"featurehub-interval\" content=\"15000\" /\u003e\n```\n\nThe interval indicates polling frequency to get feature updates and set at 15 seconds. It is normal and expected that your API key will be exposed to the end user in this case, as it is intended to be used in insecure environments.\n\n```typescript\nimport { FeatureHub } from \"featurehub-javascript-client-sdk\";\n```\n\nThe above code configured a server evaluated connection and immediately requests to connect and get the\nfeatures from the server. See below for why you might want to delay this.\n\n#### 3. Request feature state\n\nIn a standard browser situation, there is a single active connection to the FeatureHub server.\nYou can ask for the feature state in your conditional code (we use a boolean flag here):\n\n```typescript\nif (FeatureHub.feature(\"FEATURE_KEY\").enabled) {\n}\n```\n\nThere is always a possibility of a delay between loading the page and the initial state of the features loading. If your\nconditional code executes before the features load (e.g. it has never loaded before or the cache we store of features in\n`localStorage` hasn't loaded yet), you will get an \"empty\" feature - which will generally evaluate all boolean flags to disabled/false,\nand all other types of flags to empty.\n\nThey may not exist, but you can _react_ to changes in feature state. If you wish parts of your page to render\nwhen the feature repository gains state, you can listen for the event `addReadynessListener`:\n\n```typescript\nFeatureHub.config.addReadynessListener((_, firstTimeReady) =\u003e {\n  if (firstTimeReady) {\n    // its ready and its the first time it has been ready, so make appropriate screen changes\n    const value = fhConfig.feature(\"FEATURE_STRING\").str;\n    console.log(\"Value is \", value);\n  }\n});\n```\n\nAlternatively, you can listen for updates in specific features.\n\n```typescript\nFeatureHub.feature(\"FEATURE_KEY\").addListener((feature) =\u003e {\n  if (feature.flag) {\n    // perform some UI update\n  }\n});\n```\n\nYou can listen to these events at any point, the state of the features doesn't need to be loaded yet.\n\n# Quick Start for NodeJS\n\n## Step 1: Getting an apiKey\n\nGenerally for a nodejs application (unless its a batch application) you would use a _client evaluated key_. _Client Side evaluation_ is intended for use in secure environments (such as microservices, e.g Node JS) and is intended for rapid client side evaluation, per request for example. This also means all of the feature flag targeting information comes down to the server application and it can make complex feature decisions locally.\n\n## Step 2: Setting up your configuration\n\nSet the location of your FeatureHub server, your API key and other global information via\nenvironment variables:\n\n```typescript\nconst fhConfig = new EdgeFeatureHubConfig(\n  process.env.FEATUREHUB_EDGE_URL,\n  process.env.FEATUREHUB_CLIENT_API_KEY,\n).init();\n```\n\nIn this case, we are creating a global connection and adding it to the startup of the application and telling it to kick off.\n\nWe recommend that for a server application, you include the _readyness_ of the FeatureHub connection in your health check, so\ndon't let the deployment orchestration (be it a FaaS, kubernetes, ECS, etc) let any traffic route to your server unless you\nhave a healthy connection to FeatureHub (just like you could with a database):\n\n```typescript\nserver.get(\"/health/liveness\", (req, res, next) =\u003e {\n  if (fhConfig.readyness === Readyness.Ready) {\n    // other checks also here\n    res.status(200);\n    res.send(\"ok\");\n  } else {\n    res.send(\"not ready\");\n    res.status(500);\n  }\n\n  next();\n});\n```\n\n### Adding middleware\n\nTo personalise the results for each person, FeatureHub uses _Contexts_ - in browser mode there is only one as a\nbrowser represents a single user, but in your NodeJS server app, each request can represent a different person.\nYour middleware is typically where you will create the per-request context and personalise it.\n\n```typescript\nimport { FeatureHubConfig } from \"./feature_hub_config\";\n\nexport function userMiddleware(fhConfig: FeatureHubConfig) {\n  return (req: any, res: any, next: any) =\u003e {\n    const user = detectUser(req); // function to analyse the Bearer token and determine who the user is\n\n    let fhClient = fhConfig.newContext();\n\n    if (user) {\n      fhClient = fhClient.userKey(user.email);\n      // add anything else relevant to the context\n    }\n\n    fhClient = fhClient.build().then(() =\u003e {\n      req.featureContext = fhClient;\n\n      next();\n    });\n  };\n}\n```\n\n## Step 3: Using it in your application\n\nIn a GET method, determine which message to send:\n\n```typescript\napp.get(\"/\", function (req, res) {\n  if (req.featureContext.feature(\"FEATURE_KEY\").enabled) {\n    req.send(\"The feature is enabled\");\n  } else {\n    res.send(\"The feature is disabled.\");\n  }\n});\n```\n\n# Beyond the Quick Start:\n\nIn this section we cover a bundle of different variations for clients and servers.\n\n### Does my existing code from 1.x work?\n\nIf you have browser code that uses the version earlier than 1.2.0, it still works largely the same and its unlikely you will need to change anything.\nThe biggest change we made in 1.2.+ is in the browser handling. The 99% use case for a browser is a single user, so\nthat means requests for a new context (`FeatureHub.config.newContext()` for example) always actually give\nyou back exactly the same context. And we reference count your requests as well, once your connection is open,\nits open until all requests to create a new context also close them.\n\nIf you actually _want_ a second (or third, or forth) context in a browser, you can absolutely get one, you will\nneed to create one - a new `ServerEvalFeatureContext`.\n\n### Can the browser initialize like the NodeJS example?\n\nYes, the `\u003cmeta\u003e` tag headers are simply an easy way to initialise we introduced in the 1.2.0 version of the API.\nSingle Page Applications (SPA) may not have meta tags and may wish to control exactly how libraries and objects in libraries\nare made available. As it is still a browser application, you will want to delay your initialization.\n\n```typescript\nconst fhConfig = new EdgeFeatureHubConfig(\"\u003curl\u003e\", \"\u003ckey\u003e\"); // no .init()\nconst fhContext = fhConfig.newContext();\n// ... fill in any extra detail in the context\nawait fhContext.build(); // the await is optional\n```\n\nIf you want to ensure you can use the global `FeatureHub` class, then simply set it with:\n\n```typescript\nFeatureHub.set(fhConfig);\n```\n\n### What is meant by extra detail? How do I use the strategies attached to feature flags?\n\nFeatureHub is able to provide user targeting - to support progressive rollouts, targeted rollouts and even\nA/B testing. This will require you to pass ClientContext. When you create config and immediately initialize it, it doesn't contain any Client Context information, however you can customise this connection at any time and add the context:\n\nExample to specify the languages  \nand username of the person up front you can do this:\n\n```typescript\nconst fhConfig = EdgeFeatureHubConfig.config(edgeUrl, apiKey);\nfhConfig\n  .newContext()\n  .userKey(\"\u003csome-user-key\u003e\")\n  .attributeValues(\"languages\", navigator.languages)\n  .build();\n```\n\nThis tells the SDK to hold onto those pieces of information and provide targeted evaluation\nagainst them.\n\n**Important Note** - you can change these at any time, just remember to add `.build()` on the end. You also do not require the `init()` because the `.build()` will do it for you.\n\n### What is the deal with readyness?\n\nReadyness indicates when the SDK has received state or failed to receive state. There is an event on the SDK called\n`addReadynessListener`. You get two pieces of information, the readyness status and whether its the first time its been ready.\nThis is often the information you need to kick your UI into gear in some way.\n\n```typescript\nFeatureHub.config.addReadinessListener((readyness, firstTimeReady) =\u003e {\n  if (firstTimeReady) {\n    const color = FeatureHub.context.getString(\"SUBMIT_COLOR_BUTTON\");\n    this.setState({ todos: this.state.todos.changeColor(color) });\n  }\n});\n```\n\nIf you are writing a server application, it would be typical to include the features being\navailable in a health check (as in the Quick Start). If your server is not able to get its features, it should not\nreceive traffic as a general rule.\n\nYou can always ask the config what the readiness is.\n\n```typescript\nfhConfig.readiness();\n```\n\n### Changing the polling interval\n\nIf you are directly creating the EdgeFeatureHubConfig or you are using polling in your app for some other reason,\nyou may wish to change the interval. So you can change it by setting the\nprovider for the \"Edge Connector\". An example that sets it to five seconds is as follows:\n\n```typescript\nimport { FeatureHubPollingClient } from \"featurehub-javascript-client-sdk\";\nconst FREQUENCY = 5000; // 5 seconds\nEdgeFeatureHubConfig.edgeServiceProvider(\n  (repo, config) =\u003e new FeatureHubPollingClient(repo, config, FREQUENCY),\n);\n```\n\nYou can specify however many seconds you want. FeatureHub also has the ability for the server to\noverride the polling interval, either globally or per environment, but that is not covered here. Note,\nNodeJS servers use the SSE real time streaming updater, they can swap to using polling via the same\nmechanism as above.\n\nPlease note - you should do this before doing an `EdgeFeatureHubConfig.config()`.\n\n### Changing to SSE (Server Sent Events) - real time streaming updates\n\nIf you are keen to see real time updates, then swapping to the Streaming connector is achieved by:\n\n```typescript\nEdgeFeatureHubConfig.defaultEdgeServiceSupplier = (repository, config) =\u003e\n  new FeatureHubEventSourceClient(config, repository);\n```\n\nThis is a default method for feature updates in the featurehub-node-sdk.\n\n## General Documentation\n\n#### Supported feature state requests\n\nOn a context, you can ask for the following information. In the browser, the context is available with `FeatureHub.context`,\nto make it available in a server app, it is shown in the Quick Start.\n\n- Get a raw feature value through the following methods:\n  - `feature('FEATURE_KEY').value` returns whatever the value of this type is as an `any` type. This function is generic so you\n    can use `const colour = feature\u003cstring\u003e('FEATURE_COLOUR').value` for instance and it will support Typescript generic typing. This method is also available directly on `FeatureHub`.\n  - `getFlag('FEATURE_KEY') | getBoolean('FEATURE_KEY')` returns a _boolean_ type feature value - _true_ or _false_. Returns _undefined_ if the feature does not exist or not of _boolean_ type. Alternatively use `feature('FEATURE_KEY').flag`\n  - `getNumber('FEATURE_KEY')` returns a _number_ type feature value or _undefined_ if the feature does not exist, or its value not of number type, or feature has no default value. Alternatively use `feature('FEATURE_KEY').num`.\n  - `getString('FEATURE_KEY')` returns a _string_ type feature value or _undefined_ if the feature does not exist, or its value not of string type or feature has no default value. Alternatively use `feature('FEATURE_KEY').str`.\n  - `getRawJson('FEATURE_KEY')` returns a raw json feature value represented as _string_ or _undefined_ if the feature does not exist, or its value not of JSON type or feature has no default value. Alternatively use `feature('FEATURE_KEY').rawJson`.\n- Use convenience functions:\n  - `isEnabled('FEATURE_KEY')` - returns _true_\n    only if the feature is a boolean and is _true_, otherwise _false_. Alternatively use `feature('FEATURE_KEY').enabled`\n  - `isSet('FEATURE_KEY')` - in case a feature value is not set (_null_) (this can only happen for strings, numbers and json types), this check returns _false_.\n    If a feature doesn't exist - returns _false_. Otherwise, returns _true_.\n  - `getKey()`: returns feature key if feature exists\n  - `feature('FEATURE_KEY').exists` - return _true_ if feature exists, otherwise return _false_\n  - `feature('FEATURE_KEY').locked` - returns _true_ if feature is locked, otherwise _false_\n  - `feature('FEATURE_KEY').version` - returns feature update version number (every change on the feature causes its version to update).\n  - `feature('FEATURE_KEY').type` - returns type of feature (boolean, string, number or json)\n  - `feature('FEATURE_KEY').addListener` - see _Feature updates listener_ below.\n\n- Get a list of all feature keys from the feature repository\n\n  ```\n  const fhClient = await fhConfig.newContext().build();\n  console.log(\"List all feature keys: \", client.repository().simpleFeatures().keys());\n  ```\n\n- The primitives to build almost any experience you like is available in the SDK, please feel free to have a look around!\n\n## Rollout Strategies and Client Context\n\nFeatureHub supports client and server side evaluation of complex rollout strategies\nthat are applied to individual feature values in a specific environment. This includes support of preset rules, e.g. per **_user key_**, **_country_**, **_device type_**, **_platform type_** as well as **_percentage splits_** rules and custom rules that you can create according to your application needs.\n\nClient Contexts are _mutable_ objects - which means you can keep changing them as you need to.\n\n- For server side evaluation, you need to indicate when you have finished a set of changes and call `.build()`. This grabs all of the\n  attributes in a context evaluation and sends them off to the server for evaluation to get the new state of the features. If you\n  want an accurate subsequent representation of the features, you should `await` this request.\n- For client side evaluation, simply changing and using them in evaluations is all that is required. You can however use `.build()`,\n  it is simply a no-op. You also do not have to use `await` if you do not `.build()` if you are using a client side key because\n  the features are evaluated on the client side.\n\nGetting a new context is covered above, but as a refresher, once you have a `FeatureHubConfig` you can just call:\n\n```typescript\nconst fhContext = fhConfig.newContext();\n```\n\nWe will assume in the following examples you have a variable called `fhContext` that represents your context.\n\nFor more details on rollout strategies, targeting rules and feature experiments see the [core documentation](https://docs.featurehub.io/featurehub/latest/index.html#_rollout_strategies_and_targeting_rules).\n\n```typescript\nawait fhContext\n  .userKey(\"user.email@host.com\")\n  .country(StrategyAttributeCountryName.NewZealand)\n  .build();\n\nif (fhClient.isEnabled(\"FEATURE_KEY\")) {\n  //do something\n}\n```\n\n#### Coding for rollout strategies\n\nThere are several preset strategies rules we track specifically: `user key`, `country`, `device` and `platform`. However, if those do not satisfy your requirements you also have an ability to attach a custom rule. Custom rules can be created as following types: `string`, `number`, `boolean`, `date`, `date-time`, `semantic-version`, `ip-address`\n\nFeatureHub SDK will match your users according to those rules, so you need to provide attributes to match on in the SDK:\n\n**Sending preset attributes:**\n\nProvide the following attribute to support `userKey` rule:\n\n```typescript\nawait fhContext.userKey(\"ideally-unique-id\").build();\n```\n\nto support `country` rule:\n\n```typescript\nawait fhContext.country(StrategyAttributeCountryName.NewZealand).build();\n```\n\nto support `device` rule:\n\n```typescript\nawait fhContext.device(StrategyAttributeDeviceName.Browser).build();\n```\n\nto support `platform` rule:\n\n```typescript\nawait fhContext.platform(StrategyAttributePlatformName.Android).build();\n```\n\nto support `semantic-version` rule:\n\n```typescript\nawait fhContext.version(\"1.2.0\").build();\n```\n\nor if you are using multiple rules, you can combine attributes as follows:\n\n```typescript\nawait fhContext\n  .userKey(\"ideally-unique-id\")\n  .country(StrategyAttributeCountryName.NewZealand)\n  .device(StrategyAttributeDeviceName.Browser)\n  .platform(StrategyAttributePlatformName.Android)\n  .version(\"1.2.0\")\n  .build();\n```\n\nNote, because the context is mutable, you are building on what you have previously stored. You can call `.clear()` if you\nwish to remove what was there before.\n\n**Sending custom attributes:**\n\nTo add a custom key/value pair, use `attributeValue(key, value)`\n\n```typescript\nawait fhContext.attributeValue(\"first-language\", \"italian\").build();\n```\n\nOr with array of values (only applicable to custom rules):\n\n```typescript\nawait fhContext.attributeValue(\"languages\", [\"italian\", \"english\", \"german\"]).build();\n```\n\nIf you define a strategy using a custom rule, providing an array will make the SDK compare each value in turn against the rule\nand if _any_ matches, the rule will be considered fulfilled.\n\nYou can also use `fhClient.clear()` to empty your context.\n\nIn all cases, you need to call `build()` to re-trigger passing of the new attributes to the server for recalculation.\n\n**Coding for percentage splits:**\nFor percentage rollout you are only required to provide the `userKey` or `sessionKey`.\n\n```typescript\nawait fhContext.userKey(\"ideally-unique-id\").build();\n```\n\nor\n\n```typescript\nawait fhContext.sessionKey(\"session-id\").build();\n```\n\nFor more details on percentage splits and feature experiments see [Percentage Split Rule](https://docs.featurehub.io/featurehub/latest/index.html#_percentage_split_rule).\n\n#### Feature updates listener\n\nIf the SDK detects a feature update, you also have an option to attach listeners\nto these updates. The feature value may not change, but you will be able to evaluate the feature\nagain and determine if it has changed for your _Context_:\n\n```typescript\nFeatureHub.feature(\"FEATURE_KEY\").addListener((fs) =\u003e {\n  console.log(fs.key, \"is\", fhContext.isEnabled(fs.key));\n});\n```\n\nWhat you are passed is the _raw_ feature without any enhancements (including context), so ideally\nyou would not use this directly, use it from the _Context_.\n\nNote, how fast you get these updates depends on the client you use. If you are using the EventSource\nclient, it will be close to immediately after they have been updated. If you are using the Polling\nclient, it will be when the next update happens.\n\nYou can attach as many callbacks for each feature as you like. They return a handler, and you can call `.removeListener`\nif you wish to stop receiving the events.\n\n### Logging\n\nThis client exposes a class called `FHLog` which has two methods, i.e.:\n\n```typescript\nexport type FHLogMethod = (...args: any[]) =\u003e void;\nexport class FHLog {\n  public log: FHLogMethod = (...args: any[]) =\u003e {\n    console.log(args);\n  };\n  public error: FHLogMethod = (...args: any[]) =\u003e {\n    console.error(args);\n  };\n}\n```\n\nYou can replace these methods with whatever logger you use to ensure you get the right format logs (e.g. Winston, Bunyan, Log4js).\n\nThere is a `.quiet()` method available on FHLog which will silence logs.\n\n### NodeJS server usage\n\nFor the full example see [here](https://github.com/featurehub-io/featurehub-javascript-sdk/tree/main/examples/todo-backend-typescript)\n\n### React usage\n\nFor the full example see [here](https://github.com/featurehub-io/featurehub-javascript-sdk/tree/main/examples/todo-frontend-react-typescript)\n\n##Detailed documentation\n\n### The FeatureHub repository Overview\n\nThe FeatureHub repository is a single class that holds and tracks features in your system. It gets features delivered\nto it to process, tracks changes, and allows you to find and act on features in a useful way.\nIt also sends events out in certain circumstances.\n\n### Meta-Events from the repository\n\nThere are two \"meta events\" from the FeatureHub repository, readiness and \"new feature available\".\n\n#### Readiness\n\nThis is covered in some detail above, we won't repeat it here.\n\n#### New Feature State Available\n\nThe repository tracks features and their states by version number. When a new version of a feature state arrives,\nsay a flag changes from off to on, then the repository will check this version is really newer, and if so, it will\n(in the default, immediate mode) apply that change to the current feature state it is holding. From this it will\ntrigger any events on that particular feature, and it can also trigger a generic event -\n`postLoadNewFeatureStateAvailable`. This event gets triggered once no matter if a bundle of changes comes in, or a\nsingle change comes in.\n\nYou would typically use this to know if events have occurred that mean you need to go back and get event states or\nrerender a page or similar.\n\nThis event gets a little more complicated when using the second (non default) mode - _catch and release_ - discussed\nin more detail with examples below. In this mode, the repository will receive the updates, and compare them, but it\nwill _not_ apply them to the features in the repository. As such, in this mode, a change on the server that\nturns up in the repository (via GET or EventSource) will _not_ be applied to the local feature state, it will be held.\nAnd the effect of this is that this event will _not_ fire. When you tell the repository to process these \"held\"\nchanges, then the event will fire.\n\nAttaching a listener for this hook is done like this:\n\n```typescript\nfhConfig.repository().addPostLoadNewFeatureStateAvailableListener((_) =\u003e {\n  // e.g. tell user to page is going to update and re-render page\n});\n```\n\n## Reacting to feature changes\n\nUnlike the server focused APIs, Typescript/Javascript has two modes of operation.\n\n### Immediate reaction (recommended for servers)\n\nIn this mode, as changes occur to features coming from the server, the states of the features will immediately change.\nEvents are fired. This kind of operation is normally best for servers, as they want to react to what has been asked for.\n\nYou do not have to write any code to get this mode as it is the default behaviour.\n\n### Catch and Release (recommended for Web and Mobile)\n\nThis is a deliberate holding onto the updates to features until such a time as they are \"released\". This is separate\nfrom them coming down from the source and being put in the repository. In _catch and release_ mode, the repository will\nhold onto the changes (only the latest ones) and apply them when you chose to \"release them\". This means there will be\nno delay while making a GET request for the latest features for example when you wish to \"check\" for new updates to\nfeatures when shifting pages or similar.\n\nThis strategy is recommended for Web and Mobile applications as controlled visibility for the user is important.\n\n```javascript\n// don't allow feature updates to come through\nFeatureHub.config.catchAndReleaseMode = true;\n```\n\nIf you choose to not have listeners, when you call:\n\n```javascript\nfhConfig.release();\n```\n\nthen you should follow it with code to update your UI with the appropriate changes in features. You\nwon't know which ones changed, but this can be a more efficient state update than using the listeners above.\n\n## Failure\n\nIf for some reason the connection to the FeatureHub server fails - either initially or for some reason during\nthe process, you will get a readiness state callback to indicate that it has now failed.\n\n```javascript\nexport enum Readyness {\n  NotReady = 'NotReady',\n  Ready = 'Ready',\n  Failed = 'Failed'\n}\n```\n\n## Analytics\n\nAllows you to connect your application and see your features performing in Google Analytics.\n\nWhen you log an event on the repository,\nit will capture the value of all of the feature flags and feature values (in case they change),\nand log that event against your Google Analytics, once for each feature. This allows you to\nslice and dice your events by state each of the features were in. We send them as a batch, so it\nis only one request.\n\nNote that if you log the analytics event _on the client context_ (`ctx.logAnalyticsEvent`) it captures that user's features. If you log\nthem on the repository itself (`fhConfig.repository().logAnalyticsEvent...`) then it logs the features as they are\nhanded back from the server. If you are using a Server Evaluated Key, these will be the same, but you should try\nand always use the Client Context to log analytics events.\n\nThere are two different implementations, one for when you are in the browser and one for when you\nare in the server, like nodejs. You don't need to worry about this, the code detects which one it is in and\ncreates the correct instance.\n\nThere is a plan to support other Analytics tools in the future. The only one we\ncurrently support is Google Analytics, so you need:\n\n- a Google analytics key - usually in the form `UA-123456`. You must provide this up front.\n- a CID - a customer id this is associate with this. You can provide this up front or you can\n  provide it with each call, or you can set it later.\n\n1. You can set it in the constructor:\n\n```typescript\nconst collector = new GoogleAnalyticsCollector(\"UA-123456\", \"some-CID\");\n```\n\n2. You can tell the collector later.\n\n```typescript\nconst collector = new GoogleAnalyticsCollector(\"UA-123456\");\ncollector.cid = \"some-value\"; // you can set it here\n```\n\n3. When you log an event, you can pass it in the map:\n\n```typescript\nconst data = new Map\u003cstring, string\u003e();\ndata.set(\"cid\", \"some-cid\");\n\nctx.logAnalyticsEvent(\"event-name\", data);\n```\n\n4. For a NODE server, you can set as an environment variable named `GA_CID`.\n\n```typescript\nfhConfig.addAnalyticCollector(collector);\n```\n\nAs you can see from above (in option 3), to log an event, you simply tell the repository to\nlog an analytics event. It will take care of bundling everything up, passing it off to the\nGoogle Analytics collector which will post it off.\n\nRead more on how to interpret events in Google Analytics [here](https://docs.featurehub.io/featurehub/latest/analytics.html)\n\n## FeatureHub Test API\n\nWhen writing automated integration tests, it is often desirable to update your feature values, particularly flags.\nWe provide a method to do this\nusing the `FeatureUpdater` class. Use of the API is based on the rights of your SDK-URL. Generally you should\nonly give write access to service accounts in test environments.\n\nWhen specifying the key, the Edge service will get the latest value of the feature and compare your changes against\nit, compare them to your permissions and act accordingly.\n\nYou need to pass in an instance of a FeatureStateUpdate, which takes three values, all of which are optional but\nmust make sense:\n\n- `lock` - this is a boolean. If true it will attempt to lock, false - attempts to unlock. No value will not make any change.\n- `value` - this is any kind of value and is passed when you wish to _set_ a value. Do not pass it if you wish to unset the value.\n  For a flag this means setting it to false (if null), but for the others it will make it null (not passing it).\n- `updateValue` - set this to true if you wish to make the value field null. Otherwise, there is no way to distinguish\n  between not setting a value, and setting it to null.\n\nSample code might look like this:\n\n```typescript\nconst fu = new FeatureUpdater(fhConfig);\n\n// this would work presuming the correct access rights\nfu.updateKey(\n  \"FEATURE_TITLE_TO_UPPERCASE\",\n  new FeatureStateUpdate({ lock: false, value: true }),\n).then((r) =\u003e console.log(\"result is\", r));\n\n// this would not as this key doesn't exist\nfu.updateKey(\"meep\", new FeatureStateUpdate({ lock: false, value: true })).then((r) =\u003e\n  console.log(\"result is\", r),\n);\n```\n\nYou can do this in the browser and in the sample React application in the examples folder, we have exposed this\nclass to the `Window` object so you can run up the sample and play around with it. For example:\n\n```javascript\nconst x = new window.FeatureUpdater(fhConfig);\n\nx.updateKey('meep', {lock: true}).then((r) =\u003e console.log('result was', r));\nresult was false\nx.updateKey(\"FEATURE_TITLE_TO_UPPERCASE\", {lock: false}).then((r) =\u003e console.log('result was', r));;\n\nresult was true\n```\n\n### Errors\n\nIf a 4xx error is returned, then it will stop. Otherwise it will keep polling even if there is no data on the assumption\nit simply hasn't been granted access. The API does not leak information on valid vs invalid environments.\n\n## Angular\n\nThis library uses semver, which is a commonjs library. You will need to follow the recommended Angular documentation\non how to suppress the warning.\n\n## Older Versions\n\nWe have deprecated [FeatureHub Eventsource Javascript SDK](https://www.npmjs.com/package/featurehub-eventsource-sdk) which covers both client (browser) and server (node) applications in favor of splitting it into two separate NPM modules to enable support for additional browser frameworks like Angular and Vue. To transition to one of the new NPM modules, follow installation instructions below and change the imports in your code. The FeatureHub SDK API hasn't changed so you don't have to reimplement your SDK code.\n\n## Advanced Usage\n\n### Overriding Client creation\n\nThe method to create the EventSource request is defined globally and is intended to be overridden. The nodejs library\nfor example uses the clientjs library and just replaces its \"factory\" method for creating eventsource clients or polling\nclients. You can use this method if you wish to replace the method to create eventsource or polling client creation\nrequests.\n\nWhy would you want to do this? The underlying _nodejs_ libraries allow you more leeway than the browser libraries, for\nexample in NodeJS you can add extra headers to outgoing requests, or do hand-crafted proxy modification and so forth.\nHere we include two examples, one for event sourcing and one for polling under nodejs. You can do the same for the browser\nclient but there is limited value in doing so..\n\n#### Replacing eventsource clients under nodejs\n\nThe constructor for an eventsource client is the same as the standard EventSource constructor - by making it your\nown you can add anything you like to the dictionary passed to the standard eventsource library. Below is an example\nof passing an extra header:\n\n```typescript\nconst ES = require(\"eventsource\");\n\nFeatureHubEventSourceClient.eventSourceProvider = (url, dict) =\u003e {\n  if (!dict) {\n    dict = { headers: {} };\n  }\n  if (!dict.headers) {\n    dict.headers = {};\n  }\n  dict.headers[\"simple-header\"] = \"hello\";\n  return new ES(url, dict);\n};\n```\n\nThis turns up such as:\n\n```http request\n\u003e GET http://localhost:8085/features/default/6cd5017f-9803-4c45-a5ed-586041750c27/9NeB8UBVfjbgXdIXVVaHmH3VNapJ1k*jXHKXlZ3vwlkBHmWI8Yn\n\u003e accept: text/event-stream\n\u003e cache-control: no-cache\n\u003e connection: close\n\u003e host: localhost:8085\n\u003e simple-header: hello\n```\n\n#### Replacing the polling client under nodejs\n\nThe construction of the polling client request for nodejs is a little more complicated, but there is a callback which passes back\nthe full request of a poll to your code if you wish. To use it, you again need to intercept the creation mechanism and then you\nwill get the full set of request options.\n\n```typescript\nFeatureHubPollingClient.pollingClientProvider = (opt, url, freq, callback) =\u003e {\n  const nodeClient = new NodejsPollingService(opt, url, freq, callback);\n  nodeClient.modifyRequestFunction = (options: RequestOptions) =\u003e {\n    options.headers[\"simple-header\"] = \"hello\";\n  };\n  return nodeClient;\n};\n```\n\nThis kind of http request turns up as:\n\n```http request\n\u003e GET http://localhost:8085/features?sdkUrl=default%2F6cd5017f-9803-4c45-a5ed-586041750c27%2F9NeB8UBVfjbgXdIXVVaHmH3VNapJ1k*jXHKXlZ3vwlkBHmWI8Yn\n\u003e connection: close\n\u003e host: localhost:8085\n\u003e if-none-match: \"79f8dcb4\"\n\u003e simple-header: hello\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffeaturehub-io%2Ffeaturehub-javascript-sdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffeaturehub-io%2Ffeaturehub-javascript-sdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffeaturehub-io%2Ffeaturehub-javascript-sdk/lists"}