src/lib/chrome/active-tab-messaging.ts

Total Symbols
5
Lines of Code
105
Avg Complexity
3.8
Avg Coverage
96.4%

File Relationships

graph LR sendToActiveTab["sendToActiveTab"] sendToTabWithInjection["sendToTabWithInjection"] sendToSpecificTab["sendToSpecificTab"] sendToActiveTab -->|calls| sendToTabWithInjection sendToSpecificTab -->|calls| sendToTabWithInjection click sendToActiveTab "../symbols/a91f63e9f38af9db.html" click sendToTabWithInjection "../symbols/c1233e42257a3186.html" click sendToSpecificTab "../symbols/3da88adecaec2d24.html"

Symbols by Kind

function 4
interface 1

All Symbols

Name Kind Visibility Status Lines Signature
ActiveTabMessageOptions interface - 9-11 interface ActiveTabMessageOptions
sendToTab function - 13-31 sendToTab( tabId: number, tabUrl: string | undefined, message: ExtensionMessage, ): : Promise<unknown>
sendToTabWithInjection function exported- 37-63 sendToTabWithInjection( tabId: number, tabUrl: string | undefined, message: ExtensionMessage, ): : Promise<unknown>
sendToActiveTab function exported- 69-88 sendToActiveTab( message: ExtensionMessage, options: ActiveTabMessageOptions = {}, ): : Promise<unknown>
sendToSpecificTab function exported- 94-104 sendToSpecificTab( tabId: number, tabUrl: string | undefined, message: ExtensionMessage, options: ActiveTabMessageOptions = {}, ): : Promise<unknown>

Full Source

/**
 * Chrome extension tab messaging utilities.
 * Provides helpers for sending messages to content scripts,
 * with optional auto-injection when the content script is not yet loaded.
 */

import type { ExtensionMessage } from "@/types";

interface ActiveTabMessageOptions {
  injectIfNeeded?: boolean;
}

async function sendToTab(
  tabId: number,
  tabUrl: string | undefined,
  message: ExtensionMessage,
): Promise<unknown> {
  const url = tabUrl ?? "";
  if (!url.startsWith("http://") && !url.startsWith("https://")) {
    return { error: "Content script not available on this page" };
  }

  try {
    return await chrome.tabs.sendMessage(tabId, message);
  } catch (initialError) {
    return {
      error: "Content script not responding",
      details: String(initialError),
    };
  }
}

/**
 * Sends a message to a tab, auto-injecting the content script if the first
 * attempt fails (e.g. on a freshly opened page).
 */
export async function sendToTabWithInjection(
  tabId: number,
  tabUrl: string | undefined,
  message: ExtensionMessage,
): Promise<unknown> {
  const firstTry = await sendToTab(tabId, tabUrl, message);
  if (!(firstTry && typeof firstTry === "object" && "error" in firstTry)) {
    return firstTry;
  }

  try {
    const manifest = chrome.runtime.getManifest();
    const files = (manifest.content_scripts?.[0]?.js ?? []) as string[];
    if (files.length === 0)
      throw new Error("No content script files in manifest");
    await chrome.scripting.executeScript({
      target: { tabId },
      files,
    });
    return await chrome.tabs.sendMessage(tabId, message);
  } catch (injectErr) {
    return {
      error: "Content script not responding",
      details: String(injectErr),
    };
  }
}

/**
 * Sends a message to the currently active tab.
 * Optionally injects the content script if it’s not yet loaded.
 */
export async function sendToActiveTab(
  message: ExtensionMessage,
  options: ActiveTabMessageOptions = {},
): Promise<unknown> {
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
  if (!tab?.id) return { error: "No active tab" };

  if (!options.injectIfNeeded) {
    return sendToTab(tab.id, tab.url, message);
  }

  try {
    return await sendToTabWithInjection(tab.id, tab.url, message);
  } catch (initialError) {
    return {
      error: "Content script not responding",
      details: String(initialError),
    };
  }
}

/**
 * Sends a message to a specific tab by ID.
 * Optionally injects the content script if it’s not yet loaded.
 */
export async function sendToSpecificTab(
  tabId: number,
  tabUrl: string | undefined,
  message: ExtensionMessage,
  options: ActiveTabMessageOptions = {},
): Promise<unknown> {
  if (!options.injectIfNeeded) {
    return sendToTab(tabId, tabUrl, message);
  }
  return sendToTabWithInjection(tabId, tabUrl, message);
}