handleContentMessage function exported

Last updated: 2026-03-05T10:53:28.853Z

Metrics

LOC: 503 Complexity: 99 Params: 2

Signature

handleContentMessage( message: ExtensionMessage, ): : Promise<unknown>

Architecture violations

View all

  • [warning] max-cyclomatic-complexity: 'handleContentMessage' has cyclomatic complexity 99 (max 10)
  • [warning] max-lines: 'handleContentMessage' has 503 lines (max 80)

Source Code

export async function handleContentMessage(
  message: ExtensionMessage,
): Promise<unknown> {
  switch (message.type) {
    case "FILL_ALL_FIELDS": {
      const override =
        message.payload != null &&
        typeof message.payload === "object" &&
        "fillEmptyOnly" in (message.payload as object)
          ? {
              fillEmptyOnly: (message.payload as { fillEmptyOnly: boolean })
                .fillEmptyOnly,
            }
          : undefined;
      const results = await fillAllFields(override);
      showNotification(`✓ ${results.length} campos preenchidos`);
      return { success: true, filled: results.length };
    }

    case "FILL_CONTEXTUAL_AI": {
      const context = message.payload as AIContextPayload | undefined;
      const results = await fillContextualAI(context);
      showNotification(
        `✓ ${results.length} campos preenchidos com IA contextual`,
      );
      return { success: true, filled: results.length };
    }

    case "SAVE_FORM": {
      const values = await captureFormValues();
      const formData: SavedForm = {
        id: generateId(),
        name: `Form - ${new URL(window.location.href).hostname} - ${new Date().toLocaleDateString("pt-BR")}`,
        urlPattern: `${window.location.origin}${window.location.pathname}*`,
        fields: values,
        createdAt: Date.now(),
        updatedAt: Date.now(),
      };
      await saveForm(formData);
      showNotification(
        `✓ Formulário salvo com ${Object.keys(values).length} campos`,
      );
      return { success: true, form: formData };
    }

    case "LOAD_SAVED_FORM": {
      const form = parseSavedFormPayload(message.payload);
      if (!form) return { error: "Invalid payload for LOAD_SAVED_FORM" };

      // filter out ignored selectors so applyTemplate won't touch them
      const ignoredFields = await getIgnoredFieldsForUrl(window.location.href);
      const ignoredSelectors = new Set(ignoredFields.map((f) => f.selector));
      const sanitizedFields: typeof form.fields = {};
      for (const [key, value] of Object.entries(form.fields)) {
        // we only know selector-level ignores; if a key equals a selector, drop it
        if (ignoredSelectors.has(key)) continue;
        sanitizedFields[key] = value;
      }
      const sanitizedForm: SavedForm = { ...form, fields: sanitizedFields };

      const { filled } = await applyTemplate(sanitizedForm);
      showNotification(`✓ ${filled} campos carregados do template`);
      return { success: true, filled };
    }

    case "APPLY_TEMPLATE": {
      const form = parseSavedFormPayload(message.payload);
      if (!form) return { error: "Invalid payload for APPLY_TEMPLATE" };
      const { filled } = await applyTemplate(form);
      showNotification(`✓ Template "${form.name}" aplicado: ${filled} campos`);
      return { success: true, filled };
    }

    case "FILL_SINGLE_FIELD": {
      const fields = detectFormFields();
      const targetField = findSingleFieldTarget(fields);
      if (!targetField) return { error: "No target field found" };

      const result = await fillSingleField(targetField);
      if (result) {
        showNotification(
          `✓ Campo "${targetField.label || targetField.name || targetField.id || targetField.selector}" preenchido`,
        );
        return { success: true, ...result };
      }
      return { error: "Failed to fill field" };
    }

    case "GET_FORM_FIELDS": {
      const fields = detectFormFields();
      const mapped = fields.map((f) => ({
        selector: f.selector,
        fieldType: f.fieldType,
        label: f.label,
        name: f.name,
        id: f.id,
        placeholder: f.placeholder,
        required: f.required,
      }));
      return { count: mapped.length, fields: mapped };
    }

    case "DETECT_FIELDS": {
      const { fields: detected } = await detectAllFieldsAsync();
      return {
        count: detected.length,
        fields: detected.map((f): DetectedFieldSummary => {
          const item: DetectedFieldSummary = {
            selector: f.selector,
            fieldType: f.fieldType,
            label: f.label || f.name || f.id || "unknown",
            name: f.name,
            id: f.id,
            placeholder: f.placeholder,
            required: f.required,
            contextualType: f.contextualType,
            detectionMethod: f.detectionMethod,
            detectionConfidence: f.detectionConfidence,
          };
          if (f.element instanceof HTMLSelectElement) {
            item.options = Array.from(f.element.options).map((o) => ({
              value: o.value,
              text: o.text.trim(),
            }));
          }
          if (
            f.element instanceof HTMLInputElement &&
            (f.element.type === "checkbox" || f.element.type === "radio")
          ) {
            item.checkboxValue = f.element.value;
            item.checkboxChecked = f.element.checked;
          }
          return item;
        }),
      };
    }

    case "FILL_FIELD_BY_SELECTOR": {
      const selector = parseStringPayload(message.payload);
      if (!selector)
        return { error: "Invalid payload for FILL_FIELD_BY_SELECTOR" };
      const fields = detectFormFields();
      const field = fields.find((f) => f.selector === selector);
      if (!field) return { error: "Field not found" };
      const result = await fillSingleField(field);
      if (result) {
        showNotification(`✓ Campo "${field.label || selector}" preenchido`);
      }
      return result ?? { error: "Failed to fill field" };
    }

    case "RECLASSIFY_FIELD": {
      const selector = parseStringPayload(message.payload);
      if (!selector) return { error: "Invalid payload for RECLASSIFY_FIELD" };
      const classified = await reclassifyFieldBySelector(selector);
      if (!classified)
        return { error: "Field not found or could not be classified" };
      const summary: DetectedFieldSummary = {
        selector: classified.selector,
        fieldType: classified.fieldType,
        label:
          classified.label || classified.name || classified.id || "unknown",
        name: classified.name,
        id: classified.id,
        placeholder: classified.placeholder,
        required: classified.required,
        contextualType: classified.contextualType,
        detectionMethod: classified.detectionMethod,
        detectionConfidence: classified.detectionConfidence,
      };
      if (classified.element instanceof HTMLSelectElement) {
        summary.options = Array.from(classified.element.options).map((o) => ({
          value: o.value,
          text: o.text.trim(),
        }));
      }
      return summary;
    }

    case "START_WATCHING": {
      const payload = parseStartWatchingPayload(message.payload);
      if (!payload) return { error: "Invalid payload for START_WATCHING" };

      // If debounceMs/shadowDOM not provided in payload, read from settings
      let config: WatcherConfig;
      if (payload.debounceMs == null && payload.shadowDOM == null) {
        const settings = await getSettings();
        config = {
          autoRefill: payload.autoRefill ?? settings.watcherAutoRefill ?? true,
          debounceMs: settings.watcherDebounceMs,
          shadowDOM: settings.watcherShadowDOM,
        };
      } else {
        config = {
          autoRefill: payload.autoRefill ?? true,
          debounceMs: payload.debounceMs,
          shadowDOM: payload.shadowDOM,
        };
      }

      startWatching(
        (newFieldsCount) => {
          if (newFieldsCount > 0) {
            showNotification(
              `🔄 ${newFieldsCount} novo(s) campo(s) detectado(s) — re-preenchendo...`,
            );
          }
        },
        config.autoRefill,
        config,
      );
      return { success: true, watching: true };
    }

    case "STOP_WATCHING": {
      stopWatching();
      return { success: true, watching: false };
    }

    case "GET_WATCHER_STATUS": {
      return {
        watching: isWatcherActive(),
        config: isWatcherActive() ? getWatcherConfig() : null,
      };
    }

    case "INVALIDATE_CLASSIFIER": {
      invalidateClassifier();
      return { success: true };
    }

    case "RELOAD_CLASSIFIER": {
      void reloadClassifier();
      return { success: true };
    }

    case "EXPORT_E2E": {
      const parsed = parseExportE2EPayload(message.payload);
      if (!parsed) return { error: "Invalid payload for EXPORT_E2E" };

      const framework = parsed.framework as E2EFramework;
      const { fields } = await detectAllFieldsAsync();
      const results = await fillAllFields();
      const actions = buildCapturedActions(fields, results);

      // Detect submit buttons and append to actions
      const submitActions = detectSubmitActions();
      const allActions = [...actions, ...submitActions];

      // Detect assertions from the page
      const pageUrl = window.location.href;
      const assertions = detectAssertions(allActions, pageUrl);
      const negativeAssertions = detectNegativeAssertions(allActions);

      const options: E2EGenerateOptions = {
        pageUrl,
        includeAssertions: true,
        includeNegativeTest: true,
        useSmartSelectors: true,
        assertions: [...assertions, ...negativeAssertions],
      };

      const script = generateE2EScript(framework, allActions, options);

      if (!script) return { error: `Unsupported framework: ${framework}` };

      showNotification(
        `✓ Script ${framework} gerado (${allActions.length} ações)`,
      );
      return { success: true, script, actionsCount: allActions.length };
    }

    case "START_RECORDING": {
      startRecording();

      setOnStepAdded((step, index) => {
        chrome.runtime
          .sendMessage({
            type: "RECORDING_STEP_ADDED",
            payload: {
              step: {
                type: step.type,
                selector: step.selector,
                value: step.value,
                url: step.url,
                label: step.label,
                assertion: step.assertion,
              },
              index,
            },
          })
          .catch(() => {});
      });

      setOnStepUpdated((step, index) => {
        chrome.runtime
          .sendMessage({
            type: "RECORDING_STEP_UPDATED",
            payload: {
              step: {
                type: step.type,
                selector: step.selector,
                value: step.value,
                url: step.url,
                label: step.label,
                assertion: step.assertion,
              },
              index,
            },
          })
          .catch(() => {});
      });

      showNotification("🔴 Gravação iniciada");
      return { success: true };
    }

    case "STOP_RECORDING": {
      const session = stopRecording();
      if (!session) return { error: "No recording in progress" };
      showNotification(
        `⏹ Gravação finalizada (${session.steps.length} passos)`,
      );
      return { success: true, stepsCount: session.steps.length };
    }

    case "PAUSE_RECORDING": {
      pauseRecording();
      showNotification("⏸ Gravação pausada");
      return { success: true };
    }

    case "RESUME_RECORDING": {
      resumeRecording();
      showNotification("🔴 Gravação retomada");
      return { success: true };
    }

    case "GET_RECORDING_STATUS": {
      return { success: true, status: getRecordingStatus() };
    }

    case "GET_RECORDING_STEPS": {
      const session = getRecordingSession();
      if (!session) return { success: true, steps: [] };
      return {
        success: true,
        steps: session.steps.map((s, i, arr) => ({
          type: s.type,
          selector: s.selector,
          value: s.value,
          waitMs: i > 0 ? s.timestamp - arr[i - 1].timestamp : 0,
          url: s.url,
        })),
      };
    }

    case "EXPORT_RECORDING": {
      const recPayload = parseExportRecordingPayload(message.payload);
      if (!recPayload) return { error: "Invalid payload for EXPORT_RECORDING" };

      const session = getRecordingSession();
      if (!session || session.steps.length === 0) {
        return { error: "No recorded steps available" };
      }

      const recOptions: RecordingGenerateOptions = {
        testName: recPayload.testName,
        pageUrl: session.startUrl,
        includeAssertions: true,
        minWaitThreshold: 500,
      };

      const recScript = generateE2EFromRecording(
        recPayload.framework as E2EFramework,
        session.steps,
        recOptions,
      );

      if (!recScript) {
        return { error: `Unsupported framework: ${recPayload.framework}` };
      }

      showNotification(
        `✓ Script ${recPayload.framework} gerado (${session.steps.length} passos gravados)`,
      );
      return {
        success: true,
        script: recScript,
        stepsCount: session.steps.length,
      };
    }

    case "REMOVE_RECORDING_STEP": {
      const payload = message.payload as { index?: number } | undefined;
      if (typeof payload?.index !== "number") {
        return { error: "Invalid index" };
      }
      const removed = removeStep(payload.index);
      return { success: removed };
    }

    case "UPDATE_RECORDING_STEP": {
      const payload = message.payload as
        | { index?: number; patch?: { value?: string; waitTimeout?: number } }
        | undefined;
      if (typeof payload?.index !== "number" || !payload.patch) {
        return { error: "Invalid payload" };
      }
      const updated = updateStep(payload.index, payload.patch);
      return { success: updated };
    }

    case "CLEAR_RECORDING": {
      clearSession();
      return { success: true };
    }

    case "CLEAR_FORM": {
      const fields = detectFormFields();
      let cleared = 0;

      for (const field of fields) {
        const el = field.element;
        if (el instanceof HTMLInputElement) {
          if (el.type === "checkbox" || el.type === "radio") {
            el.checked = false;
          } else {
            el.value = "";
          }
          cleared++;
          el.dispatchEvent(new Event("input", { bubbles: true }));
          el.dispatchEvent(new Event("change", { bubbles: true }));
        } else if (el instanceof HTMLSelectElement) {
          el.value = "";
          cleared++;
          el.dispatchEvent(new Event("change", { bubbles: true }));
        } else if (el instanceof HTMLTextAreaElement) {
          el.value = "";
          cleared++;
          el.dispatchEvent(new Event("input", { bubbles: true }));
          el.dispatchEvent(new Event("change", { bubbles: true }));
        }
      }

      showNotification(`✓ ${cleared} campo(s) limpo(s)`);
      return { success: true, cleared };
    }

    case "DEMO_EXECUTE_STEP": {
      const payload = message.payload as ExecuteStepPayload | undefined;
      if (!payload?.step) {
        return { error: "Invalid payload for DEMO_EXECUTE_STEP" };
      }
      const result = await executeStep(payload);
      return { result };
    }

    case "DEMO_CURSOR_MOVE": {
      const cfg = message.payload as
        | { selector?: string; durationMs?: number }
        | undefined;
      if (cfg?.selector) {
        initCursorOverlay();
        showCursor();
        await moveCursorTo(cfg.selector, cfg.durationMs ?? 400);
      }
      return { success: true };
    }

    case "DEMO_CURSOR_CLICK": {
      initCursorOverlay();
      showCursor();
      await clickEffect();
      return { success: true };
    }

    case "DEMO_CURSOR_DESTROY": {
      hideCursor();
      destroyCursorOverlay();
      return { success: true };
    }

    case "DEMO_HIGHLIGHT_ELEMENT": {
      const hlPayload = message.payload as
        | { step?: unknown; durationMs?: number }
        | undefined;
      if (hlPayload?.step) {
        highlightElement(
          hlPayload.step as Parameters<typeof highlightElement>[0],
          hlPayload.durationMs ?? 300,
        );
      }
      return { success: true };
    }

    case "PING":
      return { pong: true };

    default:
      return { error: `Unknown message type: ${message.type}` };
  }
}

Dependencies (Outgoing)

graph LR handleContentMessage["handleContentMessage"] showNotification["showNotification"] generateId["generateId"] findSingleFieldTarget["findSingleFieldTarget"] handleContentMessage -->|calls| showNotification handleContentMessage -->|calls| generateId handleContentMessage -->|calls| findSingleFieldTarget style handleContentMessage fill:#dbeafe,stroke:#2563eb,stroke-width:2px click handleContentMessage "25741f17eec7ff33.html" click showNotification "8c9f4b84bc123ccf.html" click generateId "b7d96ccd023f052f.html" click findSingleFieldTarget "35389c7a7a8aca94.html"
TargetType
showNotification calls
generateId calls
findSingleFieldTarget calls

Impact (Incoming)

graph LR handleContentMessage["handleContentMessage"] FillableElement["FillableElement"] FillableElement -->|calls| handleContentMessage style handleContentMessage fill:#dbeafe,stroke:#2563eb,stroke-width:2px click handleContentMessage "25741f17eec7ff33.html" click FillableElement "2ecf5aaac3f668a8.html"
SourceType
FillableElement calls