Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

Commit 756bfea

Browse files
committed
Instrument Relay on page load
This change instruments Relay as soon as Relay registers itself with the React devtools. This means that we instrument even if the page is not open, but this is necessary when we want to inspect GraphQL queries that are made for the initial page load.
1 parent 2f584e2 commit 756bfea

4 files changed

Lines changed: 133 additions & 52 deletions

File tree

plugins/Relay/backend.js

Lines changed: 8 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,6 @@
1313
import type Bridge from '../../agent/Bridge';
1414
import type Agent from '../../agent/Agent';
1515

16-
import guid from '../../utils/guid';
17-
18-
function decorate(obj, attr, fn) {
19-
var old = obj[attr];
20-
obj[attr] = function() {
21-
var res = old.apply(this, arguments);
22-
fn.apply(this, arguments);
23-
return res;
24-
};
25-
return () => (obj[attr] = old);
26-
}
27-
2816
module.exports = (bridge: Bridge, agent: Agent, hook: Object) => {
2917
var shouldEnable = !!(
3018
hook._relayInternals &&
@@ -35,45 +23,14 @@ module.exports = (bridge: Bridge, agent: Agent, hook: Object) => {
3523
if (!shouldEnable) {
3624
return;
3725
}
38-
var NetworkLayer = hook._relayInternals.NetworkLayer;
39-
40-
bridge.send('relay:store', {id: 'relay:store', nodes: hook._relayInternals.DefaultStoreData.getNodeData()});
41-
var restore = [
42-
decorate(NetworkLayer, 'sendMutation', mut => {
43-
var id = guid();
44-
bridge.send('relay:pending', [{
45-
id,
46-
type: 'mutation',
47-
start: Date.now(),
48-
text: mut.getQueryString(),
49-
variables: mut.getVariables(),
50-
name: mut.getDebugName(),
51-
}]);
52-
mut.then(
53-
response => bridge.send('relay:success', {id, response: response.response, end: Date.now()}),
54-
error => bridge.send('relay:failure', {id, error, end: Date.now()})
55-
);
56-
}),
26+
var {
27+
DefaultStoreData,
28+
setRequestListener,
29+
} = hook._relayInternals;
5730

58-
decorate(NetworkLayer, 'sendQueries', queries => {
59-
bridge.send('relay:pending', queries.map(q => {
60-
var id = guid();
61-
q.then(
62-
response => bridge.send('relay:success', {id, response: response.response, end: Date.now()}),
63-
error => bridge.send('relay:failure', {id, error, end: Date.now()})
64-
);
65-
return {
66-
id,
67-
type: 'query',
68-
start: Date.now(),
69-
text: q.getQueryString(),
70-
variables: q.getVariables(),
71-
name: q.getDebugName(),
72-
};
73-
}));
74-
}),
75-
];
76-
hook.on('shutdown', () => {
77-
restore.forEach(fn => fn());
31+
bridge.send('relay:store', {id: 'relay:store', nodes: DefaultStoreData.getNodeData()});
32+
var removeListener = setRequestListener((event, data) => {
33+
bridge.send(event, data);
7834
});
35+
hook.on('shutdown', removeListener);
7936
};

plugins/Relay/installRelayHook.js

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @flow
10+
*/
11+
'use strict';
12+
13+
/**
14+
* NOTE: This file cannot `require` any other modules. We `.toString()` the
15+
* function in some places and inject the source into the page.
16+
*/
17+
function installRelayHook(window: Object) {
18+
var hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
19+
if (!hook) {
20+
return;
21+
}
22+
23+
function decorate(obj, attr, fn) {
24+
var old = obj[attr];
25+
obj[attr] = function() {
26+
var res = old.apply(this, arguments);
27+
fn.apply(this, arguments);
28+
return res;
29+
};
30+
}
31+
32+
var _eventQueue = [];
33+
var _listener = null;
34+
function emit(name: string, data: mixed) {
35+
_eventQueue.push({name, data});
36+
if (_listener) {
37+
_listener(name, data);
38+
}
39+
}
40+
41+
function setRequestListener(
42+
listener: (name: string, data: mixed) => void
43+
): () => void {
44+
if (_listener) {
45+
throw new Error(
46+
'Relay Devtools: Called only call setRequestListener once.'
47+
);
48+
}
49+
_listener = listener;
50+
_eventQueue.forEach(({name, data}) => {
51+
listener(name, data);
52+
});
53+
54+
return () => {
55+
_listener = null;
56+
};
57+
}
58+
59+
function recordRequest(type: 'mutation' | 'query', request) {
60+
var id = Math.random().toString(16).substr(2);
61+
request.then(
62+
response => {
63+
emit('relay:success', {
64+
id: id,
65+
end: Date.now(),
66+
response: response.response,
67+
});
68+
},
69+
error => {
70+
emit('relay:failure', {
71+
id: id,
72+
end: Date.now(),
73+
error: error,
74+
});
75+
},
76+
);
77+
return {
78+
id: id,
79+
type: type,
80+
start: Date.now(),
81+
text: request.getQueryString(),
82+
variables: request.getVariables(),
83+
name: request.getDebugName(),
84+
};
85+
}
86+
87+
function instrumentRelayRequests(relayInternals: Object) {
88+
var NetworkLayer = relayInternals.NetworkLayer;
89+
90+
decorate(NetworkLayer, 'sendMutation', mutation => {
91+
emit('relay:pending', [recordRequest('mutation', mutation)]);
92+
});
93+
94+
decorate(NetworkLayer, 'sendQueries', queries => {
95+
emit('relay:pending', queries.map(query => recordRequest('query', query)));
96+
});
97+
98+
var instrumented = {};
99+
for (var key in relayInternals) {
100+
if (relayInternals.hasOwnProperty(key)) {
101+
instrumented[key] = relayInternals[key];
102+
}
103+
}
104+
instrumented.setRequestListener = setRequestListener;
105+
return instrumented;
106+
}
107+
108+
var _relayInternals = null;
109+
Object.defineProperty(hook, '_relayInternals', ({
110+
set: function(relayInternals) {
111+
_relayInternals = instrumentRelayRequests(relayInternals);
112+
},
113+
get: function() {
114+
return _relayInternals;
115+
},
116+
}: any));
117+
}
118+
119+
module.exports = installRelayHook;

shells/chrome/src/GlobalHook.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
// devtools are installed (and skip its suggestion to install the devtools).
1515

1616
var installGlobalHook = require('../../../backend/installGlobalHook.js');
17+
var installRelayHook = require('../../../plugins/Relay/installRelayHook.js');
1718

1819
var saveNativeValues = `
1920
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeObjectCreate = Object.create;
@@ -23,7 +24,9 @@ window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeSet = Set;
2324
`;
2425

2526
var js = (
26-
';(' + installGlobalHook.toString() + '(window))'
27+
';(' + installGlobalHook.toString() + '(window))' +
28+
';(' + installRelayHook.toString() + '(window))' +
29+
saveNativeValues
2730
);
2831

2932
// This script runs before the <head> element is created, so we add the script

shells/plain/container.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
var React = require('react');
1414
var installGlobalHook = require('../../backend/installGlobalHook');
15+
var installRelayHook = require('../../plugins/Relay/installRelayHook');
1516

1617
window.React = React;
1718

@@ -24,6 +25,7 @@ var devtoolsSrc = target.getAttribute('data-devtools-src') || './build/backend.j
2425

2526
var win = target.contentWindow;
2627
installGlobalHook(win);
28+
installRelayHook(win);
2729

2830
var iframeSrc = document.getElementById('iframe-src');
2931
if (iframeSrc) {

0 commit comments

Comments
 (0)