hypha-rpc 0.21.43 → 0.21.46
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 +175 -16
- 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 +177 -17
- package/dist/hypha-rpc-websocket.mjs.map +1 -1
- package/index.mjs +1 -0
- package/package.json +1 -1
- package/src/websocket-client.js +174 -16
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-07-10T22:55:30.240Z
|
|
90
90
|
</div>
|
|
91
91
|
<script src="prettify.js"></script>
|
|
92
92
|
<script>
|
|
@@ -10389,6 +10389,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
10389
10389
|
/* harmony export */ getRemoteService: () => (/* binding */ getRemoteService),
|
|
10390
10390
|
/* harmony export */ getRemoteServiceHTTP: () => (/* reexport safe */ _http_client_js__WEBPACK_IMPORTED_MODULE_5__.getRemoteServiceHTTP),
|
|
10391
10391
|
/* harmony export */ hyphaWebsocketClient: () => (/* binding */ hyphaWebsocketClient),
|
|
10392
|
+
/* harmony export */ isLoginCompleteMessage: () => (/* binding */ isLoginCompleteMessage),
|
|
10392
10393
|
/* harmony export */ loadRequirements: () => (/* reexport safe */ _utils_index_js__WEBPACK_IMPORTED_MODULE_2__.loadRequirements),
|
|
10393
10394
|
/* harmony export */ login: () => (/* binding */ login),
|
|
10394
10395
|
/* harmony export */ logout: () => (/* binding */ logout),
|
|
@@ -10424,6 +10425,14 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
10424
10425
|
|
|
10425
10426
|
const MAX_RETRY = 1000000;
|
|
10426
10427
|
|
|
10428
|
+
// When the socket errors/closes during the handshake, wait briefly before
|
|
10429
|
+
// rejecting with the generic reason: the server usually sends a descriptive
|
|
10430
|
+
// {type:"error"} message (e.g. token/workspace mismatch) just before closing,
|
|
10431
|
+
// and on some runtimes (undici's native WebSocket on Node) the close is
|
|
10432
|
+
// surfaced BEFORE that message reaches onmessage. This grace lets the real
|
|
10433
|
+
// server reason win the race instead of a generic "error during handshake".
|
|
10434
|
+
const HANDSHAKE_ERROR_GRACE_MS = 100;
|
|
10435
|
+
|
|
10427
10436
|
class WebsocketRPCConnection {
|
|
10428
10437
|
constructor(
|
|
10429
10438
|
server_url,
|
|
@@ -10581,30 +10590,38 @@ class WebsocketRPCConnection {
|
|
|
10581
10590
|
|
|
10582
10591
|
// Handle WebSocket closing before connection_info is received.
|
|
10583
10592
|
// Without this, the promise hangs until the outer waitFor timeout (60s).
|
|
10593
|
+
// Defer the generic error/close rejection so a pending server
|
|
10594
|
+
// {type:"error"} message (handled in onmessage below) can settle first
|
|
10595
|
+
// with the actual reason. Without this, an undici close-as-error would
|
|
10596
|
+
// reject with "error during handshake" and hide the server's reason
|
|
10597
|
+
// (e.g. "workspace does not match"). If no descriptive message arrives
|
|
10598
|
+
// within the grace window, the generic rejection fires as a fallback.
|
|
10599
|
+
const rejectAfterGrace = (err) => {
|
|
10600
|
+
if (settled) return;
|
|
10601
|
+
setTimeout(() => {
|
|
10602
|
+
if (!settled) {
|
|
10603
|
+
settled = true;
|
|
10604
|
+
reject(err);
|
|
10605
|
+
}
|
|
10606
|
+
}, HANDSHAKE_ERROR_GRACE_MS);
|
|
10607
|
+
};
|
|
10608
|
+
|
|
10584
10609
|
const prevOnClose = this._websocket.onclose;
|
|
10585
10610
|
this._websocket.onclose = (event) => {
|
|
10586
|
-
|
|
10587
|
-
|
|
10588
|
-
|
|
10589
|
-
|
|
10590
|
-
|
|
10591
|
-
),
|
|
10592
|
-
);
|
|
10593
|
-
}
|
|
10611
|
+
rejectAfterGrace(
|
|
10612
|
+
new Error(
|
|
10613
|
+
`ConnectionAbortedError: WebSocket closed during handshake (code=${event.code}, reason=${event.reason || "unknown"})`,
|
|
10614
|
+
),
|
|
10615
|
+
);
|
|
10594
10616
|
// Delegate to any previously-set onclose handler
|
|
10595
10617
|
if (prevOnClose) prevOnClose.call(this._websocket, event);
|
|
10596
10618
|
};
|
|
10597
10619
|
|
|
10598
10620
|
const prevOnError = this._websocket.onerror;
|
|
10599
10621
|
this._websocket.onerror = (event) => {
|
|
10600
|
-
|
|
10601
|
-
|
|
10602
|
-
|
|
10603
|
-
new Error(
|
|
10604
|
-
`ConnectionAbortedError: WebSocket error during handshake`,
|
|
10605
|
-
),
|
|
10606
|
-
);
|
|
10607
|
-
}
|
|
10622
|
+
rejectAfterGrace(
|
|
10623
|
+
new Error(`ConnectionAbortedError: WebSocket error during handshake`),
|
|
10624
|
+
);
|
|
10608
10625
|
if (prevOnError) prevOnError.call(this._websocket, event);
|
|
10609
10626
|
};
|
|
10610
10627
|
|
|
@@ -11018,6 +11035,89 @@ function normalizeServerUrl(server_url) {
|
|
|
11018
11035
|
return server_url;
|
|
11019
11036
|
}
|
|
11020
11037
|
|
|
11038
|
+
/**
|
|
11039
|
+
* Validate a window "message" event as a hypha login-complete signal.
|
|
11040
|
+
*
|
|
11041
|
+
* Exported so it can be unit-tested without a browser. The completion message
|
|
11042
|
+
* carries ONLY the public session key (never a token); a valid message merely
|
|
11043
|
+
* decides when to tear down the inline login modal — the token itself is always
|
|
11044
|
+
* fetched over the trusted check() RPC, so a forged message cannot inject one.
|
|
11045
|
+
*/
|
|
11046
|
+
function isLoginCompleteMessage(event, expectedOrigin, expectedKey) {
|
|
11047
|
+
if (!event || event.origin !== expectedOrigin) return false;
|
|
11048
|
+
const data = event.data;
|
|
11049
|
+
if (!data || typeof data !== "object") return false;
|
|
11050
|
+
return data.type === "hypha-login-complete" && data.key === expectedKey;
|
|
11051
|
+
}
|
|
11052
|
+
|
|
11053
|
+
/**
|
|
11054
|
+
* Render the login page in an in-page modal iframe (browser only). This is the
|
|
11055
|
+
* no-popup alternative to opening login_url in a popup window. Returns
|
|
11056
|
+
* { teardown } to remove the modal; the close button invokes onCancel.
|
|
11057
|
+
*/
|
|
11058
|
+
function openLoginModal(loginUrl, { container, onCancel } = {}) {
|
|
11059
|
+
const doc = window.document;
|
|
11060
|
+
const overlay = doc.createElement("div");
|
|
11061
|
+
overlay.setAttribute("data-hypha-login-modal", "");
|
|
11062
|
+
if (!container) {
|
|
11063
|
+
Object.assign(overlay.style, {
|
|
11064
|
+
position: "fixed",
|
|
11065
|
+
inset: "0",
|
|
11066
|
+
zIndex: "2147483647",
|
|
11067
|
+
background: "rgba(0,0,0,0.5)",
|
|
11068
|
+
display: "flex",
|
|
11069
|
+
alignItems: "center",
|
|
11070
|
+
justifyContent: "center",
|
|
11071
|
+
});
|
|
11072
|
+
}
|
|
11073
|
+
const frame = doc.createElement("div");
|
|
11074
|
+
Object.assign(frame.style, {
|
|
11075
|
+
position: "relative",
|
|
11076
|
+
width: "min(480px, 92vw)",
|
|
11077
|
+
height: "min(640px, 90vh)",
|
|
11078
|
+
background: "#fff",
|
|
11079
|
+
borderRadius: "10px",
|
|
11080
|
+
overflow: "hidden",
|
|
11081
|
+
boxShadow: "0 10px 40px rgba(0,0,0,0.3)",
|
|
11082
|
+
});
|
|
11083
|
+
const closeBtn = doc.createElement("button");
|
|
11084
|
+
closeBtn.setAttribute("aria-label", "Close login");
|
|
11085
|
+
closeBtn.textContent = "×";
|
|
11086
|
+
Object.assign(closeBtn.style, {
|
|
11087
|
+
position: "absolute",
|
|
11088
|
+
top: "6px",
|
|
11089
|
+
right: "10px",
|
|
11090
|
+
zIndex: "1",
|
|
11091
|
+
border: "none",
|
|
11092
|
+
background: "transparent",
|
|
11093
|
+
fontSize: "24px",
|
|
11094
|
+
lineHeight: "1",
|
|
11095
|
+
cursor: "pointer",
|
|
11096
|
+
color: "#555",
|
|
11097
|
+
});
|
|
11098
|
+
const iframe = doc.createElement("iframe");
|
|
11099
|
+
iframe.src = loginUrl;
|
|
11100
|
+
iframe.setAttribute("title", "Hypha login");
|
|
11101
|
+
Object.assign(iframe.style, { width: "100%", height: "100%", border: "none" });
|
|
11102
|
+
|
|
11103
|
+
frame.appendChild(closeBtn);
|
|
11104
|
+
frame.appendChild(iframe);
|
|
11105
|
+
overlay.appendChild(frame);
|
|
11106
|
+
(container || doc.body).appendChild(overlay);
|
|
11107
|
+
|
|
11108
|
+
let torn = false;
|
|
11109
|
+
const teardown = () => {
|
|
11110
|
+
if (torn) return;
|
|
11111
|
+
torn = true;
|
|
11112
|
+
if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
|
|
11113
|
+
};
|
|
11114
|
+
closeBtn.addEventListener("click", () => {
|
|
11115
|
+
teardown();
|
|
11116
|
+
if (onCancel) onCancel();
|
|
11117
|
+
});
|
|
11118
|
+
return { teardown };
|
|
11119
|
+
}
|
|
11120
|
+
|
|
11021
11121
|
/**
|
|
11022
11122
|
* Login to the hypha server.
|
|
11023
11123
|
*
|
|
@@ -11028,6 +11128,13 @@ function normalizeServerUrl(server_url) {
|
|
|
11028
11128
|
* expires_in: Token expiration time (optional)
|
|
11029
11129
|
* login_timeout: Timeout for login process (default: 60)
|
|
11030
11130
|
* login_callback: Callback function for login URL (optional)
|
|
11131
|
+
* mode: "popup" (default) or "inline". In "inline" mode (browser only, and only
|
|
11132
|
+
* when no login_callback is given) the login page is rendered in an in-page
|
|
11133
|
+
* modal iframe instead of a popup window — for host apps that block popups.
|
|
11134
|
+
* Works with local-auth and custom-auth providers whose login page is
|
|
11135
|
+
* iframe-embeddable; does NOT apply to Auth0 (keep the popup for it).
|
|
11136
|
+
* container: optional DOM element to mount the inline login modal into
|
|
11137
|
+
* (default: a full-screen overlay on document.body)
|
|
11031
11138
|
* profile: Whether to return user profile (optional)
|
|
11032
11139
|
* additional_headers: Additional HTTP headers (optional)
|
|
11033
11140
|
* transport: Transport type - "websocket" (default) or "http"
|
|
@@ -11042,6 +11149,7 @@ async function login(config) {
|
|
|
11042
11149
|
const profile = config.profile;
|
|
11043
11150
|
const additional_headers = config.additional_headers;
|
|
11044
11151
|
const transport = config.transport || "websocket";
|
|
11152
|
+
const mode = config.mode || "popup";
|
|
11045
11153
|
|
|
11046
11154
|
const server = await connectToServer({
|
|
11047
11155
|
name: "initial login client",
|
|
@@ -11058,8 +11166,58 @@ async function login(config) {
|
|
|
11058
11166
|
} else {
|
|
11059
11167
|
context = await svc.start();
|
|
11060
11168
|
}
|
|
11169
|
+
const canInline =
|
|
11170
|
+
mode === "inline" &&
|
|
11171
|
+
!callback &&
|
|
11172
|
+
typeof window !== "undefined" &&
|
|
11173
|
+
window.document;
|
|
11174
|
+
|
|
11061
11175
|
if (callback) {
|
|
11176
|
+
if (mode === "inline") {
|
|
11177
|
+
_logger.warn(
|
|
11178
|
+
"login: both login_callback and mode:'inline' were given; using login_callback.",
|
|
11179
|
+
);
|
|
11180
|
+
}
|
|
11062
11181
|
await callback(context);
|
|
11182
|
+
} else if (canInline) {
|
|
11183
|
+
// No-popup mode: render the login page in an in-page modal iframe and wait
|
|
11184
|
+
// for its 'hypha-login-complete' postMessage. The token is NOT taken from
|
|
11185
|
+
// the message — it is always fetched over the trusted check() RPC (which is
|
|
11186
|
+
// also the real timeout authority and covers a dropped/early message).
|
|
11187
|
+
const origin = new URL(config.server_url).origin;
|
|
11188
|
+
const loginUrl = new URL(context.login_url, config.server_url).href;
|
|
11189
|
+
return await new Promise((resolve, reject) => {
|
|
11190
|
+
let modal = null;
|
|
11191
|
+
let msgListener = null;
|
|
11192
|
+
let done = false;
|
|
11193
|
+
const cleanup = () => {
|
|
11194
|
+
if (msgListener) window.removeEventListener("message", msgListener);
|
|
11195
|
+
if (modal) modal.teardown();
|
|
11196
|
+
};
|
|
11197
|
+
const finish = (fn, arg) => {
|
|
11198
|
+
if (done) return;
|
|
11199
|
+
done = true;
|
|
11200
|
+
cleanup();
|
|
11201
|
+
fn(arg);
|
|
11202
|
+
};
|
|
11203
|
+
const fetchToken = () =>
|
|
11204
|
+
svc
|
|
11205
|
+
.check(context.key, { timeout, profile, _rkwargs: true })
|
|
11206
|
+
.then((r) => finish(resolve, r))
|
|
11207
|
+
.catch((e) => finish(reject, e));
|
|
11208
|
+
msgListener = (event) => {
|
|
11209
|
+
if (!isLoginCompleteMessage(event, origin, context.key)) return;
|
|
11210
|
+
fetchToken();
|
|
11211
|
+
};
|
|
11212
|
+
window.addEventListener("message", msgListener);
|
|
11213
|
+
modal = openLoginModal(loginUrl, {
|
|
11214
|
+
container: config.container,
|
|
11215
|
+
onCancel: () => finish(reject, new Error("Login cancelled by user")),
|
|
11216
|
+
});
|
|
11217
|
+
// Safety net: resolves when the user completes login even if the
|
|
11218
|
+
// postMessage is missed, and enforces the login timeout server-side.
|
|
11219
|
+
fetchToken();
|
|
11220
|
+
});
|
|
11063
11221
|
} else {
|
|
11064
11222
|
_logger.log(`Please open your browser and login at ${context.login_url}`);
|
|
11065
11223
|
}
|
|
@@ -11728,6 +11886,7 @@ const hyphaWebsocketClient = {
|
|
|
11728
11886
|
schemaFunction: _utils_schema_js__WEBPACK_IMPORTED_MODULE_3__.schemaFunction,
|
|
11729
11887
|
loadRequirements: _utils_index_js__WEBPACK_IMPORTED_MODULE_2__.loadRequirements,
|
|
11730
11888
|
login,
|
|
11889
|
+
isLoginCompleteMessage,
|
|
11731
11890
|
logout,
|
|
11732
11891
|
connectToServer,
|
|
11733
11892
|
connectToServerHTTP: _http_client_js__WEBPACK_IMPORTED_MODULE_5__.connectToServerHTTP,
|