Add SuperRPA integration guide, simple extension and standalone replay page
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
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
- docs/integration/superrpa-integration.zh_CN.md: complete integration guide - rrweb-simple-ext/: minimal Chrome extension for page recording - replay.html: standalone drag-and-drop replay viewer - CLAUDE.md: project instructions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
25
rrweb-simple-ext/background.js
Normal file
25
rrweb-simple-ext/background.js
Normal file
@@ -0,0 +1,25 @@
|
||||
// background.js - service worker
|
||||
const events = [];
|
||||
let recording = false;
|
||||
let activeTabId = null;
|
||||
|
||||
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||
if (msg.action === 'startRecording') {
|
||||
events.length = 0;
|
||||
recording = true;
|
||||
activeTabId = msg.tabId;
|
||||
chrome.tabs.sendMessage(msg.tabId, { action: 'startRecord' });
|
||||
sendResponse({ ok: true });
|
||||
} else if (msg.action === 'stopRecording') {
|
||||
recording = false;
|
||||
chrome.tabs.sendMessage(activeTabId, { action: 'stopRecord' });
|
||||
sendResponse({ ok: true, eventCount: events.length });
|
||||
} else if (msg.action === 'getEvents') {
|
||||
sendResponse({ events: events });
|
||||
} else if (msg.action === 'event') {
|
||||
events.push(msg.data);
|
||||
} else if (msg.action === 'getStatus') {
|
||||
sendResponse({ recording, eventCount: events.length });
|
||||
}
|
||||
return true;
|
||||
});
|
||||
46
rrweb-simple-ext/content.js
Normal file
46
rrweb-simple-ext/content.js
Normal file
@@ -0,0 +1,46 @@
|
||||
// content.js - content script (runs in content script world)
|
||||
(function() {
|
||||
// Inject the record library + inject script into page
|
||||
function loadScripts(callback) {
|
||||
// Load record.umd.cjs first
|
||||
const script1 = document.createElement('script');
|
||||
script1.src = chrome.runtime.getURL('record.umd.cjs');
|
||||
script1.onload = function() {
|
||||
script1.remove();
|
||||
// Then load inject.js
|
||||
const script2 = document.createElement('script');
|
||||
script2.src = chrome.runtime.getURL('inject.js');
|
||||
script2.onload = function() {
|
||||
script2.remove();
|
||||
};
|
||||
(document.head || document.documentElement).appendChild(script2);
|
||||
};
|
||||
(document.head || document.documentElement).appendChild(script1);
|
||||
}
|
||||
|
||||
// Listen for messages from inject.js (page context)
|
||||
window.addEventListener('message', function(event) {
|
||||
if (event.source !== window) return;
|
||||
const data = event.data;
|
||||
if (!data || !data.__rrweb_action) return;
|
||||
|
||||
if (data.__rrweb_action === 'event') {
|
||||
// Forward event to background
|
||||
chrome.runtime.sendMessage({ action: 'event', data: data.__rrweb_data });
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for messages from background
|
||||
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
|
||||
if (msg.action === 'startRecord') {
|
||||
loadScripts();
|
||||
sendResponse({ ok: true });
|
||||
} else if (msg.action === 'stopRecord') {
|
||||
window.postMessage({ __rrweb_action: 'stop' }, '*');
|
||||
sendResponse({ ok: true });
|
||||
}
|
||||
// Don't return true for unrelated messages
|
||||
});
|
||||
|
||||
console.log('[rrweb] Content script loaded');
|
||||
})();
|
||||
30
rrweb-simple-ext/inject.js
Normal file
30
rrweb-simple-ext/inject.js
Normal file
@@ -0,0 +1,30 @@
|
||||
// inject.js - runs in page context, auto-starts recording
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
let stopFn = null;
|
||||
|
||||
// Auto-start recording
|
||||
stopFn = rrwebRecord.record({
|
||||
emit: function(event) {
|
||||
window.postMessage({
|
||||
__rrweb_action: 'event',
|
||||
__rrweb_data: event
|
||||
}, '*');
|
||||
},
|
||||
recordCrossOriginIframes: true
|
||||
});
|
||||
|
||||
// Listen for stop command
|
||||
window.addEventListener('message', function(event) {
|
||||
if (event.source !== window) return;
|
||||
if (event.data && event.data.__rrweb_action === 'stop') {
|
||||
if (stopFn) {
|
||||
stopFn();
|
||||
stopFn = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log('[rrweb] Recording started');
|
||||
})();
|
||||
20
rrweb-simple-ext/manifest.json
Normal file
20
rrweb-simple-ext/manifest.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "rrweb Simple Recorder",
|
||||
"version": "1.0.0",
|
||||
"description": "Simple rrweb page recorder",
|
||||
"permissions": ["activeTab", "storage", "scripting"],
|
||||
"background": {
|
||||
"service_worker": "background.js"
|
||||
},
|
||||
"action": {
|
||||
"default_title": "rrweb Recorder",
|
||||
"default_popup": "popup.html"
|
||||
},
|
||||
"web_accessible_resources": [
|
||||
{
|
||||
"resources": ["record.umd.cjs", "inject.js"],
|
||||
"matches": ["<all_urls>"]
|
||||
}
|
||||
]
|
||||
}
|
||||
34
rrweb-simple-ext/popup.html
Normal file
34
rrweb-simple-ext/popup.html
Normal file
@@ -0,0 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body { width: 300px; padding: 16px; font-family: sans-serif; }
|
||||
button { padding: 8px 16px; margin: 4px; cursor: pointer; border: none; border-radius: 4px; color: white; }
|
||||
.start { background: #4CAF50; }
|
||||
.start:hover { background: #45a049; }
|
||||
.stop { background: #f44336; }
|
||||
.stop:hover { background: #da190b; }
|
||||
.view { background: #2196F3; }
|
||||
.view:hover { background: #0b7dda; }
|
||||
#status { margin-top: 12px; padding: 8px; background: #f5f5f5; border-radius: 4px; font-size: 13px; }
|
||||
#events { margin-top: 8px; max-height: 200px; overflow-y: auto; font-size: 11px; white-space: pre-wrap; word-break: break-all; }
|
||||
.disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h3>rrweb Simple Recorder</h3>
|
||||
<div>
|
||||
<button id="startBtn" class="start">Start Recording</button>
|
||||
<button id="stopBtn" class="stop disabled" disabled>Stop Recording</button>
|
||||
</div>
|
||||
<div id="status">Ready</div>
|
||||
<div>
|
||||
<button id="viewBtn" class="view disabled" disabled>View Events</button>
|
||||
<button id="downloadBtn" class="view disabled" disabled>Download JSON</button>
|
||||
</div>
|
||||
<div id="events"></div>
|
||||
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
93
rrweb-simple-ext/popup.js
Normal file
93
rrweb-simple-ext/popup.js
Normal file
@@ -0,0 +1,93 @@
|
||||
const startBtn = document.getElementById('startBtn');
|
||||
const stopBtn = document.getElementById('stopBtn');
|
||||
const viewBtn = document.getElementById('viewBtn');
|
||||
const downloadBtn = document.getElementById('downloadBtn');
|
||||
const statusDiv = document.getElementById('status');
|
||||
const eventsDiv = document.getElementById('events');
|
||||
|
||||
function updateStatus(text) {
|
||||
statusDiv.textContent = text;
|
||||
}
|
||||
|
||||
function setRecording(isRecording) {
|
||||
startBtn.disabled = isRecording;
|
||||
startBtn.classList.toggle('disabled', isRecording);
|
||||
stopBtn.disabled = !isRecording;
|
||||
stopBtn.classList.toggle('disabled', !isRecording);
|
||||
}
|
||||
|
||||
startBtn.addEventListener('click', async () => {
|
||||
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||
if (!tab) return;
|
||||
|
||||
// First inject content script programmatically
|
||||
try {
|
||||
await chrome.scripting.executeScript({
|
||||
target: { tabId: tab.id },
|
||||
files: ['content.js']
|
||||
});
|
||||
} catch(e) {
|
||||
// Might already be injected, that's ok
|
||||
}
|
||||
|
||||
chrome.runtime.sendMessage({ action: 'startRecording', tabId: tab.id }, (resp) => {
|
||||
if (resp && resp.ok) {
|
||||
setRecording(true);
|
||||
updateStatus('Recording...');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
stopBtn.addEventListener('click', () => {
|
||||
chrome.runtime.sendMessage({ action: 'stopRecording' }, (resp) => {
|
||||
if (resp && resp.ok) {
|
||||
setRecording(false);
|
||||
updateStatus('Stopped. Captured ' + resp.eventCount + ' events.');
|
||||
viewBtn.disabled = false;
|
||||
viewBtn.classList.remove('disabled');
|
||||
downloadBtn.disabled = false;
|
||||
downloadBtn.classList.remove('disabled');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
viewBtn.addEventListener('click', () => {
|
||||
chrome.runtime.sendMessage({ action: 'getEvents' }, (resp) => {
|
||||
if (resp && resp.events) {
|
||||
const summary = resp.events.map((e, i) =>
|
||||
`${i}: type=${e.type} ts=${e.timestamp}`
|
||||
).join('\n');
|
||||
eventsDiv.textContent = `Total: ${resp.events.length} events\n\n${summary}`;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
downloadBtn.addEventListener('click', () => {
|
||||
chrome.runtime.sendMessage({ action: 'getEvents' }, (resp) => {
|
||||
if (resp && resp.events) {
|
||||
const blob = new Blob([JSON.stringify(resp.events, null, 2)], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'rrweb-events.json';
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Check current status on open
|
||||
chrome.runtime.sendMessage({ action: 'getStatus' }, (resp) => {
|
||||
if (resp) {
|
||||
setRecording(resp.recording);
|
||||
updateStatus(resp.recording
|
||||
? 'Recording... (' + resp.eventCount + ' events)'
|
||||
: 'Ready');
|
||||
if (resp.eventCount > 0) {
|
||||
viewBtn.disabled = false;
|
||||
viewBtn.classList.remove('disabled');
|
||||
downloadBtn.disabled = false;
|
||||
downloadBtn.classList.remove('disabled');
|
||||
}
|
||||
}
|
||||
});
|
||||
12732
rrweb-simple-ext/record.umd.cjs
Normal file
12732
rrweb-simple-ext/record.umd.cjs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user