{"id":19149859,"url":"https://github.com/harness/ff-javascript-client-sdk","last_synced_at":"2025-04-15T10:29:30.052Z","repository":{"id":38334288,"uuid":"344542907","full_name":"harness/ff-javascript-client-sdk","owner":"harness","description":null,"archived":false,"fork":false,"pushed_at":"2024-04-09T16:20:38.000Z","size":2614,"stargazers_count":10,"open_issues_count":7,"forks_count":24,"subscribers_count":5,"default_branch":"main","last_synced_at":"2024-04-09T20:05:26.342Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/harness.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"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}},"created_at":"2021-03-04T16:44:32.000Z","updated_at":"2024-04-15T12:58:41.922Z","dependencies_parsed_at":"2024-04-15T12:58:27.642Z","dependency_job_id":null,"html_url":"https://github.com/harness/ff-javascript-client-sdk","commit_stats":{"total_commits":87,"total_committers":12,"mean_commits":7.25,"dds":0.5632183908045977,"last_synced_commit":"9f6b2a8ce288b7d3637bba3d34acceb6db02e444"},"previous_names":[],"tags_count":47,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harness%2Fff-javascript-client-sdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harness%2Fff-javascript-client-sdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harness%2Fff-javascript-client-sdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harness%2Fff-javascript-client-sdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/harness","download_url":"https://codeload.github.com/harness/ff-javascript-client-sdk/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249051194,"owners_count":21204777,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":[],"created_at":"2024-11-09T08:09:53.298Z","updated_at":"2025-04-15T10:29:30.044Z","avatar_url":"https://github.com/harness.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Before you Begin\n\nHarness Feature Flags (FF) is a feature management solution that enables users to change the software’s functionality,\nwithout deploying new code. FF uses feature flags to hide code or behaviours without having to ship new versions of the\nsoftware. A feature flag is like a powerful if statement.\n\nFor more information, see https://harness.io/products/feature-flags/\n\nTo read more, see https://ngdocs.harness.io/category/vjolt35atg-feature-flags\n\nTo sign up, https://app.harness.io/auth/#/signup/\n\n# Harness Feature Flags Client SDK for JavaScript\n\nLibrary for integrating Harness Feature Flags into JavaScript UI applications.\n\n## Install\n\n```shell\nnpm i @harnessio/ff-javascript-client-sdk\n```\n\nor\n\n```shell\nyarn add @harnessio/ff-javascript-client-sdk\n```\n\n## Usage\n\n```typescript\nimport { initialize, Event } from '@harnessio/ff-javascript-client-sdk'\n```\n\nInitialize SDK with api key and target information.\n\n```typescript\ninitialize(FeatureFlagSDKKey: string, target: Target, options?: Options)\n```\n\nIn which `Target` and `Options` are defined as:\n\n```typescript\ninterface Target {\n  identifier: string\n  name?: string\n  anonymous?: boolean\n  attributes?: object\n}\n\ninterface Options {\n  baseUrl?: string\n  eventUrl?: string\n  eventsSyncInterval?: number\n  pollingInterval?: number\n  pollingEnabled?: boolean\n  streamEnabled?: boolean\n  debug?: boolean,\n  cache?: boolean | CacheOptions\n  logger?: Logger\n  maxStreamRetries?: number\n  enableAnalytics?: boolean\n}\n```\n\nFor example:\n\n```typescript\nconst client = initialize('00000000-1111-2222-3333-444444444444', {\n  identifier: YOUR_TARGET_IDENTIFIER,      // Target identifier\n  name: YOUR_TARGET_NAME,                  // Optional target name\n  attributes: {                            // Optional target attributes\n    email: 'sample@sample.com'\n  }\n})\n```\n\n\n## Streaming and Polling Mode\n\nBy default, Harness Feature Flags SDK has streaming enabled and polling enabled. Both modes can be toggled according to your preference using the SDK's configuration.\n\n### Streaming Mode\nStreaming mode establishes a continuous connection between your application and the Feature Flags service. \nThis allows for real-time updates on feature flags without requiring periodic checks. \nIf an error occurs while streaming and `pollingEnabled` is set to `true`,\nthe SDK will automatically fall back to polling mode until streaming can be reestablished. \nIf `pollingEnabled` is `false`, streaming will attempt to reconnect without falling back to polling.\n\n### Polling Mode\nIn polling mode, the SDK will periodically check with the Feature Flags service to retrieve updates for feature flags. The frequency of these checks can be adjusted using the SDK's configurations.\n\n### No Streaming or Polling\nIf both streaming and polling modes are disabled (`streamEnabled: false` and `pollingEnabled: false`), \nthe SDK will not automatically fetch feature flag updates after the initial fetch. \nThis means that after the initial load, any changes made to the feature flags on the Harness server will not be reflected in the application until the SDK is re-initialized or one of the modes is re-enabled.\n\nThis configuration might be useful in specific scenarios where you want to ensure a consistent set of feature flags \nfor a session or when the application operates in an environment where regular updates are not necessary. However, it's essential to be aware that this configuration can lead to outdated flag evaluations if the flags change on the server.\n\nTo configure the modes:\n\n```typescript\n\nconst options = {\n  streamEnabled: true, // Enable or disable streaming - default is enabled\n  pollingEnabled: true, // Enable or disable polling - default is enabled if stream enabled, or disabled if stream disabled.\n  pollingInterval: 60000, // Polling interval in ms, default is 60000ms which is the minimum. If set below this, will default to 60000ms.\n}\n\nconst client = initialize(\n  'YOUR_SDK_KEY',\n  {\n    identifier: 'Harness1',\n    attributes: {\n      lastUpdated: Date(),\n      host: location.href\n    }\n  },\n  options\n)\n```\n\n### Max Stream Retries\nYou can configure the maximum number of streaming retries before the SDK stops attempting to reconnect or falls back to polling (if enabled). The maxRetries option can be set to any positive number or Infinity for unlimited retries (which is the default).\n\n```typescript\nconst options = {\n  maxRetries: 5, // Set the maximum number of retries for streaming. Default is Infinity.\n  streamEnabled: true, \n  pollingEnabled: true, \n  pollingInterval: 60000, \n}\n\nconst client = initialize(\n    'YOUR_SDK_KEY',\n    {\n      identifier: 'Harness1',\n      attributes: {\n        lastUpdated: Date(),\n        host: location.href\n      }\n    },\n    options\n)\n\n```\nIf maxRetries is reached and pollingEnabled is true, the SDK will stay in polling mode. If pollingEnabled is false, the SDK will not poll, and evaluations will not be updated until the SDK Client is initialized again, for example if the app or page is restarted.\n\n## Listening to events from the `client` instance.\n\n```typescript\nclient.on(Event.READY, flags =\u003e {\n  // Event happens when connection to server is established\n  // flags contains all evaluations against SDK key\n})\n\nclient.on(Event.FLAGS_LOADED, evaluations =\u003e {\n  // Event happens when flags are loaded from the server\n})\n\nclient.on(Event.CACHE_LOADED, evaluations =\u003e {\n  // Event happens when flags are loaded from the cache\n})\n\nclient.on(Event.CHANGED, flagInfo =\u003e {\n  // Event happens when a changed event is pushed\n  // flagInfo contains the updated feature flag\n})\n\nclient.on(Event.DISCONNECTED, () =\u003e {\n  // Event happens when connection is disconnected\n})\n\nclient.on(Event.CONNECTED, () =\u003e {\n  // Event happens when connection has been lost and reestablished \n})\n\nclient.on(Event.POLLING, () =\u003e {\n  // Event happens when polling begins\n})\n\nclient.on(Event.POLLING_STOPPED, () =\u003e {\n  // Event happens when polling stops\n})\n\nclient.on(Event.ERROR, error =\u003e {\n  // Event happens when connection some error has occurred\n})\n\nclient.on(Event.ERROR_CACHE, error =\u003e {\n  // Event happens when an error occurs when accessing the cache\n})\n\nclient.on(Event.ERROR_AUTH, error =\u003e {\n  // Event happens when unable to authenticate\n})\n\nclient.on(Event.ERROR_FETCH_FLAGS, error =\u003e {\n  // Event happens when unable to fetch flags from the service\n})\n\nclient.on(Event.ERROR_FETCH_FLAG, error =\u003e {\n  // Event happens when unable to fetch an individual flag from the service\n})\n\nclient.on(Event.ERROR_METRICS, error =\u003e {\n  // Event happens when unable to report metrics back to the service\n})\n\nclient.on(Event.ERROR_STREAM, error =\u003e {\n  // Event happens when the stream returns an error\n})\n```\n\n### Getting value for a particular feature flag\n\nIf you would like to know that the default variation was returned when getting the value, for example, if the provided flag wasn't found in the cache then pass true for the third argument withDebug:\n```typescript\nconst result = client.variation('Dark_Theme', false, true);\n```\n\nWhen `withDebug` is set to true, the result object will have the following structure:\n\n```typescript\ninterface VariationValueWithDebug {\n  value: any,                 // The actual variation value\n  isDefaultValue: boolean     // True if the default variation was returned, false otherwise\n}\n```\n\nFor the example above, if the flag identifier 'Dark_Theme' is not found, result would look like:\n\n```typescript\n{\n  value: false,\n  isDefaultValue: true\n}\n```\n\nIf you do not need to know the default variation was returned: \n\n```typescript\nconst variationValue = client.variation('Dark_Theme', false) // second argument is default value when variation does not exist\n```\n\nIn this case, the result will be a direct value, either from the existing variation or the default value you provided. There won't be an object structure; you'll simply get the value itself.\n\nFor the example above:\n\n- If the flag identifier 'Dark_Theme' exists in storage, variationValue would be the stored value for that identifier.\n- If the flag identifier 'Dark_Theme' does not exist, variationValue would be the default value provided, in this case, false\n\n* Note the reasons for the default variation being returned can be\n  1. SDK Not Initialized Yet\n  2. Typo in Flag Identifier\n  3. Wrong project API key being used\n\n#### Listening for the `ERROR_DEFAULT_VARIATION_RETURNED` event\nYou can also listen for the `ERROR_DEFAULT_VARIATION_RETURNED` event, which is emitted whenever a default variation is returned because the flag has not been found in the cache. This is useful for logging or taking other action when a flag is not found. \n\nExample of listening for the event:\n\n```typescript\nclient.on(Event.ERROR_DEFAULT_VARIATION_RETURNED, ({ flag, defaultVariation }) =\u003e {\n  console.warn(`Default variation returned for flag: ${flag}, value: ${defaultVariation}`)\n})\n```\n\n### Cleaning up\n\nRemove a listener of an event by `client.off`.\n\n```typescript\nclient.off(Event.ERROR, error =\u003e {\n  // Do something when an error occurs\n})\n```\n\nRemove all listeners:\n\n```typescript\nclient.off()\n```\n\nOn closing your application, call `client.close()` to close the event stream.\n\n```typescript\nclient.close()\n```\n\n## Caching\n\nIn practice flags rarely change and so it can be useful to cache the last received evaluations from the server to allow\nyour application to get started as fast as possible. Setting the `cache` option as `true` or as an object (see interface\nbelow) will allow the SDK to store its evaluations to `localStorage` and retrieve at startup. This lets the SDK get\nstarted near instantly and begin serving flags, while it carries on authenticating and fetching up-to-date evaluations\nfrom the server behind the scenes.\n\n```typescript\nconst client = initialize('00000000-1111-2222-3333-444444444444', {\n    identifier: YOUR_TARGET_IDENTIFIER,\n    name: YOUR_TARGET_NAME\n  }, {\n    cache: true // enable caching\n  }\n)\n```\n\nThe `cache` option can also be passed as an object with the following options.\n\n```typescript\ninterface CacheOptions {\n  // maximum age of stored cache, in ms, before it is considered stale\n  ttl?: number\n  // storage mechanism to use, conforming to the Web Storage API standard, can be either synchronous or asynchronous\n  // defaults to localStorage\n  storage?: AsyncStorage | SyncStorage\n  /**\n   * use target attributes when deriving the cache key\n   * when set to `false` or omitted, the key will be formed using only the target identifier and SDK key\n   * when set to `true`, all target attributes with be used in addition to the target identifier and SDK key\n   * can be set to an array of target attributes to use a subset in addition to the target identifier and SDK key\n   * defaults to false\n   */\n  deriveKeyFromTargetAttributes?: boolean | string[]\n}\n```\n\n## Set evaluations\n\nIn some cases it might be worthwhile providing the SDK with a set of evaluations which it can then serve instantly. You\nmight want to consider this when you need to:\n\n- **reduce application startup time** by providing default values or a snapshot of evaluations. For example, if your\n  application is server-side generated, then it might make sense to retrieve evaluations on the server and provide them\n  in the HTML of the page to be injected into the SDK\n- **provide network redundancy** by allowing your app to detect network connectivity issues accessing the service and\n  loading evaluations from another source\n\nTo achieve this you can call the `setEvaluations` method at any time after initializing the client. The\n`setEvaluations` method takes an array of `Evaluation` objects as an argument.\n\n```typescript\nclient.setEvaluations(evals);\n```\n\nIn which `Evaluation` is defined as:\n\n```typescript\ninterface Evaluation {\n  flag: string // Feature flag identifier\n  identifier: string // variation identifier\n  value: boolean | string | number | object | undefined // variation value\n  kind: string // boolean | json | string | int\n  deleted?: boolean // mark that feature flag is deleted\n}\n```\n\n## Authentication Request Timeout\n\nThe `authRequestReadTimeout` option allows you to specify a timeout in milliseconds for the authentication request. If the request takes longer than this timeout, it will be aborted. This is useful for preventing hanging requests due to network issues or slow responses.\n\nIf the request is aborted due to this timeout the SDK will fail to initialize and an `ERROR_AUTH` and `ERROR` event will be emitted.\n\nThe default value if not specified is `0` which means that no timeout will occur.\n\n**This only applies to the authentication request. If you wish to set a read timeout on the remaining requests made by the SDK, you may register [API Middleware](#api-middleware)\n\n```typescript\nconst options = {\n  authRequestReadTimeout: 30000, // Timeout in milliseconds (default: 0 - no timeout)\n};\n\nconst client = initialize(\n  'YOUR_API_KEY',\n  {\n    identifier: 'Harness1',\n    attributes: {\n      lastUpdated: Date(),\n      host: location.href,\n    },\n  },\n  options\n);\n```\n\n## API Middleware\nThe `registerAPIRequestMiddleware` function allows you to register a middleware function to manipulate the payload (URL, body and headers) of API requests after the AUTH call has successfully completed\n\n```typescript\nfunction abortControllerMiddleware([url, options]) {\n  if (window.AbortController) {\n    const abortController = new AbortController();\n    options.signal = abortController.signal;\n\n    // Set a timeout to automatically abort the request after 30 seconds\n    setTimeout(() =\u003e abortController.abort(), 30000);\n  }\n\n  return [url, options]; // Return the modified or original arguments\n}\n\n// Register the middleware\nclient.registerAPIRequestMiddleware(abortControllerMiddleware);\n```\nThis example middleware will automatically attach an AbortController to each request, which will abort the request if it takes longer than the specified timeout. You can also customize the middleware to perform other actions, such as logging or modifying headers.\n\n\n## Logging\nBy default, the Javascript Client SDK will log errors and debug messages using the `console` object. In some cases, it\ncan be useful to instead log to a service or silently fail without logging errors.\n\n```typescript\nconst myLogger = {\n  debug: (...data) =\u003e {\n    // do something with the logged debug message\n  },\n  info: (...data) =\u003e {\n    // do something with the logged info message\n  },\n  error: (...data) =\u003e {\n    // do something with the logged error message\n  },\n  warn: (...data) =\u003e {\n    // do something with the logged warning message\n  }\n}\n\nconst client = initialize(\n  '00000000-1111-2222-3333-444444444444',\n  {\n    identifier: YOUR_TARGET_IDENTIFIER,\n    name: YOUR_TARGET_NAME\n  },\n  {\n    logger: myLogger // override logger\n  }\n)\n```\n\n## Import directly from unpkg\n\nIn case you want to import this library directly (without having to use npm or yarn):\n\n```html\n\u003cscript type=\"module\"\u003e\n  import { initialize, Event } from 'https://unpkg.com/@harnessio/ff-javascript-client-sdk/dist/sdk.client.js'\n\u003c/script\u003e\n```\n\nIf you need to support old browsers which don't support ES Module:\n\n```html\n\u003cscript src=\"https://unpkg.com/@harnessio/ff-javascript-client-sdk/dist/sdk.client-iife.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n  var initialize = HarnessFFSDK.initialize\n  var Event = HarnessFFSDK.Event\n\u003c/script\u003e\n```\n\n## Further reading\n\n[Integrating with webviews on mobile devices](mobile_device_support.md)\n\n## License\n\nApache version 2.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fharness%2Fff-javascript-client-sdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fharness%2Fff-javascript-client-sdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fharness%2Fff-javascript-client-sdk/lists"}