Skip to content

Commit

Permalink
Merge pull request #9 from customink/v1.0.0
Browse files Browse the repository at this point in the history
v1.0.0
  • Loading branch information
superkat64 authored Feb 10, 2022
2 parents e001b7b + f4907a8 commit edda3b5
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 113 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @customink/pegasus
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.0.0] - 2022-02-09

### Changed

- Updated all client-based value getting to behave sychronously
- When getting a cookie value, added a check for md5 encryption and if found, we create new cookie without encryption and expire the old cookie

### Deprecated

- md5

## [0.5.1] - 2022-02-03

### Changed
Expand Down
22 changes: 15 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,17 +86,13 @@ signal.featureFlags().then(function (flags) {
// yetAnother: undefined
// }
});

// read flag value for given feature
signal.featureFlag('flagOne').then(function (flag) {
// flag => 'test'
});
```

## Example AB Test Usage
## Example AB Test Usage/Reading values

### With Server
```js
signal.featureFlag('flagOne').then(function(flag) {
signal.featureFlagFromServer('flagOne').then(function(flag) {
if (flag === 'test') {
// custom logic if user has 'test' flag for feature 'flagOne'
else if (flag === 'control') {
Expand All @@ -106,3 +102,15 @@ signal.featureFlag('flagOne').then(function(flag) {
}
});
```
### Without Server
```js
const flag = signal.featureFlag('flagOne')
if (flag === 'test') {
// custom logic if user has 'test' flag for feature 'flagOne'
else if (flag === 'control') {
// custom logic if user has 'control' flag for feature 'flagOne'
} else {
// other logic
}
```
2 changes: 1 addition & 1 deletion dist/signalerjs.min.js

Large diffs are not rendered by default.

28 changes: 19 additions & 9 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,21 @@ After a `Signalerjs` object created with either with [server-side](./server_requ
`Signalerjs` can flag users into feature test groups by sampling on the client side. To configure this way, a string of the path to the primary JSON endpoint is the first argument of the `Signalerjs` function and an optional [configuration](./configuration.md) object is the second. [More details](./server_requirements.md)

```js
var primaryEndpoint = '/myUrl';
var config = {
const primaryEndpoint = '/myUrl';
const config = {
cookieDefaults: {
path: '/'
}
};
var signal = new Signaler(primaryEndpoint, config);
const signal = new Signaler(primaryEndpoint, config);
```

### Client-side sampling

`Signalerjs` can flag users into feature test groups by sampling on the client side. To configure this way, a features object is the first argument of the `Signalerjs` function and an optional [configuration](./configuration.md) object is the second. [More details](./feature_definition.md)

```js
var features = {
const features = {
featureOne: {
flags: ['test', 'control'],
expires: 30
Expand All @@ -33,20 +33,20 @@ var features = {
expires: 10
}
};
var config = {
const config = {
cookieDefaults: {
path: '/'
}
};
var signal = new Signaler(features, config);
const signal = new Signaler(features, config);
```

## featureFlags

Returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) containing an object with all the feature flags that the user has been sampled into. Each key in the object is the name of a feature, and each value is the feature flag value. If the value is undefined, the user has not been flagged into a group for that feature.

```js
var signal = new Signaler(features);
const signal = new Signaler(features);

signal.featureFlags().then(function (flags) {
// flags =>
Expand All @@ -60,11 +60,21 @@ signal.featureFlags().then(function (flags) {

## featureFlag

Returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) containing the flag value a user has for a given feature. If the user is not flagged into a test group for that feature, they are sampled into one at this time and the value is stored to a cookie and returned.
Returns the flag value a user has for a given test. If the user is not flagged into a test group for that feature, they are sampled into one at this time and the value is stored to a cookie and returned.

```js
var signal = new Signaler(features);

const flag = signal.featureFlag('featureOne');
```

## featureFlagFromServer

Returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) containing the flag value a user has for a given feature if you are requesting. If the user is not flagged into a test group for that feature, they are sampled into one at this time and the value is stored to a cookie and returned.

```js
const signal = new Signaler(features);

signal.featureFlag('featureOne').then(function (flag) {
// flag => 'test'
});
Expand All @@ -79,7 +89,7 @@ This sets the feature flag in a cookie. Since `featureFlag` handles flagging use
One important thing to note is that the cookie names are MD5 hashed values of the feature names. This is to obscure details from users.

```js
var signal = new Signaler(features);
const signal = new Signaler(features);

signal.setFeatureFlag('featureOne', 'test', {domain: '.example.com'});
```
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "signalerjs",
"version": "0.5.1",
"version": "1.0.0",
"description": "JavaScript A/B testing feature flag library",
"main": "dist/signalerjs.min.js",
"scripts": {
Expand Down
74 changes: 39 additions & 35 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,32 @@ import Cookies from 'cookies-js';
import sample from 'samplejs';
import {defaultDomain, daysAfterToday} from './helpers';

var cookieDefaults = {
const cookieDefaults = {
path: '/',
domain: defaultDomain(window.location.hostname),
expires: daysAfterToday(30)
};

function featuresCurrentFlags(features) {
var flags = {};
const featuresCurrentFlags = features => {
const flags = {};
features.map(featureName => {
flags[featureName] = featureFlagFromCookie(featureName);
});
return flags;
}

function featureFlagFromCookie(featureName) {
return Cookies.get(md5(featureName));
const featureFlagFromCookie = featureName => {
let cookieValue = Cookies.get(md5(featureName));
if (cookieValue) {
Cookies.set(`feature_${featureName}`, cookieValue);
Cookies.expire(md5(featureName));
} else {
cookieValue = Cookies.get(`feature_${featureName}`);
}
return cookieValue;
}

function cookieOptionsFromExpires(expires) {
const cookieOptionsFromExpires = expires => {
switch (typeof expires) {
case 'string':
return {expires};
Expand All @@ -33,10 +40,10 @@ function cookieOptionsFromExpires(expires) {
}

export default function Signaler(urlOrFeatures, config = {}) {
var transformCookieOptions = config.transformCookieOptions || (data => data);
const transformCookieOptions = config.transformCookieOptions || (data => data);
Cookies.defaults = config.cookieDefaults || cookieDefaults;

function featureFlags() {
const featureFlags = () => {
switch (typeof urlOrFeatures) {
case 'string':
return window
Expand All @@ -46,45 +53,42 @@ export default function Signaler(urlOrFeatures, config = {}) {
return featuresCurrentFlags(Object.keys(data));
});
default:
var featureFlags = featuresCurrentFlags(Object.keys(urlOrFeatures));
return new Promise(resolve => resolve(featureFlags));
return new Promise(resolve => resolve(featuresCurrentFlags(Object.keys(urlOrFeatures))));
}
}

function featureFlag(featureName) {
var cookieValue = featureFlagFromCookie(featureName);
return cookieValue
? new Promise(resolve => resolve(cookieValue))
: featureFlagFromServer(featureName);
const featureFlag = featureName => {
const cookieValue = featureFlagFromCookie(featureName);
if(cookieValue) {
return cookieValue;
} else {
const feature = urlOrFeatures[featureName];
const flag = sample(feature.flags);
const cookieOpts = cookieOptionsFromExpires(feature.expires);
setFeatureFlag(featureName, flag, cookieOpts);
return flag;
}
}

function featureFlagFromServer(featureName) {
switch (typeof urlOrFeatures) {
case 'string':
return window
.fetch(`${urlOrFeatures}/${featureName}.json`)
.then(response => response.json())
.then(data => {
var cookieOpts = cookieOptionsFromExpires(data.expires);
setFeatureFlag(featureName, data.flag, cookieOpts);
return data.flag;
});
default:
var feature = urlOrFeatures[featureName];
var flag = sample(feature.flags);
var cookieOpts = cookieOptionsFromExpires(feature.expires);
setFeatureFlag(featureName, flag, cookieOpts);
return new Promise(resolve => resolve(flag));
}
const featureFlagFromServer = featureName => {
return window
.fetch(`${urlOrFeatures}/${featureName}.json`)
.then(response => response.json())
.then(data => {
const cookieOpts = cookieOptionsFromExpires(data.expires);
setFeatureFlag(featureName, data.flag, cookieOpts);
return data.flag;
});
}

function setFeatureFlag(featureName, flag, options = {}) {
Cookies.set(md5(featureName), flag, transformCookieOptions(options));
const setFeatureFlag = (featureName, flag, options = {}) => {
Cookies.set(`feature_${featureName}`, flag, transformCookieOptions(options));
}

return {
featureFlags,
featureFlag,
featureFlagFromServer,
setFeatureFlag
};
}
Loading

0 comments on commit edda3b5

Please sign in to comment.