如何构建你的第一个 OpenReplay 插件

学习如何创建你自己的 OpenReplay 插件

如何构建你的第一个 OpenReplay 插件

如果你自托管(self-hosting)你自己的 OpenReplay 版本,你将获得一个额外的好处,即能够开发和安装自定义插件。这将让你添加 OpenReplay 开箱即用所不提供的功能和兼容性。

目前,已经有一些由 OpenReplay 团队开发和维护的插件,例如 Redux 插件,它可以让你跟踪状态变化,或者 Fetch 插件,它让你能够记录 Request 和 Response 数据。如果你想了解更多信息,可以在此查看完整的插件列表

在本教程中,你将学习如何构建你自己的插件。具体来说,我们将构建一个用于跟踪 JQuery 的 GET 方法的插件。

我们建议你从复制粘贴一个功能与你想要实现的功能类似的插件文件夹开始,这样你就能拥有所有现成的逻辑和设置代码。

话虽如此,由于我们将在这里构建的插件其行为与 Fetch 插件的行为类似,我们将复制该插件。

即便如此,你仍然需要做一些事情,比如:

  • 修改后端。
  • 向消息协议添加新消息。
  • 从源代码构建一些后端服务。

所以,如果你还没有经历过从源代码构建 OpenReplay 的过程,我建议你先用当前版本的 OpenReplay 走一遍这个过程,因为在测试你的新代码时你会多次执行它。

另一方面,如果你已经掌握了这一部分,请继续阅读!

构建一个新插件需要什么?

Section titled 构建一个新插件需要什么?

构建一个新插件分为三个部分:

  1. 首先,你必须定义你的插件将发送到平台的消息类型。该消息需要包含你想要跟踪的所有遥测数据。
  2. 然后,你必须更新前端,以创建一个能够显示来自插件数据的组件。在我们的例子中,我们将在回放器上添加一个选项卡,列出所有使用 JQuery 发出的请求。
  3. 最后,你还必须创建插件!记住,这才是我们的目的所在。插件是这一切中唯一一个你可以在 OpenReplay 之外开发、并安装到其他地方(你的应用)的部分。

请注意,本教程的其余部分将假设你已经 fork 了我们的 Github 仓库,并已将其在某处准备好且正常运行。如果你还没有,请先查阅我们文档的部署部分

第 1 步:构建你的新消息

Section titled 第 1 步:构建你的新消息

首先要做的是弄清楚你想用你的插件捕获和展示哪些数据。在我们的例子中,由于我们要构建用于跟踪 GET 请求的东西,我们应该确保捕获以下项目:

  • URL:显然,就是请求的 URL
  • Response:我们也会尝试捕获一些响应内容
  • Status:从服务器收到的状态码

我们还将添加一个 “duration” 属性,用于跟踪执行请求并获取一些数据所花费的时间,为此我们需要请求的 “timestamp”。

为此,我们必须修改 mob/messages.rb 文件。这是一个 Ruby 文件,但如果你不懂 Ruby 也不必担心,你所要做的就是添加一条描述你新消息的新记录,类似这样:

message 112, 'JQueryGET' do
  string 'Method'
  string 'url'
  string 'response'
  string 'status'
  string 'duration'
  int  'timestamp'
end

请注意,我还定义了:

  • 一个消息 ID(112),这是一个尚未被使用(你可以查看文件中的其他消息)且小于 200 的随机数。
  • 一个消息名称,这完全由你决定,只要确保它能描述你的消息类型即可。
  • 一个 “method” 字段,这样我们将来就有可能将该消息重用于其他方法。
  • 属性的类型为 “string” 和 “int”,这是因为我们的编码器会将数组和对象转换为字符串。

完成这些之后,你需要确保该消息可以从任何地方访问,因此你将在此文件夹内运行 2 个脚本:

$ ruby run.rb
$ sh format.sh

一旦完成,该消息及其 ID 将被复制并添加到后端所有需要它的地方。

现在让我们进入前端,看看我们需要更改什么。

消息准备好之后,你需要更新 messageDistributor 函数(位于 frontend/app/player/MessageDistributor/MessageDistributor.ts 文件中),它接收所有消息并决定如何处理它们。

在该函数内部,你会看到一个很大的 switch 语句,以及一个已经为你的消息添加好的新 case(你会注意到所使用的名称与你使用的名称大小写不同)。在本示例中,对于消息类型 “JQueryGET”,你将得到一个 “j_query_get” 类型。

现在你需要将该消息添加到一个列表中,前端组件将使用这些列表。你将使用 listAppend 函数来完成此操作。

该 case 应如下所示:

case 'j_query_get': 
    listAppend("jquery", Resource({
      method: msg.method,
      url: msg.url,
      payload: {},
      response: msg.response,
      status: msg.status,
      type: TYPES.JQUERY,
      time: msg.timestamp - this.sessionStart,
      duration: msg.duration,
      index
    }))
  break;

现在,为了让它正常工作,我们还有几件事需要做:

  • 我们必须添加 “jquery” 列表,为此,我们将修改文件 frontend/app/player/lists/index.js,并将 “jquery” 添加到 entityNamesWithRed 数组中。
  • 我们必须添加 TYPES.JQUERY,你可以通过修改此文件来完成:frontend/app/types/session/resource.js

完成后,我们将创建在播放器内展示数据的实际组件。类似这样:

最终效果

为此,我们将依托一个现有组件,并复制粘贴 Fetch 组件。为此,我们将复制文件夹 frontend/app/components/Session_/Fetch 并将其命名为 “JQuery”。

我们将更新新创建文件夹内的每一处对 fetch 的引用,以确保它显示为 “jquery”(在这里使用查找和替换可能比较合理)。

这将创建可视化组件,然后你必须手动添加它。首先,你需要编辑 frontend/app/components/Session_/Player/Controls/Controls.js 文件,以在屏幕右下角(显示所有控件的地方)添加 “JQuery” 按钮。

找到 JSX 代码,并添加一个类似这样的代码块:

{showJQuery && (
  <ControlButton
    disabled={disabled && !inspectorMode}
    onClick={() => toggleBottomTools(JQUERY)}
    active={bottomBlock === JQUERY && !inspectorMode}
    hasErrors={jqueryRedCount > 0}
    count={jqueryCount}
    label="JQUERY"
    noIcon
    labelClassName="!text-base font-semibold"
    containerClassName="mx-2"
  />
)}

顺便一提,请确保你定义了所有相关变量,例如 showJQueryjqueryCountJQUERYjqueryRedCount。如果你不确定如何做,搜索它们对应的 fetch 版本,你很快就能找到它们。

添加按钮后,你现在可以编辑 frontend/app/components/Session_/Player/Player.js 文件并添加实际的组件。

为此,在导入它之后,请确保在 JSX 部分内添加如下代码块:

{ bottomBlock === JQUERY &&
  <JQuery />
}

作为其中的一部分,你还必须添加 JQUERY 常量,使其从 frontend/app/duck/components/player.js 文件中导出。

这样一切就都处理好了。

现在你的前端已经准备好显示信息了,我们需要找到一种生成它的方法。那么让我们来看看插件的实际代码吧!

第 3 步:构建你的插件

Section titled 第 3 步:构建你的插件

你可以在任何你想要的地方创建这个项目。重点是提供一个可安装的包,以便在其他项目中使用。

所以请确保 package.json 文件如下所示:

{
  "name": "tracker-jquery",
  "version": "1.0.0",
  "description": "jquery openreplay tracker",
  "main": "src/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "peerDependencies": {
    "@openreplay/tracker": ">=3.6.0"
  },
  "devDependencies": {
    "@openreplay/tracker": "<latest version of the tracker>",
    "prettier": "^1.18.2",
    "replace-in-files-cli": "^1.0.0"
  }
}

然后我们将在项目的 src 文件夹内添加一个 index.js 文件。只要你导出的函数遵循某些预定义的标准,你就可以随意以你认为合适的方式来组织这个项目。

插件的结构很简单:你定义一个函数,当它被调用时,返回一个新函数,该新函数期望传入一个 app 属性。这个 app 属性包含 tracker 的实例。在返回的函数内部,只要你通过 app 对象发送一条消息,你就可以自由地做任何你想做的事。该消息将被平台接收,并根据一组规则进行处理。

消息格式将是我们在本教程开始时定义的那个。

让我向你展示一下我们的 JQuery 插件的代码是什么样的:

import { App, Messages } from '@openreplay/tracker';

export default function($) {

    const oldGET = $.get;

    return (app) => {

        const newGET = async (settingsObj) => {

            const startTime = performance.now();
            let resp = await fetch(settingsObj.url, {
                method: 'GET'
            })
            const duration = performance.now() - startTime;

            let valueResp = null;

            if(settingsObj.json) {
                valueResp = await resp.json()
            } else {
                valueResp = await resp.text()
            }
            const getStj = (res) => {
                let r = {...res}
                if (r && typeof r.body !== 'string') {
	                try {
	                    r.body = JSON.stringify(r.body)
	                } catch {
	                    r.body = "<unable to stringify>"
	                }
                }
                return JSON.stringify(r)
            }

            const msg = Messages.JQueryGET(
                    'GET',
                    String(settingsObj.url),
                    getStj(resp),
                    resp.status,
                    duration,
                    (new Date()).getTime()
                )
            console.log("Sending a message to the tracker....", msg)

            app.send(msg, true)   
            return valueResp;

        }

        $.get = newGET;

    }
}

本质上,我所做的就是用一个自定义方法替换 JQuery 的 .get 方法,这个自定义方法实际上在幕后使用了 fetch(这完全是可选的,你也可以使用 JQuery 的 GET 方法)。在所有请求执行完毕后,代码使用 app.send 方法发送一条消息。该消息是通过 Messages.JQueryGET 方法创建的,该方法是之前的 Ruby 脚本为我们创建的。

这条消息接收的属性,显然就是我们在第 1 步中定义为消息本身结构的那些。而最重要的是,它们都是必填的;如果你不得不处理一个空值,那么就传入空数组/对象/字符串/任何东西,但绝不要跳过键

一旦你完成本地测试并确信插件已经准备就绪,你就需要将其发布到 NPM。

按照本指南来了解如何做到这一点:https://docs.npmjs.com/creating-and-publishing-unscoped-public-packages

一旦发布,你就会想到处使用你的插件。在 OpenReplay tracker 中使用插件的机制始终是一样的。

  1. 首先,你必须实例化 tracker。
  2. 然后,你必须调用 use 方法,并将调用你的插件所得到的函数传递给它。
  3. 最后,使用 start 方法启动 tracker。

下面是一段代码示例,展示了在 React 应用中使用此插件是什么样子的:

import logo from './logo.svg';
import './App.css';
import tracker from 'openreplay-tracker'
import jqueryTracker from 'tracker-jquery'
import { useEffect } from 'react';
import $ from 'jquery'

const t = new tracker({
  __DISABLE_SECURE_MODE: true, //only if you're testing locally
  ingestPoint: "openreplay.<your custom domain>/ingest",
  projectKey: "<your project key>",
})

function App() {
  useEffect(()=> {
    t.use(jqueryTracker($))
    t.start()
    async function doGet() {
      let resp = await $.get({
        url: "http://localhost:3000"
      })
      console.log(resp)
    }

    doGet()
  }, [])
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

代码在页面加载后使用 Jquery 的 get 方法执行了一个 GET 请求,由于我们已经设置好了我们这个巧妙的小 tracker,我们实际上能够跟踪该请求。

下面是你应该看到的最终效果:

显示 JQuery 请求


你可以查看此仓库,获取本教程的完整源代码

如果你在本流程的任何步骤中遇到任何问题,请通过我们的 Slack 社区联系我们,直接向我们的开发人员提问!