Skip to content

Commit

Permalink
Handle client termination and recreation (#1303)
Browse files Browse the repository at this point in the history
  • Loading branch information
jerelmiller committed Mar 29, 2024
1 parent d96a9fd commit df0d242
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 65 deletions.
5 changes: 5 additions & 0 deletions .changeset/fresh-donkeys-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"apollo-client-devtools": patch
---

Fix issue where terminating the client by calling `.stop` would not disconnect it from devtools making it difficult to track newly created clients.
120 changes: 96 additions & 24 deletions development/client/src/App.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,109 @@
import React from "react";
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
import { useQuery } from "@apollo/client";
import React, { useState } from "react";
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
import {
ApolloClient,
ApolloProvider,
InMemoryCache,
makeReference,
useQuery,
} from "@apollo/client";
import ColorSchemeGenerator from "./ColorSchemeGenerator";
import Favorites from "./Favorites";
import ColorLookup from "./ColorLookup";
import { GET_SAVED_COLORS } from "./queries";
import "./App.css";

function App() {
useQuery(GET_SAVED_COLORS);
const [client, setClient] = useState(() => createClient());

return (
<Router>
<div className="App">
<header>
<Link to="/">
<h1>Colors</h1>
</Link>
<nav>
<Link to="/favorites">Favorites</Link>
<Link to="/lookup">Lookup</Link>
</nav>
</header>
<main>
<Routes>
<Route path="/favorites" element={<Favorites />} />
<Route path="/lookup" element={<ColorLookup />} />
<Route path="/" element={<ColorSchemeGenerator />} />
</Routes>
</main>
if (!client) {
return (
<div style={{ textAlign: "center" }}>
<h1>Client was terminated</h1>
<button onClick={() => setClient(createClient())}>
Recreate client
</button>
</div>
</Router>
);
}

return (
<ApolloProvider client={client}>
<BrowserRouter>
<Layout onChangeClient={(client) => setClient(client)} />
</BrowserRouter>
</ApolloProvider>
);
}

function Layout({ onChangeClient }) {
const { client } = useQuery(GET_SAVED_COLORS);

return (
<div className="App">
<header style={{ display: "flex" }}>
<Link to="/">
<h1>Colors</h1>
</Link>
<nav style={{ flex: 1 }}>
<Link to="/favorites">Favorites</Link>
<Link to="/lookup">Lookup</Link>
</nav>
<div style={{ display: "flex", gap: "1rem" }}>
<button
onClick={() => {
client.stop();
onChangeClient(createClient());
}}
>
Recreate client
</button>
<button
onClick={() => {
client.stop();
onChangeClient(null);
}}
>
Terminate client
</button>
</div>
</header>
<main>
<Routes>
<Route path="/favorites" element={<Favorites />} />
<Route path="/lookup" element={<ColorLookup />} />
<Route path="/" element={<ColorSchemeGenerator />} />
</Routes>
</main>
</div>
);
}

function createClient() {
return new ApolloClient({
cache: new InMemoryCache({
typePolicies: {
Color: {
keyFields: ["hex"],
fields: {
saved: {
read(_, { readField }) {
const hex = readField("hex");
const favoritedColors = readField(
"favoritedColors",
makeReference("ROOT_QUERY")
);
return favoritedColors.some((colorRef) => {
return hex === readField("hex", colorRef);
});
},
},
},
},
},
}),
uri: "http://localhost:4000",
});
}

export default App;
38 changes: 1 addition & 37 deletions development/client/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,7 @@ import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";

import {
ApolloClient,
InMemoryCache,
ApolloProvider,
makeReference,
} from "@apollo/client";

const client = new ApolloClient({
cache: new InMemoryCache({
typePolicies: {
Color: {
keyFields: ["hex"],
fields: {
saved: {
read(_, { readField }) {
const hex = readField("hex");
const favoritedColors = readField(
"favoritedColors",
makeReference("ROOT_QUERY")
);
return favoritedColors.some((colorRef) => {
return hex === readField("hex", colorRef);
});
},
},
},
},
},
}),
uri: "http://localhost:4000",
});

createRoot(document.getElementById("root")).render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>
);
createRoot(document.getElementById("root")).render(<App />);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
Expand Down
2 changes: 1 addition & 1 deletion src/application/machines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export function createDevtoolsMachine({ actions }: { actions: Actions }) {
timeout: "timedout",
clientNotFound: "notFound",
},
entry: "unsubscribeFromAll",
entry: ["unsubscribeFromAll", "connectToClient"],
},
timedout: {},
notFound: {
Expand Down
1 change: 1 addition & 0 deletions src/extension/devtools/devtools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const devtoolsMachine = interpret(
actions: {
connectToClient: () => {
clientPort.send({ type: "connectToClient" });
startConnectTimeout();
},
startRequestInterval: () => {
clearTimeout(connectTimeoutId);
Expand Down
28 changes: 25 additions & 3 deletions src/extension/tab/hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import {
import type { QueryResult } from "../../types";
import { getPrivateAccess } from "../../privateAccess";
import type { JSONObject } from "../../application/types/json";
import type { FetchPolicy } from "../../application/components/Explorer/Explorer";
import { createWindowActor } from "../actor";
import type { ClientMessage, DevtoolsRPCMessage } from "../messages";
import { createWindowMessageAdapter } from "../messageAdapters";
Expand All @@ -34,7 +33,7 @@ declare global {
type TCache = any;

interface Window {
__APOLLO_CLIENT__: ApolloClient<TCache>;
__APOLLO_CLIENT__?: ApolloClient<TCache>;
[DEVTOOLS_KEY]?: {
push(client: ApolloClient<any>): void;
};
Expand Down Expand Up @@ -246,8 +245,31 @@ function findClient() {
initializeDevtoolsHook(); // call immediately to reduce lag if devtools are already available
}

function watchForClientTermination(client: ApolloClient<any>) {
const originalStop = client.stop;

client.stop = () => {
knownClients.delete(client);

if (window.__APOLLO_CLIENT__ === client) {
window.__APOLLO_CLIENT__ = undefined;
}

if (hook.ApolloClient === client) {
hook.ApolloClient = undefined;
}

tab.send({ type: "disconnectFromDevtools" });
originalStop.call(client);
};
}

function registerClient(client: ApolloClient<any>) {
knownClients.add(client);
if (!knownClients.has(client)) {
knownClients.add(client);
watchForClientTermination(client);
}

hook.ApolloClient = client;
// TODO: Repurpose this callback. The message it sent was not listened by
// anything, so the broadcast was useless. Currently the devtools rely on
Expand Down

0 comments on commit df0d242

Please sign in to comment.