forked from rrweb-io/rrweb
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added 2 scripts for saving and viewing snapshots
- Loading branch information
Showing
5 changed files
with
209 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,3 +6,4 @@ dist | |
es | ||
lib | ||
temp | ||
*-snapshot.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
/* | ||
Usage: node scripts/rebuild.js rrweb-snapshot.json | ||
The script can also load GZIP compressed files, eg: .json.gz | ||
*/ | ||
const fs = require('fs'); | ||
const puppeteer = require('puppeteer'); | ||
const { promisify } = require('util'); | ||
const { unzip } = require('zlib'); | ||
|
||
// assume that rrWeb script is in this folder | ||
const rrWeb = './dist/rrweb-snapshot.js'; | ||
const rrFile = process.argv[2]; | ||
const waitSec = parseInt(process.argv[3] || 60); | ||
const PAGE_TIMEOUT = 5000; | ||
|
||
function delay(time) { | ||
return new Promise((resolve) => setTimeout(resolve, time)) | ||
} | ||
|
||
const describe = (jsHandle) => { | ||
return jsHandle.executionContext().evaluate((obj) => { | ||
return typeof obj === 'string' ? obj : `${typeof obj}=${obj}` | ||
}, jsHandle) | ||
} | ||
|
||
(async function main() { | ||
const browser = await puppeteer.launch({ | ||
args: [ | ||
'--disable-breakpad', | ||
'--disable-default-apps', | ||
'--disable-full-history-sync', | ||
'--disable-notifications', | ||
'--disable-speech-api', | ||
'--disable-translate', | ||
'--disable-web-security', | ||
'--ignore-gpu-blacklist', | ||
'--mute-audio', | ||
'--no-default-browser-check', | ||
'--no-pings', | ||
'--start-maximized', | ||
], | ||
defaultViewport: null, | ||
headless: false, | ||
}); | ||
browser.on('disconnected', process.exit); | ||
const page = await browser.newPage(); | ||
|
||
// listen to the browser console messages and scan objects | ||
page.on('console', async (msg) => { | ||
const args = await Promise.all(msg.args().map((arg) => describe(arg))); | ||
let text = ''; | ||
for (let i = 1; i < args.length; ++i) { | ||
text += `${args[i]} `; | ||
} | ||
console.log(`CONSOLE ${msg.type()} :: ${msg.text()}`); | ||
if (text.trim()) { | ||
console.log(text.trim()); | ||
} | ||
}); | ||
|
||
// restoring snapshots shouldn't need internet | ||
// enable internet to discover potential issues | ||
await page.setOfflineMode(true); | ||
// restore shouldn't need JS | ||
await page.setJavaScriptEnabled(false); | ||
|
||
await page.setContent('<html><head></head><body></body></html>'); | ||
const rrCode = await fs.promises.readFile(rrWeb, { encoding: 'utf8' }); | ||
let snap = await fs.promises.readFile(rrFile); | ||
if (rrFile.endsWith('.gz')) { | ||
snap = (await promisify(unzip)(snap)).toString(); | ||
} | ||
|
||
await page.evaluate(`(function(){ | ||
console.log('Restoring the snaphot...'); | ||
${rrCode}; | ||
rrwebSnapshot.rebuild(${snap}, {doc: document}); | ||
console.log('Snaphot restored!'); | ||
for (let s of document.getElementsByTagName("noscript")) { | ||
// Hide all restored noscript tags | ||
s.style.display = "none"; | ||
} | ||
})();`); | ||
|
||
await page.waitForSelector('*', { timeout: PAGE_TIMEOUT }); | ||
await delay(waitSec * 1000); | ||
|
||
await browser.close(); | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
/* | ||
Usage: node scripts/snapshot.js 'https://example.com/whatever' [optional-output.json] | ||
Some pages will load resources lazily, so it makes sense to scroll the page a bit, | ||
to collect all the resources in the snapshot. | ||
*/ | ||
const fs = require('fs'); | ||
const puppeteer = require('puppeteer'); | ||
|
||
// assume that rrWeb script is in this folder | ||
const rrWeb = './dist/rrweb-snapshot.js'; | ||
const url = process.argv[2]; | ||
const out = process.argv[3] || 'rrweb-snapshot.json'; | ||
|
||
const PAGE_TIMEOUT = 25000; | ||
const PAGE_DELAY = 2500; | ||
const IMG_LOAD_TIMEOUT = 5000; | ||
|
||
function delay(time) { | ||
return new Promise((resolve) => setTimeout(resolve, time)); | ||
} | ||
|
||
(async function main() { | ||
const browser = await puppeteer.launch({ | ||
args: [ | ||
'--disable-breakpad', | ||
'--disable-default-apps', | ||
'--disable-features=IsolateOrigins,site-per-process', | ||
'--disable-full-history-sync', | ||
'--disable-notifications', | ||
'--disable-renderer-backgrounding', | ||
'--disable-site-isolation-trials', | ||
'--disable-speech-api', | ||
'--disable-translate', | ||
'--disable-web-security', | ||
'--ignore-gpu-blacklist', | ||
'--mute-audio', | ||
'--no-default-browser-check', | ||
'--no-pings', | ||
'--start-maximized', | ||
], | ||
defaultViewport: null, | ||
headless: false, | ||
}); | ||
|
||
browser.on('disconnected', process.exit); | ||
const page = await browser.newPage(); | ||
page.on('console', (msg) => console.log(`CONSOLE ${msg.type()} :: ${msg.text()}`)); | ||
const rrCode = await fs.promises.readFile(rrWeb, { encoding: 'utf8' }); | ||
|
||
try { | ||
await page.goto(url, { waitUntil: 'networkidle0', timeout: PAGE_TIMEOUT }); | ||
await delay(PAGE_DELAY); | ||
} catch (err) { | ||
console.error(err); | ||
await browser.close(); | ||
return; | ||
} | ||
|
||
// hack all images with img.crossOrigin="anonymous" before calling snapshot | ||
// this is MANDATORY to capture images from websites that host images on CDNs | ||
await page.evaluate((timeout) => { | ||
const selectors = Array.from(document.getElementsByTagName('img')); | ||
const reloadP = Promise.allSettled( | ||
selectors.map((img) => { | ||
const p = new Promise((resolve, reject) => { | ||
img.addEventListener('load', () => { | ||
console.log(`re-loaded <img src=${img.currentSrc}>`); | ||
resolve(true); | ||
}); | ||
img.addEventListener('error', reject); | ||
}); | ||
img.loading = 'eager'; | ||
img.crossOrigin = 'anonymous'; | ||
return p; | ||
}), | ||
); | ||
return Promise.race([ | ||
reloadP, | ||
new Promise((resolve) => { | ||
setTimeout(() => { | ||
resolve('Timed out!'); | ||
}, timeout); | ||
}), | ||
]); | ||
}, IMG_LOAD_TIMEOUT); | ||
|
||
const snapshot = await page.evaluate(function (rrCode) { | ||
console.log('Taking the snaphot...'); | ||
eval(rrCode); | ||
return JSON.stringify( | ||
rrwebSnapshot.snapshot(document, { | ||
recordCanvas: true, | ||
inlineImages: true, | ||
inlineStylesheet: true, | ||
dataURLOptions: { type: 'image/webp', quality: 0.8 }, | ||
})[0], | ||
); | ||
}, rrCode); | ||
|
||
await fs.promises.writeFile(out, snapshot, { encoding: 'utf8' }); | ||
console.log(`Output file: "${out}" was saved`); | ||
|
||
await browser.close(); | ||
})(); |