Skip to content

Commit 9626ef4

Browse files
feat: OFREP web provider (open-feature#776)
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org> Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com> Co-authored-by: Michael Beemer <beeme1mr@users.noreply.github.com>
1 parent 14322d2 commit 9626ef4

24 files changed

+1117
-55
lines changed

.release-please-manifest.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@
99
"libs/providers/go-feature-flag-web": "0.2.0",
1010
"libs/shared/flagd-core": "0.1.11",
1111
"libs/shared/ofrep-core": "0.1.1",
12-
"libs/providers/flipt": "0.1.0"
12+
"libs/providers/flipt": "0.1.0",
13+
"libs/providers/ofrep-web": "0.1.0"
1314
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"extends": ["../../../.eslintrc.json"],
3+
"ignorePatterns": ["!**/*"],
4+
"overrides": [
5+
{
6+
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7+
"rules": {}
8+
},
9+
{
10+
"files": ["*.ts", "*.tsx"],
11+
"rules": {}
12+
},
13+
{
14+
"files": ["*.js", "*.jsx"],
15+
"rules": {}
16+
},
17+
{
18+
"files": ["*.json"],
19+
"parser": "jsonc-eslint-parser",
20+
"rules": {
21+
"@nx/dependency-checks": "error"
22+
}
23+
}
24+
]
25+
}

libs/providers/ofrep-web/README.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Client-Side OFREP Provider
2+
3+
This provider is designed to use the [OpenFeature Remote Evaluation Protocol (OFREP)](https://openfeature.dev/specification/appendix-c).
4+
5+
## Installation
6+
7+
### npm
8+
9+
```sh
10+
npm install @openfeature/ofrep-web
11+
```
12+
13+
### yarn
14+
15+
```sh
16+
yarn add @openfeature/ofrep-web @openfeature/ofrep-core @openfeature/web-sdk
17+
```
18+
19+
> [!NOTE]
20+
> yarn requires manual installation of peer dependencies
21+
22+
## Configurations and Usage
23+
24+
The provider needs the base url of the OFREP server for instantiation.
25+
26+
```ts
27+
import { OFREPWebProvider } from '@openfeature/ofrep-web';
28+
29+
OpenFeature.setProvider(new OFREPWebProvider({ baseUrl: 'https://localhost:8080', pollingInterval: 60000 }));
30+
```
31+
32+
### HTTP headers
33+
34+
The provider can use headers from either a static header map or a custom header factory.
35+
36+
#### Static Headers
37+
38+
Headers can be given as a list of tuples or as a map of headers.
39+
40+
```ts
41+
import { OFREPWebProvider } from '@openfeature/ofrep-web';
42+
43+
OpenFeature.setProvider(
44+
new OFREPWebProvider({
45+
baseUrl: 'https://localhost:8080',
46+
headers: [
47+
['Authorization', `my-api-key`],
48+
['X-My-Header', `CustomHeaderValue`],
49+
],
50+
}),
51+
);
52+
```
53+
54+
```ts
55+
import { OFREPWebProvider } from '@openfeature/ofrep-web';
56+
57+
OpenFeature.setProvider(
58+
new OFREPWebProvider({
59+
baseUrl: 'https://localhost:8080',
60+
headers: { Authorization: `my-api-key`, 'X-My-Header': `CustomHeaderValue` },
61+
}),
62+
);
63+
```
64+
65+
#### Header Factory
66+
67+
The header factory is evaluated before every flag evaluation which makes it possible to use dynamic values for the headers.
68+
69+
The following shows an example of loading a token and using it as bearer token.
70+
71+
```ts
72+
import { OFREPWebProvider } from '@openfeature/ofrep-web';
73+
74+
OpenFeature.setProvider(
75+
new OFREPWebProvider({
76+
baseUrl: 'https://localhost:8080',
77+
headersFactory: () => {
78+
const token: string = loadDynamicToken();
79+
return [['Authorization', `Bearer ${token}`]];
80+
},
81+
}),
82+
);
83+
```
84+
85+
### Fetch implementation
86+
87+
If needed, a custom fetch implementation can be injected, if e.g. the platform does not have fetch built in.
88+
89+
```ts
90+
import { OFREPWebProvider } from '@openfeature/ofrep-web';
91+
import { fetchPolyfill } from 'some-fetch-polyfill';
92+
93+
OpenFeature.setProvider(
94+
new OFREPWebProvider({
95+
baseUrl: 'https://localhost:8080',
96+
fetchImplementation: fetchPolyfill
97+
}),
98+
);
99+
```
100+
101+
## Building
102+
103+
Run `nx package providers-ofrep-web` to build the library.
104+
105+
## Running unit tests
106+
107+
Run `nx test providers-ofrep-web` to execute the unit tests via [Jest](https://jestjs.io).
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"presets": [["minify", { "builtIns": false }]]
3+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/* eslint-disable */
2+
export default {
3+
displayName: 'providers-ofrep-web',
4+
preset: '../../../jest.preset.js',
5+
globals: {
6+
'ts-jest': {
7+
tsconfig: '<rootDir>/tsconfig.spec.json',
8+
},
9+
},
10+
transform: {
11+
'^.+\\.[tj]s$': 'ts-jest',
12+
},
13+
testEnvironment: 'jsdom',
14+
moduleFileExtensions: ['ts', 'js', 'html'],
15+
setupFiles: ['./jest.polyfills.js'],
16+
};
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// jest.polyfills.js
2+
/**
3+
* @note The block below contains polyfills for Node.js globals
4+
* required for Jest to function when running JSDOM tests.
5+
* These HAVE to be require's and HAVE to be in this exact
6+
* order, since "undici" depends on the "TextEncoder" global API.
7+
*
8+
* Consider migrating to a more modern test runner if
9+
* you don't want to deal with this.
10+
*/
11+
12+
const { TextDecoder, TextEncoder } = require('node:util');
13+
14+
// eslint-disable-next-line no-undef
15+
Object.defineProperties(globalThis, {
16+
TextDecoder: { value: TextDecoder },
17+
TextEncoder: { value: TextEncoder },
18+
})
19+
20+
const { Blob, File } = require('node:buffer');
21+
const { fetch, Headers, FormData, Request, Response } = require('undici');
22+
23+
// eslint-disable-next-line no-undef
24+
Object.defineProperties(globalThis, {
25+
fetch: { value: fetch, writable: true },
26+
Blob: { value: Blob },
27+
File: { value: File },
28+
Headers: { value: Headers },
29+
FormData: { value: FormData },
30+
Request: { value: Request },
31+
Response: { value: Response },
32+
})
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "@openfeature/ofrep-web-provider",
3+
"version": "0.0.1-alpha.0",
4+
"dependencies": {
5+
"tslib": "^2.3.0"
6+
},
7+
"main": "./src/index.js",
8+
"typings": "./src/index.d.ts",
9+
"scripts": {
10+
"publish-if-not-exists": "cp $NPM_CONFIG_USERCONFIG .npmrc && if [ \"$(npm show $npm_package_name@$npm_package_version version)\" = \"$(npm run current-version -s)\" ]; then echo 'already published, skipping'; else npm publish --access public; fi",
11+
"current-version": "echo $npm_package_version"
12+
},
13+
"peerDependencies": {
14+
"@openfeature/web-sdk": ">=0.4.0",
15+
"@openfeature/ofrep-core": "^0.1.0"
16+
}
17+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
{
2+
"name": "providers-ofrep-web",
3+
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
4+
"sourceRoot": "libs/providers/ofrep-web/src",
5+
"projectType": "library",
6+
"targets": {
7+
"publish": {
8+
"executor": "nx:run-commands",
9+
"options": {
10+
"command": "npm run publish-if-not-exists",
11+
"cwd": "dist/libs/providers/ofrep-web"
12+
},
13+
"dependsOn": [
14+
{
15+
"projects": "self",
16+
"target": "package"
17+
}
18+
]
19+
},
20+
"lint": {
21+
"executor": "@nx/linter:eslint",
22+
"outputs": ["{options.outputFile}"],
23+
"options": {
24+
"lintFilePatterns": ["libs/providers/ofrep-web/**/*.ts", "libs/providers/ofrep-web/package.json"]
25+
}
26+
},
27+
"test": {
28+
"executor": "@nx/jest:jest",
29+
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
30+
"options": {
31+
"jestConfig": "libs/providers/ofrep-web/jest.config.ts",
32+
"passWithNoTests": true
33+
},
34+
"configurations": {
35+
"ci": {
36+
"ci": true,
37+
"codeCoverage": true
38+
}
39+
}
40+
},
41+
"package": {
42+
"executor": "@nx/rollup:rollup",
43+
"outputs": ["{options.outputPath}"],
44+
"options": {
45+
"project": "libs/providers/ofrep-web/package.json",
46+
"outputPath": "dist/libs/providers/ofrep-web",
47+
"entryFile": "libs/providers/ofrep-web/src/index.ts",
48+
"tsConfig": "libs/providers/ofrep-web/tsconfig.lib.json",
49+
"buildableProjectDepsInPackageJsonType": "dependencies",
50+
"compiler": "tsc",
51+
"generateExportsField": true,
52+
"umdName": "ofrep-web",
53+
"external": "all",
54+
"format": ["cjs", "esm"],
55+
"assets": [
56+
{
57+
"glob": "package.json",
58+
"input": "./assets",
59+
"output": "./src/"
60+
},
61+
{
62+
"glob": "LICENSE",
63+
"input": "./",
64+
"output": "./"
65+
},
66+
{
67+
"glob": "README.md",
68+
"input": "./libs/providers/ofrep-web",
69+
"output": "./"
70+
}
71+
]
72+
}
73+
}
74+
},
75+
"tags": []
76+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './lib/ofrep-web-provider';
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export enum BulkEvaluationStatus {
2+
SUCCESS_NO_CHANGES = 'SUCCESS_NO_CHANGES',
3+
SUCCESS_WITH_CHANGES = 'SUCCESS_WITH_CHANGES',
4+
}
5+
6+
export interface EvaluateFlagsResponse {
7+
/**
8+
* Status of the bulk evaluation.
9+
*/
10+
status: BulkEvaluationStatus;
11+
/**
12+
* The List of flags changed when doing the bulk evaluation.
13+
*/
14+
flags: string[];
15+
}

0 commit comments

Comments
 (0)