Skip to content

feat: find apollo client instance on embedded iframes#828

Open
boehlkers wants to merge 1 commit into
apollographql:mainfrom
boehlkers:enable-iframe-discovery
Open

feat: find apollo client instance on embedded iframes#828
boehlkers wants to merge 1 commit into
apollographql:mainfrom
boehlkers:enable-iframe-discovery

Conversation

@boehlkers

@boehlkers boehlkers commented Mar 9, 2022

Copy link
Copy Markdown

Per #380.

This PR adds discovery support for apollo client instances in same-origin iframes. It does so by looking through any embedded iframes and looking for window.APOLLO_CLIENT instances therein.

I also added an iframe.html file to the client repo to test and validate the behavior; committing it may be overkill since it's not core functionality but willing to get feedback on that. Can verify by starting the local app with npm run start:dev and navigating to localhost:3000/iframe.html, which will load the demo app in an iframe.

jerelmiller added a commit that referenced this pull request Jun 29, 2026
### Summary

This PR enables `apollo-client-devtools` to detect and interact with
Apollo Clients running inside **same-origin iframes**, in addition to
the main window.

### Problem

Previously we only detected clients in the top-level window. If a page
had iframes with their own Apollo Client instances, those clients were
invisible to the extension.

### Solution

1. **Updated extension manifests**: Added `all_frames: true` to content
scripts so `tab.js` and `hook.js` run in every frame (main window +
iframes)
2. **Added multi-port architecture with frameId tracking**: Changed
`background.ts` from tracking a single `tab` port per tab to a
`Map<tabPorts, frameId>` per tab, enabling multiple frames to connect
while tracking which frame each port belongs to
3. **Introduced client --> frame mapping**: Added a `clientFrames:
Map<clientId, frameId>` to track which Apollo Client belongs to which
frame, enabling targeted message routing
4. **Implemented frameId-based message routing**: RPC requests for a
specific client are now routed only to the frame that owns that client,
eliminating broadcast overhead
5. **Added SKIP_RESPONSE pattern for discovery**: The `getClients`
handler in `hook.ts` uses `SKIP_RESPONSE` so frames without Apollo
Clients don't pollute discovery responses

## Files Changed

| File | Change |
|------|--------|
|
[src/extension/chrome/manifest.json](cci:7://file:///Users/camillelawrence/Desktop/repos/apollo-client-devtools/src/extension/chrome/manifest.json:0:0-0:0)
| Add `all_frames: true` |
|
[src/extension/firefox/manifest.json](cci:7://file:///Users/camillelawrence/Desktop/repos/apollo-client-devtools/src/extension/firefox/manifest.json:0:0-0:0)
| Add `all_frames: true` |
|
[src/extension/rpc.ts](cci:7://file:///Users/camillelawrence/Desktop/repos/apollo-client-devtools/src/extension/rpc.ts:0:0-0:0)
| Export `SKIP_RESPONSE` symbol + handler logic to skip sending
responses |
|
[src/extension/background/background.ts](cci:7://file:///Users/camillelawrence/Desktop/repos/apollo-client-devtools/src/extension/background/background.ts:0:0-0:0)
| `Set<tabPorts>` --> `Map<Port, frameId>`, add `clientFrames` mapping,
implement targeted routing |
|
[src/extension/tab/hook.ts](cci:7://file:///Users/camillelawrence/Desktop/repos/apollo-client-devtools/src/extension/tab/hook.ts:0:0-0:0)
| Add `SKIP_RESPONSE` to `getClients` handler for frames without clients
|
|
[src/extension/tab/handleExplorerRequests.ts](cci:7://file:///Users/camillelawrence/Desktop/repos/apollo-client-devtools/src/extension/tab/handleExplorerRequests.ts:0:0-0:0)
| Replace throw with silent return |
|
[development/client/public/iframe.html](cci:7://file:///Users/camillelawrence/Desktop/repos/apollo-client-devtools/development/client/public/iframe.html:0:0-0:0)
| Test page for iframe scenarios |

## Automated Tests Added

Two new unit tests in `src/extension/__tests__/rpc.test.ts`:

| Test | Description |
|------|-------------|
| `does not send response when handler returns SKIP_RESPONSE` | Verifies
that when a handler returns `SKIP_RESPONSE`, no RPC response message is
posted |
| `SKIP_RESPONSE allows handler to be re-registered after unsubscribe` |
Verifies that handlers using `SKIP_RESPONSE` can be properly
unsubscribed and re-registered |

## Manual Verification Steps

1. **Single frame (regression test)**  
   - Load a page with a single Apollo Client (no iframes)
   - Open DevTools --> Apollo tab
- Verify the client is detected and all features (Queries, Mutations,
Cache) work normally

2. **Multi-frame detection**  
   - Start dev server: `npm run start:dev`
   - Open Chrome with the extension loaded: `npm run chrome`
   - Navigate to `http://localhost:3000`
   - Add a client in the main window and a client inside the iframe
   - Verify **both clients** appear in the DevTools client dropdown

3. **Targeted routing verification**  
   - With both clients (main window + iframe) registered from step 2
   - Select the iframe's client in DevTools
   - Run queries/mutations and inspect the cache
- In Chrome DevTools console for the **background script**, confirm RPC
requests are routed only to the correct frame (look for `frameId` in
console logs in dev mode)

## Limitations

- **Same-origin only**: Per browser security, `all_frames: true` only
works for same-origin iframes. Cross-origin iframes remain invisible.

## References

- Closes #380
- Related: PR #828, PR #997

---------

Co-authored-by: Jerel Miller <jerelmiller@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants