├── README.md └── featureflag ├── wm-feature-flag-client ├── README.md ├── package.json ├── config │ └── app-a.json └── src │ └── index.js └── testapp ├── package.json └── index.js /README.md: -------------------------------------------------------------------------------- 1 | # ff-poc -------------------------------------------------------------------------------- /featureflag/wm-feature-flag-client/README.md: -------------------------------------------------------------------------------- 1 | This library contains feature flagging functionality for determining feature availability across various platforms fo specific users. 2 | 3 | ## Installing 4 | To add the latset published version of this package to your application: 5 | 6 | ```bash 7 | npm install feature-flag-client 8 | ``` 9 | 10 | ## Setup 11 | TODO -------------------------------------------------------------------------------- /featureflag/testapp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "testapp", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "dependencies": { 10 | "wm-feature-flag-client": "file:../wm-feature-flag-client" 11 | }, 12 | "author": "", 13 | "license": "ISC" 14 | } 15 | -------------------------------------------------------------------------------- /featureflag/wm-feature-flag-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wm-feature-flag-client", 3 | "author": "Warner Media", 4 | "version": "1.0.0", 5 | "description": "A client library for determining the availability of certain platform features for a given user.", 6 | "license": "UNLICENSED", 7 | "private": true, 8 | "main": "./src/index.js", 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "dependencies": { 13 | "uuid": "^7.0.2", 14 | "winston": "^3.2.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /featureflag/wm-feature-flag-client/config/app-a.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "app-A", 3 | "features": [ 4 | { 5 | "name": "feature-A", 6 | "featureId": "5159095521", 7 | "saltKey": "7302928630", 8 | "rolloutRate": "20" 9 | }, 10 | { 11 | "name": "feature-B", 12 | "featureId": "3735236558", 13 | "saltKey": "0961869093", 14 | "rolloutRate": "70" 15 | }, 16 | { 17 | "name": "feature-C", 18 | "featureId": "0961869093", 19 | "saltKey": "3735236558", 20 | "rolloutRate": "50" 21 | }, 22 | { 23 | "name": "feature-D", 24 | "featureId": "7302928630", 25 | "saltKey": "5159095521", 26 | "rolloutRate": "100" 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /featureflag/testapp/index.js: -------------------------------------------------------------------------------- 1 | const ffmodule = require('wm-feature-flag-client') 2 | 3 | const init = async () => { 4 | const userId = '123' 5 | const appId = 'app-a' 6 | 7 | // initiate feature flag client 8 | const client = new ffmodule.FeatureFlagClient(userId, appId) 9 | await client.init() 10 | 11 | // check user access to two features 12 | // const featureA = await client.queryFeature('feature-A') 13 | // const featureB = await client.queryFeature('feature-B') 14 | 15 | const featuresA = await client.queryAllFeatures() 16 | 17 | // log results 18 | console.log('\nResults') 19 | // console.log(`Feature A is enabled: ${featureA.enabled}`) 20 | // console.log(`Feature B is enabled: ${featureB.enabled}`) 21 | console.log(featuresA) 22 | } 23 | 24 | init() 25 | 26 | -------------------------------------------------------------------------------- /featureflag/wm-feature-flag-client/src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module wm-feature-flag-client 3 | */ 4 | /** 5 | * Copyright (c) Warner Media. All rights reserved. 6 | */ 7 | 8 | 'use strict' 9 | const crypto = require('crypto') 10 | const { v4 } = require('uuid') 11 | const winston = require('winston'); 12 | const logger = winston.createLogger({ 13 | transports: [ new winston.transports.Console()] 14 | }) 15 | 16 | class FeatureFlagClient { 17 | constructor(userId, appId, config) { 18 | this.appId = appId 19 | this.config = config 20 | this.userId = userId 21 | } 22 | 23 | createHash(userId, salt) { 24 | const hash = crypto.createHmac('sha256', salt) 25 | hash.update(userId) 26 | const hashValue = hash.digest('hex') 27 | return hashValue 28 | } 29 | 30 | createUserId() { 31 | const userId = v4() 32 | return userId 33 | } 34 | 35 | getUserFeatureIndex(hash) { 36 | const hashSegment = parseInt(hash.slice(-2), 16) 37 | return hashSegment.toString().slice(-2) 38 | } 39 | 40 | async init() { 41 | this.config = this.config ? this.config : await this.loadConfig() 42 | this.userId = this.userId ? this.userId : this.createUserId() 43 | } 44 | 45 | async loadConfig() { 46 | // placeholder for fetching of the app's feature config file from AWS S3 47 | return new Promise((resolve, reject) => { 48 | const testConfig = require('../config/app-a.json') 49 | setTimeout(() => resolve(testConfig), 300) 50 | }) 51 | } 52 | 53 | queryFeature(featureName) { 54 | try { 55 | const featureConfig = this.config.features.filter(feature => feature.name === featureName) 56 | const saltKey = featureConfig[0].saltKey 57 | const hash = this.createHash(this.userId, saltKey) 58 | const userFeatureIndex = this.getUserFeatureIndex(hash) 59 | console.log(`\n${featureConfig[0].name}`) 60 | console.log(`rollout rate: ${featureConfig[0].rolloutRate}`) 61 | console.log(`user feature index: ${userFeatureIndex}`) 62 | const enabled = (parseInt(userFeatureIndex, 10) < featureConfig[0].rolloutRate) ? true : false 63 | return { 64 | featureName: featureConfig[0].name, 65 | enabled: enabled, 66 | userId: this.userId 67 | } 68 | } catch(err) { 69 | logger.error(`Failed to fetch feature config: ${err}`) 70 | } 71 | } 72 | 73 | queryAllFeatures() { 74 | try { 75 | const featureFlagResultsMap = [] 76 | this.config.features.map(feature => { 77 | const featureFlagData = this.queryFeature(feature.name) 78 | featureFlagResultsMap.push(featureFlagData) 79 | }) 80 | return featureFlagResultsMap 81 | } catch(err) { 82 | logger.error(`Failed to fetch feature config: ${err}`) 83 | } 84 | } 85 | 86 | } 87 | 88 | exports.FeatureFlagClient = FeatureFlagClient 89 | --------------------------------------------------------------------------------