feat: use EvenEmitter3 for web-sdk (#847)
<!-- Please use this template for your pull request. -->
<!-- Please use the sections that you need and delete other sections -->
## This PR
<!-- add the description of the PR here -->
Fixes an issue where the `events` node polyfill does not comply to the
`node:events` types.
When trying to use the web OpenFeatureEventEmitter the following error
message comes up, describing that the `events` polyfill's EventEmitter
is incompatible to `node:events` EventEmitter.
```
✘ [ERROR] TS2416: Property 'eventEmitter' in type 'OpenFeatureEventEmitter' is not assignable to the same property in base type 'GenericEventEmitter<ProviderEmittableEvents, Record<string, unknown>>'.
Type 'EventEmitter' is not assignable to type 'PlatformEventEmitter'.
Types of property 'addListener' are incompatible.
Type '(type: string | number, listener: Listener) => EventEmitter' is not assignable to type '(eventName: string | symbol, listener: (...args: any[]) => void) => PlatformEventEmitter'.
Types of parameters 'type' and 'eventName' are incompatible.
Type 'string | symbol' is not assignable to type 'string | number'.
Type 'symbol' is not assignable to type 'string | number'.
```
This PR fixes that issue by not using the `events` anymore and instead
using https://www.npmjs.com/package/eventemitter3
cc @toddbaert
### Related Issues
<!-- add here the GitHub issue that this PR resolves if applicable -->
Fixes #845
---------
Signed-off-by: Lukas Reining <lukas.reining@codecentric.de>
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
This commit is contained in:
parent
1461074f20
commit
861cf83782
|
|
@ -17,7 +17,6 @@
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-typescript": "^11.1.6",
|
"@rollup/plugin-typescript": "^11.1.6",
|
||||||
"@types/events": "^3.0.3",
|
|
||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/node": "^20.11.16",
|
"@types/node": "^20.11.16",
|
||||||
"@types/react": "^18.2.55",
|
"@types/react": "^18.2.55",
|
||||||
|
|
@ -31,7 +30,7 @@
|
||||||
"eslint-plugin-import": "^2.29.1",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
"eslint-plugin-jest": "^27.6.3",
|
"eslint-plugin-jest": "^27.6.3",
|
||||||
"eslint-plugin-jsdoc": "^48.0.6",
|
"eslint-plugin-jsdoc": "^48.0.6",
|
||||||
"events": "^3.3.0",
|
"eventemitter3": "^5.0.1",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"jest-config": "^29.7.0",
|
"jest-config": "^29.7.0",
|
||||||
"jest-cucumber": "^3.0.1",
|
"jest-cucumber": "^3.0.1",
|
||||||
|
|
@ -2154,12 +2153,6 @@
|
||||||
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
|
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/events": {
|
|
||||||
"version": "3.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.3.tgz",
|
|
||||||
"integrity": "sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/@types/glob": {
|
"node_modules/@types/glob": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz",
|
||||||
|
|
@ -4787,14 +4780,11 @@
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/events": {
|
"node_modules/eventemitter3": {
|
||||||
"version": "3.3.0",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||||
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
|
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
|
||||||
"dev": true,
|
"dev": true
|
||||||
"engines": {
|
|
||||||
"node": ">=0.8.x"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/exec-sh": {
|
"node_modules/exec-sh": {
|
||||||
"version": "0.3.6",
|
"version": "0.3.6",
|
||||||
|
|
@ -13743,7 +13733,7 @@
|
||||||
},
|
},
|
||||||
"packages/client": {
|
"packages/client": {
|
||||||
"name": "@openfeature/web-sdk",
|
"name": "@openfeature/web-sdk",
|
||||||
"version": "0.4.13",
|
"version": "0.4.14",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@openfeature/core": "0.0.27"
|
"@openfeature/core": "0.0.27"
|
||||||
|
|
@ -13754,7 +13744,7 @@
|
||||||
},
|
},
|
||||||
"packages/nest": {
|
"packages/nest": {
|
||||||
"name": "@openfeature/nestjs-sdk",
|
"name": "@openfeature/nestjs-sdk",
|
||||||
"version": "0.1.0-experimental",
|
"version": "0.1.1-experimental",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/common": "^10.2.10",
|
"@nestjs/common": "^10.2.10",
|
||||||
|
|
@ -13775,7 +13765,7 @@
|
||||||
},
|
},
|
||||||
"packages/react": {
|
"packages/react": {
|
||||||
"name": "@openfeature/react-sdk",
|
"name": "@openfeature/react-sdk",
|
||||||
"version": "0.2.0-experimental",
|
"version": "0.2.1-experimental",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@openfeature/core": "*",
|
"@openfeature/core": "*",
|
||||||
|
|
@ -13788,7 +13778,7 @@
|
||||||
},
|
},
|
||||||
"packages/server": {
|
"packages/server": {
|
||||||
"name": "@openfeature/server-sdk",
|
"name": "@openfeature/server-sdk",
|
||||||
"version": "1.12.0",
|
"version": "1.13.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@openfeature/core": "0.0.27"
|
"@openfeature/core": "0.0.27"
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,6 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-typescript": "^11.1.6",
|
"@rollup/plugin-typescript": "^11.1.6",
|
||||||
"@types/events": "^3.0.3",
|
|
||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/node": "^20.11.16",
|
"@types/node": "^20.11.16",
|
||||||
"@types/react": "^18.2.55",
|
"@types/react": "^18.2.55",
|
||||||
|
|
@ -51,7 +50,7 @@
|
||||||
"eslint-plugin-import": "^2.29.1",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
"eslint-plugin-jest": "^27.6.3",
|
"eslint-plugin-jest": "^27.6.3",
|
||||||
"eslint-plugin-jsdoc": "^48.0.6",
|
"eslint-plugin-jsdoc": "^48.0.6",
|
||||||
"events": "^3.3.0",
|
"eventemitter3": "^5.0.1",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"jest-config": "^29.7.0",
|
"jest-config": "^29.7.0",
|
||||||
"jest-cucumber": "^3.0.1",
|
"jest-cucumber": "^3.0.1",
|
||||||
|
|
@ -76,4 +75,4 @@
|
||||||
"packages/react",
|
"packages/react",
|
||||||
"packages/nest"
|
"packages/nest"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { GenericEventEmitter } from '@openfeature/core';
|
import { GenericEventEmitter } from '@openfeature/core';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
import { ProviderEmittableEvents } from './events';
|
import { ProviderEmittableEvents } from './events';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The OpenFeatureEventEmitter can be used by provider developers to emit
|
* The OpenFeatureEventEmitter can be used by provider developers to emit
|
||||||
* events at various parts of the provider lifecycle.
|
* events at various parts of the provider lifecycle.
|
||||||
|
|
@ -9,12 +10,9 @@ import { ProviderEmittableEvents } from './events';
|
||||||
* the result of the initialize method.
|
* the result of the initialize method.
|
||||||
*/
|
*/
|
||||||
export class OpenFeatureEventEmitter extends GenericEventEmitter<ProviderEmittableEvents> {
|
export class OpenFeatureEventEmitter extends GenericEventEmitter<ProviderEmittableEvents> {
|
||||||
protected readonly eventEmitter = new EventEmitter({ captureRejections: true });
|
protected readonly eventEmitter = new EventEmitter();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.eventEmitter.on('error', (err) => {
|
|
||||||
this._logger?.error('Error running event handler:', err);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,15 @@ import { AllProviderEvents, AnyProviderEvent } from './events';
|
||||||
* The GenericEventEmitter should only be used within the SDK. It supports additional properties that can be included
|
* The GenericEventEmitter should only be used within the SDK. It supports additional properties that can be included
|
||||||
* in the event details.
|
* in the event details.
|
||||||
*/
|
*/
|
||||||
export abstract class GenericEventEmitter<E extends AnyProviderEvent, AdditionalContext extends Record<string, unknown> = Record<string, unknown>>
|
export abstract class GenericEventEmitter<
|
||||||
|
E extends AnyProviderEvent,
|
||||||
|
AdditionalContext extends Record<string, unknown> = Record<string, unknown>,
|
||||||
|
>
|
||||||
implements ProviderEventEmitter<E>, ManageLogger<GenericEventEmitter<E, AdditionalContext>>
|
implements ProviderEventEmitter<E>, ManageLogger<GenericEventEmitter<E, AdditionalContext>>
|
||||||
{
|
{
|
||||||
protected abstract readonly eventEmitter: PlatformEventEmitter;
|
protected abstract readonly eventEmitter: PlatformEventEmitter;
|
||||||
|
|
||||||
private readonly _handlers: { [key in AnyProviderEvent]: WeakMap<EventHandler, EventHandler[]>} = {
|
private readonly _handlers: { [key in AnyProviderEvent]: WeakMap<EventHandler, EventHandler[]> } = {
|
||||||
[AllProviderEvents.ConfigurationChanged]: new WeakMap<EventHandler, EventHandler[]>(),
|
[AllProviderEvents.ConfigurationChanged]: new WeakMap<EventHandler, EventHandler[]>(),
|
||||||
[AllProviderEvents.ContextChanged]: new WeakMap<EventHandler, EventHandler[]>(),
|
[AllProviderEvents.ContextChanged]: new WeakMap<EventHandler, EventHandler[]>(),
|
||||||
[AllProviderEvents.Ready]: new WeakMap<EventHandler, EventHandler[]>(),
|
[AllProviderEvents.Ready]: new WeakMap<EventHandler, EventHandler[]>(),
|
||||||
|
|
@ -33,7 +36,11 @@ export abstract class GenericEventEmitter<E extends AnyProviderEvent, Additional
|
||||||
// The handlers have to be wrapped with an async function because if a synchronous functions throws an error,
|
// The handlers have to be wrapped with an async function because if a synchronous functions throws an error,
|
||||||
// the other handlers will not run.
|
// the other handlers will not run.
|
||||||
const asyncHandler = async (details?: EventDetails) => {
|
const asyncHandler = async (details?: EventDetails) => {
|
||||||
await handler(details);
|
try {
|
||||||
|
await handler(details);
|
||||||
|
} catch (err) {
|
||||||
|
this._logger?.error('Error running event handler:', err);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// The async handler has to be written to the map, because we need to get the wrapper function when deleting a listener
|
// The async handler has to be written to the map, because we need to get the wrapper function when deleting a listener
|
||||||
const existingAsyncHandlers = this._handlers[eventType].get(handler);
|
const existingAsyncHandlers = this._handlers[eventType].get(handler);
|
||||||
|
|
@ -84,7 +91,7 @@ export abstract class GenericEventEmitter<E extends AnyProviderEvent, Additional
|
||||||
* This is an un-exported type that corresponds to NodeJS.EventEmitter.
|
* This is an un-exported type that corresponds to NodeJS.EventEmitter.
|
||||||
* We can't use that type here, because this module is used in both the browser, and the server.
|
* We can't use that type here, because this module is used in both the browser, and the server.
|
||||||
* In the server, node (or whatever server runtime) provides an implementation for this.
|
* In the server, node (or whatever server runtime) provides an implementation for this.
|
||||||
* In the browser, we bundle in the popular 'events' package, which is a polyfill of NodeJS.EventEmitter.
|
* In the browser, we bundle in the popular 'EventEmitter3' package, which is a polyfill of NodeJS.EventEmitter.
|
||||||
*/
|
*/
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
interface PlatformEventEmitter {
|
interface PlatformEventEmitter {
|
||||||
|
|
@ -94,13 +101,8 @@ interface PlatformEventEmitter {
|
||||||
removeListener(eventName: string | symbol, listener: (...args: any[]) => void): this;
|
removeListener(eventName: string | symbol, listener: (...args: any[]) => void): this;
|
||||||
off(eventName: string | symbol, listener: (...args: any[]) => void): this;
|
off(eventName: string | symbol, listener: (...args: any[]) => void): this;
|
||||||
removeAllListeners(event?: string | symbol): this;
|
removeAllListeners(event?: string | symbol): this;
|
||||||
setMaxListeners(n: number): this;
|
|
||||||
getMaxListeners(): number;
|
|
||||||
listeners(eventName: string | symbol): Function[];
|
listeners(eventName: string | symbol): Function[];
|
||||||
rawListeners(eventName: string | symbol): Function[];
|
|
||||||
emit(eventName: string | symbol, ...args: any[]): boolean;
|
emit(eventName: string | symbol, ...args: any[]): boolean;
|
||||||
listenerCount(eventName: string | symbol, listener?: Function): number;
|
listenerCount(eventName: string | symbol, listener?: Function): number;
|
||||||
prependListener(eventName: string | symbol, listener: (...args: any[]) => void): this;
|
|
||||||
prependOnceListener(eventName: string | symbol, listener: (...args: any[]) => void): this;
|
|
||||||
eventNames(): Array<string | symbol>;
|
eventNames(): Array<string | symbol>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue