Repository: open-feature/js-sdk (ts)

Issue:
[BUG] Bound providers are shutdown while attached to clients
### Observed behavior

Providers that are bound to several named clients are closed if another provider is set for one of these clients.
Spec Issue: https://github.com/open-feature/spec/issues/192

### Expected Behavior

_No response_

### Steps to reproduce

_No response_

Pull request:
fix: only shutdown providers that are not attached to any client
<!-- 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 -->

Providers that are bound to several named clients are closed if another provider is set for one of these clients.
This PR only closes providers if they are not used by any client.

### Related Issues
<!-- add here the GitHub issue that this PR resolves if applicable -->

Fixes #443 

### Notes
<!-- any additional notes for this PR -->

### Follow-up Tasks
<!-- anything that is related to this PR but not done here should be noted under this section -->
<!-- if there is a need for a new issue, please link it here -->

### How to test
<!-- if applicable, add testing instructions under this section -->
Added tests for server and client.


Patch:
diff --git a/packages/shared/src/open-feature.ts b/packages/shared/src/open-feature.ts
index 7caf79a0..c33a4d61 100644
--- a/packages/shared/src/open-feature.ts
+++ b/packages/shared/src/open-feature.ts
@@ -129,10 +129,11 @@ export abstract class OpenFeatureCommonAPI<P extends CommonProvider = CommonProv
 
     this.transferListeners(oldProvider, provider, clientName, clientEmitter);
 
-    // Do not close the default provider if a named client used the default provider
-    if (!clientName || (clientName && oldProvider !== this._defaultProvider)) {
+    // Do not close a provider that is bound to any client
+    if (![...this._clientProviders.values(), this._defaultProvider].includes(oldProvider)) {
       oldProvider?.onClose?.();
     }
+
     return this;
   }
 


Test patch:
diff --git a/packages/client/test/open-feature.spec.ts b/packages/client/test/open-feature.spec.ts
index b7a8fb23..0ac27db9 100644
--- a/packages/client/test/open-feature.spec.ts
+++ b/packages/client/test/open-feature.spec.ts
@@ -80,6 +80,29 @@ describe('OpenFeature', () => {
 
       expect(namedClient.metadata.providerMetadata.name).toEqual(fakeProvider.metadata.name);
     });
+
+    it('should close a provider if it is replaced and no other client uses it', async () => {
+      const provider1 = { ...MOCK_PROVIDER, onClose: jest.fn() };
+      const provider2 = { ...MOCK_PROVIDER, onClose: jest.fn() };
+
+      OpenFeature.setProvider('client1', provider1);
+      expect(provider1.onClose).not.toHaveBeenCalled();
+      OpenFeature.setProvider('client1', provider2);
+      expect(provider1.onClose).toHaveBeenCalledTimes(1);
+    });
+
+    it('should not close provider if it is used by another client', async () => {
+      const provider1 = { ...MOCK_PROVIDER, onClose: jest.fn() };
+
+      OpenFeature.setProvider('client1', provider1);
+      OpenFeature.setProvider('client2', provider1);
+
+      OpenFeature.setProvider('client1', { ...provider1 });
+      expect(provider1.onClose).not.toHaveBeenCalled();
+
+      OpenFeature.setProvider('client2', { ...provider1 });
+      expect(provider1.onClose).toHaveBeenCalledTimes(1);
+    });
   });
 
   describe('Requirement 1.1.4', () => {
diff --git a/packages/server/test/open-feature.spec.ts b/packages/server/test/open-feature.spec.ts
index 81625d96..d4a49a9a 100644
--- a/packages/server/test/open-feature.spec.ts
+++ b/packages/server/test/open-feature.spec.ts
@@ -82,6 +82,29 @@ describe('OpenFeature', () => {
 
       expect(namedClient.metadata.providerMetadata.name).toEqual(fakeProvider.metadata.name);
     });
+
+    it('should close a provider if it is replaced and no other client uses it', async () => {
+      const provider1 = { ...MOCK_PROVIDER, onClose: jest.fn() };
+      const provider2 = { ...MOCK_PROVIDER, onClose: jest.fn() };
+
+      OpenFeature.setProvider('client1', provider1);
+      expect(provider1.onClose).not.toHaveBeenCalled();
+      OpenFeature.setProvider('client1', provider2);
+      expect(provider1.onClose).toHaveBeenCalledTimes(1);
+    });
+
+    it('should not close provider if it is used by another client', async () => {
+      const provider1 = { ...MOCK_PROVIDER, onClose: jest.fn() };
+
+      OpenFeature.setProvider('client1', provider1);
+      OpenFeature.setProvider('client2', provider1);
+
+      OpenFeature.setProvider('client1', { ...provider1 });
+      expect(provider1.onClose).not.toHaveBeenCalled();
+
+      OpenFeature.setProvider('client2', { ...provider1 });
+      expect(provider1.onClose).toHaveBeenCalledTimes(1);
+    });
   });
 
   describe('Requirement 1.1.4', () => {