@webblackbox/player-sdk
v0.2.0
Published
Archive playback, querying, analysis, and code-generation SDK for WebBlackbox sessions.
Maintainers
Readme
The session playback and analysis SDK for WebBlackbox. Opens .webblackbox archives and provides rich querying, analysis, and code generation capabilities.
Overview
- WebBlackboxPlayer — Main player class for loading and analyzing sessions
- Lazy Chunk Decode — Events are decoded on demand per chunk with LRU cache eviction
- Event Querying — Filter events by type, level, time range, text, and request ID
- Network Analysis — Waterfall visualization, realtime stream timeline, request detail extraction
- Storage Analysis — Timeline of cookie, localStorage, IndexedDB, and cache operations
- DOM Analysis — Snapshot diffing to track added, removed, and changed elements
- Performance Analysis — Web Vitals, long tasks, CPU profiles, heap snapshots, traces
- Session Comparison — Compare two sessions by event counts, error rates, and request patterns
- Code Generation — Generate curl, fetch, HAR, Playwright scripts, bug reports, and issue templates
Installation
pnpm add @webblackbox/player-sdkUsage
Opening an Archive
import { WebBlackboxPlayer } from "@webblackbox/player-sdk";
// From ArrayBuffer, Uint8Array, or Blob
const player = await WebBlackboxPlayer.open(archiveBytes);
// With encryption passphrase
const player = await WebBlackboxPlayer.open(archiveBytes, {
passphrase: "my-secret"
});
// Preload only a monotonic time window (loads intersecting chunks only)
const scopedPlayer = await WebBlackboxPlayer.open(archiveBytes, {
range: { monoStart: 12000, monoEnd: 45000 }
});
console.log(player.status); // "loaded"
console.log(player.archive.manifest); // ExportManifest
console.log(player.events.length); // Total event countQuerying Events
// Get all events
const allEvents = player.query();
// Filter by type
const networkEvents = player.query({
types: ["network.request", "network.response"]
});
// Filter by level
const errors = player.query({
levels: ["error"]
});
// Filter by time range (monotonic timestamps)
const firstMinute = player.query({
range: { monoStart: 0, monoEnd: 60000 }
});
// Text search within events
const matches = player.query({
text: "TypeError"
});
// Filter by request ID
const requestEvents = player.query({
requestId: "R-12345"
});
// Combine filters with pagination
const page = player.query({
types: ["error.exception"],
levels: ["error"],
range: { monoStart: 0, monoEnd: 120000 },
limit: 50,
offset: 0
});Full-Text Search
const results = player.search("login failed", 100);
// Returns: PlayerSearchResult[]
// { eventId, score, event }Blob Retrieval
// Get binary blob by hash (screenshots, DOM snapshots, response bodies)
const blob = await player.getBlob("abc123...");
if (blob) {
console.log(blob.mime); // "image/webp"
console.log(blob.bytes); // Uint8Array
}Analysis APIs
Network Waterfall
const waterfall = player.getNetworkWaterfall();
for (const entry of waterfall) {
console.log(entry.url);
console.log(entry.method); // "GET", "POST", etc.
console.log(entry.status); // 200, 404, etc.
console.log(entry.durationMs); // Request duration
console.log(entry.mimeType); // Response MIME type
console.log(entry.failed); // Whether request failed
console.log(entry.requestHeaders); // Request headers
console.log(entry.responseHeaders); // Response headers
}
// Filter by time range
const recentNetwork = player.getNetworkWaterfall({
monoStart: 5000,
monoEnd: 30000
});Realtime Network Timeline (WebSocket / SSE)
const timeline = player.getRealtimeNetworkTimeline();
for (const entry of timeline) {
console.log(entry.protocol); // "ws" | "sse"
console.log(entry.direction); // "sent" | "received" | "unknown"
console.log(entry.url);
console.log(entry.payloadPreview);
console.log(entry.payloadLength);
}Request Detail
// Get all events for a specific request
const reqEvents = player.getRequestEvents("R-12345");
// Returns network.request, network.response, network.finished, etc.Storage Timeline
const storage = player.getStorageTimeline();
for (const entry of storage) {
console.log(entry.kind); // "cookie" | "local" | "session" | "idb" | "cache" | "sw"
console.log(entry.operation); // set, delete, clear, etc.
console.log(entry.eventType); // Full event type
}DOM Analysis
// Get all DOM snapshots
const snapshots = player.getDomSnapshots();
// Compute diff timeline across all snapshots
const diffs = await player.getDomDiffTimeline();
for (const diff of diffs) {
console.log(diff.summary.added); // Number of added DOM paths
console.log(diff.summary.removed); // Number of removed DOM paths
console.log(diff.summary.changed); // Number of changed DOM paths
console.log(diff.addedPaths); // string[]
console.log(diff.removedPaths); // string[]
console.log(diff.changedPaths); // string[]
}
// Compare two specific snapshots
const diff = await player.compareDomSnapshots(prevId, currId);Performance Artifacts
const artifacts = player.getPerformanceArtifacts();
for (const artifact of artifacts) {
console.log(artifact.kind); // "trace" | "cpu" | "heap" | "longtask" | "vitals"
console.log(artifact.hash); // Blob hash (if applicable)
console.log(artifact.size); // Size in bytes
}Action Span Analysis
const derived = player.buildDerived();
for (const span of derived.actionSpans) {
console.log(span.actId); // Action span ID
console.log(span.startMono); // Start time
console.log(span.endMono); // End time
console.log(span.eventIds); // Related event IDs
console.log(span.triggerEventId); // Trigger event
console.log(span.requestCount); // Network requests in span
console.log(span.errorCount); // Errors in span
}
console.log(derived.totals.events); // Total event count
console.log(derived.totals.errors); // Total error count
console.log(derived.totals.requests); // Total request count
// Timeline view with request/error/screenshot context per action
const timeline = player.getActionTimeline({
range: { monoStart: 0, monoEnd: 60000 },
limit: 20
});Code Generation
curl / fetch
const curl = player.generateCurl("R-12345");
// curl -X POST 'https://api.example.com/data' -H 'Content-Type: application/json' ...
const fetch = player.generateFetch("R-12345");
// fetch('https://api.example.com/data', { method: 'POST', headers: {...}, body: '...' })HAR Export
const harJson = player.exportHar();
// Standard HTTP Archive 1.2 format
// Compatible with Chrome DevTools, Charles Proxy, etc.
// Export specific time range
const harPartial = player.exportHar({ monoStart: 0, monoEnd: 30000 });Bug Report
const report = player.generateBugReport({
title: "Login fails with 500 error",
range: { monoStart: 0, monoEnd: 30000 },
maxItems: 30
});
// Returns markdown-formatted bug report with session contextPlaywright Scripts
// Generate test script from recorded user actions
const testScript = player.generatePlaywrightScript({
name: "checkout flow",
startUrl: "https://example.com",
includeHarReplay: true
});
// Generate mock script with captured network responses
const mockScript = await player.generatePlaywrightMockScript({
name: "checkout flow",
startUrl: "https://example.com",
maxMocks: 50
});Issue Templates
// GitHub issue template
const github = player.generateGitHubIssueTemplate({
title: "Bug title"
});
// { title, body, labels, assignees }
// Jira issue template
const jira = player.generateJiraIssueTemplate({
title: "Bug title"
});
// { fields: { summary, description, issuetype, labels, project?, priority? } }Session Comparison
const other = await WebBlackboxPlayer.open(otherArchiveBytes);
const comparison = player.compareWith(other);
console.log(comparison.leftSessionId); // Baseline session ID
console.log(comparison.rightSessionId); // Compared session ID
console.log(comparison.eventDelta); // Event count difference
console.log(comparison.errorDelta); // Error count difference
console.log(comparison.requestDelta); // Request count difference
console.log(comparison.durationDeltaMs); // Duration difference
for (const td of comparison.typeDeltas) {
console.log(`${td.type}: ${td.left} vs ${td.right} (${td.delta})`);
}
for (const row of comparison.endpointRegressions) {
console.log(`${row.method} ${row.endpoint}`);
console.log(`failure rate delta: ${row.failureRateDelta}`);
console.log(`p95 delta(ms): ${row.p95DurationDeltaMs}`);
}
// Storage comparison
const storageDiff = player.compareStorageWith(other);
// DOM comparison (latest snapshots)
const domDiff = await player.compareLatestDomSnapshotWith(other);Types
type PlayerStatus = "idle" | "loaded";
type PlayerOpenInput = ArrayBuffer | Uint8Array | Blob;
type PlayerOpenOptions = {
passphrase?: string;
range?: PlayerRange;
};
type PlayerQuery = {
range?: PlayerRange;
types?: WebBlackboxEventType[];
levels?: EventLevel[];
text?: string;
requestId?: string;
limit?: number;
offset?: number;
};
type PlayerRange = {
monoStart?: number;
monoEnd?: number;
};
type PlayerSearchResult = {
eventId: string;
score: number;
event: WebBlackboxEvent;
};
type PlayerArchive = {
manifest: ExportManifest;
timeIndex: ChunkTimeIndexEntry[];
requestIndex: RequestIndexEntry[];
invertedIndex: InvertedIndexEntry[];
integrity: HashesManifest;
};