- Fastify API: demos/steps CRUD, content-addressed assets, scoped API keys (read/author/publish/admin, draft-only default per D4) - Player: image + sandboxed DOM steps, hotspots, tooltips, keyboard nav, privacy-first beacons (no IP storage, viewer tokens only — R7) - Editor: React SPA — demo list, drag-to-draw hotspots, annotations, publish - MCP server: Streamable HTTP, scope-filtered tools, untrusted-data wrapping on viewer-supplied content (R8); platform never calls a model - Capture extension (MV3): screenshot-flow recording, hotspots pre-placed from real clicks - Docker: single app image + postgres, Traefik-ready (expose only) E2E verified: capture->author->publish->play->beacon->lead->analytics + full MCP tool surface with scope filtering.
25 lines
1.1 KiB
JavaScript
25 lines
1.1 KiB
JavaScript
// Screenshot-flow capture: on each click in the recorded tab, grab a screenshot
|
|
// plus the click position, building image Steps with pre-placed hotspots.
|
|
let recording = { active: false, tabId: null, steps: [] };
|
|
|
|
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
|
if (msg.type === "start") {
|
|
recording = { active: true, tabId: msg.tabId, steps: [] };
|
|
chrome.scripting.executeScript({ target: { tabId: msg.tabId }, files: ["content.js"] });
|
|
sendResponse({ ok: true });
|
|
} else if (msg.type === "click" && recording.active && sender.tab?.id === recording.tabId) {
|
|
chrome.tabs.captureVisibleTab({ format: "png" }, (dataUrl) => {
|
|
recording.steps.push({ dataUrl, xPct: msg.xPct, yPct: msg.yPct });
|
|
sendResponse({ ok: true });
|
|
});
|
|
return true; // async
|
|
} else if (msg.type === "status") {
|
|
sendResponse({ active: recording.active, count: recording.steps.length });
|
|
} else if (msg.type === "stop") {
|
|
recording.active = false;
|
|
sendResponse({ steps: recording.steps });
|
|
} else if (msg.type === "discard") {
|
|
recording = { active: false, tabId: null, steps: [] };
|
|
sendResponse({ ok: true });
|
|
}
|
|
});
|