hypha-rpc 0.1.2 → 0.1.4

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.
@@ -172,7 +172,6 @@ class RPC extends _utils_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
172
172
  connection,
173
173
  {
174
174
  client_id = null,
175
- manager_id = null,
176
175
  default_context = null,
177
176
  name = null,
178
177
  codecs = null,
@@ -192,7 +191,6 @@ class RPC extends _utils_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
192
191
  this._name = name;
193
192
  this._app_id = app_id || "*";
194
193
  this._local_workspace = workspace;
195
- this.manager_id = manager_id;
196
194
  this._silent = silent;
197
195
  this.default_context = default_context || {};
198
196
  this._method_annotations = new WeakMap();
@@ -223,22 +221,31 @@ class RPC extends _utils_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
223
221
  },
224
222
  });
225
223
  this.on("method", this._handle_method.bind(this));
224
+ this.on("error", console.error);
226
225
 
227
226
  (0,_utils_js__WEBPACK_IMPORTED_MODULE_0__.assert)(connection.emit_message && connection.on_message);
227
+ (0,_utils_js__WEBPACK_IMPORTED_MODULE_0__.assert)(
228
+ connection.manager_id !== undefined,
229
+ "Connection must have manager_id",
230
+ );
228
231
  this._emit_message = connection.emit_message.bind(connection);
229
232
  connection.on_message(this._on_message.bind(this));
230
233
  this._connection = connection;
231
234
  const updateServices = async () => {
232
- if (!this._silent && this.manager_id) {
235
+ if (!this._silent && this._connection.manager_id) {
233
236
  console.log("Connection established, reporting services...");
234
237
  for (let service of Object.values(this._services)) {
235
238
  const serviceInfo = this._extract_service_info(service);
236
239
  await this.emit({
237
240
  type: "service-added",
238
- to: "*/" + this.manager_id,
241
+ to: "*/" + this._connection.manager_id,
239
242
  service: serviceInfo,
240
243
  });
241
244
  }
245
+ } else {
246
+ console.log(
247
+ "Connection established, no manager id to report services",
248
+ );
242
249
  }
243
250
  };
244
251
  connection.on_connect(updateServices);
@@ -365,8 +372,10 @@ class RPC extends _utils_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
365
372
  }
366
373
 
367
374
  _on_message(message) {
368
- try {
369
- (0,_utils_js__WEBPACK_IMPORTED_MODULE_0__.assert)(message instanceof ArrayBuffer);
375
+ if (typeof message === "string") {
376
+ const main = JSON.parse(message);
377
+ this._fire(main["type"], main);
378
+ } else if (message instanceof ArrayBuffer) {
370
379
  let unpacker = (0,_msgpack_msgpack__WEBPACK_IMPORTED_MODULE_1__.decodeMulti)(message);
371
380
  const { done, value } = unpacker.next();
372
381
  const main = value;
@@ -378,8 +387,8 @@ class RPC extends _utils_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
378
387
  Object.assign(main, extra.value);
379
388
  }
380
389
  this._fire(main["type"], main);
381
- } catch (error) {
382
- console.error("Failed to process message", error);
390
+ } else {
391
+ throw new Error("Invalid message format");
383
392
  }
384
393
  }
385
394
 
@@ -394,9 +403,9 @@ class RPC extends _utils_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
394
403
  }
395
404
 
396
405
  async get_manager_service(timeout) {
397
- (0,_utils_js__WEBPACK_IMPORTED_MODULE_0__.assert)(this.manager_id, "Manager id is not set");
406
+ (0,_utils_js__WEBPACK_IMPORTED_MODULE_0__.assert)(this._connection.manager_id, "Manager id is not set");
398
407
  const svc = await this.get_remote_service(
399
- `*/${this.manager_id}:default`,
408
+ `*/${this._connection.manager_id}:default`,
400
409
  timeout,
401
410
  );
402
411
  return svc;
@@ -418,7 +427,7 @@ class RPC extends _utils_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
418
427
  throw new Error("Service not found: " + service_id);
419
428
  }
420
429
 
421
- service.config["workspace"] = ws;
430
+ service.config["workspace"] = context["ws"];
422
431
  // allow access for the same workspace
423
432
  if (service.config.visibility == "public") {
424
433
  return service;
@@ -435,8 +444,8 @@ class RPC extends _utils_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
435
444
  }
436
445
  async get_remote_service(service_uri, timeout) {
437
446
  timeout = timeout === undefined ? this._method_timeout : timeout;
438
- if (!service_uri && this.manager_id) {
439
- service_uri = "*/" + this.manager_id;
447
+ if (!service_uri && this._connection.manager_id) {
448
+ service_uri = "*/" + this._connection.manager_id;
440
449
  } else if (!service_uri.includes(":")) {
441
450
  service_uri = this._client_id + ":" + service_uri;
442
451
  }
@@ -611,10 +620,10 @@ class RPC extends _utils_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
611
620
  const service = this.add_service(api, overwrite);
612
621
  const serviceInfo = this._extract_service_info(service);
613
622
  if (notify) {
614
- if (this.manager_id) {
623
+ if (this._connection.manager_id) {
615
624
  await this.emit({
616
625
  type: "service-added",
617
- to: "*/" + this.manager_id,
626
+ to: "*/" + this._connection.manager_id,
618
627
  service: serviceInfo,
619
628
  });
620
629
  } else {
@@ -638,10 +647,10 @@ class RPC extends _utils_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
638
647
  delete this._services[service];
639
648
  if (notify) {
640
649
  const serviceInfo = this._extract_service_info(api);
641
- if (this.manager_id) {
650
+ if (this._connection.manager_id) {
642
651
  this.emit({
643
652
  type: "service-removed",
644
- to: "*/" + this.manager_id,
653
+ to: "*/" + this._connection.manager_id,
645
654
  service: serviceInfo,
646
655
  });
647
656
  } else {
@@ -1040,7 +1049,8 @@ class RPC extends _utils_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
1040
1049
  if (this._method_annotations.get(method).visibility === "protected") {
1041
1050
  if (
1042
1051
  local_workspace !== remote_workspace &&
1043
- (remote_workspace !== "*" || remote_client_id !== this.manager_id)
1052
+ (remote_workspace !== "*" ||
1053
+ remote_client_id !== this._connection.manager_id)
1044
1054
  ) {
1045
1055
  throw new Error(
1046
1056
  "Permission denied for invoking protected method " +
@@ -1968,9 +1978,10 @@ class WebRTCConnection {
1968
1978
  this._reconnection_token = null;
1969
1979
  this._disconnect_handler = null;
1970
1980
  this._handle_connect = () => {};
1971
- this._data_channel.onopen = async ()=>{
1981
+ this.manager_id = null;
1982
+ this._data_channel.onopen = async () => {
1972
1983
  this._handle_connect && this._handle_connect();
1973
- }
1984
+ };
1974
1985
  this._data_channel.onmessage = async (event) => {
1975
1986
  let data = event.data;
1976
1987
  if (data instanceof Blob) {
@@ -1980,8 +1991,7 @@ class WebRTCConnection {
1980
1991
  };
1981
1992
  const self = this;
1982
1993
  this._data_channel.onclose = function () {
1983
- if(this._disconnect_handler)
1984
- this._disconnect_handler("closed");
1994
+ if (this._disconnect_handler) this._disconnect_handler("closed");
1985
1995
  console.log("websocket closed");
1986
1996
  self._data_channel = null;
1987
1997
  };
@@ -2027,7 +2037,6 @@ async function _setupRPC(config) {
2027
2037
  config.context.connection_type = "webrtc";
2028
2038
  const rpc = new _rpc_js__WEBPACK_IMPORTED_MODULE_0__.RPC(connection, {
2029
2039
  client_id: clientId,
2030
- manager_id: null,
2031
2040
  default_context: config.context,
2032
2041
  name: config.name,
2033
2042
  method_timeout: config.method_timeout || 10.0,
@@ -2054,7 +2063,7 @@ async function _createOffer(params, server, config, onInit, context) {
2054
2063
  pc.addEventListener("datachannel", async (event) => {
2055
2064
  const channel = event.channel;
2056
2065
  let ctx = null;
2057
- if (context && context.user) ctx = { user: context.user };
2066
+ if (context && context.user) ctx = { user: context.user, ws: context.ws };
2058
2067
  const rpc = await _setupRPC({
2059
2068
  channel: channel,
2060
2069
  client_id: channel.label,
@@ -2101,11 +2110,9 @@ async function getRTCService(server, service_id, config) {
2101
2110
  if (pc.connectionState === "failed") {
2102
2111
  pc.close();
2103
2112
  reject(new Error("WebRTC Connection failed"));
2104
- }
2105
- else if (pc.connectionState === "closed") {
2113
+ } else if (pc.connectionState === "closed") {
2106
2114
  reject(new Error("WebRTC Connection closed"));
2107
- }
2108
- else{
2115
+ } else {
2109
2116
  console.log("WebRTC Connection state: ", pc.connectionState);
2110
2117
  }
2111
2118
  },
@@ -4163,7 +4170,7 @@ __webpack_require__.r(__webpack_exports__);
4163
4170
 
4164
4171
 
4165
4172
 
4166
- const MAX_RETRY = 10000;
4173
+ const MAX_RETRY = 1000000;
4167
4174
 
4168
4175
  class WebsocketRPCConnection {
4169
4176
  constructor(
@@ -4184,11 +4191,13 @@ class WebsocketRPCConnection {
4184
4191
  this._handle_message = null;
4185
4192
  this._handle_connect = null; // Connection open event handler
4186
4193
  this._disconnect_handler = null; // Disconnection event handler
4187
- this._timeout = timeout * 1000; // Convert seconds to milliseconds
4194
+ this._timeout = timeout;
4188
4195
  this._WebSocketClass = WebSocketClass || WebSocket; // Allow overriding the WebSocket class
4189
- this._closing = false;
4196
+ this._closed = false;
4190
4197
  this._legacy_auth = null;
4191
4198
  this.connection_info = null;
4199
+ this._enable_reconnect = false;
4200
+ this.manager_id = null;
4192
4201
  }
4193
4202
 
4194
4203
  on_message(handler) {
@@ -4217,7 +4226,7 @@ class WebsocketRPCConnection {
4217
4226
 
4218
4227
  websocket.onerror = (event) => {
4219
4228
  console.error("WebSocket connection error:", event);
4220
- reject(event);
4229
+ reject(new Error(`ConnectionAbortedError: ${event}`));
4221
4230
  };
4222
4231
 
4223
4232
  websocket.onclose = (event) => {
@@ -4225,6 +4234,7 @@ class WebsocketRPCConnection {
4225
4234
  console.info(
4226
4235
  "Received 1003 error, attempting connection with query parameters.",
4227
4236
  );
4237
+ this._legacy_auth = true;
4228
4238
  this._attempt_connection_with_query_params(server_url)
4229
4239
  .then(resolve)
4230
4240
  .catch(reject);
@@ -4258,69 +4268,158 @@ class WebsocketRPCConnection {
4258
4268
  // Construct the full URL by appending the query string if it exists
4259
4269
  const full_url = server_url + queryString;
4260
4270
 
4261
- this._legacy_auth = true; // Assuming this flag is needed for some other logic
4262
4271
  return await this._attempt_connection(full_url, false);
4263
4272
  }
4264
4273
 
4274
+ _establish_connection() {
4275
+ return new Promise((resolve, reject) => {
4276
+ this._websocket.onmessage = (event) => {
4277
+ const data = event.data;
4278
+ const first_message = JSON.parse(data);
4279
+ if (first_message.type == "connection_info") {
4280
+ this.connection_info = first_message;
4281
+ if (this._workspace) {
4282
+ (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.assert)(
4283
+ this.connection_info.workspace === this._workspace,
4284
+ `Connected to the wrong workspace: ${this.connection_info.workspace}, expected: ${this._workspace}`,
4285
+ );
4286
+ }
4287
+ if (this.connection_info.reconnection_token) {
4288
+ this._reconnection_token = this.connection_info.reconnection_token;
4289
+ }
4290
+ this.manager_id = this.connection_info.manager_id || null;
4291
+ console.log(
4292
+ `Successfully connected to the server, workspace: ${this.connection_info.workspace}, manager_id: ${this.manager_id}`,
4293
+ );
4294
+ resolve(this.connection_info);
4295
+ } else if (first_message.type == "error") {
4296
+ const error = first_message.error || "Unknown error";
4297
+ console.error("Failed to connect, " + error);
4298
+ reject(new Error(error));
4299
+ return;
4300
+ } else {
4301
+ console.error("Unexpected message received from the server:", data);
4302
+ reject(new Error("Unexpected message received from the server"));
4303
+ return;
4304
+ }
4305
+ };
4306
+ });
4307
+ }
4308
+
4265
4309
  async open() {
4266
- if (this._closing || this._websocket) {
4267
- return; // Avoid opening a new connection if closing or already open
4268
- }
4310
+ console.log(
4311
+ "Creating a new websocket connection to",
4312
+ this._server_url.split("?")[0],
4313
+ );
4269
4314
  try {
4270
4315
  this._websocket = await this._attempt_connection(this._server_url);
4271
- if (!this._legacy_auth) {
4272
- // Send authentication info as the first message if connected without query params
4273
- const authInfo = JSON.stringify({
4274
- client_id: this._client_id,
4275
- workspace: this._workspace,
4276
- token: this._token,
4277
- reconnection_token: this._reconnection_token,
4278
- });
4279
- this._websocket.send(authInfo);
4280
- // Wait for the first message from the server
4281
- await (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.waitFor)(
4282
- new Promise((resolve, reject) => {
4283
- this._websocket.onmessage = (event) => {
4284
- const data = event.data;
4285
- const first_message = JSON.parse(data);
4286
- if (!first_message.success) {
4287
- const error = first_message.error || "Unknown error";
4288
- console.error("Failed to connect, " + error);
4289
- this.connection_info = null;
4290
- reject(new Error(error));
4291
- } else if (first_message) {
4292
- console.log(
4293
- "Successfully connected: " + JSON.stringify(first_message),
4294
- );
4295
- this.connection_info = first_message;
4296
- if (this.connection_info.reconnection_token) {
4297
- this._reconnection_token =
4298
- this.connection_info.reconnection_token;
4299
- }
4300
- }
4301
- resolve();
4302
- };
4303
- }),
4304
- this._timeout / 1000.0,
4305
- "Failed to receive the first message from the server",
4316
+ if (this._legacy_auth) {
4317
+ throw new Error(
4318
+ "NotImplementedError: Legacy authentication is not supported",
4306
4319
  );
4307
4320
  }
4308
-
4321
+ // Send authentication info as the first message if connected without query params
4322
+ const authInfo = JSON.stringify({
4323
+ client_id: this._client_id,
4324
+ workspace: this._workspace,
4325
+ token: this._token,
4326
+ reconnection_token: this._reconnection_token,
4327
+ });
4328
+ this._websocket.send(authInfo);
4329
+ // Wait for the first message from the server
4330
+ await (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.waitFor)(
4331
+ this._establish_connection(),
4332
+ this._timeout,
4333
+ "Failed to receive the first message from the server",
4334
+ );
4335
+ // Listen to messages from the server
4336
+ this._enable_reconnect = true;
4337
+ this._closed = false;
4309
4338
  this._websocket.onmessage = (event) => {
4310
4339
  this._handle_message(event.data);
4311
4340
  };
4312
4341
 
4342
+ this._websocket.onclose = this._handle_close.bind(this);
4343
+
4313
4344
  if (this._handle_connect) {
4314
- await this._handle_connect(this);
4345
+ this._handle_connect(this);
4315
4346
  }
4347
+ return this.connection_info;
4316
4348
  } catch (error) {
4317
- console.error("Failed to connect to", this._server_url, error);
4349
+ console.error(
4350
+ "Failed to connect to",
4351
+ this._server_url.split("?")[0],
4352
+ error,
4353
+ );
4354
+ throw error;
4355
+ }
4356
+ }
4357
+
4358
+ _handle_close(event) {
4359
+ if (
4360
+ !this._closed &&
4361
+ this._websocket &&
4362
+ this._websocket.readyState === WebSocket.CLOSED
4363
+ ) {
4364
+ if ([1000].includes(event.code)) {
4365
+ console.info(
4366
+ "Websocket connection closed (code: %s): %s",
4367
+ event.code,
4368
+ event.reason,
4369
+ );
4370
+ if (this._disconnect_handler) {
4371
+ this._disconnect_handler(event.reason);
4372
+ }
4373
+ this._closed = true;
4374
+ } else if (this._enable_reconnect) {
4375
+ console.warn(
4376
+ "Websocket connection closed unexpectedly (code: %s): %s",
4377
+ event.code,
4378
+ event.reason,
4379
+ );
4380
+ let retry = 0;
4381
+ const reconnect = async () => {
4382
+ try {
4383
+ console.info(
4384
+ `Reconnecting to ${this._server_url.split("?")[0]} (attempt #${retry})`,
4385
+ );
4386
+ await this.open();
4387
+ } catch (e) {
4388
+ if (`${e}`.includes("ConnectionAbortedError")) {
4389
+ console.warn("Failed to reconnect, connection aborted:", e);
4390
+ return;
4391
+ } else if (`${e}`.includes("NotImplementedError")) {
4392
+ console.warn("Failed to reconnect, connection aborted:", e);
4393
+ return;
4394
+ }
4395
+ console.warn("Failed to reconnect:", e);
4396
+ await new Promise((resolve) => setTimeout(resolve, 1000));
4397
+ if (
4398
+ this._websocket &&
4399
+ this._websocket.readyState === WebSocket.CONNECTED
4400
+ ) {
4401
+ return;
4402
+ }
4403
+ retry += 1;
4404
+ if (retry < MAX_RETRY) {
4405
+ await reconnect();
4406
+ } else {
4407
+ console.error("Failed to reconnect after", MAX_RETRY, "attempts");
4408
+ }
4409
+ }
4410
+ };
4411
+ reconnect();
4412
+ }
4413
+ } else {
4414
+ if (this._disconnect_handler) {
4415
+ this._disconnect_handler(event.reason);
4416
+ }
4318
4417
  }
4319
4418
  }
4320
4419
 
4321
4420
  async emit_message(data) {
4322
- if (this._closing) {
4323
- throw new Error("Connection is closing");
4421
+ if (this._closed) {
4422
+ throw new Error("Connection is closed");
4324
4423
  }
4325
4424
  if (!this._websocket || this._websocket.readyState !== WebSocket.OPEN) {
4326
4425
  await this.open();
@@ -4334,11 +4433,11 @@ class WebsocketRPCConnection {
4334
4433
  }
4335
4434
 
4336
4435
  disconnect(reason) {
4337
- this._closing = true;
4436
+ this._closed = true;
4338
4437
  if (this._websocket && this._websocket.readyState === WebSocket.OPEN) {
4339
4438
  this._websocket.close(1000, reason);
4340
- console.info(`WebSocket connection disconnected (${reason})`);
4341
4439
  }
4440
+ console.info(`WebSocket connection disconnected (${reason})`);
4342
4441
  }
4343
4442
  }
4344
4443
 
@@ -4401,22 +4500,20 @@ async function connectToServer(config) {
4401
4500
  config.method_timeout || 60,
4402
4501
  config.WebSocketClass,
4403
4502
  );
4404
- await connection.open();
4405
- (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.assert)(connection.connection_info, "Failed to connect to the server, no connection info obtained");
4406
- if (
4407
- config.workspace &&
4408
- connection.connection_info.workspace !== config.workspace
4409
- ) {
4503
+ const connection_info = await connection.open();
4504
+ (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.assert)(
4505
+ connection_info,
4506
+ "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.",
4507
+ );
4508
+ if (config.workspace && connection_info.workspace !== config.workspace) {
4410
4509
  throw new Error(
4411
- `Connected to the wrong workspace: ${connection.connection_info.workspace}, expected: ${config.workspace}`,
4510
+ `Connected to the wrong workspace: ${connection_info.workspace}, expected: ${config.workspace}`,
4412
4511
  );
4413
4512
  }
4414
- const workspace = connection.connection_info.workspace;
4415
- const manager_id = connection.connection_info.manager_id;
4513
+ const workspace = connection_info.workspace;
4416
4514
  const rpc = new _rpc_js__WEBPACK_IMPORTED_MODULE_0__.RPC(connection, {
4417
4515
  client_id: clientId,
4418
4516
  workspace,
4419
- manager_id,
4420
4517
  default_context: { connection_type: "websocket" },
4421
4518
  name: config.name,
4422
4519
  method_timeout: config.method_timeout,
@@ -4440,8 +4537,8 @@ async function connectToServer(config) {
4440
4537
  await rpc.disconnect();
4441
4538
  await connection.disconnect();
4442
4539
  }
4443
- if (connection.connection_info) {
4444
- wm.config = Object.assign(wm.config, connection.connection_info);
4540
+ if (connection_info) {
4541
+ wm.config = Object.assign(wm.config, connection_info);
4445
4542
  }
4446
4543
  wm.export = _export;
4447
4544
  wm.getPlugin = getPlugin;
@@ -4450,9 +4547,9 @@ async function connectToServer(config) {
4450
4547
  wm.registerCodec = rpc.register_codec.bind(rpc);
4451
4548
  wm.emit = rpc.emit;
4452
4549
  wm.on = rpc.on;
4453
- if (rpc.manager_id) {
4550
+ if (connection.manager_id) {
4454
4551
  rpc.on("force-exit", async (message) => {
4455
- if (message.from === "*/" + rpc.manager_id) {
4552
+ if (message.from === "*/" + connection.manager_id) {
4456
4553
  console.log("Disconnecting from server, reason:", message.reason);
4457
4554
  await disconnect();
4458
4555
  }