Before you Begin
Harness Feature Flags (FF) is a feature management solution that enables users to change the software’s functionality,
without deploying new code. FF uses feature flags to hide code or behaviours without having to ship new versions of the
software. A feature flag is like a powerful if statement.
For more information, see https://harness.io/products/feature-flags/
To read more, see https://ngdocs.harness.io/category/vjolt35atg-feature-flags
To sign up, https://app.harness.io/auth/#/signup/
Harness Feature Flags Client SDK for JavaScript
Library for integrating Harness Feature Flags into JavaScript UI applications.
Install
npm i @harnessio/ff-javascript-client-sdk
or
yarn add @harnessio/ff-javascript-client-sdk
Usage
import { initialize, Event } from '@harnessio/ff-javascript-client-sdk'
Initialize SDK with api key and target information.
initialize(FeatureFlagSDKKey: string, target: Target, options?: Options)
In which Target
and Options
are defined as:
interface Target {
identifier: string
name?: string
anonymous?: boolean
attributes?: object
}
interface Options {
baseUrl?: string
eventUrl?: string
eventsSyncInterval?: number
pollingInterval?: number
pollingEnabled?: boolean
streamEnabled?: boolean
debug?: boolean,
cache?: boolean | CacheOptions
logger?: Logger
}
For example:
const client = initialize('00000000-1111-2222-3333-444444444444', {
identifier: YOUR_TARGET_IDENTIFIER,
name: YOUR_TARGET_NAME,
attributes: {
email: 'sample@sample.com'
}
})
Streaming and Polling Mode
By 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.
Streaming Mode
Streaming mode establishes a continuous connection between your application and the Feature Flags service.
This allows for real-time updates on feature flags without requiring periodic checks.
If an error occurs while streaming and pollingEnabled
is set to true
,
the SDK will automatically fall back to polling mode until streaming can be reestablished.
If pollingEnabled
is false
, streaming will attempt to reconnect without falling back to polling.
Polling Mode
In 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.
No Streaming or Polling
If both streaming and polling modes are disabled (streamEnabled: false
and pollingEnabled: false
),
the SDK will not automatically fetch feature flag updates after the initial fetch.
This 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.
This configuration might be useful in specific scenarios where you want to ensure a consistent set of feature flags
for 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.
To configure the modes:
const options = {
streamEnabled: true,
pollingEnabled: true,
pollingInterval: 60000,
}
const client = initialize(
'YOUR_SDK_KEY',
{
identifier: 'Harness1',
attributes: {
lastUpdated: Date(),
host: location.href
}
},
options
)
Max Stream Retries
You 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).
const options = {
maxRetries: 5,
streamEnabled: true,
pollingEnabled: true,
pollingInterval: 60000,
}
const client = initialize(
'YOUR_SDK_KEY',
{
identifier: 'Harness1',
attributes: {
lastUpdated: Date(),
host: location.href
}
},
options
)
If 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.
Listening to events from the client
instance.
client.on(Event.READY, flags => {
})
client.on(Event.FLAGS_LOADED, evaluations => {
})
client.on(Event.CACHE_LOADED, evaluations => {
})
client.on(Event.CHANGED, flagInfo => {
})
client.on(Event.DISCONNECTED, () => {
})
client.on(Event.CONNECTED, () => {
})
client.on(Event.POLLING, () => {
})
client.on(Event.POLLING_STOPPED, () => {
})
client.on(Event.ERROR, error => {
})
client.on(Event.ERROR_CACHE, error => {
})
client.on(Event.ERROR_AUTH, error => {
})
client.on(Event.ERROR_FETCH_FLAGS, error => {
})
client.on(Event.ERROR_FETCH_FLAG, error => {
})
client.on(Event.ERROR_METRICS, error => {
})
client.on(Event.ERROR_STREAM, error => {
})
Getting value for a particular feature flag
If you would like to know that the default variation was returned when getting the value, for example, if the provided flag identifier wasn't found then pass true for the third argument withDebug:
const result = client.variation('Dark_Theme', false, true);
When withDebug
is set to true, the result object will have the following structure:
interface VariationValueWithDebug {
value: any,
isDefaultValue: boolean
}
For the example above, if the flag identifier 'Dark_Theme' is not found, result would look like:
{
value: false,
isDefaultValue: true
}
If you do not need to know the default variation was returned:
const variationValue = client.variation('Dark_Theme', false)
In 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.
For the example above:
- If the flag identifier 'Dark_Theme' exists in storage, variationValue would be the stored value for that identifier.
- If the flag identifier 'Dark_Theme' does not exist, variationValue would be the default value provided, in this case, false
Cleaning up
Remove a listener of an event by client.off
.
client.off(Event.ERROR, error => {
})
Remove all listeners:
client.off()
On closing your application, call client.close()
to close the event stream.
client.close()
Caching
In practice flags rarely change and so it can be useful to cache the last received evaluations from the server to allow
your application to get started as fast as possible. Setting the cache
option as true
or as an object (see interface
below) will allow the SDK to store its evaluations to localStorage
and retrieve at startup. This lets the SDK get
started near instantly and begin serving flags, while it carries on authenticating and fetching up-to-date evaluations
from the server behind the scenes.
const client = initialize('00000000-1111-2222-3333-444444444444', {
identifier: YOUR_TARGET_IDENTIFIER,
name: YOUR_TARGET_NAME
}, {
cache: true
}
)
The cache
option can also be passed as an object with the following options.
interface CacheOptions {
ttl?: number
storage?: AsyncStorage | SyncStorage
}
Set evaluations
In some cases it might be worthwhile providing the SDK with a set of evaluations which it can then serve instantly. You
might want to consider this when you need to:
- reduce application startup time by providing default values or a snapshot of evaluations. For example, if your
application is server-side generated, then it might make sense to retrieve evaluations on the server and provide them
in the HTML of the page to be injected into the SDK
- provide network redundancy by allowing your app to detect network connectivity issues accessing the service and
loading evaluations from another source
To achieve this you can call the setEvaluations
method at any time after initializing the client. The
setEvaluations
method takes an array of Evaluation
objects as an argument.
client.setEvaluations(evals);
In which Evaluation
is defined as:
interface Evaluation {
flag: string
identifier: string
value: boolean | string | number | object | undefined
kind: string
deleted?: boolean
}
Authentication Request Timeout
The 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.
If the request is aborted due to this timeout the SDK will fail to initialize and an ERROR_AUTH
and ERROR
event will be emitted.
**This only applies to the authentiaction request. If you wish to set a read timeout on the remaining requests made by the SDK, you may register API Middleware
const options = {
authRequestReadTimeout: 30000,
};
const client = initialize(
'YOUR_API_KEY',
{
identifier: 'Harness1',
attributes: {
lastUpdated: Date(),
host: location.href,
},
},
options
);
API Middleware
The 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
function abortControllerMiddleware([url, options]) {
if (window.AbortController) {
const abortController = new AbortController();
options.signal = abortController.signal;
setTimeout(() => abortController.abort(), 30000);
}
return [url, options];
}
client.registerAPIRequestMiddleware(abortControllerMiddleware);
This 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.
Logging
By default, the Javascript Client SDK will log errors and debug messages using the console
object. In some cases, it
can be useful to instead log to a service or silently fail without logging errors.
const myLogger = {
debug: (...data) => {
},
info: (...data) => {
},
error: (...data) => {
},
warn: (...data) => {
}
}
const client = initialize(
'00000000-1111-2222-3333-444444444444',
{
identifier: YOUR_TARGET_IDENTIFIER,
name: YOUR_TARGET_NAME
},
{
logger: myLogger
}
)
Import directly from unpkg
In case you want to import this library directly (without having to use npm or yarn):
<script type="module">
import { initialize, Event } from 'https://unpkg.com/@harnessio/ff-javascript-client-sdk/dist/sdk.client.js'
</script>
If you need to support old browsers which don't support ES Module:
<script src="https://unpkg.com/@harnessio/ff-javascript-client-sdk/dist/sdk.client.js"></script>
<script>
var initialize = HarnessFFSDK.initialize
var Event = HarnessFFSDK.Event
</script>
Further reading
Integrating with webviews on mobile devices
License
Apache version 2.