Skip to content

Commit 8d14e45

Browse files
committed
Adding apollo.
1 parent 7d32a6e commit 8d14e45

File tree

11 files changed

+2019
-1
lines changed

11 files changed

+2019
-1
lines changed

app/middlewares/debuggerAPI.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ let socket;
3232

3333
const workerOnMessage = message => {
3434
const { data } = message;
35+
36+
if (data.APOLLO_MESSAGE) {
37+
postMessage({
38+
event: data.event,
39+
payload: data,
40+
source: 'apollo-devtools-backend',
41+
}, "*");
42+
}
43+
3544
if (data && (data.__IS_REDUX_NATIVE_MESSAGE__ || data.__REPORT_REACT_DEVTOOLS_PORT__)) {
3645
return true;
3746
}
@@ -43,14 +52,20 @@ const workerOnMessage = message => {
4352
socket.send(JSON.stringify(data));
4453
};
4554

55+
const onWindowMessage = e => {
56+
if (e.data && e.data.source === 'apollo-devtools-proxy') {
57+
worker.postMessage({source: 'apollo-devtools-proxy', event: e.data.payload.event, payload: e.data.payload.payload});
58+
}
59+
}
60+
4661
const createJSRuntime = () => {
4762
// This worker will run the application javascript code,
4863
// making sure that it's run in an environment without a global
4964
// document, to make it consistent with the JSC executor environment.
5065
// eslint-disable-next-line
5166
worker = new Worker(`${__webpack_public_path__}RNDebuggerWorker.js`);
5267
worker.addEventListener('message', workerOnMessage);
53-
68+
window.addEventListener('message', onWindowMessage);
5469
actions.setDebuggerWorker(worker, 'connected');
5570
};
5671

@@ -59,6 +74,7 @@ const shutdownJSRuntime = () => {
5974
scriptExecuted = false;
6075
if (worker) {
6176
worker.terminate();
77+
window.removeEventListener('messsage', onWindowMessage);
6278
setDevMenuMethods([]);
6379
}
6480
worker = null;

app/worker/apollo/bridge.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { EventEmitter } from "events";
2+
3+
export default class Bridge extends EventEmitter {
4+
constructor(wall) {
5+
super();
6+
// Setting `this` to `self` here to fix an error in the Safari build:
7+
// ReferenceError: Cannot access uninitialized variable.
8+
// The error might be related to the webkit bug here:
9+
// https://bugs.webkit.org/show_bug.cgi?id=171543
10+
const self = this;
11+
self.setMaxListeners(Infinity);
12+
self.wall = wall;
13+
wall.listen(message => {
14+
if (typeof message === "string") {
15+
self.emit(message);
16+
} else {
17+
self.emit(message.event, message.payload);
18+
}
19+
});
20+
}
21+
22+
send(event, payload) {
23+
this.wall.send({
24+
APOLLO_MESSAGE: true,
25+
event,
26+
payload,
27+
});
28+
}
29+
30+
log(message) {
31+
this.send("log", message);
32+
}
33+
}

app/worker/apollo/broadcastQueries.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
export const initBroadCastEvents = (hook, bridge) => {
2+
let logger = ({
3+
state: { queries, mutations },
4+
// XXX replace with universal store format compat way of this
5+
dataWithOptimisticResults: inspector,
6+
}) => {
7+
bridge.send(
8+
"broadcast:new",
9+
JSON.stringify({
10+
queries,
11+
mutations,
12+
inspector,
13+
}),
14+
);
15+
};
16+
17+
bridge.on("panel:ready", () => {
18+
const client = hook.ApolloClient;
19+
const initial = {
20+
queries: client.queryManager
21+
? client.queryManager.queryStore.getStore()
22+
: {},
23+
mutations: client.queryManager
24+
? client.queryManager.mutationStore.getStore()
25+
: {},
26+
inspector: client.cache.extract(true),
27+
};
28+
bridge.send("broadcast:new", JSON.stringify(initial));
29+
});
30+
31+
hook.ApolloClient.__actionHookForDevTools(logger);
32+
};

app/worker/apollo/checkVersions.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import genUuid from "uuid/v1";
2+
3+
export const checkVersions = async (hook, bridge) => {
4+
const uuid = genUuid();
5+
6+
const { version } = hook.ApolloClient;
7+
const { devToolsVersion } = hook;
8+
9+
if (!devToolsVersion) return;
10+
const graphQLParams = {
11+
query: `
12+
query CompatibilityMessages(
13+
$uuid: String
14+
$devToolsVersion: String!
15+
$versions: [VersionInput]
16+
) {
17+
compatibilityMessages(
18+
uuid: $uuid,
19+
devToolsVersion: $devToolsVersion,
20+
versions: $versions
21+
) {
22+
message
23+
}
24+
}
25+
`,
26+
variables: {
27+
uuid,
28+
devToolsVersion,
29+
versions: [{ packageName: "apollo-client", version }],
30+
},
31+
};
32+
33+
fetch("https://devtools.apollodata.com/graphql", {
34+
method: "post",
35+
mode: "cors",
36+
headers: {
37+
Accept: "application/json",
38+
"Content-Type": "application/json",
39+
},
40+
body: JSON.stringify(graphQLParams),
41+
})
42+
.then(function(response) {
43+
return response.json();
44+
})
45+
.then(function(response) {
46+
if (!response.data.compatibilityMessages) return;
47+
response.data.compatibilityMessages.forEach(cm => {
48+
bridge.send(
49+
`console.info('Apollo devtools message:', "${cm.message}")`,
50+
);
51+
});
52+
});
53+
};

app/worker/apollo/links.js

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { execute, ApolloLink, Observable, from } from "apollo-link";
2+
import { getQueryDefinition } from "apollo-utilities";
3+
import gql from "graphql-tag";
4+
5+
/*
6+
*
7+
* supports dynamic client schemas set on the context
8+
*
9+
* schemas must be an array of the following shape
10+
*
11+
{
12+
definition: schemaString,
13+
directives: `directive @client on FIELD`,
14+
}
15+
*
16+
*/
17+
const apolloClientSchema = {
18+
directives: "directive @connection(key: String!, filter: [String]) on FIELD",
19+
};
20+
const schemaLink = () =>
21+
new ApolloLink((operation, forward) => {
22+
return forward(operation).map(result => {
23+
let { schemas = [] } = operation.getContext();
24+
result.extensions = Object.assign({}, result.extensions, {
25+
schemas: schemas.concat([apolloClientSchema]),
26+
});
27+
return result;
28+
});
29+
});
30+
31+
// forward all "errors" to next with a good shape for graphiql
32+
const errorLink = () =>
33+
new ApolloLink((operation, forward) => {
34+
return new Observable(observer => {
35+
let sub;
36+
try {
37+
sub = forward(operation).subscribe({
38+
next: observer.next.bind(observer),
39+
error: networkError =>
40+
observer.next({
41+
errors: [
42+
{
43+
message: networkError.message,
44+
locations: [networkError.stack],
45+
},
46+
],
47+
}),
48+
complete: observer.complete.bind(observer),
49+
});
50+
} catch (e) {
51+
observer.next({
52+
errors: [{ message: e.message, locations: [e.stack] }],
53+
});
54+
}
55+
56+
return () => {
57+
if (sub) sub.unsubscribe();
58+
};
59+
});
60+
});
61+
62+
const cacheLink = fetchPolicy =>
63+
new ApolloLink((operation, forward) => {
64+
// XXX how do we handle local state here? It *should* still work right?
65+
if (fetchPolicy === "no-cache") return forward(operation);
66+
67+
const { cache } = operation.getContext();
68+
const { variables, query } = operation;
69+
// quick check if this is a query
70+
try {
71+
const def = getQueryDefinition(query);
72+
const results = cache.readQuery({ query, variables });
73+
if (results) return Observable.of({ data: results });
74+
} catch (e) {}
75+
76+
return forward(operation);
77+
});
78+
79+
export const initLinkEvents = (hook, bridge) => {
80+
// handle incoming requests
81+
const subscriber = request => {
82+
const { query, variables, operationName, key, fetchPolicy } = JSON.parse(
83+
request,
84+
);
85+
try {
86+
const userLink = hook.ApolloClient.link;
87+
const cache = hook.ApolloClient.cache;
88+
89+
const devtoolsLink = from([
90+
errorLink(),
91+
cacheLink(fetchPolicy),
92+
schemaLink(),
93+
userLink,
94+
]);
95+
const obs = execute(devtoolsLink, {
96+
query: gql(query),
97+
variables,
98+
operationName,
99+
context: { __devtools_key__: key, cache },
100+
});
101+
obs.subscribe({
102+
next: data => bridge.send(`link:next:${key}`, JSON.stringify(data)),
103+
error: err => bridge.send(`link:error:${key}`, JSON.stringify(err)),
104+
complete: () => bridge.send(`link:complete:${key}`),
105+
});
106+
} catch (e) {
107+
bridge.send(`link:error:${key}`, JSON.stringify(e));
108+
}
109+
};
110+
111+
bridge.on("link:operation", subscriber);
112+
};

app/worker/index.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ import devToolsEnhancer, { composeWithDevTools } from './reduxAPI';
1717
import * as RemoteDev from './remotedev';
1818
import { getRequiredModules, ignoreRNDIntervalSpy } from './utils';
1919
import { toggleNetworkInspect } from './networkInspect';
20+
import Bridge from './apollo/bridge';
21+
import { initBroadCastEvents } from "./apollo/broadcastQueries";
22+
import { initLinkEvents } from "./apollo/links";
23+
import { checkVersions } from "./apollo/checkVersions";
2024

2125
/* eslint-disable no-underscore-dangle */
2226
self.__REMOTEDEV__ = RemoteDev;
@@ -51,6 +55,38 @@ const setupRNDebugger = async message => {
5155
checkAvailableDevMenuMethods(modules, message.networkInspect);
5256
reportDefaultReactDevToolsPort(modules);
5357
}
58+
59+
const interval = setInterval(() => {
60+
if (self.__APOLLO_CLIENT__) {
61+
clearInterval(interval);
62+
63+
const hook = {
64+
ApolloClient: self.__APOLLO_CLIENT__
65+
};
66+
67+
const bridge = new Bridge({
68+
listen(fn) {
69+
self.addEventListener("message", evt =>
70+
{
71+
return fn(evt.data);
72+
});
73+
},
74+
send(data) {
75+
postMessage(data);
76+
},
77+
});
78+
79+
if (Number(hook.ApolloClient.version[0]) !== 1) {
80+
initLinkEvents(hook, bridge);
81+
initBroadCastEvents(hook, bridge);
82+
}
83+
bridge.log("backend ready.");
84+
bridge.send("ready", hook.ApolloClient.version);
85+
checkVersions(hook, bridge);
86+
87+
}
88+
89+
}, 1000);
5490
};
5591

5692
const messageHandlers = {
@@ -66,6 +102,7 @@ const messageHandlers = {
66102
} catch (err) {
67103
error = err.message;
68104
}
105+
69106
sendReply(null /* result */, error);
70107

71108
if (!error) {

0 commit comments

Comments
 (0)