Skip to content
This repository was archived by the owner on Apr 13, 2025. It is now read-only.

Commit b71fdc2

Browse files
authored
Merge pull request #122 from codeoverflow-org/feature/9-remove-client-listeners
Unregister handlers of bundle when assigning an other instance
2 parents 1cc1047 + a7f8159 commit b71fdc2

File tree

22 files changed

+288
-120
lines changed

22 files changed

+288
-120
lines changed

nodecg-io-core/extension/bundleManager.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,17 @@ export class BundleManager extends EventEmitter {
8181
if (svcDependency === undefined) {
8282
return error(`Bundle "${bundleName} doesn't depend on the "${instance.serviceType}" service.`);
8383
}
84+
const oldInstance = svcDependency.serviceInstance;
8485

8586
// Update service instance of service dependency, remove client update callback from old service instance (if applicable)
8687
// and add the callback to the new instance.
8788
svcDependency.serviceInstance = instanceName;
8889

8990
// Let the bundle update his reference to the client
9091
svcDependency.clientUpdateCallback(instance.client);
92+
9193
this.emit("change");
94+
this.emit("reregisterInstance", oldInstance);
9295
return emptySuccess();
9396
}
9497

@@ -105,10 +108,15 @@ export class BundleManager extends EventEmitter {
105108
const svcDependency = bundle?.find((svcDep) => svcDep.serviceType === serviceType);
106109

107110
if (svcDependency !== undefined) {
111+
const oldInstance = svcDependency.serviceInstance;
112+
108113
// Unset service instance and let the bundle know that it hasn't access to this service anymore.
109114
svcDependency.serviceInstance = undefined;
110115
svcDependency.clientUpdateCallback(undefined);
116+
111117
this.emit("change");
118+
this.emit("reregisterInstance", oldInstance);
119+
112120
return true;
113121
}
114122

nodecg-io-core/extension/instanceManager.ts

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ export class InstanceManager extends EventEmitter {
1919
private readonly bundles: BundleManager,
2020
) {
2121
super();
22+
bundles.on("reregisterInstance", (serviceInstance?: string) =>
23+
this.reregisterHandlersOfInstance(serviceInstance),
24+
);
2225
}
2326

2427
/**
@@ -199,17 +202,14 @@ export class InstanceManager extends EventEmitter {
199202

200203
// Check if a error happened while creating the client
201204
if (client.failed) {
202-
this.nodecg.log.error(
203-
`The "${inst.serviceType}" service produced an error while creating a client: ${client.errorMessage}`,
204-
);
205-
inst.client = undefined;
205+
throw client.errorMessage; // Error logging happens in catch block
206206
} else {
207207
// Update service instance object
208208
inst.client = client.result;
209209
}
210210
} catch (err) {
211211
this.nodecg.log.error(
212-
`The "${inst.serviceType}" service function produced an error while creating a client: ${err}`,
212+
`The "${inst.serviceType}" service produced an error while creating a client: ${err}`,
213213
);
214214
inst.client = undefined;
215215
}
@@ -228,4 +228,46 @@ export class InstanceManager extends EventEmitter {
228228
}
229229
}
230230
}
231+
232+
/**
233+
* Removes all handlers from the service client of the instance and lets bundles readd their handlers.
234+
* @param instanceName the name of the instance which handlers should be re-registred
235+
*/
236+
private reregisterHandlersOfInstance(instanceName?: string): void {
237+
if (!instanceName) return;
238+
239+
const inst = this.getServiceInstance(instanceName);
240+
if (!inst) {
241+
this.nodecg.log.error(`Can't re-register handlers of instance "${instanceName}": instance not found`);
242+
return;
243+
}
244+
245+
const svc = this.services.getService(inst.serviceType);
246+
if (svc.failed) {
247+
this.nodecg.log.error(
248+
`Can't reregister handlers of instance "${instanceName}": can't get service: ${svc.errorMessage}`,
249+
);
250+
return;
251+
}
252+
253+
// Client should be recreated because the Service has no way to reset the handlers.
254+
if (svc.result.reCreateClientToRemoveHandlers) {
255+
this.updateInstanceClient(inst, instanceName, svc.result);
256+
return;
257+
}
258+
259+
if (!svc.result.removeHandlers) return; // Service provides no way to remove handlers, thus this service has no handlers
260+
261+
// Remove handlers
262+
try {
263+
svc.result.removeHandlers(inst.client);
264+
} catch (err) {
265+
this.nodecg.log.error(
266+
`Can't re-register handlers of instance "${instanceName}": error while removing handlers: ${err.toString()}`,
267+
);
268+
}
269+
// Readd handlers by running the `onAvailable` function of all bundles
270+
// that are using this service instance.
271+
this.bundles.handleInstanceUpdate(inst, instanceName);
272+
}
231273
}

nodecg-io-core/extension/serviceBundle.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,28 @@ export abstract class ServiceBundle<R, C extends ServiceClient<unknown>> impleme
7878
*/
7979
abstract stopClient(client: C): void;
8080

81+
/**
82+
* Removes all handlers from a service client.
83+
* This is used when a bundle no longer uses a service client it still has its handlers registered.
84+
* Then this function is called that should remove all handlers
85+
* and then all bundles that are still using this client will asked to re-register their handlers
86+
* by running the onAvailable callback of the specific bundle.
87+
*
88+
* Can be left unimplemented if the serivce doesn't has any handlers e.g. a http wrapper
89+
* @param client the client of which all handlers should be removed
90+
*/
91+
removeHandlers?(client: C): void;
92+
93+
/**
94+
* This flag can be enabled by services if they can't implement removeHandlers but also have some handlers that
95+
* should be reset if a bundleDependency has been changed.
96+
* It gets rid of the handlers by stopping the client and creating a new one, to which then only the
97+
* now wanted handlers get registered (e.g. if a bundle doesn't uses this service anymore but another still does).
98+
* Not ideal, but if your service can't implement removeHandlers for some reason it is still better than
99+
* having dangling handlers that still fire eventho they shouldn't.
100+
*/
101+
reCreateClientToRemoveHandlers = false;
102+
81103
private readSchema(pathSegments: string[]): unknown {
82104
const joinedPath = path.resolve(...pathSegments);
83105
try {

nodecg-io-core/extension/types.d.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,28 @@ export interface Service<R, C extends ServiceClient<unknown>> {
6060
* @param client the client that needs to be stopped.
6161
*/
6262
readonly stopClient(client: C): void;
63+
64+
/**
65+
* Removes all handlers from a service client.
66+
* This is used when a bundle no longer uses a service client it still has its handlers registered.
67+
* Then this function is called that should remove all handlers
68+
* and then all bundles that are still using this client will asked to re-register their handlers
69+
* by running the onAvailable callback of the specific bundle.
70+
*
71+
* Can be left unimplemented if the serivce doesn't has any handlers e.g. a http wrapper
72+
* @param client the client of which all handlers should be removed
73+
*/
74+
readonly removeHandlers?(client: C): void;
75+
76+
/**
77+
* This flag can be enabled by services if they can't implement removeHandlers but also have some handlers that
78+
* should be reset if a bundleDependency has been changed.
79+
* It gets rid of the handlers by stopping the client and creating a new one, to which then only the
80+
* now wanted handlers get registered (e.g. if a bundle doesn't uses this service anymore but another still does).
81+
* Not ideal, but if your service can't implement removeHandlers for some reason it is still better than
82+
* having dangling handlers that still fire eventho they shouldn't.
83+
*/
84+
reCreateClientToRemoveHandlers: boolean;
6385
}
6486

6587
/**

nodecg-io-discord/extension/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,8 @@ class DiscordService extends ServiceBundle<DiscordServiceConfig, DiscordServiceC
3838
const rawClient = client.getNativeClient();
3939
rawClient.destroy();
4040
}
41+
42+
removeHandlers(client: DiscordServiceClient): void {
43+
client.getNativeClient().removeAllListeners();
44+
}
4145
}

nodecg-io-irc/extension/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ class IRCService extends ServiceBundle<IRCServiceConfig, IRCServiceClient> {
6262
this.nodecg.log.info("Stopped IRC client successfully.");
6363
});
6464
}
65+
66+
removeHandlers(client: IRCServiceClient): void {
67+
client.getNativeClient().removeAllListeners();
68+
}
6569
}
6670

6771
function sendMessage(client: IRCClient, target: string, message: string): void {

nodecg-io-midi-input/extension/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,8 @@ class MidiService extends ServiceBundle<MidiInputServiceConfig, MidiInputService
5959
stopClient(client: MidiInputServiceClient): void {
6060
client.getNativeClient().close();
6161
}
62+
63+
removeHandlers(client: MidiInputServiceClient): void {
64+
client.getNativeClient().removeAllListeners();
65+
}
6266
}

nodecg-io-obs/extension/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,8 @@ class OBSService extends ServiceBundle<OBSServiceConfig, OBSServiceClient> {
4747
stopClient(client: OBSServiceClient) {
4848
client.getNativeClient().disconnect();
4949
}
50+
51+
removeHandlers(client: OBSServiceClient) {
52+
client.getNativeClient().removeAllListeners();
53+
}
5054
}

nodecg-io-sacn-receiver/extension/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,8 @@ class SacnReceiverService extends ServiceBundle<SacnReceiverServiceConfig, SacnR
3131
client.getNativeClient().close();
3232
this.nodecg.log.info("Stopped sACN Receiver successfully.");
3333
}
34+
35+
removeHandlers(client: SacnReceiverServiceClient): void {
36+
client.getNativeClient().removeAllListeners();
37+
}
3438
}

nodecg-io-serial/extension/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,8 @@ class SerialService extends ServiceBundle<SerialServiceConfig, SerialServiceClie
2424
stopClient(client: SerialServiceClient): void {
2525
client.close();
2626
}
27+
28+
removeHandlers(client: SerialServiceClient): void {
29+
client.getNativeClient().removeAllListeners();
30+
}
2731
}

0 commit comments

Comments
 (0)