fcs-js
v2.4.5
Published
Feature Control System
Downloads
8
Maintainers
Readme
Feature Control System - FCS for JS
What is it and why do I need it?
FCS (Feature Control System) is a simple library for feature-toggling and can be used to define list of features with simple and complex conditions and allowing to define environment settings and tags to resolve the status of each feature.
For example you can define features for specific users, specific active cookies, development environments, tags or even all of them combined in some logic pattern.
All your features can be managed from one place and easily updated to wider audience, rolled back to previous environment or completely removed.
FCS can be especially useful in Continues Integration workflow, where multiple changes are merged in short time to the mainline and not all of them are ready to be deployed to production.
It can be also used to limit some feature to very specific conditions, e.g. due to market regulations or conditions of some promotions.
It can be also useful for A/B testing and when enabling new features only for some segment of users to confirm new code working fine in real environment without risking issues for all the users.
How to configure list of features?
In case of small projects, you can simply keep configuration in array in some JS or JSON file.
In case of bigger projects, it's better to load it from redis or other cache provider, to distribute the list of features and their flags among different code repositories.
In case of redis integration, it's best to prepare some simple deployment script in backoffice to easily update flags for every feature, ideally with basic user interface. This way it can be used in emergency by anyone in your team with permissions, including PM or QA or even marketing team on handling particular temporary promotions (offering access to only some flags for different users).
FCS
Express.js (middlewares) and plain examples:
Examples for express.js (middlewares)
Basic plain examples (node.js and frontend)
Configuration methods:
Example of code to configure some settings:
import {FCS, FLAGS} from 'fcs-js';
const fcs = new FCS();
fcs.setFlags({
'my-feature': {flag: FLAGS.ACTIVE},
'my-feature2': {flag: FLAGS.TAG, name: 'username', value: 'john123'}
});
fcs.setTag('env', 'preproduction');
fcs.setTag('username', 'john123');
fcs.setTag('lang', 'en');
- setTag
Set a tag with a name and value
// types
function setTag(name: string, value: string | number | boolean): void;
// example
fcs.setTag('lang', 'en');
- setFlags
Register a list of features with their flags, that can be used to determine if feature is active or not
// types
enum FLAGS {
INACTIVE = 0,
ACTIVE = 1,
COOKIE = 2,
TAG = 3,
MULTI_AND = 4,
MULTI_OR = 5,
}
interface IFlag {
flag: FLAGS;
name?: string;
value?: string | string[] | number | number[] | boolean;
flags?: IFlag[];
}
interface IFlags {
[name: string]: IFlag;
}
function setFlags(flags: IFlags): void;
// example
fcs.setFlags({
'my-feature': {flag: FLAGS.ACTIVE},
'my-feature2': {flag: FLAGS.TAG, name: 'username', value: 'john123'},
'my-feature3': {flag: FLAGS.TAG, name: 'lang', value: ['pl', 'de']}
});
Getter methods:
- feature
Check if feature is active and use returned boolean to enable some code
// types
function feature(name: string): boolean;
// example
if (fcs.feature('my-feature')) {
// code is enabled
}
- getBodyCssClasses
Get string with list of CSS classes that can be appended to body element in your frontend
Returned CSS classes have "fcs-" prefix
Note: slash (/) in name of feature is automatically turned into double dash (--)
// types
function getBodyCssClasses(): string;
// example
const cssClasses = fcs.getBodyCssClasses(); //'fcs-my-feature fcs-my-feature2 fcs-test--my-feature100'
- getActiveFeatures
Get array of names of features that can be for example injected to JS to enable some dynamic code on frontend
// types
function getActiveFeatures(): string[];
// example
const features = fcs.getActiveFeatures(); //['my-feature', 'my-feature2', 'my-feature100']
Available flags:
import {FLAGS} from 'fcs-js';
FLAGS.INACTIVE = 0;
FLAGS.ACTIVE = 1;
FLAGS.COOKIE = 2;
FLAGS.TAG = 3;
FLAGS.MULTI_AND = 4;
FLAGS.MULTI_OR = 5;
- FLAG_INACTIVE
Inactive flag means that feature is disabled for everyone
fcs.setFlags({
'my-feature': {flag: FLAGS.INACTIVE} // FLAGS.INACTIVE = 0
});
if (fcs.feature('my-feature')) { // false
// no one can see it
}
- FLAG_ACTIVE
Active flag means that feature is enabled for everyone
fcs.setFlags({
'my-feature': {flag: FLAGS.ACTIVE} // FLAGS.ACTIVE = 1
});
if (fcs.feature('my-feature')) { // true
// always working
}
- FLAG_COOKIE
Cookie flag means that feature is enabled for everyone who make request to the server with active cookie matching feature by its name
Cookies need to have "fcs-" prefix
Note: slash (/) in name of feature is automatically turned into double dash (--)
fcs.setFlags({
'my-feature': {flag: FLAGS.COOKIE} // FLAGS.COOKIE = 2
});
fcs.setCookies({
'fcs-my-feature': '1'
});
if (fcs.feature('my-feature')) { // true, if fcs-my-feature cookie exists
// working for anyone with enabled cookie "fcs-my-feature" in this case
}
- FLAG_TAG
Tag flag means that feature is enabled for a tag defined by name and for single value (string) or for list of values (array:string)
fcs.setFlags({
'my-feature': {flag: FLAGS.TAG, name: 'lang', value: 'en'}, // FLAGS.TAG = 3
'my-feature2': {flag: FLAGS.TAG, name: 'lang', value: ['pl', 'de']} // FLAGS.TAG = 3
});
fcs.setTag('lang', 'en');
if (fcs.feature('my-feature')) { // true
// working for "lang" tag if its value is "en"
}
if (fcs.feature('my-feature2')) { // false
// working for "lang" tag if its value is "pl" or "de"
}
- FLAG_MULTI_AND
Multi-and flag allows to define group of multiple flags and means that feature is enabled if all of the subflags meet their conditions
fcs.setFlags({
'my-feature': {flag: FLAGS.MULTI_AND, flags: [ // FLAGS.MULTI_AND = 4
{flag: FLAGS.TAG, name: 'domain', value: 'domain.com'}, // FLAGS.TAG = 3
{flag: FLAGS.TAG, name: 'lang', value: 'en'} // FLAGS.TAG = 3
]},
'my-feature2': {flag: FLAGS.MULTI_AND, flags: [ // FLAGS.MULTI_AND = 4
{flag: FLAGS.TAG, name: 'domain', value: 'domain.com'}, // FLAGS.TAG = 3
{flag: FLAGS.TAG, name: 'lang', value: 'pl'} // FLAGS.TAG = 3
]}
});
fcs.setTag('domain', 'domain.com');
fcs.setTag('lang', 'en');
if (fcs.feature('my-feature')) { // true
// available only if domain tag is equal "domain.com" AND lang tag is "en"
}
if (fcs.feature('my-feature2')) { // false
// available only if domain tag is equal "domain.com" AND lang tag is "pl"
}
- FLAG_MULTI_OR
Multi-or flag allows to define group of multiple flags and means that feature is enabled if any of the subflags meets its condition
fcs.setFlags({
'my-feature': {flag: FLAGS.MULTI_OR, flags: [ // FLAGS.MULTI_OR = 5
{flag: FLAGS.TAG, name: 'domain', value: 'domain.com'}, // FLAGS.TAG = 3
{flag: FLAGS.TAG, name: 'domain', value: 'domain2.com'} // FLAGS.TAG = 3
]},
'my-feature2': {flag: FLAGS.MULTI_OR, flags: [ // FLAGS.MULTI_OR = 5
{flag: FLAGS.TAG, name: 'domain', value: 'domain3.com'}, // FLAGS.TAG = 3
{flag: FLAGS.TAG, name: 'domain', value: 'domain4.com'} // FLAGS.TAG = 3
]}
});
fcs.setTag('domain', 'domain.com');
if (fcs.feature('my-feature')) { // true
// available only if domain tag is equal "domain.com" OR "domain2.com"
}
if (fcs.feature('my-feature2')) { // false
// available only if domain tag is equal "domain3.com" OR "domain4.com"
}
Examples:
import {FCS, FLAGS} from 'fcs-js';
const fcs = new FCS();
// Define initial settings for your environment + project + user, usually set just once at the beginning
fcs.setTag('env', 'preproduction');
fcs.setTag('username', 'test123');
fcs.setTag('IP', '0.0.0.0');
fcs.setTag('role', 'user');
fcs.setTag('country', 'UK');
fcs.setTag('product', 'product1');
fcs.setTag('domain', 'domain.com');
fcs.setTag('lang', 'en');
fcs.setCookies({
'fcs-my-carousel': '1'
});
// Sometimes you can define also extra tags later
fcs.setTag('section', 'promotions');
fcs.setTag('promotion', '1');
// Define list of features from array (loaded from configuration file or cache provider e.g. redis)
fcs.setFlags({
'experiment': {flag: FLAGS.INACTIVE},
'blue-background': {flag: FLAGS.TAG, name: 'country', value: ['UK', 'DE']},
'xmas-promo': {flag: FLAGS.MULTI_AND, flags: [
{flag: FLAGS.TAG, name: 'domain', value: 'domain.com'},
{flag: FLAGS.TAG, name: 'lang', value: 'pl'}
]},
'chat': {flag: FLAGS.MULTI_OR, flags: [
{flag: FLAGS.COOKIE},
{flag: FLAGS.TAG, name: 'env', value: ['test123', 'test456']}
]},
'auth': {flag: FLAGS.ACTIVE},
'new-registration': {flag: FLAGS.TAG, name: 'env', value: 'preproduction'},
'my-carousel': {flag: FLAGS.COOKIE},
'new-bonus': {flag: FLAGS.TAG, name: 'env', value: ['test123', 'test456']}
});
// Get list of features to inject to your frontend to JS variable
const activeFeatures = fcs.getActiveFeatures(); // ['blue-background', 'chat', 'auth', 'new-registration', 'new-bonus']
// Get CSS classes to append them to <body> element
const cssClasses = fcs.getBodyCssClasses(); // 'fcs-blue-background fcs-chat fcs-auth fcs-new-registration fcs-new-bonus'
// Activate features in your app
if (fcs.feature('auth')) { // true
// active feature
}
if (fcs.feature('experiment')) { // false
// inactive feature
}
if (fcs.feature('my-carousel')) { // true
// feature active if cookie['fcs-my-carousel'] cookie is enabled
}
if (fcs.feature('new-bonus')) { // true
// feature active for this user when logged in (test123)
} else {
// old feature for everyone else
}
if (fcs.feature('new-registration')) { // true
// feature active in preproduction environment
}
if (fcs.feature('xmas-promo')) { // false
// feature active if both tags domain AND lang meet the current settings
}
if (fcs.feature('chat')) { // true
// feature active if cookie is enabled (cookie['fcs-chat']) OR if one of listed users is logged in
}
FCSClient
Configuration methods:
Example of code to use feature flags on frontend side:
import {FCSClient} from 'fcs-js';
const ajaxResponseFeatures = ['my-feature', 'my-feature2', 'my-feature3'];
FCSClient.setActiveFeatures(ajaxResponseFeatures);
- setActiveFeatures
Set names of features returned by server
FCSClient.setActiveFeatures(<array:string>);
// e.g. (['my-feature', 'my-feature2', 'my-feature3'])
Getter methods:
- feature
Check if feature is active and use returned boolean to enable some code
// types
function feature(name: string): boolean;
// example
if (FCSClient.feature('my-feature')) {
// code is enabled
}
- multiFeature
More flexible version of feature() method that helps to easier accommodate complex conditions on frontend side
It accepts:
- string (one feature)
- string (comma-separated list of features)
- array (list of features)
// types
function multiFeature(name: string | string[]): boolean;
// example
if (FCSClient.multiFeature('my-feature')) {
// code is enabled
}
if (FCSClient.multiFeature('my-feature,my-feature2')) {
// code is enabled
}
if (FCSClient.multiFeature(['my-feature', 'my-feature2'])) {
// code is enabled
}
- getBodyCssClasses
Get string with list of CSS classes that can be appended to body element in your frontend
Returned CSS classes have "fcs-" prefix
Note: slash (/) in name of feature is automatically turned into double dash (--)
// types
function getBodyCssClasses(): string;
// example
const cssClasses = FCSClient.getBodyCssClasses(); //'fcs-my-feature fcs-my-feature2 fcs-test--my-feature100'
- getActiveFeatures
Get array of names of active features
// types
function getActiveFeatures(): string[];
// example
const features = FCSClient.getActiveFeatures(); //['my-feature', 'my-feature2', 'my-feature100']
Examples:
import {FCSClient} from 'fcs-js';
FCSClient.setActiveFeatures(['chat', 'new-menu']);
document.body.className = FCSClient.getBodyCssClasses(); // 'fcs-chat fcs-new-menu'
if (FCSClient.feature('chat')) {
console.log('chat is active');
}
if (FCSClient.multiFeature(['new-menu', 'new-sidebar'])) {
console.log('new-menu or new-sidebar is active');
}
License
MIT License