hypha-rpc 0.1.1 → 0.1.3
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/README.md +1 -1
- package/dist/hypha-rpc-websocket.js +141 -51
- 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/package.json +6 -4
- package/src/rpc.js +10 -5
- package/src/webrtc-client.js +24 -3
- package/src/websocket-client.js +107 -43
- package/tests/websocket_client_test.js +1 -1
package/README.md
CHANGED
|
@@ -223,6 +223,7 @@ class RPC extends _utils_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
223
223
|
},
|
|
224
224
|
});
|
|
225
225
|
this.on("method", this._handle_method.bind(this));
|
|
226
|
+
this.on("error", console.error);
|
|
226
227
|
|
|
227
228
|
(0,_utils_js__WEBPACK_IMPORTED_MODULE_0__.assert)(connection.emit_message && connection.on_message);
|
|
228
229
|
this._emit_message = connection.emit_message.bind(connection);
|
|
@@ -365,8 +366,11 @@ class RPC extends _utils_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
365
366
|
}
|
|
366
367
|
|
|
367
368
|
_on_message(message) {
|
|
368
|
-
|
|
369
|
-
|
|
369
|
+
if(typeof message === "string"){
|
|
370
|
+
const main = JSON.parse(message);
|
|
371
|
+
this._fire(main["type"], main);
|
|
372
|
+
}
|
|
373
|
+
else if (message instanceof ArrayBuffer) {
|
|
370
374
|
let unpacker = (0,_msgpack_msgpack__WEBPACK_IMPORTED_MODULE_1__.decodeMulti)(message);
|
|
371
375
|
const { done, value } = unpacker.next();
|
|
372
376
|
const main = value;
|
|
@@ -378,8 +382,9 @@ class RPC extends _utils_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
378
382
|
Object.assign(main, extra.value);
|
|
379
383
|
}
|
|
380
384
|
this._fire(main["type"], main);
|
|
381
|
-
}
|
|
382
|
-
|
|
385
|
+
}
|
|
386
|
+
else{
|
|
387
|
+
throw new Error("Invalid message format");
|
|
383
388
|
}
|
|
384
389
|
}
|
|
385
390
|
|
|
@@ -418,7 +423,7 @@ class RPC extends _utils_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
418
423
|
throw new Error("Service not found: " + service_id);
|
|
419
424
|
}
|
|
420
425
|
|
|
421
|
-
service.config["workspace"] = ws;
|
|
426
|
+
service.config["workspace"] = context["ws"];
|
|
422
427
|
// allow access for the same workspace
|
|
423
428
|
if (service.config.visibility == "public") {
|
|
424
429
|
return service;
|
|
@@ -1966,6 +1971,11 @@ class WebRTCConnection {
|
|
|
1966
1971
|
this._data_channel = channel;
|
|
1967
1972
|
this._handle_message = null;
|
|
1968
1973
|
this._reconnection_token = null;
|
|
1974
|
+
this._disconnect_handler = null;
|
|
1975
|
+
this._handle_connect = () => {};
|
|
1976
|
+
this._data_channel.onopen = async ()=>{
|
|
1977
|
+
this._handle_connect && this._handle_connect();
|
|
1978
|
+
}
|
|
1969
1979
|
this._data_channel.onmessage = async (event) => {
|
|
1970
1980
|
let data = event.data;
|
|
1971
1981
|
if (data instanceof Blob) {
|
|
@@ -1975,11 +1985,21 @@ class WebRTCConnection {
|
|
|
1975
1985
|
};
|
|
1976
1986
|
const self = this;
|
|
1977
1987
|
this._data_channel.onclose = function () {
|
|
1988
|
+
if(this._disconnect_handler)
|
|
1989
|
+
this._disconnect_handler("closed");
|
|
1978
1990
|
console.log("websocket closed");
|
|
1979
1991
|
self._data_channel = null;
|
|
1980
1992
|
};
|
|
1981
1993
|
}
|
|
1982
1994
|
|
|
1995
|
+
on_disconnected(handler) {
|
|
1996
|
+
this._disconnect_handler = handler;
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
on_connect(handler) {
|
|
2000
|
+
this._handle_connect = handler;
|
|
2001
|
+
}
|
|
2002
|
+
|
|
1983
2003
|
on_message(handler) {
|
|
1984
2004
|
(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.assert)(handler, "handler is required");
|
|
1985
2005
|
this._handle_message = handler;
|
|
@@ -2039,7 +2059,7 @@ async function _createOffer(params, server, config, onInit, context) {
|
|
|
2039
2059
|
pc.addEventListener("datachannel", async (event) => {
|
|
2040
2060
|
const channel = event.channel;
|
|
2041
2061
|
let ctx = null;
|
|
2042
|
-
if (context && context.user) ctx = { user: context.user };
|
|
2062
|
+
if (context && context.user) ctx = { user: context.user, ws: context.ws};
|
|
2043
2063
|
const rpc = await _setupRPC({
|
|
2044
2064
|
channel: channel,
|
|
2045
2065
|
client_id: channel.label,
|
|
@@ -2085,7 +2105,13 @@ async function getRTCService(server, service_id, config) {
|
|
|
2085
2105
|
() => {
|
|
2086
2106
|
if (pc.connectionState === "failed") {
|
|
2087
2107
|
pc.close();
|
|
2088
|
-
reject(new Error("Connection failed"));
|
|
2108
|
+
reject(new Error("WebRTC Connection failed"));
|
|
2109
|
+
}
|
|
2110
|
+
else if (pc.connectionState === "closed") {
|
|
2111
|
+
reject(new Error("WebRTC Connection closed"));
|
|
2112
|
+
}
|
|
2113
|
+
else{
|
|
2114
|
+
console.log("WebRTC Connection state: ", pc.connectionState);
|
|
2089
2115
|
}
|
|
2090
2116
|
},
|
|
2091
2117
|
false,
|
|
@@ -2152,7 +2178,7 @@ async function registerRTCService(server, service_id, config) {
|
|
|
2152
2178
|
};
|
|
2153
2179
|
const onInit = config.on_init;
|
|
2154
2180
|
delete config.on_init;
|
|
2155
|
-
await server.registerService({
|
|
2181
|
+
return await server.registerService({
|
|
2156
2182
|
id: service_id,
|
|
2157
2183
|
config,
|
|
2158
2184
|
offer: (params, context) =>
|
|
@@ -4142,7 +4168,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
4142
4168
|
|
|
4143
4169
|
|
|
4144
4170
|
|
|
4145
|
-
const MAX_RETRY =
|
|
4171
|
+
const MAX_RETRY = 1000000;
|
|
4146
4172
|
|
|
4147
4173
|
class WebsocketRPCConnection {
|
|
4148
4174
|
constructor(
|
|
@@ -4165,9 +4191,10 @@ class WebsocketRPCConnection {
|
|
|
4165
4191
|
this._disconnect_handler = null; // Disconnection event handler
|
|
4166
4192
|
this._timeout = timeout * 1000; // Convert seconds to milliseconds
|
|
4167
4193
|
this._WebSocketClass = WebSocketClass || WebSocket; // Allow overriding the WebSocket class
|
|
4168
|
-
this.
|
|
4194
|
+
this._closed = false;
|
|
4169
4195
|
this._legacy_auth = null;
|
|
4170
4196
|
this.connection_info = null;
|
|
4197
|
+
this._enable_reconnect = false;
|
|
4171
4198
|
}
|
|
4172
4199
|
|
|
4173
4200
|
on_message(handler) {
|
|
@@ -4204,11 +4231,12 @@ class WebsocketRPCConnection {
|
|
|
4204
4231
|
console.info(
|
|
4205
4232
|
"Received 1003 error, attempting connection with query parameters.",
|
|
4206
4233
|
);
|
|
4234
|
+
this._legacy_auth = true;
|
|
4207
4235
|
this._attempt_connection_with_query_params(server_url)
|
|
4208
4236
|
.then(resolve)
|
|
4209
4237
|
.catch(reject);
|
|
4210
4238
|
} else if (this._disconnect_handler) {
|
|
4211
|
-
this._disconnect_handler(
|
|
4239
|
+
this._disconnect_handler(event.reason);
|
|
4212
4240
|
}
|
|
4213
4241
|
};
|
|
4214
4242
|
});
|
|
@@ -4237,14 +4265,49 @@ class WebsocketRPCConnection {
|
|
|
4237
4265
|
// Construct the full URL by appending the query string if it exists
|
|
4238
4266
|
const full_url = server_url + queryString;
|
|
4239
4267
|
|
|
4240
|
-
this._legacy_auth = true; // Assuming this flag is needed for some other logic
|
|
4241
4268
|
return await this._attempt_connection(full_url, false);
|
|
4242
4269
|
}
|
|
4243
4270
|
|
|
4271
|
+
_establish_connection() {
|
|
4272
|
+
return new Promise((resolve, reject) => {
|
|
4273
|
+
this._websocket.onmessage = (event) => {
|
|
4274
|
+
const data = event.data;
|
|
4275
|
+
const first_message = JSON.parse(data);
|
|
4276
|
+
if (first_message.type == "connection_info") {
|
|
4277
|
+
console.log(
|
|
4278
|
+
"Successfully connected: " + JSON.stringify(first_message),
|
|
4279
|
+
);
|
|
4280
|
+
this.connection_info = first_message;
|
|
4281
|
+
if (this.connection_info.reconnection_token) {
|
|
4282
|
+
this._reconnection_token =
|
|
4283
|
+
this.connection_info.reconnection_token;
|
|
4284
|
+
}
|
|
4285
|
+
resolve(this.connection_info);
|
|
4286
|
+
}
|
|
4287
|
+
else if (first_message.type == "error") {
|
|
4288
|
+
const error = first_message.error || "Unknown error";
|
|
4289
|
+
console.error("Failed to connect, " + error);
|
|
4290
|
+
this.connection_info = null;
|
|
4291
|
+
reject(new Error(error));
|
|
4292
|
+
return;
|
|
4293
|
+
} else {
|
|
4294
|
+
console.error(
|
|
4295
|
+
"Unexpected message received from the server:",
|
|
4296
|
+
data,
|
|
4297
|
+
);
|
|
4298
|
+
reject(new Error("Unexpected message received from the server"));
|
|
4299
|
+
return;
|
|
4300
|
+
}
|
|
4301
|
+
|
|
4302
|
+
};
|
|
4303
|
+
})
|
|
4304
|
+
}
|
|
4305
|
+
|
|
4244
4306
|
async open() {
|
|
4245
|
-
if (this.
|
|
4246
|
-
|
|
4307
|
+
if (this._closed){
|
|
4308
|
+
throw new Error("Connection is closing");
|
|
4247
4309
|
}
|
|
4310
|
+
console.log("Creating a new websocket connection to", this._server_url.split("?")[0]);
|
|
4248
4311
|
try {
|
|
4249
4312
|
this._websocket = await this._attempt_connection(this._server_url);
|
|
4250
4313
|
if (!this._legacy_auth) {
|
|
@@ -4258,48 +4321,74 @@ class WebsocketRPCConnection {
|
|
|
4258
4321
|
this._websocket.send(authInfo);
|
|
4259
4322
|
// Wait for the first message from the server
|
|
4260
4323
|
await (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.waitFor)(
|
|
4261
|
-
|
|
4262
|
-
this._websocket.onmessage = (event) => {
|
|
4263
|
-
const data = event.data;
|
|
4264
|
-
const first_message = JSON.parse(data);
|
|
4265
|
-
if (!first_message.success) {
|
|
4266
|
-
const error = first_message.error || "Unknown error";
|
|
4267
|
-
console.error("Failed to connect, " + error);
|
|
4268
|
-
this.connection_info = null;
|
|
4269
|
-
reject(new Error(error));
|
|
4270
|
-
} else if (first_message) {
|
|
4271
|
-
console.log(
|
|
4272
|
-
"Successfully connected: " + JSON.stringify(first_message),
|
|
4273
|
-
);
|
|
4274
|
-
this.connection_info = first_message;
|
|
4275
|
-
if (this.connection_info.reconnection_token) {
|
|
4276
|
-
this._reconnection_token =
|
|
4277
|
-
this.connection_info.reconnection_token;
|
|
4278
|
-
}
|
|
4279
|
-
}
|
|
4280
|
-
resolve();
|
|
4281
|
-
};
|
|
4282
|
-
}),
|
|
4324
|
+
this._establish_connection(),
|
|
4283
4325
|
this._timeout / 1000.0,
|
|
4284
4326
|
"Failed to receive the first message from the server",
|
|
4285
4327
|
);
|
|
4286
4328
|
}
|
|
4287
|
-
|
|
4329
|
+
else{
|
|
4330
|
+
throw new Error("Legacy authentication is not supported");
|
|
4331
|
+
}
|
|
4332
|
+
// Listen to messages from the server
|
|
4333
|
+
this._enable_reconnect = true;
|
|
4334
|
+
this._closed = false;
|
|
4288
4335
|
this._websocket.onmessage = (event) => {
|
|
4289
4336
|
this._handle_message(event.data);
|
|
4290
4337
|
};
|
|
4291
4338
|
|
|
4339
|
+
this._websocket.onclose = this._handle_close.bind(this);
|
|
4340
|
+
|
|
4292
4341
|
if (this._handle_connect) {
|
|
4293
|
-
|
|
4342
|
+
this._handle_connect(this);
|
|
4294
4343
|
}
|
|
4344
|
+
return this.connection_info;
|
|
4295
4345
|
} catch (error) {
|
|
4296
|
-
console.error("Failed to connect to", this._server_url, error);
|
|
4346
|
+
console.error("Failed to connect to", this._server_url.split("?")[0], error);
|
|
4347
|
+
throw error;
|
|
4348
|
+
}
|
|
4349
|
+
}
|
|
4350
|
+
|
|
4351
|
+
_handle_close(event) {
|
|
4352
|
+
if (!this._closed && this._websocket && this._websocket.readyState === WebSocket.CLOSED) {
|
|
4353
|
+
if ([1000].includes(event.code)) {
|
|
4354
|
+
console.info("Websocket connection closed (code: %s): %s", event.code, event.reason);
|
|
4355
|
+
if (this._disconnect_handler) {
|
|
4356
|
+
this._disconnect_handler(event.reason);
|
|
4357
|
+
}
|
|
4358
|
+
this._closed = true;
|
|
4359
|
+
} else if (this._enable_reconnect) {
|
|
4360
|
+
console.warn("Websocket connection closed unexpectedly (code: %s): %s", event.code, event.reason);
|
|
4361
|
+
let retry = 0;
|
|
4362
|
+
const reconnect = async () => {
|
|
4363
|
+
try {
|
|
4364
|
+
console.info("Reconnecting to", this._server_url.split("?")[0]);
|
|
4365
|
+
await this.open();
|
|
4366
|
+
} catch (e) {
|
|
4367
|
+
console.warn("Failed to reconnect:", e);
|
|
4368
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
4369
|
+
if(this._websocket && this._websocket.readyState === WebSocket.CONNECTED){
|
|
4370
|
+
return;
|
|
4371
|
+
}
|
|
4372
|
+
retry += 1;
|
|
4373
|
+
if (retry < MAX_RETRY) {
|
|
4374
|
+
await reconnect();
|
|
4375
|
+
} else {
|
|
4376
|
+
console.error("Failed to reconnect after", MAX_RETRY, "attempts");
|
|
4377
|
+
}
|
|
4378
|
+
}
|
|
4379
|
+
};
|
|
4380
|
+
reconnect();
|
|
4381
|
+
}
|
|
4382
|
+
} else {
|
|
4383
|
+
if (this._disconnect_handler) {
|
|
4384
|
+
this._disconnect_handler(event.reason);
|
|
4385
|
+
}
|
|
4297
4386
|
}
|
|
4298
4387
|
}
|
|
4299
4388
|
|
|
4300
4389
|
async emit_message(data) {
|
|
4301
|
-
if (this.
|
|
4302
|
-
throw new Error("Connection is
|
|
4390
|
+
if (this._closed) {
|
|
4391
|
+
throw new Error("Connection is closed");
|
|
4303
4392
|
}
|
|
4304
4393
|
if (!this._websocket || this._websocket.readyState !== WebSocket.OPEN) {
|
|
4305
4394
|
await this.open();
|
|
@@ -4313,11 +4402,12 @@ class WebsocketRPCConnection {
|
|
|
4313
4402
|
}
|
|
4314
4403
|
|
|
4315
4404
|
disconnect(reason) {
|
|
4316
|
-
this.
|
|
4405
|
+
this._closed = true;
|
|
4317
4406
|
if (this._websocket && this._websocket.readyState === WebSocket.OPEN) {
|
|
4318
4407
|
this._websocket.close(1000, reason);
|
|
4319
|
-
|
|
4408
|
+
|
|
4320
4409
|
}
|
|
4410
|
+
console.info(`WebSocket connection disconnected (${reason})`);
|
|
4321
4411
|
}
|
|
4322
4412
|
}
|
|
4323
4413
|
|
|
@@ -4380,18 +4470,18 @@ async function connectToServer(config) {
|
|
|
4380
4470
|
config.method_timeout || 60,
|
|
4381
4471
|
config.WebSocketClass,
|
|
4382
4472
|
);
|
|
4383
|
-
await connection.open();
|
|
4384
|
-
(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.assert)(
|
|
4473
|
+
const connection_info = await connection.open();
|
|
4474
|
+
(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.assert)(connection_info, "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.");
|
|
4385
4475
|
if (
|
|
4386
4476
|
config.workspace &&
|
|
4387
|
-
|
|
4477
|
+
connection_info.workspace !== config.workspace
|
|
4388
4478
|
) {
|
|
4389
4479
|
throw new Error(
|
|
4390
|
-
`Connected to the wrong workspace: ${
|
|
4480
|
+
`Connected to the wrong workspace: ${connection_info.workspace}, expected: ${config.workspace}`,
|
|
4391
4481
|
);
|
|
4392
4482
|
}
|
|
4393
|
-
const workspace =
|
|
4394
|
-
const manager_id =
|
|
4483
|
+
const workspace = connection_info.workspace;
|
|
4484
|
+
const manager_id = connection_info.manager_id;
|
|
4395
4485
|
const rpc = new _rpc_js__WEBPACK_IMPORTED_MODULE_0__.RPC(connection, {
|
|
4396
4486
|
client_id: clientId,
|
|
4397
4487
|
workspace,
|
|
@@ -4419,8 +4509,8 @@ async function connectToServer(config) {
|
|
|
4419
4509
|
await rpc.disconnect();
|
|
4420
4510
|
await connection.disconnect();
|
|
4421
4511
|
}
|
|
4422
|
-
if (
|
|
4423
|
-
wm.config = Object.assign(wm.config,
|
|
4512
|
+
if (connection_info) {
|
|
4513
|
+
wm.config = Object.assign(wm.config, connection_info);
|
|
4424
4514
|
}
|
|
4425
4515
|
wm.export = _export;
|
|
4426
4516
|
wm.getPlugin = getPlugin;
|