使用 GraphQL 插件
如果你在应用程序中使用 GraphQL 作为 API 的查询语言,那么 GraphQL 插件 可以帮助你跟踪针对 GraphQL 服务器执行的变更(mutations)和查询(queries)。
在本教程中,我们将使用 Apollo Boost 客户端,但只要你使用的客户端允许你设置某种中间件,你就能够在代码中使用该插件。
如果你想跟着一起操作,可以查看这个仓库,它同时包含 GraphQL 服务器和客户端应用程序。
先设置 Tracker
Section titled 先设置 Tracker在安装 GraphQL 插件之前,你需要先安装 Tracker。如果你已经知道如何操作,可以跳到下一节;否则请继续阅读。
我们将把这段代码保存在一个单独的模块中,该模块会导出两个函数:init 和 start。
第一个函数将实例化 tracker 并设置所有插件;第二个函数则只调用 start 方法。
import { tracker } from '@openreplay/tracker';
export function init({plugins}) {
tracker.configure({
projectKey: process.env.OPENREPLAY_PROJECT_KEY
});
let pluginResults = {}
if(plugins) {
Object.keys(plugins).forEach( pk => {
pluginResults[pk] = tracker.use(plugins[pk]())
})
}
return pluginResults
}
export function start() {
return tracker.start()
}
init 函数有趣的地方在于,它返回一个由所有插件返回值组成的对象。我们的某些插件会返回一个你稍后需要使用的函数(就像 GraphQL 插件的情况)。这种方式允许你一次性使用所有插件初始化 tracker,然后在任何你需要的时候使用返回的值。
通过 npm i @openreplay/tracker-graphql 安装插件后,使用以下代码来调用我们刚刚定义的 init 函数:
import trackerGraphQL from '@openreplay/tracker-graphql';
import {init} from './tracker/index'
const {graphqlTracker} = init({
plugins: {
graphqlTracker: trackerGraphQL
}
})
这里使用的 graphqlTracker 键可以是任何你想要的名称。只要在 plugins 部分中使用的键与你从 init 函数结果中解构出来的键相同,就没有问题。
使用 Apollo 客户端设置插件
Section titled 使用 Apollo 客户端设置插件在本教程中,我们将使用 Apollo Boost 库,它允许你通过他们所称的 “links” 来修改每个请求的数据流。
这些 link 就像中间件函数,你可以用它们来拦截请求的数据流,并在我们的场景中对其进行记录。
以下代码将使用 ApolloLink 函数创建一个新的 link。这个 link 将捕获操作的数据和结果,并调用我们的 graphqlTracker 函数(即上面 init 调用所返回的那个函数)。
const trackerApolloLink = new ApolloLink((operation, forward) => {
const operationDefinition = operation.query.definitions[0];
let {operationName, variables} = operation
const {kind, operation: op} = operationDefinition
const opKind = kind === 'OperationDefinition' ? op : 'unknown?'
let results = forward(operation).map((result) => {
return graphqlTracker(opKind, operationName, variables, result);
});
if(results.length === 0) { //if there are no results, then we've not tracked anything so far...
graphqlTracker(opKind, operationName, variables, {});
}
return results
});
完成上述步骤后,我们可以按如下方式使用这个新创建的 link:
import {ApolloClient, HttpLink } from 'apollo-boost';
import { ApolloProvider } from '@apollo/react-hooks';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloLink, from } from '@apollo/client';
const link = from([
trackerApolloLink,
new HttpLink({uri: () => 'http://localhost:4000/graphql'}),
]);
const client = new ApolloClient({
link,
cache: new InMemoryCache()
});
ReactDOM.render(<ApolloProvider client={client}>
<App />
</ApolloProvider>, document.getElementById('root'));
上面的代码取自 Apollo 文档,此时 tracker 和插件都已经设置完毕,所以你实际上无需再担心其他任何事情。
完成后,你的回放将显示一个新的部分,列出所有 GraphQL 操作。

话虽如此,由 tracker 自动脱敏的敏感信息(如电子邮件地址)不会被插件脱敏。因此你会遇到如下情况:DOM 中是脱敏后的数据,但操作详情中显示的却是实际数据。

虽然插件本身不提供任何脱敏函数,但我们仍然可以添加代码,将个人和私密信息从回放中隐藏起来,以帮助保护用户的隐私。
对记录的数据进行脱敏
Section titled 对记录的数据进行脱敏如果你查看我创建 trackerApolloLink 对象的那段代码示例,你会发现我所做的只是调用将信息保存到 tracker 上的 tracker 函数。
如果我不更改数据,那么所有内容都会原封不动地被保存。因此,为了在回放中对数据进行脱敏同时保持操作不变,我们需要在调用 tracker 之前克隆关键变量。这意味着克隆操作的变量和结果,而这正是我们想要的全部。
所以这里有一段代码,它将创建 ApolloLink 并在回放数据中保持数据的私密性:
/**
* Sanitize the result from a GraphQL operation
* @returns Returns the result object but with the sanitized fields changed.
*/
function sanitizeResult(res) {
//deep clonning needs to happen to make sure this only affects the new object and not
//the original object.
let sanitized = JSON.parse(JSON.stringify(res))
let ops = Object.keys(sanitized.data)
ops.forEach( o => {
if(Array.isArray(sanitized.data[o])) { //mutations don't really return arrays
sanitized.data[o] = sanitized.data[o].map( sanitizeData )
}
})
return sanitized
}
// We only want to hide the content of othe "email" field for now.
function sanitizeData(vars) {
let newVars = {...vars}
if(newVars.email) {
newVars.email = "****@***.***"
}
return newVars
}
const trackerApolloLink = new ApolloLink((operation, forward) => {
const operationDefinition = operation.query.definitions[0];
let {operationName, variables} = operation
const {kind, operation: op} = operationDefinition
const opKind = kind === 'OperationDefinition' ? op : 'unknown?'
let trackedVariables = sanitizeData({...variables})
let results = forward(operation).map((result) => {
let trackeresults = sanitizeResult(result)
graphqlTracker(opKind, operationName, trackedVariables, trackeresults);
return result //we have to return the original "result" object here, not the sanitized one
});
if(results.length === 0) { //if there are no results, then we've not tracked anything so far...
graphqlTracker(opKind, operationName, trackedVariables, {});
}
return results
});
这段代码的关键点是:
- 我们添加了两个函数,一个用于对对象中的
email进行脱敏,另一个用于对 GraphQL 操作的结果进行脱敏。 - 在
map回调(来自 link 函数)内部,我们现在不再返回 graphqlTracker 的输出,因为该函数会原封不动地返回它收到的结果值。 1. 但该结果会被返回给客户端应用程序,如果我们对结果进行了脱敏,用户将看到数据集的脱敏版本。相反,我们需要克隆该结果,以便修改被跟踪的那一份并返回原始结果。 sanitizeResult函数会对对象进行深度克隆,因为以其他方式修改它会改变结果本身。

有疑问吗?
Section titled 有疑问吗?你可以查看这个仓库,获取一个使用 Tracker 的、可运行的基于 GraphQL 的应用程序的完整源代码。
如果你在 GraphQL 项目中设置 Tracker 时遇到任何问题,请通过我们的 Slack 社区 联系我们,直接向我们的开发人员提问!