@featurevisor/sdk 0.12.1 → 0.13.0
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.
- package/CHANGELOG.md +11 -0
- package/README.md +86 -12
- package/dist/index.js +1 -1
- package/dist/index.js.gz +0 -0
- package/dist/index.js.map +1 -1
- package/lib/client.d.ts +5 -0
- package/lib/client.js +20 -13
- package/lib/client.js.map +1 -1
- package/lib/createInstance.d.ts +8 -0
- package/lib/createInstance.js +50 -22
- package/lib/createInstance.js.map +1 -1
- package/lib/emitter.d.ts +12 -0
- package/lib/emitter.js +46 -0
- package/lib/emitter.js.map +1 -0
- package/package.json +3 -3
- package/src/client.ts +34 -13
- package/src/createInstance.spec.ts +26 -0
- package/src/createInstance.ts +78 -22
- package/src/emitter.ts +53 -0
package/lib/createInstance.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { FeaturevisorSDK } from "./client";
|
|
2
2
|
import { createLogger } from "./logger";
|
|
3
|
+
import { Emitter } from "./emitter";
|
|
3
4
|
function fetchDatafileContent(datafileUrl, options) {
|
|
4
5
|
if (options.handleDatafileFetch) {
|
|
5
6
|
return options.handleDatafileFetch(datafileUrl);
|
|
6
7
|
}
|
|
7
8
|
return fetch(datafileUrl).then(function (res) { return res.json(); });
|
|
8
9
|
}
|
|
9
|
-
function getInstanceFromSdk(sdk, options, logger) {
|
|
10
|
+
function getInstanceFromSdk(sdk, options, logger, emitter, statuses) {
|
|
10
11
|
var intervalId;
|
|
11
|
-
var
|
|
12
|
+
var on = emitter.addListener.bind(emitter);
|
|
13
|
+
var off = emitter.removeListener.bind(emitter);
|
|
12
14
|
var instance = {
|
|
13
15
|
// variation
|
|
14
16
|
getVariation: sdk.getVariation.bind(sdk),
|
|
@@ -32,15 +34,22 @@ function getInstanceFromSdk(sdk, options, logger) {
|
|
|
32
34
|
getVariableObject: sdk.getVariableObject.bind(sdk),
|
|
33
35
|
// additions
|
|
34
36
|
setLogLevels: logger.setLevels.bind(logger),
|
|
37
|
+
// emitter
|
|
38
|
+
on: on,
|
|
39
|
+
addListener: on,
|
|
40
|
+
off: off,
|
|
41
|
+
removeListener: off,
|
|
42
|
+
removeAllListeners: emitter.removeAllListeners.bind(emitter),
|
|
43
|
+
// refresh
|
|
35
44
|
refresh: function () {
|
|
36
45
|
logger.debug("refreshing datafile");
|
|
37
|
-
if (refreshInProgress) {
|
|
46
|
+
if (statuses.refreshInProgress) {
|
|
38
47
|
return logger.warn("refresh in progress, skipping");
|
|
39
48
|
}
|
|
40
49
|
if (!options.datafileUrl) {
|
|
41
50
|
return logger.error("cannot refresh since `datafileUrl` is not provided");
|
|
42
51
|
}
|
|
43
|
-
refreshInProgress = true;
|
|
52
|
+
statuses.refreshInProgress = true;
|
|
44
53
|
fetchDatafileContent(options.datafileUrl, options)
|
|
45
54
|
.then(function (datafile) {
|
|
46
55
|
var currentRevision = sdk.getRevision();
|
|
@@ -48,17 +57,15 @@ function getInstanceFromSdk(sdk, options, logger) {
|
|
|
48
57
|
var isNotSameRevision = currentRevision !== newRevision;
|
|
49
58
|
sdk.setDatafile(datafile);
|
|
50
59
|
logger.info("refreshed datafile");
|
|
51
|
-
|
|
52
|
-
|
|
60
|
+
emitter.emit("refresh");
|
|
61
|
+
if (isNotSameRevision) {
|
|
62
|
+
emitter.emit("update");
|
|
53
63
|
}
|
|
54
|
-
|
|
55
|
-
options.onUpdate();
|
|
56
|
-
}
|
|
57
|
-
refreshInProgress = false;
|
|
64
|
+
statuses.refreshInProgress = false;
|
|
58
65
|
})
|
|
59
66
|
.catch(function (e) {
|
|
60
67
|
logger.error("failed to refresh datafile", { error: e });
|
|
61
|
-
refreshInProgress = false;
|
|
68
|
+
statuses.refreshInProgress = false;
|
|
62
69
|
});
|
|
63
70
|
},
|
|
64
71
|
startRefreshing: function () {
|
|
@@ -81,6 +88,9 @@ function getInstanceFromSdk(sdk, options, logger) {
|
|
|
81
88
|
}
|
|
82
89
|
clearInterval(intervalId);
|
|
83
90
|
},
|
|
91
|
+
isReady: function () {
|
|
92
|
+
return statuses.ready;
|
|
93
|
+
},
|
|
84
94
|
};
|
|
85
95
|
if (options.datafileUrl && options.refreshInterval) {
|
|
86
96
|
instance.startRefreshing();
|
|
@@ -99,9 +109,26 @@ export function createInstance(options) {
|
|
|
99
109
|
throw new Error("Featurevisor SDK instance cannot be created without both `datafile` and `datafileUrl` options");
|
|
100
110
|
}
|
|
101
111
|
var logger = options.logger || createLogger();
|
|
112
|
+
var emitter = new Emitter();
|
|
113
|
+
var statuses = {
|
|
114
|
+
ready: false,
|
|
115
|
+
refreshInProgress: false,
|
|
116
|
+
};
|
|
102
117
|
if (!options.datafileUrl && options.refreshInterval) {
|
|
103
118
|
logger.warn("refreshing datafile requires `datafileUrl` option");
|
|
104
119
|
}
|
|
120
|
+
if (options.onReady) {
|
|
121
|
+
emitter.addListener("ready", options.onReady);
|
|
122
|
+
}
|
|
123
|
+
if (options.onActivation) {
|
|
124
|
+
emitter.addListener("activation", options.onActivation);
|
|
125
|
+
}
|
|
126
|
+
if (options.onRefresh) {
|
|
127
|
+
emitter.addListener("refresh", options.onRefresh);
|
|
128
|
+
}
|
|
129
|
+
if (options.onUpdate) {
|
|
130
|
+
emitter.addListener("update", options.onUpdate);
|
|
131
|
+
}
|
|
105
132
|
// datafile content is already provided
|
|
106
133
|
if (options.datafile) {
|
|
107
134
|
var sdk_1 = new FeaturevisorSDK({
|
|
@@ -109,15 +136,15 @@ export function createInstance(options) {
|
|
|
109
136
|
onActivation: options.onActivation,
|
|
110
137
|
configureBucketValue: options.configureBucketValue,
|
|
111
138
|
logger: logger,
|
|
139
|
+
emitter: emitter,
|
|
112
140
|
interceptAttributes: options.interceptAttributes,
|
|
141
|
+
fromInstance: true,
|
|
113
142
|
});
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
return getInstanceFromSdk(sdk_1, options, logger);
|
|
143
|
+
statuses.ready = true;
|
|
144
|
+
setTimeout(function () {
|
|
145
|
+
emitter.emit("ready");
|
|
146
|
+
}, 0);
|
|
147
|
+
return getInstanceFromSdk(sdk_1, options, logger, emitter, statuses);
|
|
121
148
|
}
|
|
122
149
|
// datafile has to be fetched
|
|
123
150
|
var sdk = new FeaturevisorSDK({
|
|
@@ -125,21 +152,22 @@ export function createInstance(options) {
|
|
|
125
152
|
onActivation: options.onActivation,
|
|
126
153
|
configureBucketValue: options.configureBucketValue,
|
|
127
154
|
logger: logger,
|
|
155
|
+
emitter: emitter,
|
|
128
156
|
interceptAttributes: options.interceptAttributes,
|
|
157
|
+
fromInstance: true,
|
|
129
158
|
});
|
|
130
159
|
if (options.datafileUrl) {
|
|
131
160
|
fetchDatafileContent(options.datafileUrl, options)
|
|
132
161
|
.then(function (datafile) {
|
|
133
162
|
sdk.setDatafile(datafile);
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
163
|
+
statuses.ready = true;
|
|
164
|
+
emitter.emit("ready");
|
|
137
165
|
})
|
|
138
166
|
.catch(function (e) {
|
|
139
167
|
logger.error("failed to fetch datafile:");
|
|
140
168
|
console.error(e);
|
|
141
169
|
});
|
|
142
170
|
}
|
|
143
|
-
return getInstanceFromSdk(sdk, options, logger);
|
|
171
|
+
return getInstanceFromSdk(sdk, options, logger, emitter, statuses);
|
|
144
172
|
}
|
|
145
173
|
//# sourceMappingURL=createInstance.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createInstance.js","sourceRoot":"","sources":["../src/createInstance.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAA4C,MAAM,UAAU,CAAC;AACrF,OAAO,EAAE,YAAY,EAAU,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"createInstance.js","sourceRoot":"","sources":["../src/createInstance.ts"],"names":[],"mappings":"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"}
|
package/lib/emitter.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type EventName = "ready" | "refresh" | "update" | "activation";
|
|
2
|
+
export interface Listeners {
|
|
3
|
+
[key: string]: Function[];
|
|
4
|
+
}
|
|
5
|
+
export declare class Emitter {
|
|
6
|
+
private _listeners;
|
|
7
|
+
constructor();
|
|
8
|
+
addListener(eventName: EventName, fn: Function): void;
|
|
9
|
+
removeListener(eventName: EventName, fn: Function): void;
|
|
10
|
+
removeAllListeners(eventName?: EventName): void;
|
|
11
|
+
emit(eventName: EventName, ...args: any[]): void;
|
|
12
|
+
}
|
package/lib/emitter.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
var Emitter = /** @class */ (function () {
|
|
2
|
+
function Emitter() {
|
|
3
|
+
this._listeners = {};
|
|
4
|
+
}
|
|
5
|
+
Emitter.prototype.addListener = function (eventName, fn) {
|
|
6
|
+
if (typeof this._listeners[eventName] === "undefined") {
|
|
7
|
+
this._listeners[eventName] = [];
|
|
8
|
+
}
|
|
9
|
+
this._listeners[eventName].push(fn);
|
|
10
|
+
};
|
|
11
|
+
Emitter.prototype.removeListener = function (eventName, fn) {
|
|
12
|
+
if (typeof this._listeners[eventName] === "undefined") {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
var index = this._listeners[eventName].indexOf(fn);
|
|
16
|
+
if (index !== -1) {
|
|
17
|
+
this._listeners[eventName].splice(index, 1);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
Emitter.prototype.removeAllListeners = function (eventName) {
|
|
21
|
+
var _this = this;
|
|
22
|
+
if (eventName) {
|
|
23
|
+
this._listeners[eventName] = [];
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
Object.keys(this._listeners).forEach(function (key) {
|
|
27
|
+
_this._listeners[key] = [];
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
Emitter.prototype.emit = function (eventName) {
|
|
32
|
+
var args = [];
|
|
33
|
+
for (var _i = 1; _i < arguments.length; _i++) {
|
|
34
|
+
args[_i - 1] = arguments[_i];
|
|
35
|
+
}
|
|
36
|
+
if (typeof this._listeners[eventName] === "undefined") {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
this._listeners[eventName].forEach(function (fn) {
|
|
40
|
+
fn.apply(void 0, args);
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
return Emitter;
|
|
44
|
+
}());
|
|
45
|
+
export { Emitter };
|
|
46
|
+
//# sourceMappingURL=emitter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emitter.js","sourceRoot":"","sources":["../src/emitter.ts"],"names":[],"mappings":"AAMA;IAGE;QACE,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC;IACvB,CAAC;IAEM,6BAAW,GAAlB,UAAmB,SAAoB,EAAE,EAAY;QACnD,IAAI,OAAO,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,KAAK,WAAW,EAAE;YACrD,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC;SACjC;QAED,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACtC,CAAC;IAEM,gCAAc,GAArB,UAAsB,SAAoB,EAAE,EAAY;QACtD,IAAI,OAAO,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,KAAK,WAAW,EAAE;YACrD,OAAO;SACR;QAED,IAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAErD,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE;YAChB,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;SAC7C;IACH,CAAC;IAEM,oCAAkB,GAAzB,UAA0B,SAAqB;QAA/C,iBAQC;QAPC,IAAI,SAAS,EAAE;YACb,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC;SACjC;aAAM;YACL,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,UAAC,GAAG;gBACvC,KAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;YAC5B,CAAC,CAAC,CAAC;SACJ;IACH,CAAC;IAEM,sBAAI,GAAX,UAAY,SAAoB;QAAE,cAAc;aAAd,UAAc,EAAd,qBAAc,EAAd,IAAc;YAAd,6BAAc;;QAC9C,IAAI,OAAO,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,KAAK,WAAW,EAAE;YACrD,OAAO;SACR;QAED,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,UAAC,EAAE;YACpC,EAAE,eAAI,IAAI,EAAE;QACd,CAAC,CAAC,CAAC;IACL,CAAC;IACH,cAAC;AAAD,CAAC,AA9CD,IA8CC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@featurevisor/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Featurevisor SDK for Node.js and the browser",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "lib/index.js",
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
},
|
|
43
43
|
"license": "MIT",
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@featurevisor/types": "^0.
|
|
45
|
+
"@featurevisor/types": "^0.13.0",
|
|
46
46
|
"compare-versions": "^6.0.0-rc.1",
|
|
47
47
|
"murmurhash": "^2.0.1"
|
|
48
48
|
},
|
|
49
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "84bc69876c7b5ea080d0dff53b305bd47b3d43e1"
|
|
50
50
|
}
|
package/src/client.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
} from "./feature";
|
|
20
20
|
import { getBucketedNumber } from "./bucket";
|
|
21
21
|
import { createLogger, Logger } from "./logger";
|
|
22
|
+
import { Emitter } from "./emitter";
|
|
22
23
|
|
|
23
24
|
export type ActivationCallback = (
|
|
24
25
|
featureName: string,
|
|
@@ -34,7 +35,9 @@ export interface SdkOptions {
|
|
|
34
35
|
onActivation?: ActivationCallback; // @TODO: move it to FeaturevisorInstance in next breaking semver
|
|
35
36
|
configureBucketValue?: ConfigureBucketValue;
|
|
36
37
|
logger?: Logger; // @TODO: keep it in FeaturevisorInstance only in next breaking semver
|
|
38
|
+
emitter?: Emitter; // @TODO: keep it in FeaturevisorInstance only in next breaking semver
|
|
37
39
|
interceptAttributes?: (attributes: Attributes) => Attributes; // @TODO: move it to FeaturevisorInstance in next breaking semver
|
|
40
|
+
fromInstance?: boolean;
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
type FieldType = VariationType | VariableType;
|
|
@@ -71,7 +74,9 @@ export class FeaturevisorSDK {
|
|
|
71
74
|
private datafileReader: DatafileReader;
|
|
72
75
|
private configureBucketValue?: ConfigureBucketValue;
|
|
73
76
|
private logger: Logger;
|
|
77
|
+
private emitter?: Emitter;
|
|
74
78
|
private interceptAttributes?: (attributes: Attributes) => Attributes;
|
|
79
|
+
private fromInstance: boolean;
|
|
75
80
|
|
|
76
81
|
constructor(options: SdkOptions) {
|
|
77
82
|
if (options.onActivation) {
|
|
@@ -88,7 +93,13 @@ export class FeaturevisorSDK {
|
|
|
88
93
|
this.interceptAttributes = options.interceptAttributes;
|
|
89
94
|
}
|
|
90
95
|
|
|
96
|
+
if (options.emitter) {
|
|
97
|
+
this.emitter = options.emitter;
|
|
98
|
+
}
|
|
99
|
+
|
|
91
100
|
this.setDatafile(options.datafile);
|
|
101
|
+
|
|
102
|
+
this.fromInstance = options.fromInstance || false;
|
|
92
103
|
}
|
|
93
104
|
|
|
94
105
|
setDatafile(datafile: DatafileContent | string) {
|
|
@@ -243,23 +254,33 @@ export class FeaturevisorSDK {
|
|
|
243
254
|
return undefined;
|
|
244
255
|
}
|
|
245
256
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
257
|
+
const finalAttributes = this.interceptAttributes
|
|
258
|
+
? this.interceptAttributes(attributes)
|
|
259
|
+
: attributes;
|
|
260
|
+
|
|
261
|
+
const captureAttributes: Attributes = {};
|
|
250
262
|
|
|
251
|
-
|
|
263
|
+
const attributesForCapturing = this.datafileReader
|
|
264
|
+
.getAllAttributes()
|
|
265
|
+
.filter((a) => a.capture === true);
|
|
252
266
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
.
|
|
267
|
+
attributesForCapturing.forEach((a) => {
|
|
268
|
+
if (typeof finalAttributes[a.key] !== "undefined") {
|
|
269
|
+
captureAttributes[a.key] = attributes[a.key];
|
|
270
|
+
}
|
|
271
|
+
});
|
|
256
272
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
273
|
+
if (this.emitter) {
|
|
274
|
+
this.emitter.emit(
|
|
275
|
+
"activation",
|
|
276
|
+
featureKey,
|
|
277
|
+
variationValue,
|
|
278
|
+
finalAttributes,
|
|
279
|
+
captureAttributes,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
262
282
|
|
|
283
|
+
if (this.fromInstance && this.onActivation) {
|
|
263
284
|
this.onActivation(featureKey, variationValue, finalAttributes, captureAttributes);
|
|
264
285
|
}
|
|
265
286
|
|
|
@@ -21,6 +21,28 @@ describe("sdk: createInstance", function () {
|
|
|
21
21
|
expect(typeof sdk.getVariation).toEqual("function");
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
+
it("should trigger onReady event once", function (done) {
|
|
25
|
+
let readyCount = 0;
|
|
26
|
+
|
|
27
|
+
const sdk = createInstance({
|
|
28
|
+
datafile: {
|
|
29
|
+
schemaVersion: "1",
|
|
30
|
+
revision: "1.0",
|
|
31
|
+
features: [],
|
|
32
|
+
attributes: [],
|
|
33
|
+
segments: [],
|
|
34
|
+
},
|
|
35
|
+
onReady: () => {
|
|
36
|
+
readyCount += 1;
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
setTimeout(() => {
|
|
41
|
+
expect(readyCount).toEqual(1);
|
|
42
|
+
done();
|
|
43
|
+
}, 0);
|
|
44
|
+
});
|
|
45
|
+
|
|
24
46
|
it("should intercept attributes", function () {
|
|
25
47
|
let intercepted = false;
|
|
26
48
|
|
|
@@ -126,10 +148,14 @@ describe("sdk: createInstance", function () {
|
|
|
126
148
|
},
|
|
127
149
|
});
|
|
128
150
|
|
|
151
|
+
expect(sdk.isReady()).toEqual(false);
|
|
152
|
+
|
|
129
153
|
setTimeout(function () {
|
|
130
154
|
expect(refreshed).toEqual(true);
|
|
131
155
|
expect(updated).toEqual(true);
|
|
132
156
|
|
|
157
|
+
expect(sdk.isReady()).toEqual(true);
|
|
158
|
+
|
|
133
159
|
sdk.stopRefreshing();
|
|
134
160
|
|
|
135
161
|
done();
|
package/src/createInstance.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DatafileContent, Attributes } from "@featurevisor/types";
|
|
2
2
|
import { FeaturevisorSDK, ConfigureBucketValue, ActivationCallback } from "./client";
|
|
3
3
|
import { createLogger, Logger } from "./logger";
|
|
4
|
+
import { Emitter } from "./emitter";
|
|
4
5
|
|
|
5
6
|
export type ReadyCallback = () => void;
|
|
6
7
|
|
|
@@ -21,6 +22,8 @@ export interface InstanceOptions {
|
|
|
21
22
|
onUpdate?: () => void;
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
export type Event = "ready" | "refresh" | "update" | "activation";
|
|
26
|
+
|
|
24
27
|
// @TODO: consider renaming it to FeaturevisorSDK in next breaking semver
|
|
25
28
|
export interface FeaturevisorInstance {
|
|
26
29
|
/**
|
|
@@ -54,9 +57,18 @@ export interface FeaturevisorInstance {
|
|
|
54
57
|
* Additions
|
|
55
58
|
*/
|
|
56
59
|
setLogLevels: Logger["setLevels"];
|
|
60
|
+
|
|
57
61
|
refresh: () => void;
|
|
58
62
|
startRefreshing: () => void;
|
|
59
63
|
stopRefreshing: () => void;
|
|
64
|
+
|
|
65
|
+
addListener: Emitter["addListener"];
|
|
66
|
+
on: Emitter["addListener"];
|
|
67
|
+
removeListener: Emitter["removeListener"];
|
|
68
|
+
off: Emitter["removeListener"];
|
|
69
|
+
removeAllListeners: Emitter["removeAllListeners"];
|
|
70
|
+
|
|
71
|
+
isReady: () => boolean;
|
|
60
72
|
}
|
|
61
73
|
|
|
62
74
|
function fetchDatafileContent(datafileUrl, options: InstanceOptions): Promise<DatafileContent> {
|
|
@@ -67,13 +79,26 @@ function fetchDatafileContent(datafileUrl, options: InstanceOptions): Promise<Da
|
|
|
67
79
|
return fetch(datafileUrl).then((res) => res.json());
|
|
68
80
|
}
|
|
69
81
|
|
|
82
|
+
interface Listeners {
|
|
83
|
+
[key: string]: Function[];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface Statuses {
|
|
87
|
+
ready: boolean;
|
|
88
|
+
refreshInProgress: boolean;
|
|
89
|
+
}
|
|
90
|
+
|
|
70
91
|
function getInstanceFromSdk(
|
|
71
92
|
sdk: FeaturevisorSDK,
|
|
72
93
|
options: InstanceOptions,
|
|
73
94
|
logger: Logger,
|
|
95
|
+
emitter: Emitter,
|
|
96
|
+
statuses: Statuses,
|
|
74
97
|
): FeaturevisorInstance {
|
|
75
98
|
let intervalId;
|
|
76
|
-
|
|
99
|
+
|
|
100
|
+
const on = emitter.addListener.bind(emitter);
|
|
101
|
+
const off = emitter.removeListener.bind(emitter);
|
|
77
102
|
|
|
78
103
|
const instance: FeaturevisorInstance = {
|
|
79
104
|
// variation
|
|
@@ -102,10 +127,18 @@ function getInstanceFromSdk(
|
|
|
102
127
|
// additions
|
|
103
128
|
setLogLevels: logger.setLevels.bind(logger),
|
|
104
129
|
|
|
130
|
+
// emitter
|
|
131
|
+
on: on,
|
|
132
|
+
addListener: on,
|
|
133
|
+
off: off,
|
|
134
|
+
removeListener: off,
|
|
135
|
+
removeAllListeners: emitter.removeAllListeners.bind(emitter),
|
|
136
|
+
|
|
137
|
+
// refresh
|
|
105
138
|
refresh() {
|
|
106
139
|
logger.debug("refreshing datafile");
|
|
107
140
|
|
|
108
|
-
if (refreshInProgress) {
|
|
141
|
+
if (statuses.refreshInProgress) {
|
|
109
142
|
return logger.warn("refresh in progress, skipping");
|
|
110
143
|
}
|
|
111
144
|
|
|
@@ -113,7 +146,7 @@ function getInstanceFromSdk(
|
|
|
113
146
|
return logger.error("cannot refresh since `datafileUrl` is not provided");
|
|
114
147
|
}
|
|
115
148
|
|
|
116
|
-
refreshInProgress = true;
|
|
149
|
+
statuses.refreshInProgress = true;
|
|
117
150
|
|
|
118
151
|
fetchDatafileContent(options.datafileUrl, options)
|
|
119
152
|
.then((datafile) => {
|
|
@@ -124,19 +157,17 @@ function getInstanceFromSdk(
|
|
|
124
157
|
sdk.setDatafile(datafile);
|
|
125
158
|
logger.info("refreshed datafile");
|
|
126
159
|
|
|
127
|
-
|
|
128
|
-
options.onRefresh();
|
|
129
|
-
}
|
|
160
|
+
emitter.emit("refresh");
|
|
130
161
|
|
|
131
|
-
if (isNotSameRevision
|
|
132
|
-
|
|
162
|
+
if (isNotSameRevision) {
|
|
163
|
+
emitter.emit("update");
|
|
133
164
|
}
|
|
134
165
|
|
|
135
|
-
refreshInProgress = false;
|
|
166
|
+
statuses.refreshInProgress = false;
|
|
136
167
|
})
|
|
137
168
|
.catch((e) => {
|
|
138
169
|
logger.error("failed to refresh datafile", { error: e });
|
|
139
|
-
refreshInProgress = false;
|
|
170
|
+
statuses.refreshInProgress = false;
|
|
140
171
|
});
|
|
141
172
|
},
|
|
142
173
|
|
|
@@ -165,6 +196,10 @@ function getInstanceFromSdk(
|
|
|
165
196
|
|
|
166
197
|
clearInterval(intervalId);
|
|
167
198
|
},
|
|
199
|
+
|
|
200
|
+
isReady() {
|
|
201
|
+
return statuses.ready;
|
|
202
|
+
},
|
|
168
203
|
};
|
|
169
204
|
|
|
170
205
|
if (options.datafileUrl && options.refreshInterval) {
|
|
@@ -190,11 +225,32 @@ export function createInstance(options: InstanceOptions) {
|
|
|
190
225
|
}
|
|
191
226
|
|
|
192
227
|
const logger = options.logger || createLogger();
|
|
228
|
+
const emitter = new Emitter();
|
|
229
|
+
const statuses: Statuses = {
|
|
230
|
+
ready: false,
|
|
231
|
+
refreshInProgress: false,
|
|
232
|
+
};
|
|
193
233
|
|
|
194
234
|
if (!options.datafileUrl && options.refreshInterval) {
|
|
195
235
|
logger.warn("refreshing datafile requires `datafileUrl` option");
|
|
196
236
|
}
|
|
197
237
|
|
|
238
|
+
if (options.onReady) {
|
|
239
|
+
emitter.addListener("ready", options.onReady);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (options.onActivation) {
|
|
243
|
+
emitter.addListener("activation", options.onActivation);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (options.onRefresh) {
|
|
247
|
+
emitter.addListener("refresh", options.onRefresh);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (options.onUpdate) {
|
|
251
|
+
emitter.addListener("update", options.onUpdate);
|
|
252
|
+
}
|
|
253
|
+
|
|
198
254
|
// datafile content is already provided
|
|
199
255
|
if (options.datafile) {
|
|
200
256
|
const sdk = new FeaturevisorSDK({
|
|
@@ -202,18 +258,17 @@ export function createInstance(options: InstanceOptions) {
|
|
|
202
258
|
onActivation: options.onActivation,
|
|
203
259
|
configureBucketValue: options.configureBucketValue,
|
|
204
260
|
logger,
|
|
261
|
+
emitter,
|
|
205
262
|
interceptAttributes: options.interceptAttributes,
|
|
263
|
+
fromInstance: true,
|
|
206
264
|
});
|
|
207
265
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
onReady();
|
|
213
|
-
}, 0);
|
|
214
|
-
}
|
|
266
|
+
statuses.ready = true;
|
|
267
|
+
setTimeout(function () {
|
|
268
|
+
emitter.emit("ready");
|
|
269
|
+
}, 0);
|
|
215
270
|
|
|
216
|
-
return getInstanceFromSdk(sdk, options, logger);
|
|
271
|
+
return getInstanceFromSdk(sdk, options, logger, emitter, statuses);
|
|
217
272
|
}
|
|
218
273
|
|
|
219
274
|
// datafile has to be fetched
|
|
@@ -222,7 +277,9 @@ export function createInstance(options: InstanceOptions) {
|
|
|
222
277
|
onActivation: options.onActivation,
|
|
223
278
|
configureBucketValue: options.configureBucketValue,
|
|
224
279
|
logger,
|
|
280
|
+
emitter,
|
|
225
281
|
interceptAttributes: options.interceptAttributes,
|
|
282
|
+
fromInstance: true,
|
|
226
283
|
});
|
|
227
284
|
|
|
228
285
|
if (options.datafileUrl) {
|
|
@@ -230,9 +287,8 @@ export function createInstance(options: InstanceOptions) {
|
|
|
230
287
|
.then((datafile) => {
|
|
231
288
|
sdk.setDatafile(datafile);
|
|
232
289
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
290
|
+
statuses.ready = true;
|
|
291
|
+
emitter.emit("ready");
|
|
236
292
|
})
|
|
237
293
|
.catch((e) => {
|
|
238
294
|
logger.error("failed to fetch datafile:");
|
|
@@ -240,5 +296,5 @@ export function createInstance(options: InstanceOptions) {
|
|
|
240
296
|
});
|
|
241
297
|
}
|
|
242
298
|
|
|
243
|
-
return getInstanceFromSdk(sdk, options, logger);
|
|
299
|
+
return getInstanceFromSdk(sdk, options, logger, emitter, statuses);
|
|
244
300
|
}
|
package/src/emitter.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export type EventName = "ready" | "refresh" | "update" | "activation";
|
|
2
|
+
|
|
3
|
+
export interface Listeners {
|
|
4
|
+
[key: string]: Function[];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class Emitter {
|
|
8
|
+
private _listeners: Listeners;
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
this._listeners = {};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public addListener(eventName: EventName, fn: Function): void {
|
|
15
|
+
if (typeof this._listeners[eventName] === "undefined") {
|
|
16
|
+
this._listeners[eventName] = [];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
this._listeners[eventName].push(fn);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public removeListener(eventName: EventName, fn: Function): void {
|
|
23
|
+
if (typeof this._listeners[eventName] === "undefined") {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const index = this._listeners[eventName].indexOf(fn);
|
|
28
|
+
|
|
29
|
+
if (index !== -1) {
|
|
30
|
+
this._listeners[eventName].splice(index, 1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public removeAllListeners(eventName?: EventName): void {
|
|
35
|
+
if (eventName) {
|
|
36
|
+
this._listeners[eventName] = [];
|
|
37
|
+
} else {
|
|
38
|
+
Object.keys(this._listeners).forEach((key) => {
|
|
39
|
+
this._listeners[key] = [];
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public emit(eventName: EventName, ...args: any[]): void {
|
|
45
|
+
if (typeof this._listeners[eventName] === "undefined") {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this._listeners[eventName].forEach((fn) => {
|
|
50
|
+
fn(...args);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|