Skip to content
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
76408f0
Update OF packages
thomaspoignant Feb 16, 2024
d2d0a3a
POC remote evaluation
thomaspoignant Feb 16, 2024
2679a2d
Adding ETag management
thomaspoignant Feb 16, 2024
b3b7cc7
Add polling
thomaspoignant Feb 16, 2024
1f26cca
Merge remote-tracking branch 'origin/main' into ofrep-web-provider-poc
thomaspoignant Mar 15, 2024
9ec4dca
Merge remote-tracking branch 'origin/main' into ofrep-web-provider-poc
thomaspoignant Mar 18, 2024
75f7547
OFREP web provider
thomaspoignant Mar 25, 2024
bb249bc
Merge remote-tracking branch 'origin/main' into ofrep-web-provider-poc
thomaspoignant Mar 25, 2024
23af98e
Emit configurationChanged event
thomaspoignant Mar 25, 2024
dc3db76
add tests
thomaspoignant Mar 25, 2024
180f3d4
Add more tests
thomaspoignant Mar 26, 2024
88575d6
Merge remote-tracking branch 'origin/main' into ofrep-web-provider-poc
thomaspoignant Mar 27, 2024
301fe78
Store errors in the cache
thomaspoignant Mar 29, 2024
91578a8
Adding tests
thomaspoignant Mar 29, 2024
700d814
Address review comments
thomaspoignant Mar 29, 2024
166d3ed
Commenting the checks because of STALE evaluation not returning CACHED
thomaspoignant Mar 29, 2024
08a6e04
regenerate package lock
beeme1mr Mar 29, 2024
71f0b25
Merge branch 'main' into ofrep-web-provider-poc
beeme1mr Mar 29, 2024
b97f648
add missing comma
beeme1mr Mar 29, 2024
2055c33
Handle 428 retry-after header
thomaspoignant Mar 29, 2024
9b93ab2
Fix test in ofrep-core
thomaspoignant Apr 1, 2024
ca3483d
Merge branch 'main' into ofrep-web-provider-poc
beeme1mr Apr 1, 2024
259ef62
Return the list of flags
thomaspoignant Apr 1, 2024
47d7589
Keep the new context in memory even if we are not able to call the API
thomaspoignant Apr 1, 2024
98bd26b
Merge branch 'main' into ofrep-web-provider-poc
thomaspoignant Apr 1, 2024
b33b944
Merge branch 'main' into ofrep-web-provider-poc
thomaspoignant Apr 1, 2024
109571b
Merge branch 'main' into ofrep-web-provider-poc
beeme1mr Apr 1, 2024
98b610b
Handle all errors not only the 400
thomaspoignant Apr 2, 2024
1d021a0
Move initialize to the top
thomaspoignant Apr 3, 2024
65577aa
Merge remote-tracking branch 'origin/main' into ofrep-web-provider-poc
thomaspoignant Apr 3, 2024
37436b4
Add option from shared
thomaspoignant Apr 3, 2024
5f9fe91
Adding the peer dependency
thomaspoignant Apr 4, 2024
d932a4d
Rename Ofrep to OFREP
thomaspoignant Apr 4, 2024
d4453bd
Update README
thomaspoignant Apr 4, 2024
5a4c8f0
Merge remote-tracking branch 'origin/main' into ofrep-web-provider-poc
thomaspoignant Apr 4, 2024
6f1666d
Add peer dependency to ofrep-core
thomaspoignant Apr 4, 2024
8036e22
Use retryAfter method from ofrep-core
thomaspoignant Apr 4, 2024
ad2674c
Add the right version
thomaspoignant Apr 4, 2024
71cecda
remove coverage folder
thomaspoignant Apr 4, 2024
72edcb8
Merge remote-tracking branch 'origin/main' into ofrep-web-provider-poc
thomaspoignant Apr 4, 2024
c3c9076
Create a type for in memory cache
thomaspoignant Apr 4, 2024
127f79d
Renaming options type
thomaspoignant Apr 4, 2024
8b81130
fix typo
thomaspoignant Apr 5, 2024
9da3a6d
Use not experimental version
thomaspoignant Apr 5, 2024
7197251
Merge branch 'main' into ofrep-web-provider-poc
thomaspoignant Apr 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
"libs/providers/go-feature-flag-web": "0.2.0",
"libs/shared/flagd-core": "0.1.11",
"libs/shared/ofrep-core": "0.0.7-experimental",
"libs/providers/flipt": "0.1.0"
"libs/providers/flipt": "0.1.0",
"libs/providers/ofrep-web": "0.1.0"
}
25 changes: 25 additions & 0 deletions libs/providers/ofrep-web/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"extends": ["../../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.json"],
"parser": "jsonc-eslint-parser",
"rules": {
"@nx/dependency-checks": "error"
}
}
]
}
107 changes: 107 additions & 0 deletions libs/providers/ofrep-web/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Client-Side OFREP Provider

This provider is designed to use the [OpenFeature Remote Evaluation Protocol (OFREP)](https://openfeature.dev/specification/appendix-c).

## Installation

### npm

```sh
npm install @openfeature/ofrep-web
```

### yarn

```sh
yarn add @openfeature/ofrep-web @openfeature/ofrep-core @openfeature/web-sdk
```

> [!NOTE]
> yarn requires manual installation of peer dependencies

## Configurations and Usage

The provider needs the base url of the OFREP server for instantiation.

```ts
import { OFREPWebProvider } from '@openfeature/ofrep-web';

OpenFeature.setProvider(new OFREPWebProvider({ baseUrl: 'https://localhost:8080', pollingInterval: 60000 }));
```

### HTTP headers

The provider can use headers from either a static header map or a custom header factory.

#### Static Headers

Headers can be given as a list of tuples or as a map of headers.

```ts
import { OFREPWebProvider } from '@openfeature/ofrep-web';

OpenFeature.setProvider(
new OFREPWebProvider({
baseUrl: 'https://localhost:8080',
headers: [
['Authorization', `my-api-key`],
['X-My-Header', `CustomHeaderValue`],
],
}),
);
```

```ts
import { OFREPWebProvider } from '@openfeature/ofrep-web';

OpenFeature.setProvider(
new OFREPWebProvider({
baseUrl: 'https://localhost:8080',
headers: { Authorization: `my-api-key`, 'X-My-Header': `CustomHeaderValue` },
}),
);
```

#### Header Factory

The header factory is evaluated before every flag evaluation which makes it possible to use dynamic values for the headers.

The following shows an example of loading a token and using it as bearer token.

```ts
import { OFREPWebProvider } from '@openfeature/ofrep-web';

OpenFeature.setProvider(
new OFREPWebProvider({
baseUrl: 'https://localhost:8080',
headersFactory: () => {
const token: string = loadDynamicToken();
return [['Authorization', `Bearer ${token}`]];
},
}),
);
```

### Fetch implementation

If needed, a custom fetch implementation can be injected, if e.g. the platform does not have fetch built in.

```ts
import { OFREPWebProvider } from '@openfeature/ofrep-web';
import { fetchPolyfill } from 'some-fetch-polyfill';

OpenFeature.setProvider(
new OFREPWebProvider({
baseUrl: 'https://localhost:8080',
fetchImplementation: fetchPolyfill
}),
);
```

## Building

Run `nx package providers-ofrep-web` to build the library.

## Running unit tests

Run `nx test providers-ofrep-web` to execute the unit tests via [Jest](https://jestjs.io).
3 changes: 3 additions & 0 deletions libs/providers/ofrep-web/babel.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": [["minify", { "builtIns": false }]]
}
16 changes: 16 additions & 0 deletions libs/providers/ofrep-web/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* eslint-disable */
export default {
displayName: 'providers-ofrep-web',
preset: '../../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
transform: {
'^.+\\.[tj]s$': 'ts-jest',
},
testEnvironment: 'jsdom',
moduleFileExtensions: ['ts', 'js', 'html'],
setupFiles: ['./jest.polyfills.js'],
};
32 changes: 32 additions & 0 deletions libs/providers/ofrep-web/jest.polyfills.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// jest.polyfills.js
/**
* @note The block below contains polyfills for Node.js globals
* required for Jest to function when running JSDOM tests.
* These HAVE to be require's and HAVE to be in this exact
* order, since "undici" depends on the "TextEncoder" global API.
*
* Consider migrating to a more modern test runner if
* you don't want to deal with this.
*/

const { TextDecoder, TextEncoder } = require('node:util');

// eslint-disable-next-line no-undef
Object.defineProperties(globalThis, {
TextDecoder: { value: TextDecoder },
TextEncoder: { value: TextEncoder },
})

const { Blob, File } = require('node:buffer');
const { fetch, Headers, FormData, Request, Response } = require('undici');

// eslint-disable-next-line no-undef
Object.defineProperties(globalThis, {
fetch: { value: fetch, writable: true },
Blob: { value: Blob },
File: { value: File },
Headers: { value: Headers },
FormData: { value: FormData },
Request: { value: Request },
Response: { value: Response },
})
17 changes: 17 additions & 0 deletions libs/providers/ofrep-web/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@openfeature/ofrep-web-provider",
"version": "0.0.1-alpha.0",
"dependencies": {
"tslib": "^2.3.0"
},
"main": "./src/index.js",
"typings": "./src/index.d.ts",
"scripts": {
"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",
"current-version": "echo $npm_package_version"
},
"peerDependencies": {
"@openfeature/web-sdk": ">=0.4.0",
"@openfeature/ofrep-core": "^0.0.6-experimental"
}
}
76 changes: 76 additions & 0 deletions libs/providers/ofrep-web/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{
"name": "providers-ofrep-web",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/providers/ofrep-web/src",
"projectType": "library",
"targets": {
"publish": {
"executor": "nx:run-commands",
"options": {
"command": "npm run publish-if-not-exists",
"cwd": "dist/libs/providers/ofrep-web"
},
"dependsOn": [
{
"projects": "self",
"target": "package"
}
]
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/providers/ofrep-web/**/*.ts", "libs/providers/ofrep-web/package.json"]
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/providers/ofrep-web/jest.config.ts",
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
}
},
"package": {
"executor": "@nx/rollup:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"project": "libs/providers/ofrep-web/package.json",
"outputPath": "dist/libs/providers/ofrep-web",
"entryFile": "libs/providers/ofrep-web/src/index.ts",
"tsConfig": "libs/providers/ofrep-web/tsconfig.lib.json",
"buildableProjectDepsInPackageJsonType": "dependencies",
"compiler": "tsc",
"generateExportsField": true,
"umdName": "ofrep-web",
"external": "all",
"format": ["cjs", "esm"],
"assets": [
{
"glob": "package.json",
"input": "./assets",
"output": "./src/"
},
{
"glob": "LICENSE",
"input": "./",
"output": "./"
},
{
"glob": "README.md",
"input": "./libs/providers/ofrep-web",
"output": "./"
}
]
}
}
},
"tags": []
}
1 change: 1 addition & 0 deletions libs/providers/ofrep-web/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './lib/ofrep-web-provider';
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export enum BulkEvaluationStatus {
SUCCESS_NO_CHANGES = 'SUCCESS_NO_CHANGES',
SUCCESS_WITH_CHANGES = 'SUCCESS_WITH_CHANGES',
}

export interface EvaluateFlagsResponse {
/**
* Status of the bulk evaluation.
*/
status: BulkEvaluationStatus;
/**
* The List of flags changed when doing the bulk evaluation.
*/
flags: string[];
}
7 changes: 7 additions & 0 deletions libs/providers/ofrep-web/src/lib/model/in-memory-cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { FlagValue, ResolutionDetails } from '@openfeature/web-sdk';
import { ResolutionError } from './resolution-error';

/**
* inMemoryCache is a type representing the internal cache of the flags.
*/
export type InMemoryCache = { [key: string]: ResolutionDetails<FlagValue> | ResolutionError };
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { OFREPProviderBaseOptions } from '@openfeature/ofrep-core';

export type OFREPWebProviderOptions = OFREPProviderBaseOptions & {
/**
* pollInterval is the time in milliseconds to wait between we call the OFREP
* API to get the latest evaluation of your flags.
*
* If a negative number is provided, the provider will not poll the OFREP API.
* Default: 30000
*/
pollInterval?: number; // in milliseconds
};
16 changes: 16 additions & 0 deletions libs/providers/ofrep-web/src/lib/model/resolution-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ResolutionReason } from '@openfeature/web-sdk';
import { EvaluationFailureErrorCode } from '@openfeature/ofrep-core';

export type ResolutionError = {
reason: ResolutionReason;
errorCode: EvaluationFailureErrorCode;
errorDetails?: string;
};

export function isResolutionError(response: unknown): response is ResolutionError {
if (!response || typeof response !== 'object') {
return false;
}

return 'reason' in response && 'errorCode' in response && !('value' in response);
}
Loading