hypha-rpc 0.20.93 → 0.20.95
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 +71 -142
- package/dist/hypha-rpc-websocket.min.js +1 -1
- package/dist/hypha-rpc-websocket.min.mjs +1 -1
- package/dist/hypha-rpc-websocket.mjs +71 -142
- package/index.d.ts +1 -4
- package/package.json +1 -1
- package/src/http-client.js +31 -117
- package/src/rpc.js +14 -29
- package/src/websocket-client.js +28 -0
|
@@ -30,13 +30,12 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
30
30
|
/* harmony import */ var _utils_index_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./utils/index.js */ "./src/utils/index.js");
|
|
31
31
|
/* harmony import */ var _utils_schema_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utils/schema.js */ "./src/utils/schema.js");
|
|
32
32
|
/* harmony import */ var _msgpack_msgpack__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @msgpack/msgpack */ "./node_modules/@msgpack/msgpack/dist.es5+esm/decode.mjs");
|
|
33
|
-
/* harmony import */ var _msgpack_msgpack__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @msgpack/msgpack */ "./node_modules/@msgpack/msgpack/dist.es5+esm/encode.mjs");
|
|
34
33
|
/**
|
|
35
34
|
* HTTP Streaming RPC Client for Hypha.
|
|
36
35
|
*
|
|
37
36
|
* This module provides HTTP-based RPC transport as an alternative to WebSocket.
|
|
38
37
|
* It uses:
|
|
39
|
-
* - HTTP GET with streaming (
|
|
38
|
+
* - HTTP GET with streaming (msgpack) for server-to-client messages
|
|
40
39
|
* - HTTP POST for client-to-server messages
|
|
41
40
|
*
|
|
42
41
|
* This is more resilient to network issues than WebSocket because:
|
|
@@ -97,9 +96,7 @@ const MAX_RETRY = 1000000;
|
|
|
97
96
|
* HTTP Streaming RPC Connection.
|
|
98
97
|
*
|
|
99
98
|
* Uses HTTP GET with streaming for receiving messages and HTTP POST for sending messages.
|
|
100
|
-
*
|
|
101
|
-
* - NDJSON (default): JSON lines for text-based messages
|
|
102
|
-
* - msgpack: Binary format with length-prefixed frames for binary data support
|
|
99
|
+
* Uses msgpack binary format with length-prefixed frames for efficient binary data support.
|
|
103
100
|
*/
|
|
104
101
|
class HTTPStreamingRPCConnection {
|
|
105
102
|
/**
|
|
@@ -112,7 +109,6 @@ class HTTPStreamingRPCConnection {
|
|
|
112
109
|
* @param {string} reconnection_token - Token for reconnection (optional)
|
|
113
110
|
* @param {number} timeout - Request timeout in seconds (default: 60)
|
|
114
111
|
* @param {number} token_refresh_interval - Interval for token refresh (default: 2 hours)
|
|
115
|
-
* @param {string} format - Stream format - "json" (NDJSON) or "msgpack" (default: "json")
|
|
116
112
|
*/
|
|
117
113
|
constructor(
|
|
118
114
|
server_url,
|
|
@@ -122,7 +118,6 @@ class HTTPStreamingRPCConnection {
|
|
|
122
118
|
reconnection_token = null,
|
|
123
119
|
timeout = 60,
|
|
124
120
|
token_refresh_interval = 2 * 60 * 60,
|
|
125
|
-
format = "json",
|
|
126
121
|
) {
|
|
127
122
|
(0,_utils_index_js__WEBPACK_IMPORTED_MODULE_1__.assert)(server_url && client_id, "server_url and client_id are required");
|
|
128
123
|
this._server_url = server_url.replace(/\/$/, "");
|
|
@@ -132,7 +127,6 @@ class HTTPStreamingRPCConnection {
|
|
|
132
127
|
this._reconnection_token = reconnection_token;
|
|
133
128
|
this._timeout = timeout;
|
|
134
129
|
this._token_refresh_interval = token_refresh_interval;
|
|
135
|
-
this._format = format;
|
|
136
130
|
|
|
137
131
|
this._handle_message = null;
|
|
138
132
|
this._handle_disconnected = null;
|
|
@@ -143,8 +137,7 @@ class HTTPStreamingRPCConnection {
|
|
|
143
137
|
this.connection_info = null;
|
|
144
138
|
this.manager_id = null;
|
|
145
139
|
|
|
146
|
-
this.
|
|
147
|
-
this._stream_controller = null;
|
|
140
|
+
this._abort_controller = null;
|
|
148
141
|
}
|
|
149
142
|
|
|
150
143
|
/**
|
|
@@ -172,7 +165,7 @@ class HTTPStreamingRPCConnection {
|
|
|
172
165
|
/**
|
|
173
166
|
* Get HTTP headers with authentication.
|
|
174
167
|
*
|
|
175
|
-
* @param {boolean} for_stream - If true, set Accept header
|
|
168
|
+
* @param {boolean} for_stream - If true, set Accept header for msgpack stream
|
|
176
169
|
* @returns {Object} Headers object
|
|
177
170
|
*/
|
|
178
171
|
_get_headers(for_stream = false) {
|
|
@@ -180,11 +173,7 @@ class HTTPStreamingRPCConnection {
|
|
|
180
173
|
"Content-Type": "application/msgpack",
|
|
181
174
|
};
|
|
182
175
|
if (for_stream) {
|
|
183
|
-
|
|
184
|
-
headers["Accept"] = "application/x-msgpack-stream";
|
|
185
|
-
} else {
|
|
186
|
-
headers["Accept"] = "application/x-ndjson";
|
|
187
|
-
}
|
|
176
|
+
headers["Accept"] = "application/x-msgpack-stream";
|
|
188
177
|
}
|
|
189
178
|
if (this._token) {
|
|
190
179
|
headers["Authorization"] = `Bearer ${this._token}`;
|
|
@@ -196,16 +185,11 @@ class HTTPStreamingRPCConnection {
|
|
|
196
185
|
* Open the streaming connection.
|
|
197
186
|
*/
|
|
198
187
|
async open() {
|
|
199
|
-
console.info(
|
|
200
|
-
`Opening HTTP streaming connection to ${this._server_url} (format=${this._format})`,
|
|
201
|
-
);
|
|
188
|
+
console.info(`Opening HTTP streaming connection to ${this._server_url}`);
|
|
202
189
|
|
|
203
|
-
// Build stream URL
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
if (this._format === "msgpack") {
|
|
207
|
-
stream_url += "&format=msgpack";
|
|
208
|
-
}
|
|
190
|
+
// Build stream URL - workspace is part of path, default to "public" for anonymous
|
|
191
|
+
const ws = this._workspace || "public";
|
|
192
|
+
const stream_url = `${this._server_url}/${ws}/rpc?client_id=${this._client_id}`;
|
|
209
193
|
|
|
210
194
|
// Start streaming in background
|
|
211
195
|
this._startStreamLoop(stream_url);
|
|
@@ -256,11 +240,11 @@ class HTTPStreamingRPCConnection {
|
|
|
256
240
|
try {
|
|
257
241
|
// OPTIMIZATION: Browser fetch automatically streams responses
|
|
258
242
|
// and negotiates HTTP/2 when available for better performance
|
|
243
|
+
this._abort_controller = new AbortController();
|
|
259
244
|
const response = await fetch(url, {
|
|
260
245
|
method: "GET",
|
|
261
246
|
headers: this._get_headers(true),
|
|
262
|
-
|
|
263
|
-
keepalive: true,
|
|
247
|
+
signal: this._abort_controller.signal,
|
|
264
248
|
});
|
|
265
249
|
|
|
266
250
|
if (!response.ok) {
|
|
@@ -272,13 +256,8 @@ class HTTPStreamingRPCConnection {
|
|
|
272
256
|
|
|
273
257
|
retry = 0; // Reset retry counter on successful connection
|
|
274
258
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
await this._processMsgpackStream(response);
|
|
278
|
-
} else {
|
|
279
|
-
// NDJSON stream (line-based)
|
|
280
|
-
await this._processNdjsonStream(response);
|
|
281
|
-
}
|
|
259
|
+
// Process binary msgpack stream with 4-byte length prefix
|
|
260
|
+
await this._processMsgpackStream(response);
|
|
282
261
|
} catch (error) {
|
|
283
262
|
if (this._closed) break;
|
|
284
263
|
console.error(`Connection error: ${error.message}`);
|
|
@@ -305,36 +284,6 @@ class HTTPStreamingRPCConnection {
|
|
|
305
284
|
}
|
|
306
285
|
}
|
|
307
286
|
|
|
308
|
-
/**
|
|
309
|
-
* Process NDJSON (line-based JSON) stream.
|
|
310
|
-
*/
|
|
311
|
-
async _processNdjsonStream(response) {
|
|
312
|
-
const reader = response.body.getReader();
|
|
313
|
-
const decoder = new TextDecoder();
|
|
314
|
-
let buffer = "";
|
|
315
|
-
|
|
316
|
-
while (!this._closed) {
|
|
317
|
-
const { done, value } = await reader.read();
|
|
318
|
-
|
|
319
|
-
if (done) break;
|
|
320
|
-
|
|
321
|
-
buffer += decoder.decode(value, { stream: true });
|
|
322
|
-
const lines = buffer.split("\n");
|
|
323
|
-
buffer = lines.pop() || "";
|
|
324
|
-
|
|
325
|
-
for (const line of lines) {
|
|
326
|
-
if (!line.trim()) continue;
|
|
327
|
-
|
|
328
|
-
try {
|
|
329
|
-
const message = JSON.parse(line);
|
|
330
|
-
await this._handleStreamMessage(message);
|
|
331
|
-
} catch (error) {
|
|
332
|
-
console.warn(`Failed to parse JSON message: ${error.message}`);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
287
|
/**
|
|
339
288
|
* Check if frame data is a control message and decode it.
|
|
340
289
|
*
|
|
@@ -445,40 +394,6 @@ class HTTPStreamingRPCConnection {
|
|
|
445
394
|
}
|
|
446
395
|
}
|
|
447
396
|
|
|
448
|
-
/**
|
|
449
|
-
* Handle a decoded stream message.
|
|
450
|
-
*/
|
|
451
|
-
async _handleStreamMessage(message) {
|
|
452
|
-
// Handle connection info
|
|
453
|
-
if (message.type === "connection_info") {
|
|
454
|
-
this.connection_info = message;
|
|
455
|
-
return;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// Handle ping (keep-alive)
|
|
459
|
-
if (message.type === "ping") {
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// Handle reconnection token refresh
|
|
464
|
-
if (message.type === "reconnection_token") {
|
|
465
|
-
this._reconnection_token = message.reconnection_token;
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
// Handle errors
|
|
470
|
-
if (message.type === "error") {
|
|
471
|
-
console.error(`Server error: ${message.message}`);
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// Pass to message handler (convert to msgpack for RPC)
|
|
476
|
-
if (this._handle_message) {
|
|
477
|
-
const data = (0,_msgpack_msgpack__WEBPACK_IMPORTED_MODULE_4__.encode)(message);
|
|
478
|
-
await this._handle_message(data);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
397
|
/**
|
|
483
398
|
* Send a message to the server via HTTP POST.
|
|
484
399
|
*
|
|
@@ -493,20 +408,21 @@ class HTTPStreamingRPCConnection {
|
|
|
493
408
|
throw new Error("Connection is closed");
|
|
494
409
|
}
|
|
495
410
|
|
|
496
|
-
// Build POST URL -
|
|
497
|
-
const
|
|
498
|
-
|
|
411
|
+
// Build POST URL - workspace is part of path (must be set after connection)
|
|
412
|
+
const ws = this._workspace || "public";
|
|
413
|
+
let post_url = `${this._server_url}/${ws}/rpc?client_id=${this._client_id}`;
|
|
499
414
|
|
|
500
415
|
// Ensure data is Uint8Array
|
|
501
416
|
const body = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
502
417
|
|
|
503
|
-
//
|
|
504
|
-
//
|
|
418
|
+
// Note: keepalive has a 64KB body size limit in browsers, so only use
|
|
419
|
+
// it for small payloads. For large payloads, skip keepalive.
|
|
420
|
+
const useKeepalive = body.length < 60000;
|
|
505
421
|
const response = await fetch(post_url, {
|
|
506
422
|
method: "POST",
|
|
507
423
|
headers: this._get_headers(false),
|
|
508
424
|
body: body,
|
|
509
|
-
keepalive: true,
|
|
425
|
+
...(useKeepalive && { keepalive: true }),
|
|
510
426
|
});
|
|
511
427
|
|
|
512
428
|
if (!response.ok) {
|
|
@@ -534,6 +450,12 @@ class HTTPStreamingRPCConnection {
|
|
|
534
450
|
|
|
535
451
|
this._closed = true;
|
|
536
452
|
|
|
453
|
+
// Abort any active stream fetch to release the connection immediately
|
|
454
|
+
if (this._abort_controller) {
|
|
455
|
+
this._abort_controller.abort();
|
|
456
|
+
this._abort_controller = null;
|
|
457
|
+
}
|
|
458
|
+
|
|
537
459
|
if (this._handle_disconnected) {
|
|
538
460
|
this._handle_disconnected(reason);
|
|
539
461
|
}
|
|
@@ -582,8 +504,6 @@ async function _connectToServerHTTP(config) {
|
|
|
582
504
|
config.reconnection_token,
|
|
583
505
|
config.method_timeout || 30,
|
|
584
506
|
config.token_refresh_interval || 2 * 60 * 60,
|
|
585
|
-
// Default to msgpack for full binary support and proper RPC message handling
|
|
586
|
-
config.format || "msgpack",
|
|
587
507
|
);
|
|
588
508
|
|
|
589
509
|
const connection_info = await connection.open();
|
|
@@ -603,15 +523,11 @@ async function _connectToServerHTTP(config) {
|
|
|
603
523
|
server_base_url: connection_info.public_base_url,
|
|
604
524
|
});
|
|
605
525
|
|
|
606
|
-
await (
|
|
607
|
-
() => rpc._services_registered,
|
|
608
|
-
null,
|
|
609
|
-
config.method_timeout || 120,
|
|
610
|
-
"Timeout waiting for services to register",
|
|
611
|
-
);
|
|
526
|
+
await rpc.waitFor("services_registered", config.method_timeout || 120);
|
|
612
527
|
|
|
613
528
|
const wm = await rpc.get_manager_service({
|
|
614
529
|
timeout: config.method_timeout || 30,
|
|
530
|
+
case_conversion: "camel",
|
|
615
531
|
});
|
|
616
532
|
wm.rpc = rpc;
|
|
617
533
|
|
|
@@ -649,7 +565,7 @@ async function _connectToServerHTTP(config) {
|
|
|
649
565
|
wm.serve = (0,_utils_schema_js__WEBPACK_IMPORTED_MODULE_2__.schemaFunction)(serve, {
|
|
650
566
|
name: "serve",
|
|
651
567
|
description: "Run event loop forever",
|
|
652
|
-
parameters: {},
|
|
568
|
+
parameters: { type: "object", properties: {} },
|
|
653
569
|
});
|
|
654
570
|
|
|
655
571
|
if (connection_info) {
|
|
@@ -1401,7 +1317,8 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
1401
1317
|
main["ctx"] = JSON.parse(JSON.stringify(main));
|
|
1402
1318
|
Object.assign(main["ctx"], this.default_context);
|
|
1403
1319
|
this._fire(main["type"], main);
|
|
1404
|
-
} else if (message instanceof ArrayBuffer) {
|
|
1320
|
+
} else if (message instanceof ArrayBuffer || ArrayBuffer.isView(message)) {
|
|
1321
|
+
// Handle both ArrayBuffer (WebSocket) and Uint8Array/ArrayBufferView (HTTP transport)
|
|
1405
1322
|
let unpacker = (0,_msgpack_msgpack__WEBPACK_IMPORTED_MODULE_2__.decodeMulti)(message);
|
|
1406
1323
|
const { done, value } = unpacker.next();
|
|
1407
1324
|
const main = value;
|
|
@@ -1445,31 +1362,12 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
1445
1362
|
}
|
|
1446
1363
|
}
|
|
1447
1364
|
|
|
1448
|
-
//
|
|
1365
|
+
// Clean up client_disconnected subscription
|
|
1449
1366
|
if (this._clientDisconnectedSubscription) {
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
.then((manager) => {
|
|
1455
|
-
if (
|
|
1456
|
-
manager.unsubscribe &&
|
|
1457
|
-
typeof manager.unsubscribe === "function"
|
|
1458
|
-
) {
|
|
1459
|
-
return manager.unsubscribe("client_disconnected");
|
|
1460
|
-
}
|
|
1461
|
-
})
|
|
1462
|
-
.catch((e) => {
|
|
1463
|
-
console.debug(
|
|
1464
|
-
`Error unsubscribing from client_disconnected: ${e}`,
|
|
1465
|
-
);
|
|
1466
|
-
});
|
|
1467
|
-
}
|
|
1468
|
-
// Remove the local event handler
|
|
1469
|
-
this.off("client_disconnected");
|
|
1470
|
-
} catch (e) {
|
|
1471
|
-
console.debug(`Error unsubscribing from client_disconnected: ${e}`);
|
|
1472
|
-
}
|
|
1367
|
+
// Remove the local event handler (no need to unsubscribe from server -
|
|
1368
|
+
// the server will clean up when it detects the disconnection)
|
|
1369
|
+
this.off("client_disconnected");
|
|
1370
|
+
this._clientDisconnectedSubscription = null;
|
|
1473
1371
|
}
|
|
1474
1372
|
|
|
1475
1373
|
// Clean up background tasks
|
|
@@ -2591,11 +2489,14 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
2591
2489
|
) {
|
|
2592
2490
|
let target_id = encoded_method._rtarget;
|
|
2593
2491
|
if (remote_workspace && !target_id.includes("/")) {
|
|
2594
|
-
if (
|
|
2595
|
-
|
|
2492
|
+
// Don't modify target_id if it starts with */ (workspace manager service)
|
|
2493
|
+
if (!target_id.startsWith("*/")) {
|
|
2494
|
+
if (remote_workspace !== target_id) {
|
|
2495
|
+
target_id = remote_workspace + "/" + target_id;
|
|
2496
|
+
}
|
|
2497
|
+
// Fix the target id to be an absolute id
|
|
2498
|
+
encoded_method._rtarget = target_id;
|
|
2596
2499
|
}
|
|
2597
|
-
// Fix the target id to be an absolute id
|
|
2598
|
-
encoded_method._rtarget = target_id;
|
|
2599
2500
|
}
|
|
2600
2501
|
let method_id = encoded_method._rmethod;
|
|
2601
2502
|
let with_promise = encoded_method._rpromise || false;
|
|
@@ -7177,6 +7078,20 @@ function normalizeServerUrl(server_url) {
|
|
|
7177
7078
|
return server_url;
|
|
7178
7079
|
}
|
|
7179
7080
|
|
|
7081
|
+
/**
|
|
7082
|
+
* Login to the hypha server.
|
|
7083
|
+
*
|
|
7084
|
+
* Configuration options:
|
|
7085
|
+
* server_url: The server URL (required)
|
|
7086
|
+
* workspace: Target workspace (optional)
|
|
7087
|
+
* login_service_id: Login service ID (default: "public/hypha-login")
|
|
7088
|
+
* expires_in: Token expiration time (optional)
|
|
7089
|
+
* login_timeout: Timeout for login process (default: 60)
|
|
7090
|
+
* login_callback: Callback function for login URL (optional)
|
|
7091
|
+
* profile: Whether to return user profile (optional)
|
|
7092
|
+
* additional_headers: Additional HTTP headers (optional)
|
|
7093
|
+
* transport: Transport type - "websocket" (default) or "http"
|
|
7094
|
+
*/
|
|
7180
7095
|
async function login(config) {
|
|
7181
7096
|
const service_id = config.login_service_id || "public/hypha-login";
|
|
7182
7097
|
const workspace = config.workspace;
|
|
@@ -7185,11 +7100,13 @@ async function login(config) {
|
|
|
7185
7100
|
const callback = config.login_callback;
|
|
7186
7101
|
const profile = config.profile;
|
|
7187
7102
|
const additional_headers = config.additional_headers;
|
|
7103
|
+
const transport = config.transport || "websocket";
|
|
7188
7104
|
|
|
7189
7105
|
const server = await connectToServer({
|
|
7190
7106
|
name: "initial login client",
|
|
7191
7107
|
server_url: config.server_url,
|
|
7192
7108
|
additional_headers: additional_headers,
|
|
7109
|
+
transport: transport,
|
|
7193
7110
|
});
|
|
7194
7111
|
try {
|
|
7195
7112
|
const svc = await server.getService(service_id);
|
|
@@ -7213,15 +7130,27 @@ async function login(config) {
|
|
|
7213
7130
|
}
|
|
7214
7131
|
}
|
|
7215
7132
|
|
|
7133
|
+
/**
|
|
7134
|
+
* Logout from the hypha server.
|
|
7135
|
+
*
|
|
7136
|
+
* Configuration options:
|
|
7137
|
+
* server_url: The server URL (required)
|
|
7138
|
+
* login_service_id: Login service ID (default: "public/hypha-login")
|
|
7139
|
+
* logout_callback: Callback function for logout URL (optional)
|
|
7140
|
+
* additional_headers: Additional HTTP headers (optional)
|
|
7141
|
+
* transport: Transport type - "websocket" (default) or "http"
|
|
7142
|
+
*/
|
|
7216
7143
|
async function logout(config) {
|
|
7217
7144
|
const service_id = config.login_service_id || "public/hypha-login";
|
|
7218
7145
|
const callback = config.logout_callback;
|
|
7219
7146
|
const additional_headers = config.additional_headers;
|
|
7147
|
+
const transport = config.transport || "websocket";
|
|
7220
7148
|
|
|
7221
7149
|
const server = await connectToServer({
|
|
7222
7150
|
name: "initial logout client",
|
|
7223
7151
|
server_url: config.server_url,
|
|
7224
7152
|
additional_headers: additional_headers,
|
|
7153
|
+
transport: transport,
|
|
7225
7154
|
});
|
|
7226
7155
|
try {
|
|
7227
7156
|
const svc = await server.getService(service_id);
|