hypha-rpc 0.21.34 → 0.21.35
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/coverage/html/index.html +1 -1
- package/dist/hypha-rpc-websocket.js +71 -19
- 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 +71 -19
- package/dist/hypha-rpc-websocket.mjs.map +1 -1
- package/package.json +1 -1
- package/src/rpc.js +9 -1
- package/src/websocket-client.js +13 -0
- package/hypha-rpc-0.21.15.tgz +0 -0
package/coverage/html/index.html
CHANGED
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
<div class='footer quiet pad2 space-top1 center small'>
|
|
87
87
|
Code coverage generated by
|
|
88
88
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
|
89
|
-
at 2026-
|
|
89
|
+
at 2026-03-10T23:48:04.149Z
|
|
90
90
|
</div>
|
|
91
91
|
<script src="prettify.js"></script>
|
|
92
92
|
<script>
|
|
@@ -2979,15 +2979,47 @@ class HTTPStreamingRPCConnection {
|
|
|
2979
2979
|
|
|
2980
2980
|
/**
|
|
2981
2981
|
* Process msgpack stream with 4-byte length prefix.
|
|
2982
|
+
*
|
|
2983
|
+
* Includes a read timeout to detect dead connections. The Hypha server
|
|
2984
|
+
* sends pings every ~30s, so if no data arrives within READ_TIMEOUT_MS,
|
|
2985
|
+
* the connection is considered dead and the stream is cancelled to
|
|
2986
|
+
* trigger reconnection.
|
|
2982
2987
|
*/
|
|
2983
2988
|
async _processMsgpackStream(response) {
|
|
2984
2989
|
const reader = response.body.getReader();
|
|
2990
|
+
// Expose the reader so external code (e.g. daemon heartbeat) can cancel it
|
|
2991
|
+
this._reader = reader;
|
|
2985
2992
|
// Growing buffer to avoid O(n^2) re-allocation on every chunk
|
|
2986
2993
|
let buffer = new Uint8Array(4096);
|
|
2987
2994
|
let bufferLen = 0;
|
|
2988
2995
|
|
|
2996
|
+
// Read timeout: server sends pings every ~30s, so 120s = 4 missed pings = dead
|
|
2997
|
+
const READ_TIMEOUT_MS = 120_000;
|
|
2998
|
+
|
|
2989
2999
|
while (!this._closed) {
|
|
2990
|
-
|
|
3000
|
+
let readResult;
|
|
3001
|
+
let timeoutId;
|
|
3002
|
+
try {
|
|
3003
|
+
readResult = await Promise.race([
|
|
3004
|
+
reader.read(),
|
|
3005
|
+
new Promise((_, reject) => {
|
|
3006
|
+
timeoutId = setTimeout(
|
|
3007
|
+
() => reject(new Error("Stream read timeout (no data for 120s)")),
|
|
3008
|
+
READ_TIMEOUT_MS,
|
|
3009
|
+
);
|
|
3010
|
+
}),
|
|
3011
|
+
]);
|
|
3012
|
+
clearTimeout(timeoutId);
|
|
3013
|
+
} catch (error) {
|
|
3014
|
+
clearTimeout(timeoutId);
|
|
3015
|
+
console.warn(`Stream read error: ${error.message}`);
|
|
3016
|
+
try {
|
|
3017
|
+
reader.cancel();
|
|
3018
|
+
} catch {}
|
|
3019
|
+
break;
|
|
3020
|
+
}
|
|
3021
|
+
|
|
3022
|
+
const { done, value } = readResult;
|
|
2991
3023
|
|
|
2992
3024
|
if (done) break;
|
|
2993
3025
|
|
|
@@ -3067,6 +3099,8 @@ class HTTPStreamingRPCConnection {
|
|
|
3067
3099
|
bufferLen = remaining;
|
|
3068
3100
|
}
|
|
3069
3101
|
}
|
|
3102
|
+
|
|
3103
|
+
this._reader = null;
|
|
3070
3104
|
}
|
|
3071
3105
|
|
|
3072
3106
|
/**
|
|
@@ -4002,12 +4036,10 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
4002
4036
|
|
|
4003
4037
|
try {
|
|
4004
4038
|
// Get fresh manager service (one RPC roundtrip, ~50-100ms)
|
|
4005
|
-
const _t0 = Date.now();
|
|
4006
4039
|
const manager = await this.get_manager_service({
|
|
4007
4040
|
timeout: 20,
|
|
4008
4041
|
case_conversion: "camel",
|
|
4009
4042
|
});
|
|
4010
|
-
console.error(`[DEBUG] get_manager_service took ${Date.now() - _t0}ms`);
|
|
4011
4043
|
|
|
4012
4044
|
// Fire manager_refreshed IMMEDIATELY — before service re-registration.
|
|
4013
4045
|
// This allows connectToServer's wm proxy to be updated as soon as
|
|
@@ -4015,7 +4047,7 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
4015
4047
|
this._fire("manager_refreshed", { manager });
|
|
4016
4048
|
|
|
4017
4049
|
const services = Object.values(this._services);
|
|
4018
|
-
|
|
4050
|
+
let servicesCount = services.length;
|
|
4019
4051
|
let registeredCount = 0;
|
|
4020
4052
|
const failedServices = [];
|
|
4021
4053
|
|
|
@@ -4032,14 +4064,12 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
4032
4064
|
continue;
|
|
4033
4065
|
}
|
|
4034
4066
|
try {
|
|
4035
|
-
const _t1 = Date.now();
|
|
4036
4067
|
const serviceInfo = this._extract_service_info(service);
|
|
4037
4068
|
await withTimeout(
|
|
4038
4069
|
manager.registerService(serviceInfo),
|
|
4039
4070
|
serviceRegistrationTimeout,
|
|
4040
4071
|
`Timeout registering service ${service.id || "unknown"}`,
|
|
4041
4072
|
);
|
|
4042
|
-
console.error(`[DEBUG] registerService(${service.id}) took ${Date.now() - _t1}ms`);
|
|
4043
4073
|
registeredCount++;
|
|
4044
4074
|
} catch (serviceError) {
|
|
4045
4075
|
failedServices.push(service.id || "unknown");
|
|
@@ -4075,6 +4105,11 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
4075
4105
|
failed: failedServices,
|
|
4076
4106
|
});
|
|
4077
4107
|
|
|
4108
|
+
// Track whether all services were registered so the
|
|
4109
|
+
// reconnection loop can detect partial failures and retry.
|
|
4110
|
+
this._connection._services_registered_ok =
|
|
4111
|
+
failedServices.length === 0;
|
|
4112
|
+
|
|
4078
4113
|
// Subscribe to client_disconnected events if the manager supports it
|
|
4079
4114
|
try {
|
|
4080
4115
|
if (
|
|
@@ -4167,6 +4202,9 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
4167
4202
|
error: managerError.toString(),
|
|
4168
4203
|
total_services: Object.keys(this._services).length,
|
|
4169
4204
|
});
|
|
4205
|
+
// Mark registration as failed so the reconnection loop
|
|
4206
|
+
// can detect and retry
|
|
4207
|
+
this._connection._services_registered_ok = false;
|
|
4170
4208
|
}
|
|
4171
4209
|
} else {
|
|
4172
4210
|
// console.debug("Connection established", connectionInfo);
|
|
@@ -4184,10 +4222,11 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
4184
4222
|
// This ensures no remote function call hangs forever when the connection drops
|
|
4185
4223
|
if (typeof connection.on_disconnected === "function") {
|
|
4186
4224
|
connection.on_disconnected((reason) => {
|
|
4187
|
-
// If reconnection is enabled
|
|
4188
|
-
//
|
|
4189
|
-
//
|
|
4190
|
-
|
|
4225
|
+
// If reconnection is enabled AND the connection is not permanently closed,
|
|
4226
|
+
// don't reject pending calls immediately — they may succeed after reconnect.
|
|
4227
|
+
// When _closed is true (max retries exhausted or server refused reconnect),
|
|
4228
|
+
// we must reject immediately so callers are not left hanging forever.
|
|
4229
|
+
if (connection._enable_reconnect && !connection._closed) {
|
|
4191
4230
|
this._logger.info(
|
|
4192
4231
|
`Connection lost (${reason}), reconnection enabled - pending calls will be handled by timeout`,
|
|
4193
4232
|
);
|
|
@@ -4541,7 +4580,15 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
4541
4580
|
delete this._rintfCallerIndex[clientId];
|
|
4542
4581
|
let cleaned = 0;
|
|
4543
4582
|
for (const sid of serviceIds) {
|
|
4544
|
-
|
|
4583
|
+
const svc = this._services[sid];
|
|
4584
|
+
if (svc) {
|
|
4585
|
+
if (typeof svc._dispose === "function") {
|
|
4586
|
+
try {
|
|
4587
|
+
svc._dispose();
|
|
4588
|
+
} catch (e) {
|
|
4589
|
+
/* ignore errors from dispose handlers */
|
|
4590
|
+
}
|
|
4591
|
+
}
|
|
4545
4592
|
delete this._services[sid];
|
|
4546
4593
|
cleaned++;
|
|
4547
4594
|
}
|
|
@@ -10713,6 +10760,19 @@ class WebsocketRPCConnection {
|
|
|
10713
10760
|
throw new Error("Connection lost during reconnection settle");
|
|
10714
10761
|
}
|
|
10715
10762
|
|
|
10763
|
+
// Check if service re-registration succeeded.
|
|
10764
|
+
// The on_connected callback sets this flag; if any
|
|
10765
|
+
// services failed to register, retry instead of
|
|
10766
|
+
// declaring success with missing services.
|
|
10767
|
+
if (this._services_registered_ok === false) {
|
|
10768
|
+
this._logger.warn(
|
|
10769
|
+
"Service re-registration failed, retrying...",
|
|
10770
|
+
);
|
|
10771
|
+
throw new Error(
|
|
10772
|
+
"Service re-registration failed after reconnection",
|
|
10773
|
+
);
|
|
10774
|
+
}
|
|
10775
|
+
|
|
10716
10776
|
this._logger.warn(
|
|
10717
10777
|
`Successfully reconnected to server ${this._server_url} (services re-registered)`,
|
|
10718
10778
|
);
|
|
@@ -11059,9 +11119,7 @@ async function connectToServer(config) {
|
|
|
11059
11119
|
config.ping_interval,
|
|
11060
11120
|
config.logger,
|
|
11061
11121
|
);
|
|
11062
|
-
const _tOpen = Date.now();
|
|
11063
11122
|
const connection_info = await connection.open();
|
|
11064
|
-
console.error(`[DEBUG] connection.open() took ${Date.now() - _tOpen}ms`);
|
|
11065
11123
|
(0,_utils_index_js__WEBPACK_IMPORTED_MODULE_2__.assert)(
|
|
11066
11124
|
connection_info,
|
|
11067
11125
|
"Failed to connect to the server, no connection info obtained. This issue is most likely due to an outdated Hypha server version. Please use `imjoy-rpc` for compatibility, or upgrade the Hypha server to the latest version.",
|
|
@@ -11095,7 +11153,6 @@ async function connectToServer(config) {
|
|
|
11095
11153
|
}
|
|
11096
11154
|
|
|
11097
11155
|
const workspace = connection_info.workspace;
|
|
11098
|
-
const _tRpc = Date.now();
|
|
11099
11156
|
const rpc = new _rpc_js__WEBPACK_IMPORTED_MODULE_0__.RPC(connection, {
|
|
11100
11157
|
client_id: clientId,
|
|
11101
11158
|
workspace,
|
|
@@ -11112,17 +11169,12 @@ async function connectToServer(config) {
|
|
|
11112
11169
|
encryption_private_key: config.encryption_private_key || null,
|
|
11113
11170
|
encryption_public_key: config.encryption_public_key || null,
|
|
11114
11171
|
});
|
|
11115
|
-
console.error(`[DEBUG] RPC constructor took ${Date.now() - _tRpc}ms`);
|
|
11116
|
-
const _tWait = Date.now();
|
|
11117
11172
|
await rpc.waitFor("services_registered", config.method_timeout || 120);
|
|
11118
|
-
console.error(`[DEBUG] waitFor('services_registered') took ${Date.now() - _tWait}ms`);
|
|
11119
|
-
const _tWm = Date.now();
|
|
11120
11173
|
const wm = await rpc.get_manager_service({
|
|
11121
11174
|
timeout: config.method_timeout,
|
|
11122
11175
|
case_conversion: "camel",
|
|
11123
11176
|
kwargs_expansion: config.kwargs_expansion || false,
|
|
11124
11177
|
});
|
|
11125
|
-
console.error(`[DEBUG] get_manager_service (connectToServer) took ${Date.now() - _tWm}ms`);
|
|
11126
11178
|
wm.rpc = rpc;
|
|
11127
11179
|
|
|
11128
11180
|
// Auto-refresh workspace manager proxy after reconnection.
|