Files
rrweb/browser-extension-development-plan.md
xugp 71438691b3
Some checks failed
Tests / Tests (push) Has been cancelled
ESLint Check / ESLint Check and Report Upload (push) Has been cancelled
Prettier Check / Format Check (push) Has been cancelled
Prettier Check / Format Code (push) Has been cancelled
ESLint Check / Build Base for Bundle Size Comparison (push) Has been cancelled
feat: enhance web extension with export functionality and utility improvements
- Add export functionality to SessionList and Player pages
- Add new utility modules: dataOperations, format, path, settings
- Update manifest with export and download permissions
- Enhance storage utility with new data operations
- Add various test scripts and documentation files
2026-04-16 10:44:50 +08:00

19 KiB
Raw Blame History

rrweb 浏览器插件开发计划

📋 项目概述

基于 rrweb 开发一个功能完善的浏览器插件,支持录制用户操作、本地回放、多格式导出等功能。


🎯 开发阶段规划

阶段一基础录制功能1-2周

核心功能清单

  • 录制开始/停止
  • 基本数据收集(事件、时间戳)
  • 简单 popup UI(状态显示、控制按钮)
  • 文件保存到默认路径(自动 JSON 导出)
  • 快捷键支持Ctrl+Shift+R 开始/停止)

UI 设计(简化版)

┌─────────────────────────────────────┐
│ 📹 rrweb 录制插件                    │
├─────────────────────────────────────┤
│ 状态: [❌] 停止录制                  │
│                                     │
│ [🎬 开始录制]  [⚙️ 设置]           │
│                                     │
│ 快捷键: Ctrl+Shift+R                  │
│ 帮助 📄                            │
└─────────────────────────────────────┘

技术实现

  • Background Service 管理录制状态
  • Content Script 注入录制脚本
  • 基础的数据收集和存储
  • 简单的 popup 界面

阶段二UI 增强和回放2-3周

功能增强

  • 🎨 现代化 UI 设计
    • 使用 Tailwind CSS
    • 添加图标和动画
    • 响应式布局
  • 🎬 本地回放功能
    • 在 popup 中预览录制
    • 基本播放控制(播放/暂停/进度条)
  • 📊 录制状态显示
    • 实时事件计数
    • 录制时长显示
    • 文件大小显示
  • 🔧 设置页面
    • 基础配置选项
    • 隐私控制设置

UI 设计(增强版)

┌─────────────────────────────────────┐
│ 📹 rrweb 录制插件                    │
├─────────────────────────────────────┤
│ ● 正在录制  [00:15:23]               │
│ 事件数: 1,234  |  文件: 2.3MB      │
│                                     │
│ ┌─────────────────────────────┐     │
│ │ [🎬 实时预览]               │     │
│ │ 👆 点击、输入、滚动等操作    │     │
│ └─────────────────────────────┘     │
│                                     │
│ ┌─────────────────────────────┐     │
│ │ [▶ 播放录制]  [⏸ 暂停]     │     │
│ │ [📤 导出]  [🗑️ 删除]       │     │
│ └─────────────────────────────┘     │
│                                     │
│ 设置 ⚙️ 历史 📄 快捷键 ⌨️        │
└─────────────────────────────────────┘

技术实现

  • 现代化 UI 组件设计
  • 录制数据实时预览
  • 基础回放功能实现
  • 设置页面配置管理

阶段三数据管理和导出1-2周

核心功能

  • 💾 自定义保存路径
    • 用户选择文件夹
    • 支持自定义文件名格式
    • 路径持久化存储
  • 📤 多格式导出
    • JSON 原始数据
    • HTML 回放页面
    • ZIP 压缩包
  • 📄 录制历史管理
    • 录制列表显示
    • 详情预览
    • 批量操作
  • 🗑️ 数据清理
    • 手动删除
    • 自动清理选项

历史管理界面

┌─────────────────────────────────────┐
📄 录制历史                           │
├─────────────────────────────────────┤
│ 🔍 搜索: [_________________]        │
│                                     │
│ ┌─────────────────────────────┐     │
│ │ 🎬 product-demo.web.app    │     │
│ │ ● 02:35 | 342事件 | 1.2MB  │     │
│ │ 2024-01-15 14:30           │     │
│ │ ┌─────────────────────────┐ │     │
│ │ │ [▶ 播放] [📤 导出] [🗑️] │ │     │
│ │ └─────────────────────────┘ │     │
│ │                             │     │
│ │ 🎬 login-test.com          │     │
│ │ ● 01:15 | 156事件 | 0.8MB  │     │
│ │ 2024-01-15 10:20           │     │
│ └─────────────────────────────┘     │
│                                     │
│ [←] [1/5] [→]  清空全部 🗑️        │
└─────────────────────────────────────┘

技术实现

  • 文件路径管理系统
  • 多格式导出工具
  • 录制历史数据管理
  • 用户配置持久化

🛠️ 技术架构设计

1. 项目结构

web-extension/
├── manifest.json              # 扩展配置
├── popup/                     # 弹出窗口
│   ├── index.html            # popup HTML
│   ├── popup.css             # 样式文件
│   ├── popup.js              # popup 脚本
│   └── components/           # UI 组件
│       ├── StatusIndicator.js
│       ├── RecordingPreview.js
│       └── ControlButtons.js
├── options/                   # 设置页面
│   ├── index.html
│   ├── options.css
│   ├── options.js
│   └── settings.html
├── background/                # 后台服务
│   ├── service-worker.js
│   └── recording-manager.js
├── content/                   # 内容脚本
│   ├── inject.js             # 注入脚本
│   └── enhanced-recorder.js  # 增强录制器
├── utils/                     # 工具函数
│   ├── file-manager.js
│   ├── export-manager.js
│   └── shortcut-manager.js
├── assets/                    # 资源文件
│   ├── icons/
│   └── styles/
└── lib/                       # 第三方库
    └── rrweb-dist/

2. 核心模块实现

Background Service Worker

// background/service-worker.js
class RecordingManager {
  constructor() {
    this.recordings = new Map();
    this.currentRecording = null;
  }

  async startRecording(tabId) {
    try {
      const recorder = new EnhancedRecorder({
        tabId,
        onData: (events) => this.saveEvents(tabId, events),
        options: await this.getRecordingOptions()
      });

      this.currentRecording = {
        tabId,
        recorder,
        startTime: Date.now(),
        events: [],
        status: 'recording'
      };

      await this.saveToStorage();

      // 更新 popup 状态
      chrome.runtime.sendMessage({
        type: 'RECORDING_STARTED',
        data: { tabId }
      });

    } catch (error) {
      console.error('Start recording failed:', error);
    }
  }

  async stopRecording(tabId) {
    if (!this.currentRecording) return;

    const recording = this.currentRecording;
    recording.recorder.stop();
    recording.status = 'stopped';
    recording.endTime = Date.now();

    // 保存文件
    await this.saveRecordingFile(recording);

    this.currentRecording = null;
    await this.saveToStorage();

    chrome.runtime.sendMessage({
      type: 'RECORDING_STOPPED',
      data: { tabId }
    });
  }
}

Enhanced Recorder

// content/enhanced-recorder.js
class EnhancedRecorder {
  constructor({ tabId, onData, options }) {
    this.tabId = tabId;
    this.onData = onData;
    this.options = options;
    this.events = [];
    this.recorder = null;

    this.init();
  }

  init() {
    this.recorder = record({
      emit: (event) => {
        this.events.push(event);
        this.onData(this.events);
      },
      recordCrossOriginIframes: true,
      blockClass: 'rr-block',
      maskTextClass: 'rr-mask',
      ...this.options
    });
  }

  stop() {
    if (this.recorder) {
      this.recorder();
      this.recorder = null;
    }
  }
}

File Manager

// utils/file-manager.js
class FileManager {
  constructor() {
    this.settings = this.loadSettings();
  }

  async saveRecording(data) {
    const filename = this.generateFilename();
    const filepath = await this.getFilePath(filename);

    // 转换为 JSON
    const json = JSON.stringify({
      version: '1.0',
      timestamp: data.timestamp,
      events: data.events,
      metadata: {
        duration: data.duration,
        eventCount: data.events.length,
        url: data.url
      }
    }, null, 2);

    // 保存文件
    await this.writeFile(filepath, json);

    return {
      filename,
      filepath,
      size: json.length
    };
  }

  generateFilename() {
    const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
    return `rrweb-${timestamp}.json`;
  }

  async getFilePath(filename) {
    const savePath = this.settings.savePath || this.getDefaultPath();
    const fullPath = `${savePath}/${filename}`;

    // 确保目录存在
    await this.ensureDirectory(savePath);

    return fullPath;
  }
}

Export Manager

// utils/export-manager.js
class ExportManager {
  async exportJSON(events, filename) {
    const data = this.prepareEventData(events);
    const blob = new Blob([JSON.stringify(data, null, 2)], {
      type: 'application/json'
    });

    return this.downloadFile(blob, `${filename}.json`);
  }

  async exportHTML(events, filename) {
    const html = this.generateReplayHTML(events);
    const blob = new Blob([html], { type: 'text/html' });

    return this.downloadFile(blob, `${filename}.html`);
  }

  async exportZIP(events, filename) {
    const JSZip = await import('jszip');
    const zip = new JSZip();

    // 添加 JSON 数据
    const data = this.prepareEventData(events);
    zip.file('recording.json', JSON.stringify(data, null, 2));

    // 添加回放 HTML
    zip.file('replay.html', this.generateReplayHTML(events));

    // 添加 README
    zip.file('README.md', this.generateReadme(events));

    const content = await zip.generateAsync({ type: 'blob' });
    return this.downloadFile(content, `${filename}.zip`);
  }

  generateReplayHTML(events) {
    return `
<!DOCTYPE html>
<html>
<head>
    <title>rrweb 回放</title>
    <script src="https://unpkg.com/rrweb@latest/dist/rrweb.umd.cjs"></script>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        #replayer { width: 100%; height: 600px; border: 1px solid #ccc; }
    </style>
</head>
<body>
    <h1>rrweb 录制回放</h1>
    <div id="replayer"></div>
    <script>
        const replayer = new rrweb.Replayer(${JSON.stringify(events)});
        replayer.play();
    </script>
</body>
</html>`;
  }
}

3. UI 组件设计

Popup Main Component

<!-- popup/index.html -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>rrweb 录制插件</title>
    <script src="popup.js" type="module"></script>
    <link rel="stylesheet" href="popup.css">
</head>
<body>
    <div class="app">
        <!-- 状态栏 -->
        <div class="status-bar" id="statusBar">
            <div class="status-indicator" id="statusIndicator"></div>
            <span id="statusText">停止录制</span>
            <span id="statsText"></span>
        </div>

        <!-- 预览区域 -->
        <div class="preview-section" id="previewSection">
            <div class="preview-container">
                <iframe id="previewFrame" sandbox="allow-same-origin"></iframe>
            </div>
        </div>

        <!-- 控制按钮 -->
        <div class="controls">
            <button id="recordBtn" class="btn btn-primary">
                <span class="btn-icon">🎬</span>
                <span class="btn-text">开始录制</span>
            </button>
            <button id="playBtn" class="btn btn-secondary" disabled>
                <span class="btn-icon"></span>
                <span class="btn-text">播放</span>
            </button>
            <button id="exportBtn" class="btn btn-secondary" disabled>
                <span class="btn-icon">📤</span>
                <span class="btn-text">导出</span>
            </button>
            <button id="deleteBtn" class="btn btn-danger" disabled>
                <span class="btn-icon">🗑️</span>
                <span class="btn-text">删除</span>
            </button>
        </div>

        <!-- 底部导航 -->
        <div class="nav-bar">
            <button class="nav-item active" data-page="main">
                <span class="nav-icon">📹</span>
                <span class="nav-text">主页</span>
            </button>
            <button class="nav-item" data-page="history">
                <span class="nav-icon">📄</span>
                <span class="nav-text">历史</span>
            </button>
            <button class="nav-item" data-page="settings">
                <span class="nav-icon">⚙️</span>
                <span class="nav-text">设置</span>
            </button>
            <button class="nav-item" data-page="help">
                <span class="nav-icon"></span>
                <span class="nav-text">帮助</span>
            </button>
        </div>
    </div>
</body>
</html>

CSS 样式

/* popup.css */
@import 'https://cdn.tailwindcss.com';

:root {
  --primary-color: #3B82F6;
  --success-color: #10B981;
  --danger-color: #EF4444;
}

.app {
  width: 400px;
  height: 600px;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

.status-bar {
  display: flex;
  align-items: center;
  padding: 12px;
  background: #F3F4F6;
  border-bottom: 1px solid #E5E7EB;
}

.status-indicator {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-right: 8px;
  background: #9CA3AF;
}

.status-indicator.recording {
  background: #EF4444;
  animation: pulse 2s infinite;
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}

.btn {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 20px;
  border: none;
  border-radius: 8px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s;
}

.btn-primary {
  background: var(--primary-color);
  color: white;
}

.btn-primary:hover {
  background: #2563EB;
  transform: translateY(-1px);
}

.btn-secondary {
  background: #6B7280;
  color: white;
}

.btn-secondary:hover {
  background: #4B5563;
}

.btn-danger {
  background: var(--danger-color);
  color: white;
}

.btn-danger:hover {
  background: #DC2626;
}

.nav-bar {
  display: flex;
  border-top: 1px solid #E5E7EB;
}

.nav-item {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 12px;
  background: white;
  border: none;
  cursor: pointer;
  transition: background 0.2s;
}

.nav-item.active {
  background: #F9FAFB;
  color: var(--primary-color);
}

.nav-item:hover {
  background: #F3F4F6;
}

📊 开发时间估算

阶段 主要任务 预估时间 优先级
阶段一 基础录制功能 1-2周 🏆
录制核心逻辑 3天
基础 UI 2天
快捷键 1天
文件保存 2天
阶段二 UI 增强和回放 2-3周 🥈
UI 设计优化 4天
本地回放 5天
状态管理 3天
设置页面 3天
阶段三 数据管理和导出 1-2周 🥉
自定义路径 3天
多格式导出 4天
历史管理 3天
数据清理 2天

🎯 功能特性清单

基础录制功能

  • 开始/停止录制
  • 实时状态显示
  • 事件计数
  • 录制时长
  • 快捷键支持

UI 增强功能

  • 现代化界面设计
  • 实时预览窗口
  • 录制控制按钮
  • 状态指示器
  • 响应式布局

回放功能

  • 本地回放
  • 播放/暂停控制
  • 进度条
  • 速度调节

数据管理

  • 自定义保存路径
  • 文件命名规则
  • 录制历史
  • 搜索功能
  • 批量操作

导出功能

  • JSON 格式导出
  • HTML 回放页面
  • ZIP 压缩包
  • 批量导出
  • 云端同步(后续版本)

隐私保护

  • 敏感信息遮罩
  • 自定义屏蔽规则
  • 网站白名单
  • 数据加密(后续版本)

🛠️ 技术栈

组件 技术选择 理由
UI Framework Tailwind CSS + Alpine.js 快速开发,美观
State Management Zustand/Redux 轻量级状态管理
File System File System API 原生文件访问
Storage IndexedDB + Chrome Storage 大数据存储
Icons Lucide Icons 现代化图标库
Charts Chart.js 数据可视化

🚀 实施计划

第一步:评估现有扩展

  • 测试当前 rrweb web-extension 功能
  • 确认录制质量是否满足需求
  • 分析需要改进的地方

第二步:阶段一实施

  • 实现基础录制功能
  • 创建简单的 popup UI
  • 添加文件保存功能
  • 实现快捷键支持

第三步:阶段二实施

  • UI 设计优化
  • 实现本地回放
  • 添加设置页面
  • 完善状态管理

第四步:阶段三实施

  • 实现自定义保存路径
  • 开发多格式导出
  • 创建历史管理界面
  • 添加数据清理功能

📝 注意事项

  1. 浏览器兼容性

    • 支持 Chrome 88+
    • 支持 Firefox 75+
    • 避免使用不兼容的 API
  2. 性能考虑

    • 大文件处理优化
    • 内存管理
    • 录制性能监控
  3. 用户隐私

    • 明确的录制提示
    • 敏感信息保护
    • 数据安全存储
  4. 用户体验

    • 流畅的界面交互
    • 清晰的状态反馈
    • 易用的操作流程

🎉 总结

本计划提供了 rrweb 浏览器插件开发的完整路线图,从基础录制功能到高级的数据管理和导出功能。通过分阶段实施,可以确保每个阶段都有明确的交付成果,同时保持开发的灵活性。

主要特点:

  • 功能完整,覆盖录制、回放、管理、导出
  • 现代化 UI 设计,用户体验良好
  • 技术方案成熟,可扩展性强
  • 分阶段实施,风险可控

预期成果: 一个功能完善、界面美观、易于使用的浏览器录制插件。