hypha-rpc 0.20.85 → 0.20.87

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.
@@ -51,6 +51,31 @@ function _appendBuffer(buffer1, buffer2) {
51
51
  return tmp.buffer;
52
52
  }
53
53
 
54
+ /**
55
+ * Wrap a promise with a timeout.
56
+ * @param {Promise} promise - The promise to wrap.
57
+ * @param {number} timeoutMs - The timeout in milliseconds.
58
+ * @param {string} message - Optional error message for timeout.
59
+ * @returns {Promise} - The wrapped promise that will reject on timeout.
60
+ */
61
+ function withTimeout(promise, timeoutMs, message = "Operation timed out") {
62
+ return new Promise((resolve, reject) => {
63
+ const timeoutId = setTimeout(() => {
64
+ reject(new Error(`TimeoutError: ${message}`));
65
+ }, timeoutMs);
66
+
67
+ promise
68
+ .then((result) => {
69
+ clearTimeout(timeoutId);
70
+ resolve(result);
71
+ })
72
+ .catch((error) => {
73
+ clearTimeout(timeoutId);
74
+ reject(error);
75
+ });
76
+ });
77
+ }
78
+
54
79
  function indexObject(obj, is) {
55
80
  if (!is) throw new Error("undefined index");
56
81
  if (typeof is === "string") return indexObject(obj, is.split("."));
@@ -418,19 +443,35 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
418
443
  let registeredCount = 0;
419
444
  const failedServices = [];
420
445
 
446
+ // Use timeout for service registration to prevent hanging
447
+ const serviceRegistrationTimeout = this._method_timeout || 30000;
448
+
421
449
  for (let service of services) {
422
450
  try {
423
451
  const serviceInfo = this._extract_service_info(service);
424
- await manager.registerService(serviceInfo);
452
+ await withTimeout(
453
+ manager.registerService(serviceInfo),
454
+ serviceRegistrationTimeout,
455
+ `Timeout registering service ${service.id || "unknown"}`,
456
+ );
425
457
  registeredCount++;
426
458
  console.debug(
427
459
  `Successfully registered service: ${service.id || "unknown"}`,
428
460
  );
429
461
  } catch (serviceError) {
430
462
  failedServices.push(service.id || "unknown");
431
- console.error(
432
- `Failed to register service ${service.id || "unknown"}: ${serviceError}`,
433
- );
463
+ if (
464
+ serviceError.message &&
465
+ serviceError.message.includes("TimeoutError")
466
+ ) {
467
+ console.error(
468
+ `Timeout registering service ${service.id || "unknown"}`,
469
+ );
470
+ } else {
471
+ console.error(
472
+ `Failed to register service ${service.id || "unknown"}: ${serviceError}`,
473
+ );
474
+ }
434
475
  }
435
476
  }
436
477
 
@@ -478,10 +519,12 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
478
519
  }
479
520
  };
480
521
 
481
- // Subscribe to the event topic first
482
- this._clientDisconnectedSubscription = await manager.subscribe([
483
- "client_disconnected",
484
- ]);
522
+ // Subscribe to the event topic first with timeout
523
+ this._clientDisconnectedSubscription = await withTimeout(
524
+ manager.subscribe(["client_disconnected"]),
525
+ serviceRegistrationTimeout,
526
+ "Timeout subscribing to client_disconnected events",
527
+ );
485
528
 
486
529
  // Then register the local event handler
487
530
  this.on("client_disconnected", handleClientDisconnected);
@@ -1232,7 +1275,7 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
1232
1275
  config.workspace || this._local_workspace || this._connection.workspace;
1233
1276
  if (!config.workspace) {
1234
1277
  throw new Error(
1235
- "Workspace is not set. Please ensure the connection has a workspace or set local_workspace."
1278
+ "Workspace is not set. Please ensure the connection has a workspace or set local_workspace.",
1236
1279
  );
1237
1280
  }
1238
1281
  const skipContext = config.require_context;
@@ -2654,7 +2697,11 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
2654
2697
  local_workspace,
2655
2698
  ),
2656
2699
  };
2657
- } else if (aObject.constructor === Object || Array.isArray(aObject) || aObject instanceof RemoteService) {
2700
+ } else if (
2701
+ aObject.constructor === Object ||
2702
+ Array.isArray(aObject) ||
2703
+ aObject instanceof RemoteService
2704
+ ) {
2658
2705
  bObject = isarray ? [] : {};
2659
2706
  const keys = Object.keys(aObject);
2660
2707
  for (let k of keys) {
@@ -6278,12 +6325,22 @@ class WebsocketRPCConnection {
6278
6325
  }
6279
6326
  } catch (e) {
6280
6327
  if (`${e}`.includes("ConnectionAbortedError:")) {
6281
- console.warn("Failed to reconnect, connection aborted:", e);
6328
+ console.warn("Server refused to reconnect:", e);
6329
+ // Mark as closed and notify the application
6330
+ this._closed = true;
6331
+ if (this._handle_disconnected) {
6332
+ this._handle_disconnected(`Server refused reconnection: ${e}`);
6333
+ }
6282
6334
  return;
6283
6335
  } else if (`${e}`.includes("NotImplementedError:")) {
6284
6336
  console.error(
6285
6337
  `${e}\nIt appears that you are trying to connect to a hypha server that is older than 0.20.0, please upgrade the hypha server or use the websocket client in imjoy-rpc(https://www.npmjs.com/package/imjoy-rpc) instead`,
6286
6338
  );
6339
+ // Mark as closed to prevent further reconnection attempts
6340
+ this._closed = true;
6341
+ if (this._handle_disconnected) {
6342
+ this._handle_disconnected(`Server too old: ${e}`);
6343
+ }
6287
6344
  return;
6288
6345
  }
6289
6346
 
@@ -6337,21 +6394,20 @@ class WebsocketRPCConnection {
6337
6394
  await reconnect();
6338
6395
  } else {
6339
6396
  console.error(
6340
- `Failed to reconnect after ${MAX_RETRY} attempts, giving up. Exiting process.`,
6397
+ `Failed to reconnect after ${MAX_RETRY} attempts, giving up.`,
6341
6398
  );
6399
+ // Mark as closed to prevent further reconnection attempts
6400
+ this._closed = true;
6342
6401
  // Notify about max retry exceeded
6343
6402
  if (this._handle_disconnected) {
6344
6403
  this._handle_disconnected(
6345
6404
  "Max reconnection attempts exceeded",
6346
6405
  );
6347
6406
  }
6348
- // Exit process to prevent stuck event loop
6349
- if (typeof process !== "undefined" && process.exit) {
6350
- console.error(
6351
- "Forcing process exit due to unrecoverable connection failure",
6352
- );
6353
- process.exit(1);
6354
- }
6407
+ // Note: We intentionally do NOT call process.exit() here.
6408
+ // Instead, we mark the connection as closed and let the
6409
+ // application handle the failure through the disconnected
6410
+ // handler or by checking connection state.
6355
6411
  }
6356
6412
  }, finalDelay);
6357
6413
  this._reconnect_timeouts.add(timeoutId);
@@ -6468,7 +6524,7 @@ async function logout(config) {
6468
6524
  if (!svc.logout) {
6469
6525
  throw new Error(
6470
6526
  "Logout is not supported by this server. " +
6471
- "Please upgrade the Hypha server to a version that supports logout."
6527
+ "Please upgrade the Hypha server to a version that supports logout.",
6472
6528
  );
6473
6529
  }
6474
6530
 
@@ -6476,7 +6532,9 @@ async function logout(config) {
6476
6532
  if (callback) {
6477
6533
  await callback(context);
6478
6534
  } else {
6479
- console.log(`Please open your browser to logout at ${context.logout_url}`);
6535
+ console.log(
6536
+ `Please open your browser to logout at ${context.logout_url}`,
6537
+ );
6480
6538
  }
6481
6539
  return context;
6482
6540
  } catch (error) {