flagsmith-nodejs 1.0.7 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,8 +2,8 @@
2
2
  <module type="WEB_MODULE" version="4">
3
3
  <component name="NewModuleRootManager">
4
4
  <content url="file://$MODULE_DIR$">
5
- <excludeFolder url="file://$MODULE_DIR$/.tmp" />
6
5
  <excludeFolder url="file://$MODULE_DIR$/temp" />
6
+ <excludeFolder url="file://$MODULE_DIR$/.tmp" />
7
7
  <excludeFolder url="file://$MODULE_DIR$/tmp" />
8
8
  </content>
9
9
  <orderEntry type="inheritedJdk" />
package/.idea/modules.xml CHANGED
@@ -2,7 +2,7 @@
2
2
  <project version="4">
3
3
  <component name="ProjectModuleManager">
4
4
  <modules>
5
- <module fileurl="file://$PROJECT_DIR$/.idea/bullet-train-nodejs-client.iml" filepath="$PROJECT_DIR$/.idea/bullet-train-nodejs-client.iml" />
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/flagsmith-nodejs-client.iml" filepath="$PROJECT_DIR$/.idea/flagsmith-nodejs-client.iml" />
6
6
  </modules>
7
7
  </component>
8
8
  </project>
@@ -0,0 +1 @@
1
+ *.json
package/.prettierrc.js ADDED
@@ -0,0 +1,9 @@
1
+ module.exports = {
2
+ bracketSpacing: true,
3
+ printWidth: 100,
4
+ singleQuote: true,
5
+ tabWidth: 4,
6
+ trailingComma: 'none',
7
+ useTabs: false,
8
+ arrowParens: 'avoid'
9
+ };
package/CONTRIBUTING.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # Contributing
2
+
2
3
  We're always looking to improve this project, open source contribution is encouraged so long as they adhere to our guidelines.
3
4
 
4
5
  # Pull Requests
@@ -7,7 +8,7 @@ The Solid State team will be monitoring for pull requests. When we get one, a me
7
8
 
8
9
  **A couple things to keep in mind:**
9
10
 
10
- - If you've changed APIs, update the documentation.
11
- - Keep the code style (indents, wrapping) consistent.
12
- - If your PR involves a lot of commits, squash them using ```git rebase -i``` as this makes it easier for us to review.
13
- - Keep lines under 80 characters.
11
+ - If you've changed APIs, update the documentation.
12
+ - Keep the code style (indents, wrapping) consistent.
13
+ - If your PR involves a lot of commits, squash them using `git rebase -i` as this makes it easier for us to review.
14
+ - Keep lines under 80 characters.
package/LICENCE.md CHANGED
@@ -9,4 +9,4 @@ Redistribution and use in source and binary forms, with or without modification,
9
9
 
10
10
  3. Neither the name of the Sentry nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11
11
 
12
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
12
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md CHANGED
@@ -1,90 +1,19 @@
1
- <img width="100%" src="https://raw.githubusercontent.com/SolidStateGroup/bullet-train-frontend/master/hero.png"/>
1
+ <img width="100%" src="https://github.com/Flagsmith/flagsmith/raw/main/static-files/hero.png"/>
2
2
 
3
- # Flagsmith Client
3
+ # Flagsmith NodeJS Client
4
4
 
5
5
  [![npm version](https://badge.fury.io/js/flagsmith-nodejs.svg)](https://badge.fury.io/js/flagsmith-nodejs)
6
6
  [![](https://data.jsdelivr.com/v1/package/npm/flagsmith-nodejs/badge)](https://www.jsdelivr.com/package/npm/flagsmith-nodejs)
7
7
 
8
- The SDK clients for NodeJS [https://bullet-train.io/](https://www.bullet-train.io/). Flagsmith allows you to manage feature flags and remote config across multiple projects, environments and organisations.
8
+ The SDK clients for NodeJS [https://www.flagsmith.com/](https://www.flagsmith.com/). Flagsmith allows you to manage feature flags and remote config across multiple projects, environments and organisations.
9
9
 
10
- ## Getting Started
10
+ ## Adding to your project
11
11
 
12
- These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See running in production for notes on how to deploy the project on a live system.
13
-
14
- ## Installing
15
-
16
- ### VIA npm
17
-
18
- ```npm i flagsmith-nodejs --save```
19
-
20
- ## Usage
21
-
22
- ### Retrieving feature flags for your project
23
-
24
- *For full documentation visit [https://docs.bullet-train.io](https://docs.bullet-train.io)*
25
-
26
- ```javascript
27
- var flagsmith = require("flagsmith-nodejs");
28
-
29
- flagsmith.init({
30
- environmentID:"<YOUR_ENVIRONMENT_KEY>"
31
- });
32
-
33
- flagsmith.hasFeature("header", '<My User Id>')
34
- .then((featureEnabled) => {
35
- if (featureEnabled) {
36
- //Show my awesome cool new feature to this one user
37
- }
38
- });
39
- flagsmith.hasFeature("header")
40
- .then((featureEnabled) => {
41
- if (featureEnabled) {
42
- //Show my awesome cool new feature to the world
43
- }
44
- });
45
-
46
- flagsmith.getValue("header", '<My User Id>')
47
- .then((value) => {
48
- //Show some unique value to this user
49
- });
50
-
51
- flagsmith.getValue("header")
52
- .then((value) => {
53
- //Show a value to the world
54
- });
55
- ```
56
-
57
- ### Available Options
58
-
59
- | Property | Description | Required | Default Value |
60
- | ------------- |:-------------:| -----:| -----:|
61
- | ```environmentID``` | Defines which project environment you wish to get flags for. *example ACME Project - Staging.* | **YES** | null
62
- | ```onError``` | Callback function on failure to retrieve flags. ``` (error)=>{...} ``` | **NO** | null
63
- | ```defaultFlags``` | Defines the default flags if there are any | **NO** | null
64
- | ```api``` | Use this property to define where you're getting feature flags from, e.g. if you're self hosting. | **NO** | https://bullet-train-api.dokku1.solidstategroup.com/api/v1/
65
-
66
- ### Available Functions
67
-
68
- | Property | Description |
69
- | ------------- |:-------------:|
70
- | ```init``` | Initialise the sdk against a particular environment
71
- | ```hasFeature(key)``` | Get the value of a particular feature e.g. ```flagsmith.hasFeature("powerUserFeature") // true```
72
- | ```hasFeature(key, userId)``` | Get the value of a particular feature for a user e.g. ```flagsmith.hasFeature("powerUserFeature", 1234) // true```
73
- | ```getValue(key)``` | Get the value of a particular feature e.g. ```flagsmith.getValue("font_size") // 10```
74
- | ```getValue(key, userId)``` | Get the value of a particular feature for a specificed userId e.g. ```flagsmith.getValue("font_size", 1234) // 15```
75
- | ```getFlags()``` | Trigger a manual fetch of the environment features, if a user is identified it will fetch their features
76
- | ```getFlagsForUser(userId)``` | Trigger a manual fetch of the environment features with a given userId
77
- | ```setTrait(userId, key, value)``` | Set the name/value pair for the specified userId
78
- | ```getTrait(userId, key)``` | Retrieve the value for the specified userId and Trait key
79
-
80
- ### Identifying users
81
-
82
- Identifying users allows you to target specific users from the [Flagsmith dashboard](https://www.bullet-train.io/).
83
- You can include an optional user identifier as part of the `hasFeature` and `getValue` methods to retrieve unique user flags and variables.
12
+ For full documentation visit [https://docs.flagsmith.com/clients/node/](https://docs.flagsmith.com/clients/node/)
84
13
 
85
14
  ## Contributing
86
15
 
87
- Please read [CONTRIBUTING.md](https://gist.github.com/kyle-ssg/c36a03aebe492e45cbd3eefb21cb0486) for details on our code of conduct, and the process for submitting pull requests to us.
16
+ Please read [CONTRIBUTING.md](https://gist.github.com/kyle-ssg/c36a03aebe492e45cbd3eefb21cb0486) for details on our code of conduct, and the process for submitting pull requests
88
17
 
89
18
  ## Getting Help
90
19
 
@@ -92,14 +21,10 @@ If you encounter a bug or feature request we would like to hear about it. Before
92
21
 
93
22
  ## Get in touch
94
23
 
95
- If you have any questions about our projects you can email <a href="mailto:support@bullet-train.io">support@bullet-train.io</a>.
24
+ If you have any questions about our projects you can email <a href="mailto:support@flagsmith.com">support@flagsmith.com</a>.
96
25
 
97
26
  ## Useful links
98
27
 
99
- [Website](https://bullet-train.io)
100
-
101
- [Documentation](https://docs.bullet-train.io/)
102
-
103
- [Code Examples](https://github.com/flagsmithHQ/bullet-train-docs)
28
+ [Website](https://www.flagsmith.com/)
104
29
 
105
- [Youtube Tutorials](https://www.youtube.com/channel/UCki7GZrOdZZcsV9rAIRchCw)
30
+ [Documentation](https://docs.flagsmith.com/)
package/example/README.md CHANGED
@@ -1,21 +1,21 @@
1
- Flagsmith example
2
- ==================================
1
+ # Flagsmith example
3
2
 
4
-
5
- Getting Started
6
- ---------------
3
+ ## Getting Started
7
4
 
8
5
  # Setup via cli
9
6
 
10
- ```npm i ssg-node -g```
7
+ `npm i ssg-node -g`
11
8
 
12
- ```ssg-node PROJECT_NAME```
9
+ `ssg-node PROJECT_NAME`
13
10
 
14
11
  # Run
15
- ```$ npm start```
12
+
13
+ `$ npm start`
16
14
 
17
15
  # Nodemon (Restart server on changes)
18
- ```npm run dev```
16
+
17
+ `npm run dev`
19
18
 
20
19
  # The project
21
- - ``/server/api`` contains a simple express api that interacts with Flagsmith
20
+
21
+ - `/server/api` contains a simple express api that interacts with Flagsmith
@@ -54,19 +54,16 @@
54
54
  "concat-map": "0.0.1"
55
55
  }
56
56
  },
57
- "bullet-train-nodejs": {
58
- "version": "0.1.1",
59
- "resolved": "https://registry.npmjs.org/bullet-train-nodejs/-/bullet-train-nodejs-0.1.1.tgz",
60
- "integrity": "sha512-gkLPyJEZChvzfhTrGXFJuP2LMU/7pnybjIHDJ9cu4sK52q3eHim49Qh+p8KQykWmebymLpGHW9baPrRqr235PQ==",
61
- "requires": {
62
- "node-fetch": "^2.1.2"
63
- }
64
- },
65
57
  "bytes": {
66
58
  "version": "3.0.0",
67
59
  "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
68
60
  "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
69
61
  },
62
+ "clone": {
63
+ "version": "2.1.2",
64
+ "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
65
+ "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18="
66
+ },
70
67
  "commander": {
71
68
  "version": "2.17.1",
72
69
  "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
@@ -470,10 +467,13 @@
470
467
  "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz",
471
468
  "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw=="
472
469
  },
473
- "node-fetch": {
474
- "version": "2.1.2",
475
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz",
476
- "integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U="
470
+ "node-cache": {
471
+ "version": "5.1.2",
472
+ "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz",
473
+ "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==",
474
+ "requires": {
475
+ "clone": "2.x"
476
+ }
477
477
  },
478
478
  "object-assign": {
479
479
  "version": "4.1.1",
@@ -15,7 +15,7 @@
15
15
  "npm": "3.10.x"
16
16
  },
17
17
  "dependencies": {
18
- "flagsmith-nodejs": "^1.0.0",
18
+ "node-cache": "^5.1.2",
19
19
  "ssg-node-express": "4.16.4",
20
20
  "ssg-util": "0.0.3"
21
21
  },
@@ -1,26 +1,32 @@
1
1
  const Router = require('express').Router;
2
- const environmentID = "QjgYur4LQTwe5HpvbvhpzK";
3
- const flagsmith = require("flagsmith-nodejs");
2
+ const environmentID = 'uCDQzKWgejrutqSYYsKWen';
3
+ const flagsmith = require('../../../');
4
+ const NodeCache = require('node-cache');
4
5
 
5
6
  flagsmith.init({
6
7
  environmentID
8
+ // this is an example of a user-defined cache
9
+ /*cache: new NodeCache({
10
+ stdTTL: 5
11
+ })*/
7
12
  });
8
13
 
9
14
  module.exports = () => {
10
15
  const api = Router();
11
16
 
12
- api.get('/', (req, res) => {
13
- flagsmith.getValue("font_size")
14
- .then((font_size) => {
15
- res.json({font_size})
16
- });
17
+ api.get('/', async (req, res) => {
18
+ const font_size = await flagsmith.getValue('font_size');
19
+ res.json({ font_size });
17
20
  });
18
21
 
19
- api.get('/:user', (req, res) => {
20
- flagsmith.getValue("font_size", "flagsmith_sample_user")
21
- .then((font_size) => {
22
- res.json({font_size})
23
- });
22
+ api.get('/flags', async (req, res) => {
23
+ const flags = await flagsmith.getFlags();
24
+ res.json(flags);
25
+ });
26
+
27
+ api.get('/:user', async (req, res) => {
28
+ const font_size = await flagsmith.getValue('font_size', req.params.user);
29
+ res.json({ font_size });
24
30
  });
25
31
 
26
32
  return api;
@@ -1,5 +1,3 @@
1
- global.fetch = require('fetchify')(Promise).fetch; // polyfil
2
-
3
1
  const http = require('http');
4
2
  const express = require('express');
5
3
  const api = require('./api');
@@ -16,14 +14,15 @@ app.use(bodyParser.json());
16
14
  // api router
17
15
  app.use('/', api());
18
16
 
19
-
20
17
  app.server.listen(PORT);
21
18
  console.log('Server started on port ' + app.server.address().port);
22
19
  console.log();
23
- console.log('Go to http://localhost:'+PORT+'/');
20
+ console.log('Go to http://localhost:' + PORT + '/');
24
21
  console.log('To get an example feature state');
25
22
  console.log();
26
- console.log('Go to http://localhost:'+PORT+'/flagsmith_sample_user');
23
+ console.log('Go to http://localhost:' + PORT + '/flagsmith_sample_user');
27
24
  console.log('To get an example feature state for a user');
25
+ console.log('Go to http://localhost:' + PORT + '/flags');
26
+ console.log('To get an example response for getFlags');
28
27
 
29
28
  module.exports = app;
@@ -0,0 +1,243 @@
1
+ let fetch = require('node-fetch');
2
+ // https://github.com/node-fetch/node-fetch/issues/450
3
+ if (typeof fetch.default !== "undefined") fetch = fetch.default
4
+
5
+ module.exports = class FlagsmithCore {
6
+ normalizeFlags(flags) {
7
+ const _flags = {};
8
+
9
+ for (const { feature, enabled, feature_state_value } of flags) {
10
+ const normalizedKey = feature.name.toLowerCase().replace(/ /g, '_');
11
+ _flags[normalizedKey] = {
12
+ enabled,
13
+ value: feature_state_value
14
+ };
15
+ }
16
+
17
+ return _flags;
18
+ }
19
+
20
+ normalizeTraits(traits) {
21
+ const _traits = {};
22
+
23
+ for (const { trait_key, trait_value } of traits) {
24
+ const normalizedKey = trait_key.toLowerCase().replace(/ /g, '_');
25
+ _traits[normalizedKey] = trait_value;
26
+ }
27
+
28
+ return _traits;
29
+ }
30
+
31
+ async getJSON(url, method, body) {
32
+ const { environmentID } = this;
33
+ const options = {
34
+ method: method || 'GET',
35
+ body,
36
+ headers: {
37
+ 'x-environment-key': environmentID
38
+ }
39
+ };
40
+
41
+ if (method !== 'GET') {
42
+ options.headers['Content-Type'] = 'application/json; charset=utf-8';
43
+ }
44
+
45
+ const res = await fetch(url, options);
46
+ const result = await res.json();
47
+
48
+ if (res.status >= 400) {
49
+ throw new Error(result.detail);
50
+ }
51
+
52
+ return result;
53
+ }
54
+
55
+ init({ environmentID, api, onError, cache }) {
56
+ if (!environmentID) {
57
+ throw new Error('Please specify a environment id');
58
+ }
59
+
60
+ this.environmentID = environmentID;
61
+
62
+ this.api = api || 'https://api.flagsmith.com/api/v1';
63
+ this.onError = onError;
64
+
65
+ if (cache) {
66
+ const missingMethods = [];
67
+
68
+ ['has', 'get', 'set'].forEach(method => {
69
+ if (!cache[method]) missingMethods.push(method);
70
+ });
71
+
72
+ if (missingMethods.length > 0) {
73
+ throw new Error(
74
+ `Please implement the following methods in your cache: ${missingMethods.join(
75
+ ', '
76
+ )}`
77
+ );
78
+ }
79
+ }
80
+
81
+ this.cache = cache;
82
+ }
83
+
84
+ async getFlags() {
85
+ if (this.cache && (await this.cache.has('flags'))) {
86
+ return this.cache.get('flags');
87
+ }
88
+
89
+ const { onError, api } = this;
90
+
91
+ try {
92
+ const flags = await this.getJSON(`${api}/flags/`);
93
+ const normalizedFlags = this.normalizeFlags(flags);
94
+
95
+ if (this.cache) await this.cache.set('flags', normalizedFlags);
96
+
97
+ return normalizedFlags;
98
+ } catch (err) {
99
+ onError && onError({ message: err.message });
100
+ throw err;
101
+ }
102
+ }
103
+
104
+ async getFlagsForUser(identity) {
105
+ const cacheKey = `flags-${identity}`;
106
+
107
+ if (this.cache && (await this.cache.has(cacheKey))) {
108
+ return this.cache.get(cacheKey);
109
+ }
110
+
111
+ const { onError, api } = this;
112
+
113
+ if (!identity) {
114
+ const errMsg = 'getFlagsForUser() called without a user identity';
115
+ onError && onError({ message: errMsg });
116
+ throw new Error(errMsg);
117
+ }
118
+
119
+ try {
120
+ const { flags } = await this.getJSON(`${api}/identities/?identifier=${identity}`);
121
+ const normalizedFlags = this.normalizeFlags(flags);
122
+
123
+ if (this.cache) await this.cache.set(cacheKey, normalizedFlags);
124
+
125
+ return normalizedFlags;
126
+ } catch (err) {
127
+ onError && onError({ message: err.message });
128
+ throw err;
129
+ }
130
+ }
131
+
132
+ async getUserIdentity(identity) {
133
+ const cacheKey = `flags_traits-${identity}`;
134
+
135
+ if (this.cache && (await this.cache.has(cacheKey))) {
136
+ return this.cache.get(cacheKey);
137
+ }
138
+
139
+ const { onError, api } = this;
140
+
141
+ if (!identity) {
142
+ const errMsg = 'getUserIdentity() called without a user identity';
143
+ onError && onError({ message: errMsg });
144
+ throw new Error(errMsg);
145
+ }
146
+
147
+ try {
148
+ const { flags, traits } = await this.getJSON(
149
+ `${api}/identities/?identifier=${identity}`
150
+ );
151
+
152
+ const normalizedFlags = this.normalizeFlags(flags);
153
+ const normalizedTraits = this.normalizeTraits(traits);
154
+ const res = { flags: normalizedFlags, traits: normalizedTraits };
155
+
156
+ if (this.cache) await this.cache.set(cacheKey, res);
157
+
158
+ return res;
159
+ } catch (err) {
160
+ onError && onError({ message: err.message });
161
+ throw err;
162
+ }
163
+ }
164
+
165
+ async getValue(key, userId) {
166
+ const flags = userId ? await this.getFlagsForUser(userId) : await this.getFlags();
167
+
168
+ return this.getValueFromFeatures(key, flags);
169
+ }
170
+
171
+ async hasFeature(key, userId) {
172
+ const flags = userId ? await this.getFlagsForUser(userId) : await this.getFlags();
173
+
174
+ return this.checkFeatureEnabled(key, flags);
175
+ }
176
+
177
+ getValueFromFeatures(key, flags) {
178
+ if (!flags) return null;
179
+
180
+ const flag = flags[key];
181
+
182
+ //todo record check for value
183
+ return flag ? flag.value : null;
184
+ }
185
+
186
+ checkFeatureEnabled(key, flags) {
187
+ if (!flags) return false;
188
+
189
+ const flag = flags[key];
190
+ return flag && flag.enabled;
191
+ }
192
+
193
+ async getTrait(identity, key) {
194
+ const { onError } = this;
195
+
196
+ if (!identity || !key) {
197
+ const errMsg = `getTrait() called without a ${
198
+ !identity ? 'user identity' : 'trait key'
199
+ }`;
200
+ onError && onError({ message: errMsg });
201
+ throw new Error(errMsg);
202
+ }
203
+
204
+ try {
205
+ const { traits } = await this.getUserIdentity(identity);
206
+ return traits[key];
207
+ } catch (err) {
208
+ onError && onError({ message: err.message });
209
+ throw err;
210
+ }
211
+ }
212
+
213
+ async setTrait(identity, key, value) {
214
+ const { onError, api } = this;
215
+
216
+ if (!identity || !key) {
217
+ const errMsg = `setTrait() called without a ${
218
+ !identity ? 'user identity' : 'trait key'
219
+ }`;
220
+ onError &&
221
+ onError({
222
+ message: errMsg
223
+ });
224
+ throw new Error(errMsg);
225
+ }
226
+
227
+ const body = {
228
+ identity: {
229
+ identifier: identity
230
+ },
231
+ trait_key: key,
232
+ trait_value: value
233
+ };
234
+
235
+ try {
236
+ await this.getJSON(`${api}/traits/`, 'POST', JSON.stringify(body));
237
+ return await this.getUserIdentity(identity);
238
+ } catch (err) {
239
+ onError && onError({ message: err.message });
240
+ throw err;
241
+ }
242
+ }
243
+ };
package/index.d.ts CHANGED
@@ -3,51 +3,52 @@ declare module 'flagsmith-nodejs' {
3
3
  * Initialise the sdk against a particular environment
4
4
  */
5
5
  export function init(config: {
6
- environmentID: string
7
- onError?: Function
8
- defaultFlags?: string[]
9
- api?: string
10
- }): void
6
+ environmentID: string;
7
+ onError?: Function;
8
+ defaultFlags?: string[];
9
+ api?: string;
10
+ cache?: ICache;
11
+ }): void;
11
12
 
12
13
  /**
13
14
  * Get the whether a flag is enabled e.g. flagsmith.hasFeature("powerUserFeature")
14
15
  */
15
- export function hasFeature(key: string): Promise<boolean>
16
+ export function hasFeature(key: string): Promise<boolean>;
16
17
 
17
18
  /**
18
19
  * Get the value of a whether a flag is enabled for a user e.g. flagsmith.hasFeature("powerUserFeature", 1234)
19
20
  */
20
- export function hasFeature(key: string, userId: string): Promise<boolean>
21
+ export function hasFeature(key: string, userId: string): Promise<boolean>;
21
22
 
22
23
  /**
23
24
  * Get the value of a particular remote config e.g. flagsmith.getValue("font_size")
24
25
  */
25
- export function getValue(key: string): Promise<string|number|boolean>
26
+ export function getValue(key: string): Promise<string | number | boolean>;
26
27
 
27
28
  /**
28
29
  * Get the value of a particular remote config for a specified user e.g. flagsmith.getValue("font_size", 1234)
29
30
  */
30
- export function getValue(key: string, userId: string): Promise<string|number|boolean>
31
+ export function getValue(key: string, userId: string): Promise<string | number | boolean>;
31
32
 
32
33
  /**
33
34
  * Trigger a manual fetch of the environment features
34
35
  */
35
- export function getFlags(): Promise<IFlags>
36
+ export function getFlags(): Promise<IFlags>;
36
37
 
37
38
  /**
38
39
  * Trigger a manual fetch of the environment features for a given user id
39
40
  */
40
- export function getFlagsForUser(userId: string): Promise<IFlags>
41
+ export function getFlagsForUser(userId: string): Promise<IFlags>;
41
42
 
42
43
  /**
43
44
  * Trigger a manual fetch of both the environment features and users' traits for a given user id
44
45
  */
45
- export function getUserIdentity(userId: string): Promise<IUserIdentity>
46
+ export function getUserIdentity(userId: string): Promise<IUserIdentity>;
46
47
 
47
48
  /**
48
49
  * Trigger a manual fetch of a specific trait for a given user id
49
50
  */
50
- export function getTrait(userId: string, key: string): Promise<ITraits>
51
+ export function getTrait(userId: string, key: string): Promise<ITraits>;
51
52
 
52
53
  /**
53
54
  * Set a specific trait for a given user id
@@ -55,24 +56,30 @@ declare module 'flagsmith-nodejs' {
55
56
  export function setTrait(
56
57
  userId: string,
57
58
  key: string,
58
- value: string|number|boolean
59
- ): IUserIdentity
59
+ value: string | number | boolean
60
+ ): IUserIdentity;
60
61
 
61
62
  interface IFeature {
62
- enabled: boolean
63
- value?: string|number|boolean
63
+ enabled: boolean;
64
+ value?: string | number | boolean;
64
65
  }
65
66
 
66
67
  interface IFlags {
67
- [key: string]: IFeature
68
+ [key: string]: IFeature;
68
69
  }
69
70
 
70
71
  interface ITraits {
71
- [key: string]: string
72
+ [key: string]: string;
72
73
  }
73
74
 
74
75
  interface IUserIdentity {
75
- flags: IFeature
76
- traits: ITraits
76
+ flags: IFeature;
77
+ traits: ITraits;
78
+ }
79
+
80
+ interface ICache {
81
+ has(key: string): boolean | Promise<boolean>;
82
+ get(key: string): any | Promise<any>;
83
+ set(key: string, val: any): void | Promise<void>;
77
84
  }
78
85
  }
package/index.js CHANGED
@@ -1,6 +1,3 @@
1
- const config = require("./config");
2
- const fetch = require('node-fetch').default;
3
- const core = require('./flagsmith-core');
4
- const flagsmith = core({fetch: fetch});
1
+ const FlagsmithCore = require('./flagsmith-core');
5
2
 
6
- module.exports = flagsmith;
3
+ module.exports = new FlagsmithCore();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flagsmith-nodejs",
3
- "version": "1.0.7",
3
+ "version": "1.1.1",
4
4
  "description": "Flagsmith lets you manage features flags and remote config across web, mobile and server side applications. Deliver true Continuous Integration. Get builds out faster. Control who has access to new features.",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -39,7 +39,13 @@
39
39
  }
40
40
  ],
41
41
  "license": "MIT",
42
+ "scripts": {
43
+ "lint": "prettier --write ."
44
+ },
42
45
  "dependencies": {
43
46
  "node-fetch": "^2.1.2"
47
+ },
48
+ "devDependencies": {
49
+ "prettier": "^2.2.1"
44
50
  }
45
51
  }
@@ -1,5 +0,0 @@
1
- <component name="ProjectCodeStyleConfiguration">
2
- <state>
3
- <option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
4
- </state>
5
- </component>
package/.idea/misc.xml DELETED
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="JavaScriptSettings">
4
- <option name="languageLevel" value="ES6" />
5
- </component>
6
- </project>
@@ -1,184 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="BranchesTreeState">
4
- <expand />
5
- <select />
6
- </component>
7
- <component name="ChangeListManager">
8
- <list default="true" id="26715e52-92d0-449c-b0d3-02a97b26ec9d" name="Default Changelist" comment="">
9
- <change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
10
- <change beforePath="$PROJECT_DIR$/bullet-train-core.js" beforeDir="false" afterPath="$PROJECT_DIR$/flagsmith.js" afterDir="false" />
11
- <change beforePath="$PROJECT_DIR$/example/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/example/README.md" afterDir="false" />
12
- <change beforePath="$PROJECT_DIR$/example/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/example/package.json" afterDir="false" />
13
- <change beforePath="$PROJECT_DIR$/example/server/api/index.js" beforeDir="false" afterPath="$PROJECT_DIR$/example/server/api/index.js" afterDir="false" />
14
- <change beforePath="$PROJECT_DIR$/example/server/index.js" beforeDir="false" afterPath="$PROJECT_DIR$/example/server/index.js" afterDir="false" />
15
- <change beforePath="$PROJECT_DIR$/index.d.ts" beforeDir="false" afterPath="$PROJECT_DIR$/index.d.ts" afterDir="false" />
16
- <change beforePath="$PROJECT_DIR$/index.js" beforeDir="false" afterPath="$PROJECT_DIR$/index.js" afterDir="false" />
17
- <change beforePath="$PROJECT_DIR$/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/package-lock.json" afterDir="false" />
18
- <change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
19
- </list>
20
- <option name="SHOW_DIALOG" value="false" />
21
- <option name="HIGHLIGHT_CONFLICTS" value="true" />
22
- <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
23
- <option name="LAST_RESOLUTION" value="IGNORE" />
24
- </component>
25
- <component name="Git.Pull.Settings">
26
- <option name="BRANCH" value="master" />
27
- </component>
28
- <component name="Git.Settings">
29
- <option name="RECENT_BRANCH_BY_REPOSITORY">
30
- <map>
31
- <entry key="$PROJECT_DIR$" value="master" />
32
- </map>
33
- </option>
34
- <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
35
- </component>
36
- <component name="ProjectId" id="1f0YMK9KVPUqTVp0gi8Qb1dD1il" />
37
- <component name="ProjectViewState">
38
- <option name="hideEmptyMiddlePackages" value="true" />
39
- <option name="showLibraryContents" value="true" />
40
- </component>
41
- <component name="PropertiesComponent">
42
- <property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
43
- <property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
44
- <property name="WebServerToolWindowFactoryState" value="false" />
45
- <property name="last_opened_file_path" value="$PROJECT_DIR$" />
46
- <property name="nodejs_package_manager_path" value="npm" />
47
- <property name="ts.external.directory.path" value="$APPLICATION_HOME_DIR$/plugins/JavaScriptLanguage/jsLanguageServicesImpl/external" />
48
- <property name="vue.rearranger.settings.migration" value="true" />
49
- </component>
50
- <component name="RecentsManager">
51
- <key name="CopyFile.RECENT_KEYS">
52
- <recent name="$PROJECT_DIR$" />
53
- </key>
54
- </component>
55
- <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
56
- <component name="SvnConfiguration">
57
- <configuration />
58
- </component>
59
- <component name="TaskManager">
60
- <task active="true" id="Default" summary="Default task">
61
- <changelist id="26715e52-92d0-449c-b0d3-02a97b26ec9d" name="Default Changelist" comment="" />
62
- <created>1595356427022</created>
63
- <option name="number" value="Default" />
64
- <option name="presentableId" value="Default" />
65
- <updated>1595356427022</updated>
66
- <workItem from="1595356429123" duration="1282000" />
67
- <workItem from="1598381125903" duration="3394000" />
68
- <workItem from="1599668504494" duration="605000" />
69
- <workItem from="1603729502044" duration="618000" />
70
- <workItem from="1604853185978" duration="406000" />
71
- </task>
72
- <task id="LOCAL-00001" summary="Version bump">
73
- <created>1595357856348</created>
74
- <option name="number" value="00001" />
75
- <option name="presentableId" value="LOCAL-00001" />
76
- <option name="project" value="LOCAL" />
77
- <updated>1595357856349</updated>
78
- </task>
79
- <task id="LOCAL-00002" summary="Add types">
80
- <created>1598381689227</created>
81
- <option name="number" value="00002" />
82
- <option name="presentableId" value="LOCAL-00002" />
83
- <option name="project" value="LOCAL" />
84
- <updated>1598381689227</updated>
85
- </task>
86
- <task id="LOCAL-00003" summary="Adjust typescript doc wordings">
87
- <created>1598381831762</created>
88
- <option name="number" value="00003" />
89
- <option name="presentableId" value="LOCAL-00003" />
90
- <option name="project" value="LOCAL" />
91
- <updated>1598381831762</updated>
92
- </task>
93
- <task id="LOCAL-00004" summary="Improve types">
94
- <created>1598384180847</created>
95
- <option name="number" value="00004" />
96
- <option name="presentableId" value="LOCAL-00004" />
97
- <option name="project" value="LOCAL" />
98
- <updated>1598384180847</updated>
99
- </task>
100
- <task id="LOCAL-00005" summary="Version bump">
101
- <created>1603729534660</created>
102
- <option name="number" value="00005" />
103
- <option name="presentableId" value="LOCAL-00005" />
104
- <option name="project" value="LOCAL" />
105
- <updated>1603729534660</updated>
106
- </task>
107
- <option name="localTasksCounter" value="6" />
108
- <servers />
109
- </component>
110
- <component name="TypeScriptGeneratedFilesManager">
111
- <option name="version" value="3" />
112
- </component>
113
- <component name="Vcs.Log.Tabs.Properties">
114
- <option name="TAB_STATES">
115
- <map>
116
- <entry key="1">
117
- <value>
118
- <State>
119
- <option name="SHOW_ONLY_AFFECTED_CHANGES" value="true" />
120
- <option name="FILTERS">
121
- <map>
122
- <entry key="branch">
123
- <value>
124
- <list>
125
- <option value="HEAD" />
126
- </list>
127
- </value>
128
- </entry>
129
- <entry key="roots">
130
- <value>
131
- <list>
132
- <option value="$PROJECT_DIR$" />
133
- </list>
134
- </value>
135
- </entry>
136
- </map>
137
- </option>
138
- </State>
139
- </value>
140
- </entry>
141
- </map>
142
- </option>
143
- <option name="OPEN_GENERIC_TABS">
144
- <map>
145
- <entry key="1" value="TOOL_WINDOW" />
146
- </map>
147
- </option>
148
- <option name="oldMeFiltersMigrated" value="true" />
149
- </component>
150
- <component name="VcsManagerConfiguration">
151
- <MESSAGE value="Add types" />
152
- <MESSAGE value="Adjust typescript doc wordings" />
153
- <MESSAGE value="Improve types" />
154
- <MESSAGE value="Version bump" />
155
- <option name="LAST_COMMIT_MESSAGE" value="Version bump" />
156
- </component>
157
- <component name="WindowStateProjectService">
158
- <state x="843" y="48" key="CommitChangelistDialog2" timestamp="1603729534387">
159
- <screen x="0" y="0" width="1920" height="1080" />
160
- </state>
161
- <state x="843" y="48" key="CommitChangelistDialog2/0.0.1920.1080/1920.0.1920.1080@0.0.1920.1080" timestamp="1603729534387" />
162
- <state x="843" y="48" key="CommitChangelistDialog2/1920.0.1920.1080/0.0.1920.1080@0.0.1920.1080" timestamp="1598381831357" />
163
- <state x="2763" y="48" key="CommitChangelistDialog2/1920.0.1920.1080/0.0.1920.1080@1920.0.1920.1080" timestamp="1598384180673" />
164
- <state x="384" y="97" key="DiffContextDialog" timestamp="1598381684021">
165
- <screen x="0" y="0" width="1920" height="1080" />
166
- </state>
167
- <state x="384" y="97" key="DiffContextDialog/1920.0.1920.1080/0.0.1920.1080@0.0.1920.1080" timestamp="1598381684021" />
168
- <state x="854" y="276" key="Vcs.Push.Dialog.v2" timestamp="1603729535566">
169
- <screen x="0" y="0" width="1920" height="1080" />
170
- </state>
171
- <state x="854" y="276" key="Vcs.Push.Dialog.v2/0.0.1920.1080/1920.0.1920.1080@0.0.1920.1080" timestamp="1603729535566" />
172
- <state x="854" y="276" key="Vcs.Push.Dialog.v2/1920.0.1920.1080/0.0.1920.1080@0.0.1920.1080" timestamp="1598381832772" />
173
- <state x="2774" y="276" key="Vcs.Push.Dialog.v2/1920.0.1920.1080/0.0.1920.1080@1920.0.1920.1080" timestamp="1598384181696" />
174
- <state x="-1302" y="279" width="1302" height="612" key="find.popup" timestamp="1604853580359">
175
- <screen x="-1920" y="0" width="1920" height="1080" />
176
- </state>
177
- <state x="-1302" y="279" width="1302" height="612" key="find.popup/0.0.1920.1080/-1920.0.1920.1080@-1920.0.1920.1080" timestamp="1604853580359" />
178
- <state x="-1589" y="241" width="670" height="676" key="search.everywhere.popup" timestamp="1604853471394">
179
- <screen x="-1920" y="0" width="1920" height="1080" />
180
- </state>
181
- <state x="-1589" y="241" width="670" height="676" key="search.everywhere.popup/0.0.1920.1080/-1920.0.1920.1080@-1920.0.1920.1080" timestamp="1604853471394" />
182
- <state x="331" y="241" width="670" height="676" key="search.everywhere.popup/1920.0.1920.1080/0.0.1920.1080@0.0.1920.1080" timestamp="1598381551576" />
183
- </component>
184
- </project>
package/config.js DELETED
@@ -1,12 +0,0 @@
1
- /**
2
- * Created by kylejohnson on 02/10/2016.
3
- * Global config
4
- */
5
- const env = process.env.config;
6
- switch (env) {
7
- default: {
8
- config = require('./env/config-local');
9
- }
10
- }
11
-
12
- module.exports = config;
@@ -1,3 +0,0 @@
1
- module.exports = {
2
- api : "https://api.bullet-train.io/api/v1/"
3
- };
package/flagsmith.js DELETED
@@ -1,236 +0,0 @@
1
- let fetch;
2
- const BULLET_TRAIN_KEY = "BULLET_TRAIN_DB";
3
-
4
- const Flagsmith = class {
5
-
6
- constructor(props) {
7
- fetch = props.fetch;
8
-
9
- this.checkFeatureEnabled = this.checkFeatureEnabled.bind(this);
10
- this.getFlags = this.getFlags.bind(this);
11
- this.getFlagsForUser = this.getFlagsForUser.bind(this);
12
- this.getUserIdentity = this.getUserIdentity.bind(this);
13
- this.getValue = this.getValue.bind(this);
14
- this.getValueFromFeatures = this.getValueFromFeatures.bind(this);
15
- this.hasFeature = this.hasFeature.bind(this);
16
- this.init = this.init.bind(this);
17
-
18
- this.getJSON = function (url, method, body) {
19
- const { environmentID } = this;
20
- const options = {
21
- method: method || 'GET',
22
- body,
23
- headers: {
24
- 'x-environment-key': environmentID
25
- }
26
- };
27
- if (method !== "GET") {
28
- options.headers['Content-Type'] = 'application/json; charset=utf-8';
29
- }
30
- return fetch(url, options)
31
- .then(res => res.json());
32
- };
33
- }
34
-
35
- getFlagsForUser (identity) {
36
- const { onError, api } = this;
37
-
38
- if (!identity) {
39
- onError && onError({message: 'getFlagsForUser() called without a user identity'});
40
- return Promise.reject('getFlagsForUser() called without a user identity');
41
- }
42
-
43
- const handleResponse = (res) => {
44
- // Handle server response
45
- let flags = {};
46
- res.flags.forEach(feature => {
47
- flags[feature.feature.name.toLowerCase().replace(/ /g, '_')] = {
48
- enabled: feature.enabled,
49
- value: feature.feature_state_value
50
- };
51
- });
52
- return flags;
53
- };
54
-
55
- return this.getJSON(api + 'identities/?identifier=' + identity)
56
- .then(res => {
57
- return handleResponse(res);
58
- }).catch(({ message }) => {
59
- onError && onError({ message });
60
- return Promise.reject(message);
61
- });
62
- }
63
-
64
- getUserIdentity (identity) {
65
- const { onError, api } = this;
66
-
67
- if (!identity) {
68
- onError && onError({message: 'getUserIdentity() called without a user identity'});
69
- return Promise.reject('getUserIdentity() called without a user identity');
70
- }
71
-
72
- const handleResponse = (res) => {
73
- // Handle server response
74
- let flags = {};
75
- let traits = {};
76
- res.flags.forEach(feature => {
77
- flags[feature.feature.name.toLowerCase().replace(/ /g, '_')] = {
78
- enabled: feature.enabled,
79
- value: feature.feature_state_value
80
- };
81
- });
82
- res.traits.forEach(({trait_key, trait_value}) => {
83
- traits[trait_key.toLowerCase().replace(/ /g, '_')] = trait_value;
84
- });
85
- return { flags, traits };
86
- };
87
-
88
- return this.getJSON(api + 'identities/?identifier=' + identity)
89
- .then(res => {
90
- return handleResponse(res);
91
- }).catch(({ message }) => {
92
- onError && onError({ message });
93
- return Promise.reject(message);
94
- });
95
- }
96
-
97
- getFlags() {
98
- const { onError, api } = this;
99
-
100
- const handleResponse = (res) => {
101
- // Handle server response
102
- let flags = {};
103
- res.forEach(feature => {
104
- flags[feature.feature.name.toLowerCase().replace(/ /g, '_')] = {
105
- enabled: feature.enabled,
106
- value: feature.feature_state_value
107
- };
108
- });
109
- return flags;
110
- };
111
-
112
- return this.getJSON(api + "flags/")
113
- .then(res => {
114
- return handleResponse(res);
115
- }).catch(({ message }) => {
116
- onError && onError({ message });
117
- return Promise.reject(message);
118
- });
119
- };
120
-
121
- init({
122
- environmentID,
123
- api,
124
- disableCache,
125
- onError,
126
- }) {
127
-
128
- this.environmentID = environmentID;
129
-
130
- this.api = api;
131
- this.disableCache = disableCache;
132
- this.onError = onError;
133
-
134
- if (!environmentID) {
135
- throw ('Please specify a environment id');
136
- }
137
- if (api === undefined) {
138
- this.api = config.api;
139
- }
140
- }
141
-
142
- getValue (key, userId) {
143
- if (userId) {
144
- return this.getFlagsForUser(userId).then((flags) => {
145
- return this.getValueFromFeatures(key, flags);
146
- })
147
- } else {
148
- return this.getFlags().then((flags) => {
149
- return this.getValueFromFeatures(key, flags);
150
- });
151
- }
152
- }
153
-
154
- hasFeature (key, userId) {
155
- if (userId) {
156
- return this.getFlagsForUser(userId).then((flags) => {
157
- return this.checkFeatureEnabled(key, flags);
158
- })
159
- } else {
160
- return this.getFlags().then((flags) => {
161
- return this.checkFeatureEnabled(key, flags);
162
- });
163
- }
164
- }
165
-
166
- getValueFromFeatures (key, flags) {
167
- if (!flags) {
168
- return null;
169
- }
170
- const flag = flags[key];
171
- let res = null;
172
- if (flag) {
173
- res = flag.value;
174
- }
175
- //todo record check for value
176
-
177
- return res;
178
- }
179
-
180
- checkFeatureEnabled (key, flags) {
181
- if (!flags) {
182
- return false;
183
- }
184
- const flag = flags[key];
185
- let res = false;
186
- if (flag && flag.enabled) {
187
- res = true;
188
- }
189
-
190
- return res;
191
- }
192
-
193
- getTrait (identity, key) {
194
- const { onError } = this;
195
-
196
- if (!identity || !key) {
197
- onError && onError({message: `getTrait() called without a ${!identity ? 'user identity' : 'trait key'}`});
198
- return Promise.reject(`getTrait() called without a ${!identity ? 'user identity' : 'trait key'}`);
199
- }
200
-
201
- return this.getUserIdentity(identity)
202
- .then(({traits}) => traits[key])
203
- .catch(({ message }) => {
204
- onError && onError({ message });
205
- return Promise.reject(message);
206
- });
207
- }
208
-
209
- setTrait (identity, key, value) {
210
- const { onError, api } = this;
211
-
212
- if (!identity || !key) {
213
- onError && onError({message: `setTrait() called without a ${!identity ? 'user identity' : 'trait key'}`});
214
- return Promise.reject(`setTrait() called without a ${!identity ? 'user identity' : 'trait key'}`);
215
- }
216
-
217
- const body = {
218
- "identity": {
219
- "identifier": identity
220
- },
221
- "trait_key": key,
222
- "trait_value": value
223
- }
224
-
225
- return this.getJSON(`${api}traits/`, 'POST', JSON.stringify(body))
226
- .then(() => this.getUserIdentity(identity))
227
- .catch(({ message }) => {
228
- onError && onError({ message });
229
- return Promise.reject(message);
230
- });
231
- }
232
- };
233
-
234
- module.exports = function ({ fetch }) {
235
- return new Flagsmith({ fetch });
236
- };