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

View all

  • [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)

graph LR handle["handle"] MessageHandler["MessageHandler"] ExtensionMessage["ExtensionMessage"] MessageType["MessageType"] createLogger["createLogger"] getDemoFlows["getDemoFlows"] saveDemoFlow["saveDemoFlow"] deleteDemoFlow["deleteDemoFlow"] parseFlowScript["parseFlowScript"] createReplayOrchestrator["createReplayOrchestrator"] ReplayOrchestrator["ReplayOrchestrator"] createScreenRecorder["createScreenRecorder"] ScreenRecorder["ScreenRecorder"] convertRecordingToFlow["convertRecordingToFlow"] RecordedStep["RecordedStep"] RecordedStepType["RecordedStepType"] handle -->|uses| MessageHandler handle -->|uses| ExtensionMessage handle -->|uses| MessageType handle -->|uses| createLogger handle -->|uses| getDemoFlows handle -->|uses| saveDemoFlow handle -->|uses| deleteDemoFlow handle -->|uses| parseFlowScript handle -->|uses| createReplayOrchestrator handle -->|uses| ReplayOrchestrator handle -->|uses| createScreenRecorder handle -->|uses| ScreenRecorder handle -->|uses| convertRecordingToFlow handle -->|uses| RecordedStep handle -->|uses| RecordedStepType style handle fill:#dbeafe,stroke:#2563eb,stroke-width:2px click handle "3b3925f07e1ac5c3.html" click MessageHandler "ab334f3bc9eb52d7.html" click ExtensionMessage "c70465261f6c12b8.html" click MessageType "2ef3f4e4b1044d26.html" click createLogger "70597a0a6b5e9ebb.html" click getDemoFlows "7a890fea8b78bfa5.html" click saveDemoFlow "9e1045b40d770fef.html" click deleteDemoFlow "b4c66edca3bc33c4.html" click parseFlowScript "010c355825278e12.html" click createReplayOrchestrator "9db31b7fc6c3a4c7.html" click ReplayOrchestrator "bf6330bdd180ab3f.html" click createScreenRecorder "44f19d27f7c2aaba.html" click ScreenRecorder "40597caf22067688.html" click convertRecordingToFlow "757a5ffbc62eadcc.html" click RecordedStep "62534895a8071c82.html" click RecordedStepType "d7bef5e662e858d6.html"

No incoming dependencies.