PageSpy 实现原理

在专栏的前几篇文章中,我们从 "是什么、为什么、怎么用" 三个角度分别介绍了 PageSpy,大家对这款产品也有了初步的印象。

接下来我会再分享 PageSpy 的内部实现,对实现原理好奇的同学可以阅读这篇文章。

三个仓库

PageSpy 主要由三个仓库组成:

仓库职能

SDK 做了什么

下面这些行为在实际开发中非常常见:

js 复制代码
// 输出日志
console.log(res.data.users)

// 发起请求
fetch("https://example.com/todos")

// 更新缓存
localStorage.setItem("foo", "foo")

在客户端调用上面这些原生方法后,大家可以看到 PageSpy 会以控制台级别的 UI 风格展示输出。首先可以明确的是,PageSpy 调试端展示的数据来源于 SDK 告诉它的,但 SDK 是如何做到的呢?

以 console.log(res.data.users) 为例,SDK 大致如下处理:

typescript 复制代码
// SDK 通过插件的形式组织能力
export default class ConsolePlugin implements PageSpyPlugin {
  
  public async onCreated() {
    const type: SpyConsole.ProxyType[] = ['log', 'info', 'error', 'warn'];
    type.forEach((item) => {
      this.console[item] = window.console[item];
      // 对原生方法进行包装(重写)
      window.console[item] = (...args: any[]) => {
        this.printLog({
          logType: item,
          logs: args,
          url: window.location.href,
        });
      };
    });
  }
​
  private printLog(data: SpyConsole.DataItem) {
    if (data.logs && data.logs.length) {
      // 调用原生方法,以保证行为预期
      this.console[data.logType](...data.logs);
      // 对参数进行整理,随后发送到调试端
      socketStore.broadcastMessage(log);
    }
  }
}

可以看到我们只是对原生方法进行了简单的包装,整个过程中没有银弹。上面提到 SDK 通过插件的形式组织能力,除了 ConsolePlugin 之外,这里列出当前包含的所有插件:

  • plugins/console:包装 console.<log | info | warn | error>,处理日志信息的输出;

    为什么只代理这四个?因为最常用。

  • plugins/network:包装 fetch | XMLHttpRequest | navigator.sendBeacon 的网络请求;

  • plugins/system:检查 ES5 之后的标准 API 在客户端环境的兼容性;

    SDK 本身的兼容性配置参考:["chrome > 75", "safari > 12", "> 0.1%", "not dead", "not op_mini all"]

  • plugins/database:处理 window.indexedDB 的数据;

  • plugins/storage:包装 localStorage | sessionStorage | cookie 获取缓存数据;

  • plugins/error:处理 onerror | 资源加载错误 | unhandledrejection 报错事件;

  • plugins/page:通过 document.documentElement.outerHTML 获取页面结构数据;

除了以上内容之外,SDK 还有「事件调试」的能力。所谓「事件调试」,即对调试端发送过来的信息做出响应,例如:

  • 调试端 "上线" 了,SDK 会将缓存的信息全部推给调试端;
  • 调试端在「Console」面板输入代码并执行,比如打印 window 信息;
  • 调试端在「Page」面板点击 "刷新" 功能按钮;
  • 调试端在「Storage - indexedDB」面板点击上下翻页查询数据;
  • ......等等事件

我们没有将事件调试的能力做成插件的形式,是出于两方面的原因:

  1. 它强依赖于 Socket 的实例;
  2. 事件响应的行为都是由各个插件去主张,也就是各个插件去监听由调试端发送过来和自身相关的事件,然后执行响应的逻辑;

即便如此,「事件调试」所具备的能力并不亚于插件。

另外这里再起个话题,PageSpy 的 「Console」面板支持打印 window,但是大家都知道 window 是自引用对象,它的 window.self / window.globalThis / window.window 指向自身,简单的 JSON.stringify(window) 肯定行不通,对于这种情况 PageSpy 是如何考虑、处理的呢?敬请关注下篇文章:《打印 window 对象,PageSpy 是如何处理的?》

SDK 对上下文的感知

在我们成功部署 PageSpy 后,第一步都是在客户端引入 SDK 文件,假设你将 PageSpy 部署在 https://example.com,那么会有:

html 复制代码
<script crossorigin="anonymous" src="https://example.com/page-spy/index.min.js"></script>

随后就是对 PageSpy 实例化:

html 复制代码
<script>
  window.$pageSpy = new PageSpy();
</script>

一般在实例化时无需指定 api 和 clientOrigin 参数,这是因为默认情况下,PageSpy 会通过引入 SDK 的路径,在这里是 https://example.com/page-spy/index.min.js,去分析并决定 Server 的地址和调试端的地址:

  • Server 地址用于向服务端发起请求,例如请求创建房间、建立 WebSocket 连接等;
  • 调试端地址,当客户端引入 PageSpy 后左下角会出现图标控件,点击后会出现带有连接信息的弹窗,上面的 "Copy" 按钮会直接生成调试端的链接并复制;

那么内部的实现逻辑如下:

typescript 复制代码
// 获取引入地址,在这里是 https://example.com/page-spy/index.min.js
const scriptLink = (document.currentScript as HTMLScriptElement)?.src;

const resolveConfig = () => {
  if (!scriptLink) {
    return null;
  }
  const { host, origin } = new URL(scriptLink);
  return {
    api: host, // 在这里是 example.com
    clientOrigin: origin, // 在这里是 https://example.com
    project: 'default',
    autoRender: true,
    title: '',
  };
};

服务端做了什么

Server 在整个产品体系中,主要提供了以下接口能力:

  • /api/v1/room/create:供客户端的 SDK 创建房间;
  • /api/v1/room/list:供调试端查询所有的房间(调试连接);
  • /api/v1/ws/room/join:供 SDK、调试端两端建立 WebSocket 连接;

除了对外暴露的接口外,Server 还会:

  • 当判断调试的房间无效时,会在一段时间后自动销毁房间,比如 SDK 断开连接;
  • 在两端的 ws 消息交互过程中,根据消息的类型,决定消息是单播或者广播;

调试端做了什么

调试端的职责就很明显了:充分利用 SDK 提供过来的信息,提供一套交互友好的调试界面,因为各位开发者们在使用 PageSpy 的整个时间周期内,几乎面对的都是调试端。

交互图例

数据安全

在产品开源后,我们了解到很多开发者对于产品是否会收集数据的担忧。在此我们向大家郑重承诺:

PageSpy 不会主动收集任何用户数据。

相关推荐
钱栈up6 小时前
GitHub 热榜上手|AnyPS5:44k star 免模拟转译 PS5
github
怕浪猫6 小时前
Claude Code vs Codex:Computer Use 实现原理深度拆解
算法·面试·github
冬奇Lab6 小时前
一天一个开源项目(第248篇):CleanYourTV —— 让 AI 编程助手通过 Wi-Fi 给安卓电视去广告、换上 Apple TV 风格主屏
开源·资讯
wflynn7 小时前
GitHub 今日推荐|gpu-unlock-ada:Ada 显卡张量算力翻倍解锁
开源·github·c
SL_staff9 小时前
JVS私有化文档系统上线避坑指南:5大运维与业务协同断点及检测加固实践
java·开源·全栈
漫步的猪11 小时前
# 告别混乱与付费墙!Linkit:完全免费、本地优先的开源 AI 智能书签与收藏夹管理神器
人工智能·ai·开源·react·免费·书签·收藏夹
徐小夕11 小时前
doc2docx方案,开源!
前端·vue.js·github
徐小夕11 小时前
JitOFD:一款面向信创与档案场景的 Web 端 OFD 编辑器
前端·vue.js·github
miofly11 小时前
NVIDIA RTX Spark 编码代理性能与本地 AI 开发机配置
开源·github
wflynn11 小时前
GitHub 周榜趋势速报 | 2026-10-25
开源·github