@openfeature/ofrep-web-provider 0.3.1 → 0.3.3
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/index.cjs.js +105 -59
- package/index.esm.d.ts +1 -0
- package/index.esm.js +103 -55
- package/package.json +10 -8
- package/src/lib/model/in-memory-cache.d.ts +7 -3
- package/src/lib/model/ofrep-web-provider-options.d.ts +1 -1
- package/src/lib/model/resolution-error.d.ts +2 -3
- package/src/lib/ofrep-web-provider.d.ts +10 -7
package/index.cjs.js
CHANGED
|
@@ -1,12 +1,40 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
-
|
|
5
|
-
var tslib = require('tslib');
|
|
6
|
-
var core = require('@openfeature/core');
|
|
7
3
|
var ofrepCore = require('@openfeature/ofrep-core');
|
|
8
4
|
var webSdk = require('@openfeature/web-sdk');
|
|
9
5
|
|
|
6
|
+
/******************************************************************************
|
|
7
|
+
Copyright (c) Microsoft Corporation.
|
|
8
|
+
|
|
9
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
10
|
+
purpose with or without fee is hereby granted.
|
|
11
|
+
|
|
12
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
13
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
14
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
15
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
16
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
17
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
18
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
19
|
+
***************************************************************************** */
|
|
20
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
24
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
25
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
26
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
27
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
28
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
29
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
34
|
+
var e = new Error(message);
|
|
35
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
36
|
+
};
|
|
37
|
+
|
|
10
38
|
var BulkEvaluationStatus;
|
|
11
39
|
(function (BulkEvaluationStatus) {
|
|
12
40
|
BulkEvaluationStatus["SUCCESS_NO_CHANGES"] = "SUCCESS_NO_CHANGES";
|
|
@@ -20,6 +48,16 @@ function isResolutionError(response) {
|
|
|
20
48
|
return 'reason' in response && 'errorCode' in response && !('value' in response);
|
|
21
49
|
}
|
|
22
50
|
|
|
51
|
+
const ErrorMessageMap = {
|
|
52
|
+
[webSdk.ErrorCode.FLAG_NOT_FOUND]: 'Flag was not found',
|
|
53
|
+
[webSdk.ErrorCode.GENERAL]: 'General error',
|
|
54
|
+
[webSdk.ErrorCode.INVALID_CONTEXT]: 'Context is invalid or could be parsed',
|
|
55
|
+
[webSdk.ErrorCode.PARSE_ERROR]: 'Flag or flag configuration could not be parsed',
|
|
56
|
+
[webSdk.ErrorCode.PROVIDER_FATAL]: 'Provider is in a fatal error state',
|
|
57
|
+
[webSdk.ErrorCode.PROVIDER_NOT_READY]: 'Provider is not yet ready',
|
|
58
|
+
[webSdk.ErrorCode.TARGETING_KEY_MISSING]: 'Targeting key is missing',
|
|
59
|
+
[webSdk.ErrorCode.TYPE_MISMATCH]: 'Flag is not of expected type',
|
|
60
|
+
};
|
|
23
61
|
class OFREPWebProvider {
|
|
24
62
|
constructor(options, logger) {
|
|
25
63
|
var _a;
|
|
@@ -30,6 +68,7 @@ class OFREPWebProvider {
|
|
|
30
68
|
this.runsOn = 'client';
|
|
31
69
|
this.events = new webSdk.OpenFeatureEventEmitter();
|
|
32
70
|
this._flagCache = {};
|
|
71
|
+
this._flagSetMetadataCache = {};
|
|
33
72
|
this._options = options;
|
|
34
73
|
this._logger = logger;
|
|
35
74
|
this._etag = null;
|
|
@@ -47,11 +86,11 @@ class OFREPWebProvider {
|
|
|
47
86
|
* @param context - the context to use for the evaluation
|
|
48
87
|
*/
|
|
49
88
|
initialize(context) {
|
|
50
|
-
return
|
|
89
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
51
90
|
var _a;
|
|
52
91
|
try {
|
|
53
92
|
this._context = context;
|
|
54
|
-
yield this.
|
|
93
|
+
yield this._fetchFlags(context);
|
|
55
94
|
if (this._pollingInterval > 0) {
|
|
56
95
|
this.startPolling();
|
|
57
96
|
}
|
|
@@ -59,7 +98,7 @@ class OFREPWebProvider {
|
|
|
59
98
|
}
|
|
60
99
|
catch (error) {
|
|
61
100
|
if (error instanceof ofrepCore.OFREPApiUnauthorizedError || error instanceof ofrepCore.OFREPForbiddenError) {
|
|
62
|
-
throw new webSdk.ProviderFatalError('Initialization failed', error);
|
|
101
|
+
throw new webSdk.ProviderFatalError('Initialization failed', { cause: error });
|
|
63
102
|
}
|
|
64
103
|
throw error;
|
|
65
104
|
}
|
|
@@ -68,18 +107,17 @@ class OFREPWebProvider {
|
|
|
68
107
|
/* eslint-disable @typescript-eslint/no-unused-vars*/
|
|
69
108
|
/* to make overrides easier we keep these unused vars */
|
|
70
109
|
resolveBooleanEvaluation(flagKey, defaultValue, context) {
|
|
71
|
-
return this.
|
|
110
|
+
return this._resolve(flagKey, 'boolean', defaultValue);
|
|
72
111
|
}
|
|
73
112
|
resolveStringEvaluation(flagKey, defaultValue, context) {
|
|
74
|
-
return this.
|
|
113
|
+
return this._resolve(flagKey, 'string', defaultValue);
|
|
75
114
|
}
|
|
76
115
|
resolveNumberEvaluation(flagKey, defaultValue, context) {
|
|
77
|
-
return this.
|
|
116
|
+
return this._resolve(flagKey, 'number', defaultValue);
|
|
78
117
|
}
|
|
79
118
|
resolveObjectEvaluation(flagKey, defaultValue, context) {
|
|
80
|
-
return this.
|
|
119
|
+
return this._resolve(flagKey, 'object', defaultValue);
|
|
81
120
|
}
|
|
82
|
-
/* eslint-enable @typescript-eslint/no-unused-vars */
|
|
83
121
|
/**
|
|
84
122
|
* onContextChange is called when the context changes, it will re-evaluate the flags with the new context
|
|
85
123
|
* and update the cache.
|
|
@@ -87,7 +125,7 @@ class OFREPWebProvider {
|
|
|
87
125
|
* @param newContext - the new evaluation context
|
|
88
126
|
*/
|
|
89
127
|
onContextChange(oldContext, newContext) {
|
|
90
|
-
return
|
|
128
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
91
129
|
var _a, _b, _c;
|
|
92
130
|
try {
|
|
93
131
|
this._context = newContext;
|
|
@@ -96,7 +134,7 @@ class OFREPWebProvider {
|
|
|
96
134
|
// we do nothing because we should not call the endpoint
|
|
97
135
|
return;
|
|
98
136
|
}
|
|
99
|
-
yield this.
|
|
137
|
+
yield this._fetchFlags(newContext);
|
|
100
138
|
}
|
|
101
139
|
catch (error) {
|
|
102
140
|
if (error instanceof ofrepCore.OFREPApiTooManyRequestsError) {
|
|
@@ -122,7 +160,7 @@ class OFREPWebProvider {
|
|
|
122
160
|
return Promise.resolve();
|
|
123
161
|
}
|
|
124
162
|
/**
|
|
125
|
-
*
|
|
163
|
+
* _fetchFlags is a function that will call the bulk evaluate flags endpoint to get the flags values.
|
|
126
164
|
* @param context - the context to use for the evaluation
|
|
127
165
|
* @private
|
|
128
166
|
* @returns EvaluationStatus if the evaluation the API returned a 304, 200.
|
|
@@ -131,9 +169,9 @@ class OFREPWebProvider {
|
|
|
131
169
|
* @throws ParseError if the API returned a 400 with the error code ParseError
|
|
132
170
|
* @throws GeneralError if the API returned a 400 with an unknown error code
|
|
133
171
|
*/
|
|
134
|
-
|
|
135
|
-
return
|
|
136
|
-
var _a
|
|
172
|
+
_fetchFlags(context) {
|
|
173
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
174
|
+
var _a;
|
|
137
175
|
try {
|
|
138
176
|
const evalReq = {
|
|
139
177
|
context,
|
|
@@ -144,31 +182,38 @@ class OFREPWebProvider {
|
|
|
144
182
|
return { status: BulkEvaluationStatus.SUCCESS_NO_CHANGES, flags: [] };
|
|
145
183
|
}
|
|
146
184
|
if (response.httpStatus !== 200) {
|
|
147
|
-
|
|
185
|
+
throw new webSdk.GeneralError(`Failed OFREP bulk evaluation request, status: ${response.httpStatus}`);
|
|
148
186
|
}
|
|
149
187
|
const bulkSuccessResp = response.value;
|
|
150
188
|
const newCache = {};
|
|
151
|
-
(
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
189
|
+
if ('flags' in bulkSuccessResp && Array.isArray(bulkSuccessResp.flags)) {
|
|
190
|
+
bulkSuccessResp.flags.forEach((evalResp) => {
|
|
191
|
+
if (ofrepCore.isEvaluationFailureResponse(evalResp)) {
|
|
192
|
+
newCache[evalResp.key] = {
|
|
193
|
+
reason: webSdk.StandardResolutionReasons.ERROR,
|
|
194
|
+
flagMetadata: evalResp.metadata,
|
|
195
|
+
errorCode: evalResp.errorCode,
|
|
196
|
+
errorDetails: evalResp.errorDetails,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
if (ofrepCore.isEvaluationSuccessResponse(evalResp) && evalResp.key) {
|
|
200
|
+
newCache[evalResp.key] = {
|
|
201
|
+
value: evalResp.value,
|
|
202
|
+
variant: evalResp.variant,
|
|
203
|
+
reason: evalResp.reason,
|
|
204
|
+
flagMetadata: evalResp.metadata,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
const listUpdatedFlags = this._getListUpdatedFlags(this._flagCache, newCache);
|
|
209
|
+
this._flagCache = newCache;
|
|
210
|
+
this._etag = (_a = response.httpResponse) === null || _a === void 0 ? void 0 : _a.headers.get('etag');
|
|
211
|
+
this._flagSetMetadataCache = typeof bulkSuccessResp.metadata === 'object' ? bulkSuccessResp.metadata : {};
|
|
212
|
+
return { status: BulkEvaluationStatus.SUCCESS_WITH_CHANGES, flags: listUpdatedFlags };
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
throw new Error('No flags in OFREP bulk evaluation response');
|
|
216
|
+
}
|
|
172
217
|
}
|
|
173
218
|
catch (error) {
|
|
174
219
|
if (error instanceof ofrepCore.OFREPApiTooManyRequestsError && error.retryAfterDate !== null) {
|
|
@@ -204,33 +249,34 @@ class OFREPWebProvider {
|
|
|
204
249
|
return changedKeys;
|
|
205
250
|
}
|
|
206
251
|
/**
|
|
207
|
-
*
|
|
252
|
+
* _resolve is a function retrieving the value from a flag in the cache.
|
|
208
253
|
* @param flagKey - name of the flag to retrieve
|
|
209
254
|
* @param type - type of the flag
|
|
255
|
+
* @param defaultValue - default value
|
|
210
256
|
* @private
|
|
211
257
|
*/
|
|
212
|
-
|
|
258
|
+
_resolve(flagKey, type, defaultValue) {
|
|
213
259
|
const resolved = this._flagCache[flagKey];
|
|
214
260
|
if (!resolved) {
|
|
215
|
-
|
|
261
|
+
return {
|
|
262
|
+
value: defaultValue,
|
|
263
|
+
flagMetadata: this._flagSetMetadataCache,
|
|
264
|
+
reason: webSdk.StandardResolutionReasons.ERROR,
|
|
265
|
+
errorCode: webSdk.ErrorCode.FLAG_NOT_FOUND,
|
|
266
|
+
errorMessage: ErrorMessageMap[webSdk.ErrorCode.FLAG_NOT_FOUND],
|
|
267
|
+
};
|
|
216
268
|
}
|
|
217
269
|
if (isResolutionError(resolved)) {
|
|
218
|
-
|
|
219
|
-
case ofrepCore.EvaluationFailureErrorCode.FlagNotFound:
|
|
220
|
-
throw new webSdk.FlagNotFoundError(`flag key ${flagKey} not found: ${resolved.errorDetails}`);
|
|
221
|
-
case ofrepCore.EvaluationFailureErrorCode.TargetingKeyMissing:
|
|
222
|
-
throw new core.TargetingKeyMissingError(`targeting key missing for flag key ${flagKey}: ${resolved.errorDetails}`);
|
|
223
|
-
case ofrepCore.EvaluationFailureErrorCode.InvalidContext:
|
|
224
|
-
throw new core.InvalidContextError(`invalid context for flag key ${flagKey}: ${resolved.errorDetails}`);
|
|
225
|
-
case ofrepCore.EvaluationFailureErrorCode.ParseError:
|
|
226
|
-
throw new core.ParseError(`parse error for flag key ${flagKey}: ${resolved.errorDetails}`);
|
|
227
|
-
case ofrepCore.EvaluationFailureErrorCode.General:
|
|
228
|
-
default:
|
|
229
|
-
throw new webSdk.GeneralError(`general error during flag evaluation for flag key ${flagKey}: ${resolved.errorDetails}`);
|
|
230
|
-
}
|
|
270
|
+
return Object.assign(Object.assign({}, resolved), { value: defaultValue, errorMessage: ErrorMessageMap[resolved.errorCode] });
|
|
231
271
|
}
|
|
232
272
|
if (typeof resolved.value !== type) {
|
|
233
|
-
|
|
273
|
+
return {
|
|
274
|
+
value: defaultValue,
|
|
275
|
+
flagMetadata: resolved.flagMetadata,
|
|
276
|
+
reason: webSdk.StandardResolutionReasons.ERROR,
|
|
277
|
+
errorCode: webSdk.ErrorCode.TYPE_MISMATCH,
|
|
278
|
+
errorMessage: ErrorMessageMap[webSdk.ErrorCode.TYPE_MISMATCH],
|
|
279
|
+
};
|
|
234
280
|
}
|
|
235
281
|
return {
|
|
236
282
|
variant: resolved.variant,
|
|
@@ -246,14 +292,14 @@ class OFREPWebProvider {
|
|
|
246
292
|
* @private
|
|
247
293
|
*/
|
|
248
294
|
startPolling() {
|
|
249
|
-
this._pollingIntervalId = setInterval(() =>
|
|
295
|
+
this._pollingIntervalId = setInterval(() => __awaiter(this, void 0, void 0, function* () {
|
|
250
296
|
var _a, _b;
|
|
251
297
|
try {
|
|
252
298
|
const now = new Date();
|
|
253
299
|
if (this._retryPollingAfter !== undefined && this._retryPollingAfter > now) {
|
|
254
300
|
return;
|
|
255
301
|
}
|
|
256
|
-
const res = yield this.
|
|
302
|
+
const res = yield this._fetchFlags(this._context);
|
|
257
303
|
if (res.status === BulkEvaluationStatus.SUCCESS_WITH_CHANGES) {
|
|
258
304
|
(_a = this.events) === null || _a === void 0 ? void 0 : _a.emit(webSdk.ClientProviderEvents.ConfigurationChanged, {
|
|
259
305
|
message: 'Flags updated',
|
package/index.esm.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./src/index";
|
package/index.esm.js
CHANGED
|
@@ -1,7 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { StandardResolutionReasons,
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { OFREPApi, OFREPApiUnauthorizedError, OFREPForbiddenError, OFREPApiTooManyRequestsError, OFREPApiFetchError, isEvaluationFailureResponse, isEvaluationSuccessResponse } from '@openfeature/ofrep-core';
|
|
2
|
+
import { ErrorCode, OpenFeatureEventEmitter, StandardResolutionReasons, ProviderFatalError, ClientProviderEvents, OpenFeatureError, GeneralError } from '@openfeature/web-sdk';
|
|
3
|
+
|
|
4
|
+
/******************************************************************************
|
|
5
|
+
Copyright (c) Microsoft Corporation.
|
|
6
|
+
|
|
7
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
8
|
+
purpose with or without fee is hereby granted.
|
|
9
|
+
|
|
10
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
11
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
12
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
13
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
14
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
15
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
16
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
17
|
+
***************************************************************************** */
|
|
18
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
22
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
23
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
24
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
25
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
26
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
27
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
32
|
+
var e = new Error(message);
|
|
33
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
34
|
+
};
|
|
5
35
|
|
|
6
36
|
var BulkEvaluationStatus;
|
|
7
37
|
(function (BulkEvaluationStatus) {
|
|
@@ -16,6 +46,16 @@ function isResolutionError(response) {
|
|
|
16
46
|
return 'reason' in response && 'errorCode' in response && !('value' in response);
|
|
17
47
|
}
|
|
18
48
|
|
|
49
|
+
const ErrorMessageMap = {
|
|
50
|
+
[ErrorCode.FLAG_NOT_FOUND]: 'Flag was not found',
|
|
51
|
+
[ErrorCode.GENERAL]: 'General error',
|
|
52
|
+
[ErrorCode.INVALID_CONTEXT]: 'Context is invalid or could be parsed',
|
|
53
|
+
[ErrorCode.PARSE_ERROR]: 'Flag or flag configuration could not be parsed',
|
|
54
|
+
[ErrorCode.PROVIDER_FATAL]: 'Provider is in a fatal error state',
|
|
55
|
+
[ErrorCode.PROVIDER_NOT_READY]: 'Provider is not yet ready',
|
|
56
|
+
[ErrorCode.TARGETING_KEY_MISSING]: 'Targeting key is missing',
|
|
57
|
+
[ErrorCode.TYPE_MISMATCH]: 'Flag is not of expected type',
|
|
58
|
+
};
|
|
19
59
|
class OFREPWebProvider {
|
|
20
60
|
constructor(options, logger) {
|
|
21
61
|
var _a;
|
|
@@ -26,6 +66,7 @@ class OFREPWebProvider {
|
|
|
26
66
|
this.runsOn = 'client';
|
|
27
67
|
this.events = new OpenFeatureEventEmitter();
|
|
28
68
|
this._flagCache = {};
|
|
69
|
+
this._flagSetMetadataCache = {};
|
|
29
70
|
this._options = options;
|
|
30
71
|
this._logger = logger;
|
|
31
72
|
this._etag = null;
|
|
@@ -47,7 +88,7 @@ class OFREPWebProvider {
|
|
|
47
88
|
var _a;
|
|
48
89
|
try {
|
|
49
90
|
this._context = context;
|
|
50
|
-
yield this.
|
|
91
|
+
yield this._fetchFlags(context);
|
|
51
92
|
if (this._pollingInterval > 0) {
|
|
52
93
|
this.startPolling();
|
|
53
94
|
}
|
|
@@ -55,7 +96,7 @@ class OFREPWebProvider {
|
|
|
55
96
|
}
|
|
56
97
|
catch (error) {
|
|
57
98
|
if (error instanceof OFREPApiUnauthorizedError || error instanceof OFREPForbiddenError) {
|
|
58
|
-
throw new ProviderFatalError('Initialization failed', error);
|
|
99
|
+
throw new ProviderFatalError('Initialization failed', { cause: error });
|
|
59
100
|
}
|
|
60
101
|
throw error;
|
|
61
102
|
}
|
|
@@ -64,18 +105,17 @@ class OFREPWebProvider {
|
|
|
64
105
|
/* eslint-disable @typescript-eslint/no-unused-vars*/
|
|
65
106
|
/* to make overrides easier we keep these unused vars */
|
|
66
107
|
resolveBooleanEvaluation(flagKey, defaultValue, context) {
|
|
67
|
-
return this.
|
|
108
|
+
return this._resolve(flagKey, 'boolean', defaultValue);
|
|
68
109
|
}
|
|
69
110
|
resolveStringEvaluation(flagKey, defaultValue, context) {
|
|
70
|
-
return this.
|
|
111
|
+
return this._resolve(flagKey, 'string', defaultValue);
|
|
71
112
|
}
|
|
72
113
|
resolveNumberEvaluation(flagKey, defaultValue, context) {
|
|
73
|
-
return this.
|
|
114
|
+
return this._resolve(flagKey, 'number', defaultValue);
|
|
74
115
|
}
|
|
75
116
|
resolveObjectEvaluation(flagKey, defaultValue, context) {
|
|
76
|
-
return this.
|
|
117
|
+
return this._resolve(flagKey, 'object', defaultValue);
|
|
77
118
|
}
|
|
78
|
-
/* eslint-enable @typescript-eslint/no-unused-vars */
|
|
79
119
|
/**
|
|
80
120
|
* onContextChange is called when the context changes, it will re-evaluate the flags with the new context
|
|
81
121
|
* and update the cache.
|
|
@@ -92,7 +132,7 @@ class OFREPWebProvider {
|
|
|
92
132
|
// we do nothing because we should not call the endpoint
|
|
93
133
|
return;
|
|
94
134
|
}
|
|
95
|
-
yield this.
|
|
135
|
+
yield this._fetchFlags(newContext);
|
|
96
136
|
}
|
|
97
137
|
catch (error) {
|
|
98
138
|
if (error instanceof OFREPApiTooManyRequestsError) {
|
|
@@ -118,7 +158,7 @@ class OFREPWebProvider {
|
|
|
118
158
|
return Promise.resolve();
|
|
119
159
|
}
|
|
120
160
|
/**
|
|
121
|
-
*
|
|
161
|
+
* _fetchFlags is a function that will call the bulk evaluate flags endpoint to get the flags values.
|
|
122
162
|
* @param context - the context to use for the evaluation
|
|
123
163
|
* @private
|
|
124
164
|
* @returns EvaluationStatus if the evaluation the API returned a 304, 200.
|
|
@@ -127,9 +167,9 @@ class OFREPWebProvider {
|
|
|
127
167
|
* @throws ParseError if the API returned a 400 with the error code ParseError
|
|
128
168
|
* @throws GeneralError if the API returned a 400 with an unknown error code
|
|
129
169
|
*/
|
|
130
|
-
|
|
170
|
+
_fetchFlags(context) {
|
|
131
171
|
return __awaiter(this, void 0, void 0, function* () {
|
|
132
|
-
var _a
|
|
172
|
+
var _a;
|
|
133
173
|
try {
|
|
134
174
|
const evalReq = {
|
|
135
175
|
context,
|
|
@@ -140,31 +180,38 @@ class OFREPWebProvider {
|
|
|
140
180
|
return { status: BulkEvaluationStatus.SUCCESS_NO_CHANGES, flags: [] };
|
|
141
181
|
}
|
|
142
182
|
if (response.httpStatus !== 200) {
|
|
143
|
-
|
|
183
|
+
throw new GeneralError(`Failed OFREP bulk evaluation request, status: ${response.httpStatus}`);
|
|
144
184
|
}
|
|
145
185
|
const bulkSuccessResp = response.value;
|
|
146
186
|
const newCache = {};
|
|
147
|
-
(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
187
|
+
if ('flags' in bulkSuccessResp && Array.isArray(bulkSuccessResp.flags)) {
|
|
188
|
+
bulkSuccessResp.flags.forEach((evalResp) => {
|
|
189
|
+
if (isEvaluationFailureResponse(evalResp)) {
|
|
190
|
+
newCache[evalResp.key] = {
|
|
191
|
+
reason: StandardResolutionReasons.ERROR,
|
|
192
|
+
flagMetadata: evalResp.metadata,
|
|
193
|
+
errorCode: evalResp.errorCode,
|
|
194
|
+
errorDetails: evalResp.errorDetails,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (isEvaluationSuccessResponse(evalResp) && evalResp.key) {
|
|
198
|
+
newCache[evalResp.key] = {
|
|
199
|
+
value: evalResp.value,
|
|
200
|
+
variant: evalResp.variant,
|
|
201
|
+
reason: evalResp.reason,
|
|
202
|
+
flagMetadata: evalResp.metadata,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
const listUpdatedFlags = this._getListUpdatedFlags(this._flagCache, newCache);
|
|
207
|
+
this._flagCache = newCache;
|
|
208
|
+
this._etag = (_a = response.httpResponse) === null || _a === void 0 ? void 0 : _a.headers.get('etag');
|
|
209
|
+
this._flagSetMetadataCache = typeof bulkSuccessResp.metadata === 'object' ? bulkSuccessResp.metadata : {};
|
|
210
|
+
return { status: BulkEvaluationStatus.SUCCESS_WITH_CHANGES, flags: listUpdatedFlags };
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
throw new Error('No flags in OFREP bulk evaluation response');
|
|
214
|
+
}
|
|
168
215
|
}
|
|
169
216
|
catch (error) {
|
|
170
217
|
if (error instanceof OFREPApiTooManyRequestsError && error.retryAfterDate !== null) {
|
|
@@ -200,33 +247,34 @@ class OFREPWebProvider {
|
|
|
200
247
|
return changedKeys;
|
|
201
248
|
}
|
|
202
249
|
/**
|
|
203
|
-
*
|
|
250
|
+
* _resolve is a function retrieving the value from a flag in the cache.
|
|
204
251
|
* @param flagKey - name of the flag to retrieve
|
|
205
252
|
* @param type - type of the flag
|
|
253
|
+
* @param defaultValue - default value
|
|
206
254
|
* @private
|
|
207
255
|
*/
|
|
208
|
-
|
|
256
|
+
_resolve(flagKey, type, defaultValue) {
|
|
209
257
|
const resolved = this._flagCache[flagKey];
|
|
210
258
|
if (!resolved) {
|
|
211
|
-
|
|
259
|
+
return {
|
|
260
|
+
value: defaultValue,
|
|
261
|
+
flagMetadata: this._flagSetMetadataCache,
|
|
262
|
+
reason: StandardResolutionReasons.ERROR,
|
|
263
|
+
errorCode: ErrorCode.FLAG_NOT_FOUND,
|
|
264
|
+
errorMessage: ErrorMessageMap[ErrorCode.FLAG_NOT_FOUND],
|
|
265
|
+
};
|
|
212
266
|
}
|
|
213
267
|
if (isResolutionError(resolved)) {
|
|
214
|
-
|
|
215
|
-
case EvaluationFailureErrorCode.FlagNotFound:
|
|
216
|
-
throw new FlagNotFoundError(`flag key ${flagKey} not found: ${resolved.errorDetails}`);
|
|
217
|
-
case EvaluationFailureErrorCode.TargetingKeyMissing:
|
|
218
|
-
throw new TargetingKeyMissingError(`targeting key missing for flag key ${flagKey}: ${resolved.errorDetails}`);
|
|
219
|
-
case EvaluationFailureErrorCode.InvalidContext:
|
|
220
|
-
throw new InvalidContextError(`invalid context for flag key ${flagKey}: ${resolved.errorDetails}`);
|
|
221
|
-
case EvaluationFailureErrorCode.ParseError:
|
|
222
|
-
throw new ParseError(`parse error for flag key ${flagKey}: ${resolved.errorDetails}`);
|
|
223
|
-
case EvaluationFailureErrorCode.General:
|
|
224
|
-
default:
|
|
225
|
-
throw new GeneralError(`general error during flag evaluation for flag key ${flagKey}: ${resolved.errorDetails}`);
|
|
226
|
-
}
|
|
268
|
+
return Object.assign(Object.assign({}, resolved), { value: defaultValue, errorMessage: ErrorMessageMap[resolved.errorCode] });
|
|
227
269
|
}
|
|
228
270
|
if (typeof resolved.value !== type) {
|
|
229
|
-
|
|
271
|
+
return {
|
|
272
|
+
value: defaultValue,
|
|
273
|
+
flagMetadata: resolved.flagMetadata,
|
|
274
|
+
reason: StandardResolutionReasons.ERROR,
|
|
275
|
+
errorCode: ErrorCode.TYPE_MISMATCH,
|
|
276
|
+
errorMessage: ErrorMessageMap[ErrorCode.TYPE_MISMATCH],
|
|
277
|
+
};
|
|
230
278
|
}
|
|
231
279
|
return {
|
|
232
280
|
variant: resolved.variant,
|
|
@@ -249,7 +297,7 @@ class OFREPWebProvider {
|
|
|
249
297
|
if (this._retryPollingAfter !== undefined && this._retryPollingAfter > now) {
|
|
250
298
|
return;
|
|
251
299
|
}
|
|
252
|
-
const res = yield this.
|
|
300
|
+
const res = yield this._fetchFlags(this._context);
|
|
253
301
|
if (res.status === BulkEvaluationStatus.SUCCESS_WITH_CHANGES) {
|
|
254
302
|
(_a = this.events) === null || _a === void 0 ? void 0 : _a.emit(ClientProviderEvents.ConfigurationChanged, {
|
|
255
303
|
message: 'Flags updated',
|
package/package.json
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openfeature/ofrep-web-provider",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"
|
|
5
|
-
"tslib": "^2.3.0",
|
|
6
|
-
"@openfeature/ofrep-core": "0.2.0"
|
|
7
|
-
},
|
|
3
|
+
"version": "0.3.3",
|
|
4
|
+
"license": "Apache-2.0",
|
|
8
5
|
"main": "./index.cjs.js",
|
|
9
6
|
"typings": "./src/index.d.ts",
|
|
10
7
|
"scripts": {
|
|
@@ -12,15 +9,20 @@
|
|
|
12
9
|
"current-version": "echo $npm_package_version"
|
|
13
10
|
},
|
|
14
11
|
"peerDependencies": {
|
|
15
|
-
"@openfeature/web-sdk": "
|
|
12
|
+
"@openfeature/web-sdk": "^1.4.0"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@openfeature/ofrep-core": "^1.0.0"
|
|
16
16
|
},
|
|
17
17
|
"exports": {
|
|
18
18
|
"./package.json": "./package.json",
|
|
19
19
|
".": {
|
|
20
20
|
"module": "./index.esm.js",
|
|
21
|
+
"types": "./index.esm.d.ts",
|
|
21
22
|
"import": "./index.cjs.mjs",
|
|
22
23
|
"default": "./index.cjs.js"
|
|
23
24
|
}
|
|
24
25
|
},
|
|
25
|
-
"module": "./index.esm.js"
|
|
26
|
-
|
|
26
|
+
"module": "./index.esm.js",
|
|
27
|
+
"types": "./index.esm.d.ts"
|
|
28
|
+
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import { FlagValue, ResolutionDetails } from '@openfeature/web-sdk';
|
|
2
|
-
import { ResolutionError } from './resolution-error';
|
|
1
|
+
import type { FlagMetadata, FlagValue, ResolutionDetails } from '@openfeature/web-sdk';
|
|
2
|
+
import type { ResolutionError } from './resolution-error';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Cache of flag values from bulk evaluation.
|
|
5
5
|
*/
|
|
6
6
|
export type FlagCache = {
|
|
7
7
|
[key: string]: ResolutionDetails<FlagValue> | ResolutionError;
|
|
8
8
|
};
|
|
9
|
+
/**
|
|
10
|
+
* Cache of metadata from bulk evaluation.
|
|
11
|
+
*/
|
|
12
|
+
export type MetadataCache = FlagMetadata;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { OFREPProviderBaseOptions } from '@openfeature/ofrep-core';
|
|
1
|
+
import type { OFREPProviderBaseOptions } from '@openfeature/ofrep-core';
|
|
2
2
|
export type OFREPWebProviderOptions = OFREPProviderBaseOptions & {
|
|
3
3
|
/**
|
|
4
4
|
* pollInterval is the time in milliseconds to wait between we call the OFREP
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { ResolutionReason } from '@openfeature/web-sdk';
|
|
2
|
-
import { EvaluationFailureErrorCode } from '@openfeature/ofrep-core';
|
|
1
|
+
import type { ErrorCode, ResolutionReason } from '@openfeature/web-sdk';
|
|
3
2
|
export type ResolutionError = {
|
|
4
3
|
reason: ResolutionReason;
|
|
5
|
-
errorCode:
|
|
4
|
+
errorCode: ErrorCode;
|
|
6
5
|
errorDetails?: string;
|
|
7
6
|
};
|
|
8
7
|
export declare function isResolutionError(response: unknown): response is ResolutionError;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { EvaluationContext, Hook, JsonValue, Logger,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import type { EvaluationContext, Hook, JsonValue, Logger, Provider, ResolutionDetails } from '@openfeature/web-sdk';
|
|
2
|
+
import { OpenFeatureEventEmitter } from '@openfeature/web-sdk';
|
|
3
|
+
import type { FlagCache } from './model/in-memory-cache';
|
|
4
|
+
import type { OFREPWebProviderOptions } from './model/ofrep-web-provider-options';
|
|
4
5
|
export declare class OFREPWebProvider implements Provider {
|
|
5
6
|
DEFAULT_POLL_INTERVAL: number;
|
|
6
7
|
readonly metadata: {
|
|
@@ -16,6 +17,7 @@ export declare class OFREPWebProvider implements Provider {
|
|
|
16
17
|
private _pollingInterval;
|
|
17
18
|
private _retryPollingAfter;
|
|
18
19
|
private _flagCache;
|
|
20
|
+
private _flagSetMetadataCache;
|
|
19
21
|
private _context;
|
|
20
22
|
private _pollingIntervalId?;
|
|
21
23
|
constructor(options: OFREPWebProviderOptions, logger?: Logger);
|
|
@@ -44,7 +46,7 @@ export declare class OFREPWebProvider implements Provider {
|
|
|
44
46
|
*/
|
|
45
47
|
onClose?(): Promise<void>;
|
|
46
48
|
/**
|
|
47
|
-
*
|
|
49
|
+
* _fetchFlags is a function that will call the bulk evaluate flags endpoint to get the flags values.
|
|
48
50
|
* @param context - the context to use for the evaluation
|
|
49
51
|
* @private
|
|
50
52
|
* @returns EvaluationStatus if the evaluation the API returned a 304, 200.
|
|
@@ -53,7 +55,7 @@ export declare class OFREPWebProvider implements Provider {
|
|
|
53
55
|
* @throws ParseError if the API returned a 400 with the error code ParseError
|
|
54
56
|
* @throws GeneralError if the API returned a 400 with an unknown error code
|
|
55
57
|
*/
|
|
56
|
-
private
|
|
58
|
+
private _fetchFlags;
|
|
57
59
|
/**
|
|
58
60
|
* _getListUpdatedFlags is a function that will compare the old cache with the new cache and
|
|
59
61
|
* return the list of flags that have been updated / deleted / created.
|
|
@@ -63,12 +65,13 @@ export declare class OFREPWebProvider implements Provider {
|
|
|
63
65
|
*/
|
|
64
66
|
private _getListUpdatedFlags;
|
|
65
67
|
/**
|
|
66
|
-
*
|
|
68
|
+
* _resolve is a function retrieving the value from a flag in the cache.
|
|
67
69
|
* @param flagKey - name of the flag to retrieve
|
|
68
70
|
* @param type - type of the flag
|
|
71
|
+
* @param defaultValue - default value
|
|
69
72
|
* @private
|
|
70
73
|
*/
|
|
71
|
-
private
|
|
74
|
+
private _resolve;
|
|
72
75
|
/**
|
|
73
76
|
* Start polling for flag updates, it will call the bulk update function every pollInterval
|
|
74
77
|
* @private
|