codeant-ai-for-open-source[bot] commented on code in PR #40870:
URL: https://github.com/apache/superset/pull/40870#discussion_r3375872991


##########
superset-embedded-sdk/src/index.ts:
##########
@@ -248,20 +267,33 @@ export async function embedDashboard({
   }
 
   const [guestToken, ourPort]: [string, Switchboard] = await Promise.all([
-    fetchGuestToken(),
+    fetchGuestTokenWithTimeout(),
     mountIframe(),
   ]);

Review Comment:
   **Suggestion:** If the token promise times out/rejects, `Promise.all` 
rejects after `mountIframe()` has already mutated the DOM and opened iframe 
comms, but no cleanup runs because the function exits before `unmount` is 
returned. Add cleanup on the error path (e.g., catch around the initial await) 
to remove the iframe and dispose resources before rethrowing. [missing cleanup]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ Failed initial token fetch leaves iframe mounted without cleanup.
   - ⚠️ Host receives error yet keeps orphaned embedded dashboard iframe.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. A host app (or the embedded test app described in
   `superset-frontend/playwright/pages/EmbeddedPage.ts:24-29`) calls 
`embedDashboard` from
   `superset-embedded-sdk/src/index.ts:126-139`, providing a `fetchGuestToken` 
that can
   reject (e.g., backend error) or take longer than the configured timeout in
   `withTimeout.ts:25-42`.
   
   2. Inside `embedDashboard`, the initial setup concurrently starts
   `fetchGuestTokenWithTimeout()` and `mountIframe()` via `Promise.all` at
   `index.ts:269-272`. `mountIframe` at `index.ts:184-267` creates an iframe, 
sets its `src`,
   and inserts it into the DOM with `mountPoint.replaceChildren(iframe)` at 
line 264, as well
   as wiring a `load` listener that establishes a `MessageChannel` and 
constructs a
   `Switchboard` instance at lines 226-250.
   
   3. If `fetchGuestTokenWithTimeout()` rejects (from the host callback or 
timeout),
   `Promise.all` at `index.ts:269-272` rejects after `mountIframe()` has 
already mutated the
   DOM and created the `Switchboard`. Execution jumps out of `embedDashboard` 
before the code
   that emits the initial guest token (`index.ts:274-275`), starts the 
Switchboard
   (`ourPort.start()` at 300), defines iframe methods (`index.ts:301-314`), 
sets up the
   refresh timer (`index.ts:293-296`), or creates the `unmount` function
   (`index.ts:316-325`).
   
   4. The caller receives a rejected Promise from `embedDashboard` but the 
iframe appended at
   `index.ts:264` remains mounted, along with its message ports and event 
listeners, and the
   host has no `EmbeddedDashboard.unmount` handle to clean it up. Until the 
host explicitly
   manipulates `mountPoint` or reloads the page, the orphaned iframe and 
associated resources
   persist despite the failed initialization, matching the suggestion that the 
rejection path
   lacks cleanup for partially initialized state.
   ```
   </details>
   
   [Fix in 
Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=175dd144e4cf4603ba1c70130ed8afd9&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 | [Fix in VSCode 
Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=175dd144e4cf4603ba1c70130ed8afd9&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-embedded-sdk/src/index.ts
   **Line:** 269:272
   **Comment:**
        *Missing Cleanup: If the token promise times out/rejects, `Promise.all` 
rejects after `mountIframe()` has already mutated the DOM and opened iframe 
comms, but no cleanup runs because the function exits before `unmount` is 
returned. Add cleanup on the error path (e.g., catch around the initial await) 
to remove the iframe and dispose resources before rethrowing.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40870&comment_hash=2b085243f8260447eb3aea47688e88848ce3473f3fceeb112ef32b3099f65c8c&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40870&comment_hash=2b085243f8260447eb3aea47688e88848ce3473f3fceeb112ef32b3099f65c8c&reaction=dislike'>👎</a>



##########
superset-embedded-sdk/src/index.ts:
##########
@@ -248,20 +267,33 @@ export async function embedDashboard({
   }
 
   const [guestToken, ourPort]: [string, Switchboard] = await Promise.all([
-    fetchGuestToken(),
+    fetchGuestTokenWithTimeout(),
     mountIframe(),
   ]);
 
   ourPort.emit('guestToken', { guestToken });
   log('sent guest token');
 
+  // Track the pending refresh timer so it can be cancelled on unmount, and
+  // stop the cycle once unmounted so it cannot leak across mount/unmount 
cycles.
+  let refreshTimer: ReturnType<typeof setTimeout> | undefined;
+  let unmounted = false;
+
   async function refreshGuestToken() {
-    const newGuestToken = await fetchGuestToken();
+    if (unmounted) return;
+    const newGuestToken = await fetchGuestTokenWithTimeout();
+    if (unmounted) return;
     ourPort.emit('guestToken', { guestToken: newGuestToken });
-    setTimeout(refreshGuestToken, getGuestTokenRefreshTiming(newGuestToken));
+    refreshTimer = setTimeout(
+      refreshGuestToken,
+      getGuestTokenRefreshTiming(newGuestToken),
+    );
   }

Review Comment:
   **Suggestion:** The refresh callback awaits token fetch without any error 
handling, so a timeout/rejection will throw out of the `setTimeout` callback as 
an unhandled promise rejection and permanently stop further token refreshes. 
Wrap this flow in `try/catch` and always schedule the next retry (or controlled 
backoff) so one transient failure does not break auth refresh for the rest of 
the session. [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Embedded dashboards stop refreshing guest tokens on fetch errors.
   - ⚠️ Expired guest tokens break iframe API requests until reload.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. In a host app or test harness, call `embedDashboard` exported from
   `superset-embedded-sdk/src/index.ts:126-139`, passing a valid `mountPoint` 
and a
   `fetchGuestToken` implementation that can reject (e.g., network error or 
backend 500).
   
   2. `embedDashboard` completes the initial token fetch via `Promise.all` at
   `index.ts:269-272`, then schedules the first refresh with `refreshTimer =
   setTimeout(refreshGuestToken, getGuestTokenRefreshTiming(guestToken))` at
   `index.ts:293-296`, where the delay is computed from the token expiry in
   `getGuestTokenRefreshTiming` at 
`superset-embedded-sdk/src/guestTokenRefresh.ts:26-32`.
   
   3. When the refresh timer fires, `refreshGuestToken` at `index.ts:282-291` 
runs; since
   `unmounted` is still `false`, it hits `const newGuestToken = await
   fetchGuestTokenWithTimeout();` at line 284. `fetchGuestTokenWithTimeout` 
wraps the host
   callback in `withTimeout` from 
`superset-embedded-sdk/src/withTimeout.ts:25-42`, which
   rejects if the underlying promise rejects or the timeout elapses.
   
   4. On such a rejection, the `await` at `index.ts:284` throws inside the async
   `refreshGuestToken` function, so the subsequent lines 285-290 (emitting the 
new token and
   scheduling the next `setTimeout`) never execute. Because `setTimeout` 
ignores the returned
   Promise from an async callback, this rejection becomes an unhandled promise 
rejection and
   no new `refreshTimer` is scheduled. The refresh loop stops after this single 
failure, and
   once the original token expires (per `guestTokenRefresh.ts:26-32`), the 
embedded iframe
   continues running with an expired guest token and no further refresh 
attempts until the
   host reloads the page or re-invokes `embedDashboard`.
   ```
   </details>
   
   [Fix in 
Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=33c67e3a9ed841fca3c0f9a610b8a29c&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 | [Fix in VSCode 
Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=33c67e3a9ed841fca3c0f9a610b8a29c&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-embedded-sdk/src/index.ts
   **Line:** 282:291
   **Comment:**
        *Logic Error: The refresh callback awaits token fetch without any error 
handling, so a timeout/rejection will throw out of the `setTimeout` callback as 
an unhandled promise rejection and permanently stop further token refreshes. 
Wrap this flow in `try/catch` and always schedule the next retry (or controlled 
backoff) so one transient failure does not break auth refresh for the rest of 
the session.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40870&comment_hash=1fcdbe313a1819c35f2eb830d67f3c9ca3fb2889fb67dc9f23fc9c8973c094ae&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40870&comment_hash=1fcdbe313a1819c35f2eb830d67f3c9ca3fb2889fb67dc9f23fc9c8973c094ae&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to