hypha-rpc 0.21.44 → 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.
@@ -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-07-06T10:50:07.482Z
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),
@@ -11034,6 +11035,89 @@ function normalizeServerUrl(server_url) {
11034
11035
  return server_url;
11035
11036
  }
11036
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
+
11037
11121
  /**
11038
11122
  * Login to the hypha server.
11039
11123
  *
@@ -11044,6 +11128,13 @@ function normalizeServerUrl(server_url) {
11044
11128
  * expires_in: Token expiration time (optional)
11045
11129
  * login_timeout: Timeout for login process (default: 60)
11046
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)
11047
11138
  * profile: Whether to return user profile (optional)
11048
11139
  * additional_headers: Additional HTTP headers (optional)
11049
11140
  * transport: Transport type - "websocket" (default) or "http"
@@ -11058,6 +11149,7 @@ async function login(config) {
11058
11149
  const profile = config.profile;
11059
11150
  const additional_headers = config.additional_headers;
11060
11151
  const transport = config.transport || "websocket";
11152
+ const mode = config.mode || "popup";
11061
11153
 
11062
11154
  const server = await connectToServer({
11063
11155
  name: "initial login client",
@@ -11074,8 +11166,58 @@ async function login(config) {
11074
11166
  } else {
11075
11167
  context = await svc.start();
11076
11168
  }
11169
+ const canInline =
11170
+ mode === "inline" &&
11171
+ !callback &&
11172
+ typeof window !== "undefined" &&
11173
+ window.document;
11174
+
11077
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
+ }
11078
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
+ });
11079
11221
  } else {
11080
11222
  _logger.log(`Please open your browser and login at ${context.login_url}`);
11081
11223
  }
@@ -11744,6 +11886,7 @@ const hyphaWebsocketClient = {
11744
11886
  schemaFunction: _utils_schema_js__WEBPACK_IMPORTED_MODULE_3__.schemaFunction,
11745
11887
  loadRequirements: _utils_index_js__WEBPACK_IMPORTED_MODULE_2__.loadRequirements,
11746
11888
  login,
11889
+ isLoginCompleteMessage,
11747
11890
  logout,
11748
11891
  connectToServer,
11749
11892
  connectToServerHTTP: _http_client_js__WEBPACK_IMPORTED_MODULE_5__.connectToServerHTTP,