SDKs
JavaScript SDK
JavaScript SDK is universal, meaning it works in both Node.js and browser environments.
Installation
Install with npm:
$ npm install --save @featurevisor/sdk
Initialization
The SDK can be initialized in two different ways depending on your needs.
Understanding datafiles
You are recommended to learn more about building datafiles before proceeding further.
Synchronous
If you already have the content of your datafile available:
import { createInstance } from "@featurevisor/sdk";
const datafileUrl =
"https://cdn.yoursite.com/production/datafile-tag-all.json";
const datafileContent = await fetch(datafileUrl).then((res) => res.json());
const f = createInstance({
datafile: datafileContent
});
Asynchronous
If you want to delegate the responsibility of fetching the datafile to the SDK:
// your-app/index.js
import { createInstance } from "@featurevisor/sdk";
const datafileUrl =
"https://cdn.yoursite.com/production/datafile-tag-all.json";
const f = createInstance({
datafileUrl: datafileUrl,
onReady: function () {
// datafile has been fetched successfully,
// and you can start using the SDK
}
});
If you need to take further control on how the datafile is fetched, you can pass a custom handleDatafileFetch
function:
const f = createInstance({
datafileUrl: datafileUrl,
onReady: function () {
console.log("sdk is ready");
},
handleDatafileFetch: function (url) {
// you can pass custom headers here, etc
// or even use XMLHttpRequest instead of fetch,
// as long as it returns a promise with datafile content
return fetch(url).then((res) => res.json());
},
});
Context
Contexts are attribute values that we pass to SDK for evaluating features.
They are objects where keys are the attribute keys, and values are the attribute values.
const context = {
myAttributeKey: "myAttributeValue",
anotherAttributeKey: "anotherAttributeValue",
};
Checking if enabled
Once the SDK is initialized, you can check if a feature is enabled or not:
const featureKey = "my_feature";
const context = {
userId: "123",
country: "nl",
};
const isEnabled = f.isEnabled(featureKey, context);
Getting variations
If your feature has any variations defined, you can get evaluate them as follows:
const featureKey = "my_feature";
const context = {
userId: "123",
country: "nl",
};
const variation = f.getVariation(featureKey, context);
Getting variables
Your features may also include variables:
const variableKey = "bgColor";
const bgColorValue = f.getVariable(featureKey, variableKey, context);
Type specific methods
Next to generic getVariable()
methods, there are also type specific methods available for convenience:
boolean
f.getVariableBoolean(featureKey, variableKey, context);
string
f.getVariableString(featureKey, variableKey, context);
integer
f.getVariableInteger(featureKey, variableKey, context);
double
f.getVariableDouble(featureKey, variableKey, context);
array
f.getVariableArray(featureKey, variableKey, context);
object
f.getVariableObject<T>(featureKey, variableKey, context);
json
f.getVariableJSON<T>(featureKey, variableKey, context);
Activation
Activation is useful when you want to track what features and their variations are exposed to your users.
It works the same as f.getVariation()
method, but it will also bubble an event up that you can listen to.
const f = createInstance({
datafile: datafileContent,
// handler for activations
onActivation: function (
featureKey,
variationValue,
fullContext,
captureContext
) {
// fullContext (object):
// - all the attributes used for evaluating
// captureContext (object):
// - attributes that you want to capture,
// - marked as `capture: true` in Attribute YAMLs
}
});
const variation = f.activate(featureKey, context);
From the onActivation
handler, you can send the activation event to your analytics service.
Initial features
You may want to initialize your SDK with a set of features before SDK has successfully fetched the datafile (if using datafileUrl
option).
This helps in cases when you fail to fetch the datafile, but you still wish your SDK instance to continue serving a set of sensible default values. And as soon as the datafile is fetched successfully, the SDK will start serving values from there.
import { createInstance } from "@featurevisor/sdk";
const f = createInstance({
datafileUrl: "...",
initialFeatures: {
myFeatureKey: {
enabled: true,
// optional
variation: "treatment",
variables: {
myVariableKey: "myVariableValue",
}
},
anotherFeatureKey: {
enabled: false,
},
},
});
Stickiness
Featurevisor relies on consistent bucketing making sure the same user always sees the same variation in a deterministic way. You can learn more about it in Bucketing section.
But there are times when your targeting conditions (segments) can change and this may lead to some users being re-bucketed into a different variation. This is where stickiness becomes important.
If you have already identified your user in your application, and know what features should be exposed to them in what variations, you can initialize the SDK with a set of sticky features:
import { createInstance } from "@featurevisor/sdk";
const f = createInstance({
datafileUrl: "...",
stickyFeatures: {
myFeatureKey: {
enabled: true,
// optional
variation: "treatment",
variables: {
myVariableKey: "myVariableValue",
}
},
anotherFeatureKey: {
enabled: false,
}
},
});
Once initialized with sticky features, the SDK will look for values there first before evaluating the targeting conditions and going through the bucketing process.
You can also set sticky features after the SDK is initialized:
f.setStickyFeatures({
myFeatureKey: {
enabled: true,
variation: "treatment",
variables: {},
},
anotherFeatureKey: {
enabled: false,
}
});
This will be handy when you want to:
- update sticky features in the SDK without re-initializing it (or restarting the app), and
- handle evaluation of features for multiple users from the same instance of the SDK (e.g. in a server dealing with incoming requests from multiple users)
Logging
By default, Featurevisor SDKs will print out logs to the console for warn
and error
levels.
Levels
You can customize it further:
import { createInstance, createLogger } from "@featurevisor/sdk";
const f = createInstance({
// ...
logger: createLogger({
levels: [
"error",
"warn",
"info",
"debug"
]
})
});
You can also set log levels from instance afterwards:
f.setLogLevels(["error", "warn"]);
Handler
You can also pass your own log handler, if you do not wish to print the logs to the console:
const f = createInstance({
// ...
logger: createLogger({
levels: ["error", "warn", "info", "debug"],
handler: function (level, message, details) {
// do something with the log
}
})
});
Further log levels like info
and debug
will help you understand how the feature variations and variables are evaluated in the runtime against given context.
Intercepting context
You can intercept context before they are used for evaluation:
import { createInstance } from "@featurevisor/sdk";
const defaultContext = {
country: "nl",
};
const f = createInstance({
// ...
interceptContext: function (context) {
// return updated context
return {
...defaultContext,
...context,
};
}
});
This is useful when you wish to add a default set of attributes as context for all your evaluations, giving you the convenience of not having to pass them in every time.
Refreshing datafile
Refreshing the datafile is convenient when you want to update the datafile in runtime, for example when you want to update the feature variations and variables config without having to restart your application.
It is only possible to refresh datafile in Featurevisor if you are using the datafileUrl
option when creating your SDK instance.
Manual refresh
import { createInstance } from "@featurevisor/sdk";
const f = createInstance({
datafileUrl: datafileUrl,
});
f.refresh();
Refresh by interval
If you want to refresh your datafile every X number of seconds, you can pass the refreshInterval
option when creating your SDK instance:
import { createInstance } from "@featurevisor/sdk";
const f = createInstance({
datafileUrl: datafileUrl,
refreshInterval: 30, // 30 seconds
});
You can stop the interval by calling:
f.stopRefreshing();
If you want to resume refreshing:
f.startRefreshing();
Listening for updates
Every successful refresh will trigger the onRefresh()
option:
import { createInstance } from "@featurevisor/sdk";
const f = createInstance({
datafileUrl: datafileUrl,
onRefresh: function () {
// datafile has been refreshed successfully
}
});
Not every refresh is going to be of a new datafile version. If you want to know if datafile content has changed in any particular refresh, you can listen to onUpdate
option:
import { createInstance } from "@featurevisor/sdk";
const f = createInstance({
datafileUrl: datafileUrl,
onUpdate: function () {
// datafile has been refreshed, and
// new datafile content is different from the previous one
}
});
Events
Featurevisor SDK implements a simple event emitter that allows you to listen to events that happen in the runtime.
Listening to events
You can listen to these events that can occur at various stages in your application:
ready
When the SDK is ready to be used if used in an asynchronous way involving datafileUrl
option:
f.on("ready", function () {
// sdk is ready to be used
});
The ready
event is fired maximum once.
You can also synchronously check if the SDK is ready:
if (f.isReady()) {
// sdk is ready to be used
}
For convenience, you can check the readiness with a Promise-based API as well:
// using Promise
f.onReady().then(function () {
// sdk is ready to be used
});
// using async/await
await f.onReady();
activation
When a feature is activated:
f.on("activation", function (
featureKey,
variationValue,
fullContext,
captureContext
) {
// fullContext (object):
// - all the attributes used for evaluating
// captureContext (object):
// - attributes that you want to capture,
// - marked as `capture: true` in Attribute YAMLs
});
refresh
When the datafile is refreshed:
f.on("refresh", function () {
// datafile has been refreshed successfully
});
This will only occur if you are using refreshInterval
option.
update
When the datafile is refreshed, and new datafile content is different from the previous one:
f.on("update", function () {
// datafile has been refreshed, and
// new datafile content is different from the previous one
});
This will only occur if you are using refreshInterval
option.
Stop listening
You can stop listening to specific events by calling off()
or removeListener()
:
const onReadyHandler = function () {
// ...
};
f.on("ready", onReadyHandler);
f.removeListener("ready", onReadyHandler);
Remove all listeners
If you wish to remove all listeners of any specific event type:
f.removeAllListeners("ready");
f.removeAllListeners("activation");
If you wish to remove all active listeners of all event types in one go:
f.removeAllListeners();
Evaluation details
Besides logging with debug level enabled, you can also get more details about how the feature variations and variables are evaluated in the runtime against given context:
// flag
const evaluation = f.evaluateFlag(featureKey, context);
// variation
const evaluation = f.evaluateVariation(featureKey, context);
// variable
const evaluation = f.evaluateVariable(featureKey, variableKey, context);
The returned object will always contain the following properties:
featureKey
: the feature keyreason
: the reason how the value was evaluated
And optionally these properties depending on whether you are evaluating a feature variation or a variable:
bucketValue
: the bucket value between 0 and 100,000ruleKey
: the rule keyerror
: the error objectenabled
: if feature itself is enabled or notvariation
: the variation objectvariationValue
: the variation valuevariableKey
: the variable keyvariableValue
: the variable valuevariableSchema
: the variable schema