Skip to content

Commit 4bc76c0

Browse files
committed
initial waitForInit implementation
1 parent b57f1f8 commit 4bc76c0

File tree

10 files changed

+215
-34
lines changed

10 files changed

+215
-34
lines changed

README.md

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
[![APLv2][license-badge]][license]
66

77
# Before you Begin
8+
89
Harness Feature Flags (FF) is a feature management solution that enables users to change the software’s functionality, without deploying new code. FF uses feature flags to hide code or behaviours without having to ship new versions of the software. A feature flag is like a powerful if statement.
910

1011
For more information, see https://harness.io/products/feature-flags/
@@ -17,7 +18,7 @@ To sign up, https://app.harness.io/auth/#/signup/
1718

1819
### Setup
1920

20-
```npm install @harness/ff-nodejs-server-sdk```
21+
`npm install @harness/ff-nodejs-server-sdk`
2122

2223
### Import the package (CommonJS)
2324

@@ -34,18 +35,21 @@ import { Client } from 'ff-nodejs-server-sdk';
3435
### Initialize
3536

3637
This is the most simple way to initialize SDK using only a server type key
38+
3739
```
3840
const client = new Client('your server type SDK key');
3941
```
4042

4143
Advanced initialization can be done using options
44+
4245
```
4346
const client = new Client('your server type SDK key', {
4447
enableStream: false
4548
});
4649
```
4750

4851
### Define a target
52+
4953
```
5054
const target = {
5155
identifier: 'harness',
@@ -55,21 +59,40 @@ const target = {
5559
```
5660

5761
### Evaluate the flag with default value set to false
62+
5863
```typescript
5964
const value = await client.boolVariation('test', target, false);
6065
```
6166

6267
### Shutting down SDK
68+
6369
```
6470
client.close();
6571
```
6672

6773
### Avaialable public methods
74+
6875
```typescript
69-
function boolVariation(identifier: string, target: Target, defaultValue = true): Promise<boolean>;
70-
function stringVariation(identifier, target: Target, defaultValue = ''): Promise<string>;
71-
function numberVariation(identifier, target: Target, defaultValue = 1.0): Promise<number>;
72-
function jsonVariation(identifier, target: Target, defaultValue = {}): Promise<Record<string, unknown>>;
76+
function boolVariation(
77+
identifier: string,
78+
target: Target,
79+
defaultValue = true,
80+
): Promise<boolean>;
81+
function stringVariation(
82+
identifier,
83+
target: Target,
84+
defaultValue = '',
85+
): Promise<string>;
86+
function numberVariation(
87+
identifier,
88+
target: Target,
89+
defaultValue = 1.0,
90+
): Promise<number>;
91+
function jsonVariation(
92+
identifier,
93+
target: Target,
94+
defaultValue = {},
95+
): Promise<Record<string, unknown>>;
7396
function close();
7497
```
7598

@@ -107,6 +130,32 @@ setInterval(async() => {
107130
console.log("Evaluation for flag test and target none: ", value);
108131
}, 10000);
109132
```
133+
134+
## Wait for initialization example
135+
136+
```
137+
const { Client } = require('ff-nodejs-server-sdk');
138+
139+
console.log('Starting application');
140+
const client = new Client('1c100d25-4c3f-487b-b198-3b3d01df5794');
141+
client
142+
.waitForInitialization()
143+
.then(() => {
144+
setInterval(async () => {
145+
const target = {
146+
identifier: 'harness',
147+
};
148+
const value = await client.boolVariation('test', target, false);
149+
console.log('Evaluation for flag test and target: ', value, target);
150+
}, 10000);
151+
152+
console.log('Application started');
153+
})
154+
.catch((error) => {
155+
console.log('Error', error);
156+
});
157+
```
158+
110159
## License
111160

112161
Licensed under the APLv2.

example/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ff-nodejs-server-sdk",
3-
"version": "1.0.1",
3+
"version": "1.1.1",
44
"description": "Feature flags SDK for NodeJS environments",
55
"main": "dist/cjs/index.js",
66
"module": "dist/esm/index.js",

src/client.ts

Lines changed: 116 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,35 @@ import * as events from 'events';
22
import jwt_decode from 'jwt-decode';
33
import axios from 'axios';
44
import axiosRetry from 'axios-retry';
5-
import { Claims, Options, Target } from './types';
5+
import { Claims, Options, StreamEvent, Target } from './types';
66
import { Configuration, ClientApi, FeatureConfig, Variation } from './openapi';
77
import { VERSION } from './version';
8-
import { PollingProcessor } from './polling';
8+
import { PollerEvent, PollingProcessor } from './polling';
99
import { StreamProcessor } from './streaming';
1010
import { Evaluator } from './evaluator';
1111
import { defaultOptions } from './constants';
1212
import { Repository, StorageRepository } from './repository';
13-
import { MetricsProcessor, MetricsProcessorInterface } from './metrics';
13+
import {
14+
MetricEvent,
15+
MetricsProcessor,
16+
MetricsProcessorInterface,
17+
} from './metrics';
1418

1519
axios.defaults.timeout = 30000;
1620
axiosRetry(axios, { retries: 3, retryDelay: axiosRetry.exponentialDelay });
1721
const log = defaultOptions.logger;
1822

23+
enum Processor {
24+
POLL,
25+
STREAM,
26+
METRICS,
27+
}
28+
29+
export enum ClientEvent {
30+
READY = 'ready',
31+
FAILED = 'failed',
32+
CHANGED = 'changed',
33+
}
1934
export default class Client {
2035
private evaluator: Evaluator;
2136
private repository: Repository;
@@ -30,6 +45,12 @@ export default class Client {
3045
private pollProcessor: PollingProcessor;
3146
private streamProcessor: StreamProcessor;
3247
private metricsProcessor: MetricsProcessorInterface;
48+
private initialized = false;
49+
private failure = false;
50+
private waitForInitialize: Promise<Client>;
51+
private pollerReady = false;
52+
private streamReady = false;
53+
private metricReady = false;
3354

3455
constructor(sdkKey: string, options: Options = {}) {
3556
this.sdkKey = sdkKey;
@@ -54,9 +75,47 @@ export default class Client {
5475
);
5576
this.evaluator = new Evaluator(this.repository);
5677
this.api = new ClientApi(this.configuration);
78+
this.processEvents();
5779
this.run();
5880
}
5981

82+
private processEvents(): void {
83+
this.eventBus.on(PollerEvent.READY, () => {
84+
this.initialize(Processor.POLL);
85+
});
86+
87+
this.eventBus.on(PollerEvent.ERROR, () => {
88+
this.failure = true;
89+
this.eventBus.emit(ClientEvent.FAILED);
90+
});
91+
92+
this.eventBus.on(StreamEvent.READY, () => {
93+
this.initialize(Processor.STREAM);
94+
});
95+
96+
this.eventBus.on(StreamEvent.ERROR, () => {
97+
this.failure = true;
98+
this.eventBus.emit(ClientEvent.FAILED);
99+
});
100+
101+
this.eventBus.on(MetricEvent.READY, () => {
102+
this.initialize(Processor.METRICS);
103+
});
104+
105+
this.eventBus.on(MetricEvent.ERROR, () => {
106+
this.failure = true;
107+
this.eventBus.emit(ClientEvent.FAILED);
108+
});
109+
110+
this.eventBus.on(StreamEvent.CONNECTED, () => {
111+
this.pollProcessor.stop();
112+
});
113+
114+
this.eventBus.on(StreamEvent.DISCONNECTED, () => {
115+
this.pollProcessor.start();
116+
});
117+
}
118+
60119
private async authenticate(): Promise<void> {
61120
try {
62121
const response = await this.api.authenticate({
@@ -70,10 +129,63 @@ export default class Client {
70129
this.environment = decoded.environment;
71130
this.cluster = decoded.clusterIdentifier || '1';
72131
} catch (error) {
132+
this.failure = true;
73133
console.error('Error while authenticating, err: ', error);
74134
}
75135
}
76136

137+
waitForInitialization(): Promise<Client> {
138+
if (this.waitForInitialize) {
139+
return this.waitForInitialize;
140+
}
141+
142+
if (this.initialized) {
143+
this.waitForInitialize = Promise.resolve(this);
144+
} else if (this.failure) {
145+
this.waitForInitialize = Promise.reject(this.failure);
146+
} else {
147+
this.waitForInitialize = new Promise((resolve, reject) => {
148+
this.eventBus.once(ClientEvent.READY, () => {
149+
resolve(this);
150+
});
151+
this.eventBus.once(ClientEvent.FAILED, reject);
152+
});
153+
}
154+
return this.waitForInitialize;
155+
}
156+
157+
private initialize(processor: Processor): void {
158+
switch (processor) {
159+
case Processor.POLL:
160+
this.pollerReady = true;
161+
log.debug('PollingProcessor ready');
162+
break;
163+
case Processor.STREAM:
164+
this.streamReady = true;
165+
log.debug('StreamingProcessor ready');
166+
break;
167+
case Processor.METRICS:
168+
this.metricReady = true;
169+
log.debug('MetricsProcessor ready');
170+
break;
171+
}
172+
173+
if (this.options.enableStream && !this.streamReady) {
174+
return;
175+
}
176+
177+
if (this.options.enableAnalytics && !this.metricReady) {
178+
return;
179+
}
180+
181+
if (!this.pollerReady) {
182+
return;
183+
}
184+
185+
this.initialized = true;
186+
this.eventBus.emit(ClientEvent.READY);
187+
}
188+
77189
private async run(): Promise<void> {
78190
await this.authenticate();
79191

@@ -108,6 +220,7 @@ export default class Client {
108220
this.cluster,
109221
this.configuration,
110222
this.options,
223+
this.eventBus,
111224
);
112225
this.metricsProcessor.start();
113226
}

src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Client from './client';
1+
import Client, { ClientEvent } from './client';
22
import LRU from 'lru-cache';
33
import { Options, Target } from './types';
44
import { Logger } from './log';
@@ -7,6 +7,7 @@ import { FileStore } from './store';
77

88
export {
99
Client,
10+
ClientEvent,
1011
Options,
1112
Target,
1213
AsyncKeyValueStore,
@@ -22,6 +23,9 @@ export default {
2223
this.instance = new Client(sdkKey, options);
2324
}
2425
},
26+
waitForInitialization: function (): Promise<Client> {
27+
return this.instance.waitForInitialization();
28+
},
2529
boolVariation: function (
2630
identifier: string,
2731
target: Target,

src/metrics.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as events from 'events';
12
import {
23
defaultOptions,
34
FEATURE_IDENTIFIER_ATTRIBUTE,
@@ -27,6 +28,11 @@ import { VERSION } from './version';
2728

2829
const log = defaultOptions.logger;
2930

31+
export enum MetricEvent {
32+
READY = 'metrics_ready',
33+
ERROR = 'metrics_error',
34+
}
35+
3036
interface AnalyticsEvent {
3137
target: Target;
3238
featureConfig: FeatureConfig;
@@ -49,6 +55,7 @@ export const MetricsProcessor = (
4955
cluster = '1',
5056
conf: Configuration,
5157
options: Options,
58+
eventBus: events.EventEmitter,
5259
): MetricsProcessorInterface => {
5360
const data: Map<string, AnalyticsEvent> = new Map<string, AnalyticsEvent>();
5461
let syncInterval: NodeJS.Timeout;
@@ -202,6 +209,7 @@ export const MetricsProcessor = (
202209
options.eventsSyncInterval,
203210
);
204211
syncInterval = setInterval(_send, options.eventsSyncInterval);
212+
eventBus.emit(MetricEvent.READY);
205213
};
206214

207215
const close = (): void => {

0 commit comments

Comments
 (0)