handle function application
Last updated: 2026-03-05T10:53:28.852Z
Metrics
LOC: 203
Complexity: 41
Params: 1
Signature
handle(message: ExtensionMessage): : Promise<unknown>
Architecture violations
- [warning] max-cyclomatic-complexity: 'handle' has cyclomatic complexity 41 (max 10)
- [warning] max-lines: 'handle' has 203 lines (max 80)
Source Code
async function handle(message: ExtensionMessage): Promise<unknown> {
switch (message.type) {
// ── Flow CRUD ────────────────────────────────────────────────────
case "DEMO_GET_FLOWS":
return getDemoFlows();
case "DEMO_SAVE_FLOW": {
const parsed = flowScriptSchema.safeParse(message.payload);
if (!parsed.success) {
const detail = parsed.error.issues
.map((i) => `${i.path.join(".")}: ${i.message}`)
.join("; ");
log.warn("DEMO_SAVE_FLOW validation failed:", detail);
return { error: `Payload inválido: ${detail}` };
}
await saveDemoFlow(parsed.data);
return { success: true };
}
case "DEMO_CONVERT_RECORDING": {
const p = message.payload as
| {
steps?: Array<Record<string, unknown>>;
name?: string;
seed?: string;
}
| undefined;
if (!Array.isArray(p?.steps) || !p?.name) {
return {
error: "Missing steps or name in DEMO_CONVERT_RECORDING payload",
};
}
// Map panel RecordStep[] → e2e-export RecordedStep[]
const recorded: RecordedStep[] = p.steps.map((s, i) => ({
type: (s["type"] as RecordedStepType) ?? "click",
selector: typeof s["selector"] === "string" ? s["selector"] : undefined,
value: typeof s["value"] === "string" ? s["value"] : undefined,
url: typeof s["url"] === "string" ? s["url"] : undefined,
label: typeof s["label"] === "string" ? s["label"] : undefined,
waitTimeout: typeof s["waitMs"] === "number" ? s["waitMs"] : undefined,
assertion:
s["assertion"] != null && typeof s["assertion"] === "object"
? (s["assertion"] as Parameters<
typeof convertRecordingToFlow
>[0]["steps"][0]["assertion"])
: undefined,
timestamp: i * 1000,
}));
const startUrl = recorded.find((s) => s.type === "navigate")?.url ?? "";
const session = {
steps: recorded,
startUrl,
startTime: Date.now(),
status: "stopped" as const,
};
const flow = convertRecordingToFlow(session, {
name: p.name,
seed: p.seed ?? "demo",
});
await saveDemoFlow(flow);
return { success: true };
}
case "DEMO_DELETE_FLOW": {
const id = typeof message.payload === "string" ? message.payload : null;
if (!id) return { error: "Expected flow ID string" };
await deleteDemoFlow(id);
return { success: true };
}
// ── Replay control ───────────────────────────────────────────────
case "DEMO_REPLAY_START": {
const p = message.payload as
| {
flowId?: string;
tabId?: number;
config?: Record<string, unknown>;
}
| undefined;
if (!p?.flowId || !p?.tabId) {
return { error: "Missing flowId or tabId" };
}
const { getDemoFlowById } = await import("@/lib/demo/demo-storage");
const flow = await getDemoFlowById(p.flowId);
if (!flow) return { error: `Flow "${p.flowId}" not found` };
activeOrchestrator = createReplayOrchestrator(p.tabId, {
onProgress(progress) {
// Broadcast progress to popup / devtools
chrome.runtime
.sendMessage({
type: "DEMO_REPLAY_PROGRESS" as MessageType,
payload: progress,
})
.catch(() => {});
},
onComplete(result) {
chrome.runtime
.sendMessage({
type: "DEMO_REPLAY_COMPLETE" as MessageType,
payload: result,
})
.catch(() => {});
activeOrchestrator = null;
},
});
activeOrchestrator.start(flow, p.config);
return { success: true, status: activeOrchestrator.status };
}
case "DEMO_REPLAY_PAUSE":
activeOrchestrator?.pause();
return { status: activeOrchestrator?.status ?? "idle" };
case "DEMO_REPLAY_RESUME":
activeOrchestrator?.resume();
return { status: activeOrchestrator?.status ?? "idle" };
case "DEMO_REPLAY_STOP":
activeOrchestrator?.stop();
activeOrchestrator = null;
return { status: "completed" };
case "DEMO_REPLAY_STATUS":
return { status: activeOrchestrator?.status ?? "idle" };
// ── Screen recording helpers ─────────────────────────────────────
/**
* Returns a chrome.tabCapture streamId so the DevTools panel can call
* navigator.mediaDevices.getUserMedia() directly and record locally.
* This avoids shipping the video blob through the messaging bus.
*/
case "DEMO_GET_STREAM_ID": {
const opts = message.payload as { tabId?: number } | undefined;
if (!opts?.tabId) return { error: "Missing tabId" };
const streamId = await new Promise<string | null>((resolve) => {
chrome.tabCapture.getMediaStreamId(
{ targetTabId: opts.tabId },
(id) => {
if (chrome.runtime.lastError) {
log.warn(
"getMediaStreamId error:",
chrome.runtime.lastError.message,
);
resolve(null);
} else {
resolve(id);
}
},
);
});
if (!streamId) return { error: "Failed to get stream ID" };
return { streamId };
}
// ── Screen recording (legacy — background-side recorder) ─────────
case "DEMO_RECORD_SCREEN_START": {
const opts = message.payload as { tabId?: number } | undefined;
if (!opts?.tabId) return { error: "Missing tabId for screen recording" };
activeRecorder = createScreenRecorder();
await activeRecorder.start(opts.tabId);
return { success: true };
}
case "DEMO_RECORD_SCREEN_STOP": {
if (!activeRecorder || activeRecorder.state !== "recording") {
return { error: "No active recording" };
}
const blob = await activeRecorder.stop();
activeRecorder = null;
// Convert blob to base64 for messaging (small demos only)
const buffer = await blob.arrayBuffer();
const base64 = btoa(
new Uint8Array(buffer).reduce(
(data, byte) => data + String.fromCharCode(byte),
"",
),
);
return {
success: true,
mimeType: blob.type,
sizeBytes: blob.size,
base64,
};
}
default:
return null;
}
}
Members
| Name | Kind | Visibility | Status | Signature |
|---|---|---|---|---|
| onProgress | method | - | onProgress(progress) | |
| onComplete | method | - | onComplete(result) |
Dependencies (Outgoing)
| Target | Type |
|---|---|
| MessageHandler | uses |
| ExtensionMessage | uses |
| MessageType | uses |
| createLogger | uses |
| getDemoFlows | uses |
| saveDemoFlow | uses |
| deleteDemoFlow | uses |
| parseFlowScript | uses |
| createReplayOrchestrator | uses |
| ReplayOrchestrator | uses |
| createScreenRecorder | uses |
| ScreenRecorder | uses |
| convertRecordingToFlow | uses |
| RecordedStep | uses |
| RecordedStepType | uses |
No incoming dependencies.