hypha-rpc 0.20.79 → 0.20.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hypha-rpc-websocket.js +221 -0
- package/dist/hypha-rpc-websocket.js.map +1 -1
- package/dist/hypha-rpc-websocket.min.js +1 -1
- package/dist/hypha-rpc-websocket.min.js.map +1 -1
- package/dist/hypha-rpc-websocket.min.mjs +1 -1
- package/dist/hypha-rpc-websocket.min.mjs.map +1 -1
- package/dist/hypha-rpc-websocket.mjs +221 -0
- package/dist/hypha-rpc-websocket.mjs.map +1 -1
- package/package.json +1 -1
- package/src/rpc.js +221 -0
|
@@ -447,6 +447,57 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
447
447
|
registered: registeredCount,
|
|
448
448
|
failed: failedServices,
|
|
449
449
|
});
|
|
450
|
+
|
|
451
|
+
// Subscribe to client_disconnected events if the manager supports it
|
|
452
|
+
try {
|
|
453
|
+
if (
|
|
454
|
+
manager.subscribe &&
|
|
455
|
+
typeof manager.subscribe === "function"
|
|
456
|
+
) {
|
|
457
|
+
console.debug("Subscribing to client_disconnected events");
|
|
458
|
+
|
|
459
|
+
const handleClientDisconnected = async (event) => {
|
|
460
|
+
// The client ID is in event.data.id based on the event structure
|
|
461
|
+
const clientId = event.data?.id || event.client;
|
|
462
|
+
const workspace = event.data?.workspace;
|
|
463
|
+
if (clientId && workspace) {
|
|
464
|
+
// Construct the full client path with workspace prefix
|
|
465
|
+
const fullClientId = `${workspace}/${clientId}`;
|
|
466
|
+
console.debug(
|
|
467
|
+
`Client ${fullClientId} disconnected, cleaning up sessions`,
|
|
468
|
+
);
|
|
469
|
+
await this._handleClientDisconnected(fullClientId);
|
|
470
|
+
} else if (clientId) {
|
|
471
|
+
console.debug(
|
|
472
|
+
`Client ${clientId} disconnected, cleaning up sessions`,
|
|
473
|
+
);
|
|
474
|
+
await this._handleClientDisconnected(clientId);
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
// Subscribe to the event topic first
|
|
479
|
+
this._clientDisconnectedSubscription = await manager.subscribe([
|
|
480
|
+
"client_disconnected",
|
|
481
|
+
]);
|
|
482
|
+
|
|
483
|
+
// Then register the local event handler
|
|
484
|
+
this.on("client_disconnected", handleClientDisconnected);
|
|
485
|
+
|
|
486
|
+
console.debug(
|
|
487
|
+
"Successfully subscribed to client_disconnected events",
|
|
488
|
+
);
|
|
489
|
+
} else {
|
|
490
|
+
console.debug(
|
|
491
|
+
"Manager does not support subscribe method, skipping client_disconnected handling",
|
|
492
|
+
);
|
|
493
|
+
this._clientDisconnectedSubscription = null;
|
|
494
|
+
}
|
|
495
|
+
} catch (subscribeError) {
|
|
496
|
+
console.warn(
|
|
497
|
+
`Failed to subscribe to client_disconnected events: ${subscribeError}`,
|
|
498
|
+
);
|
|
499
|
+
this._clientDisconnectedSubscription = null;
|
|
500
|
+
}
|
|
450
501
|
} catch (managerError) {
|
|
451
502
|
console.error(
|
|
452
503
|
`Failed to get manager service for registering services: ${managerError}`,
|
|
@@ -640,6 +691,9 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
640
691
|
}
|
|
641
692
|
|
|
642
693
|
close() {
|
|
694
|
+
// Clean up all pending sessions before closing
|
|
695
|
+
this._cleanupOnDisconnect();
|
|
696
|
+
|
|
643
697
|
// Clear all heartbeat intervals
|
|
644
698
|
for (const session_id in this._object_store) {
|
|
645
699
|
if (this._object_store.hasOwnProperty(session_id)) {
|
|
@@ -652,9 +706,176 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
652
706
|
}
|
|
653
707
|
}
|
|
654
708
|
}
|
|
709
|
+
|
|
710
|
+
// Unsubscribe from client_disconnected events if subscribed
|
|
711
|
+
if (this._clientDisconnectedSubscription) {
|
|
712
|
+
try {
|
|
713
|
+
// Get the manager service to unsubscribe (non-blocking)
|
|
714
|
+
if (this._connection && this._connection.manager_id) {
|
|
715
|
+
this.get_remote_service("*/" + this._connection.manager_id)
|
|
716
|
+
.then((manager) => {
|
|
717
|
+
if (
|
|
718
|
+
manager.unsubscribe &&
|
|
719
|
+
typeof manager.unsubscribe === "function"
|
|
720
|
+
) {
|
|
721
|
+
return manager.unsubscribe("client_disconnected");
|
|
722
|
+
}
|
|
723
|
+
})
|
|
724
|
+
.catch((e) => {
|
|
725
|
+
console.debug(
|
|
726
|
+
`Error unsubscribing from client_disconnected: ${e}`,
|
|
727
|
+
);
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
// Remove the local event handler
|
|
731
|
+
this.off("client_disconnected");
|
|
732
|
+
} catch (e) {
|
|
733
|
+
console.debug(`Error unsubscribing from client_disconnected: ${e}`);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
655
737
|
this._fire("disconnected");
|
|
656
738
|
}
|
|
657
739
|
|
|
740
|
+
async _handleClientDisconnected(clientId) {
|
|
741
|
+
try {
|
|
742
|
+
console.debug(`Handling disconnection for client: ${clientId}`);
|
|
743
|
+
|
|
744
|
+
// Clean up all sessions for the disconnected client
|
|
745
|
+
const sessionsCleaned = this._cleanupSessionsForClient(clientId);
|
|
746
|
+
|
|
747
|
+
if (sessionsCleaned > 0) {
|
|
748
|
+
console.debug(
|
|
749
|
+
`Cleaned up ${sessionsCleaned} sessions for disconnected client: ${clientId}`,
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// Fire an event to notify about the client disconnection
|
|
754
|
+
this._fire("remote_client_disconnected", {
|
|
755
|
+
client_id: clientId,
|
|
756
|
+
sessions_cleaned: sessionsCleaned,
|
|
757
|
+
});
|
|
758
|
+
} catch (e) {
|
|
759
|
+
console.error(
|
|
760
|
+
`Error handling client disconnection for ${clientId}: ${e}`,
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
_cleanupSessionsForClient(clientId) {
|
|
766
|
+
let sessionsCleaned = 0;
|
|
767
|
+
|
|
768
|
+
// Iterate through all top-level session keys
|
|
769
|
+
for (const sessionKey of Object.keys(this._object_store)) {
|
|
770
|
+
if (sessionKey === "services" || sessionKey === "message_cache") {
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
const session = this._object_store[sessionKey];
|
|
775
|
+
if (!session || typeof session !== "object") {
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// Check if this session belongs to the disconnected client
|
|
780
|
+
// Sessions have a target_id property that identifies which client they're calling
|
|
781
|
+
if (session.target_id === clientId) {
|
|
782
|
+
// Reject any pending promises in this session
|
|
783
|
+
if (session.reject && typeof session.reject === "function") {
|
|
784
|
+
console.debug(`Rejecting session ${sessionKey}`);
|
|
785
|
+
try {
|
|
786
|
+
session.reject(new Error(`Client disconnected: ${clientId}`));
|
|
787
|
+
} catch (e) {
|
|
788
|
+
console.warn(`Error rejecting session ${sessionKey}: ${e}`);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
if (session.resolve && typeof session.resolve === "function") {
|
|
793
|
+
console.debug(`Resolving session ${sessionKey} with error`);
|
|
794
|
+
try {
|
|
795
|
+
session.resolve(new Error(`Client disconnected: ${clientId}`));
|
|
796
|
+
} catch (e) {
|
|
797
|
+
console.warn(`Error resolving session ${sessionKey}: ${e}`);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// Clear any timers
|
|
802
|
+
if (session.timer && typeof session.timer.clear === "function") {
|
|
803
|
+
try {
|
|
804
|
+
session.timer.clear();
|
|
805
|
+
} catch (e) {
|
|
806
|
+
console.warn(`Error clearing timer for ${sessionKey}: ${e}`);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// Clear heartbeat tasks
|
|
811
|
+
if (session.heartbeat_task) {
|
|
812
|
+
try {
|
|
813
|
+
clearInterval(session.heartbeat_task);
|
|
814
|
+
} catch (e) {
|
|
815
|
+
console.warn(`Error clearing heartbeat for ${sessionKey}: ${e}`);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// Remove the entire session
|
|
820
|
+
delete this._object_store[sessionKey];
|
|
821
|
+
sessionsCleaned++;
|
|
822
|
+
console.debug(`Cleaned up session: ${sessionKey}`);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
return sessionsCleaned;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
_cleanupOnDisconnect() {
|
|
830
|
+
try {
|
|
831
|
+
console.debug("Cleaning up all sessions due to local RPC disconnection");
|
|
832
|
+
|
|
833
|
+
// Get all keys to delete after cleanup
|
|
834
|
+
const keysToDelete = [];
|
|
835
|
+
|
|
836
|
+
for (const key of Object.keys(this._object_store)) {
|
|
837
|
+
if (key === "services") {
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
const value = this._object_store[key];
|
|
842
|
+
|
|
843
|
+
if (typeof value === "object" && value !== null) {
|
|
844
|
+
// Reject any pending promises
|
|
845
|
+
if (value.reject && typeof value.reject === "function") {
|
|
846
|
+
try {
|
|
847
|
+
value.reject(new Error("RPC connection closed"));
|
|
848
|
+
} catch (e) {
|
|
849
|
+
console.debug(`Error rejecting promise during cleanup: ${e}`);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// Clean up timers and tasks
|
|
854
|
+
if (value.heartbeat_task) {
|
|
855
|
+
clearInterval(value.heartbeat_task);
|
|
856
|
+
}
|
|
857
|
+
if (value.timer && typeof value.timer.clear === "function") {
|
|
858
|
+
try {
|
|
859
|
+
value.timer.clear();
|
|
860
|
+
} catch (e) {
|
|
861
|
+
console.debug(`Error clearing timer: ${e}`);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Mark ALL keys for deletion except services
|
|
867
|
+
keysToDelete.push(key);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Delete all marked sessions
|
|
871
|
+
for (const key of keysToDelete) {
|
|
872
|
+
delete this._object_store[key];
|
|
873
|
+
}
|
|
874
|
+
} catch (e) {
|
|
875
|
+
console.error(`Error during cleanup on disconnect: ${e}`);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
658
879
|
async disconnect() {
|
|
659
880
|
this.close();
|
|
660
881
|
await this._connection.disconnect();
|