Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions development/client/public/iframe.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">

<head>
<title>Color App in an IFrame</title>
</head>

<body>
<iframe src="../index.html" style="width: 100%; height: 600px;">
</body>

</html>
57 changes: 57 additions & 0 deletions src/extension/tab/__tests__/hook.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { ApolloClient, InMemoryCache } from '@apollo/client';
import { findClient } from '../hook';

const cache = new InMemoryCache();

describe('findClient', () => {
beforeEach(() => {
window.__APOLLO_CLIENT__ = undefined;
window.document.body.innerHTML = ``;
});

it('should find the client on the provided window object', () => {
const expectedClient = new ApolloClient({ cache });

const client = findClient();
expect(client).toEqual(expectedClient);
});

it('should find the client on an embedded iframe', () => {
const expectedClient = new ApolloClient({ cache, connectToDevTools: false });

const iframe = document.createElement('iframe');

window.document.body.appendChild(iframe);

iframe.contentWindow!.__APOLLO_CLIENT__ = expectedClient;

const client = findClient();
expect(client).toEqual(expectedClient);
});

it('should find the client in a nested iframe', () => {
const expectedClient = new ApolloClient({ cache, connectToDevTools: false });

const iframe = document.createElement('iframe');
const nestedIframe = document.createElement('iframe');

window.document.body.appendChild(iframe);

iframe.contentDocument!.body.appendChild(nestedIframe);
nestedIframe.contentWindow!.__APOLLO_CLIENT__ = expectedClient;

const client = findClient();
expect(client).toEqual(expectedClient);
});

it('should return undefined if no client instance is found', () => {
const iframe = document.createElement('iframe');
const nestedIframe = document.createElement('iframe');

window.document.body.appendChild(iframe);
iframe.contentDocument!.body.appendChild(nestedIframe);

const client = findClient();
expect(client).toBeUndefined();
});
});
43 changes: 37 additions & 6 deletions src/extension/tab/hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ declare global {
type TCache = any;

interface Window {
__APOLLO_CLIENT__: ApolloClient<TCache>;
__APOLLO_CLIENT__?: ApolloClient<TCache>;
}
}

Expand All @@ -46,6 +46,36 @@ type Hook = {
getCache: () => void;
};

/**
* Look through the window and any available iframes to find a client instance
* in an __APOLLO_CLIENT__ property.
*/
export function findClient(): ApolloClient<any> | undefined {
let currentWindow: Window | undefined = window;

// Account for nested iframes by doing a breadth-first-search through
// all iframes, and all iframes contained within those iframes, and so on.
const windowsQueue: Window[] = [];
do {
if (currentWindow.__APOLLO_CLIENT__) {
return currentWindow.__APOLLO_CLIENT__;
}

const iframesInDocument = currentWindow.document.getElementsByTagName('iframe');
for (const iframe of iframesInDocument) {
// Check for contentDocument to ensure we have access to it; i.e. same-origin policy
if (iframe.contentWindow && iframe.contentDocument) {
windowsQueue.unshift(iframe.contentWindow);
}
}

currentWindow = windowsQueue.shift();
}
while (currentWindow);

return undefined;
}

function initializeHook() {
const hook: Hook = {
ApolloClient: undefined,
Expand Down Expand Up @@ -213,14 +243,15 @@ function initializeHook() {
}
});

function findClient() {
function findAndRegisterClient() {
let interval;
let count = 0;

function initializeDevtoolsHook() {
if (count++ > 10) clearInterval(interval);
if (window.__APOLLO_CLIENT__) {
hook.ApolloClient = window.__APOLLO_CLIENT__;
const foundClient = findClient();
if (foundClient) {
hook.ApolloClient = foundClient;
hook.ApolloClient.__actionHookForDevTools(handleActionHookForDevtools);
hook.getQueries = () =>
getQueries((hook.ApolloClient as any).queryManager.queries);
Expand All @@ -231,7 +262,7 @@ function initializeHook() {
(
hook.ApolloClient as any
).queryManager.mutationStore?.getStore()
: // Apollo Client 3.3
: // Apollo Client 3.3
(hook.ApolloClient as any).queryManager.mutationStore
);
hook.getCache = () => hook.ApolloClient?.cache.extract(true);
Expand All @@ -245,7 +276,7 @@ function initializeHook() {
}

// Attempt to find the client on a 1-second interval for 10 seconds max
findClient();
findAndRegisterClient();
}

initializeHook();