All files / src/utils displayContext.ts

95.74% Statements 45/47
84.61% Branches 11/13
100% Functions 3/3
95.74% Lines 45/47

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 1031x                 1x   1x 1x   1x   1x     1x 1x 1x     1x 1x   1x                           1x 1x 12x 12x   1x                           1x 1x 3x   3x 3x 1x 1x 1x   2x 2x 2x 3x 1x 1x 1x 1x 1x 1x 1x 3x     2x 2x   1x                   1x 2x 2x 2x 2x  
/**
 * Helpers for detecting and switching the extension's display context.
 *
 * The same `index.html` is rendered as the toolbar popup and (via
 * {@link openExtensionTab}) a full browser tab. A `?view=tab` query param
 * marks the tab view so the UI can resize to fill the window and hide
 * popup-only controls such as the maximize button.
 * @module displayContext
 * @source
 */
 
import { Logger } from '@/utils/Logger';
import { getErrorMessage } from '@/helpers/exceptions';
 
const logger = new Logger('displayContext');
 
/**
 * Query-string key that flags the full-tab view.
 * @category Utils
 */
export const VIEW_PARAM = 'view';
/**
 * Value of {@link VIEW_PARAM} that identifies the full-tab view.
 * @category Utils
 */
export const TAB_VIEW = 'tab';
 
/**
 * Reports whether the current document is rendered in a full browser tab
 * (as opposed to the toolbar popup), based on the `?view=tab`
 * query param set by {@link openExtensionTab}.
 * @category Utils
 * @returns `true` when running in the full-tab view, otherwise `false`.
 * @example
 * ```ts
 * // URL: chrome-extension://<id>/index.html?view=tab
 * isTabView(); // => true
 * // URL: chrome-extension://<id>/index.html
 * isTabView(); // => false
 * ```
 * @source
 */
export function isTabView(): boolean {
  return new URLSearchParams(window.location.search).get(VIEW_PARAM) === TAB_VIEW;
}
 
/**
 * Opens the extension in a new, focused browser tab and closes the popup.
 * The opened URL carries the `?view=tab` param so {@link isTabView} resolves to
 * `true` there. Falls back to `window.open` outside the extension runtime.
 * If a tab is already open at that URL it is focused instead of duplicated.
 * @category Utils
 * @returns A promise that resolves once the tab has been requested.
 * @example
 * ```ts
 * // From a popup click handler:
 * await openExtensionTab();
 * // => focuses the existing index.html?view=tab tab, or opens one, then window.close()
 * ```
 * @source
 */
export async function openExtensionTab(): Promise<void> {
  const path = `index.html?${VIEW_PARAM}=${TAB_VIEW}`;
 
  // Outside the extension runtime (e.g. dev preview): fall back to a plain tab.
  if (typeof chrome?.tabs === 'undefined' || typeof chrome?.runtime?.getURL !== 'function') {
    window.open(`/${path}`, '_blank');
    return;
  }
 
  const url = chrome.runtime.getURL(path);
  try {
    const existing = await findExtensionTab(url);
    if (existing?.id == null) {
      await chrome.tabs.create({ url, active: true });
    } else {
      await chrome.tabs.update(existing.id, { active: true });
      if (typeof chrome.windows !== 'undefined' && existing.windowId != null) {
        await chrome.windows.update(existing.windowId, { focused: true });
      }
    }
  } catch (error) {
    logger.error(`Failed to open extension tab: ${getErrorMessage(error)}`, { error });
  }
  window.close();
}
 
/**
 * Finds an open browser tab whose URL matches the given extension URL.
 * @param url - The full extension URL to look for, including any query string.
 * @returns The matching tab, or `undefined` when no tab is open at that URL.
 * @example
 * ```ts
 * await findExtensionTab(chrome.runtime.getURL("index.html?view=tab"));
 * // => { id: 5, windowId: 9, ... } | undefined
 * ```
 * @source
 */
async function findExtensionTab(url: string): Promise<chrome.tabs.Tab | undefined> {
  const tabs = await chrome.tabs.query({});
  return tabs.find((tab) => tab.url === url || tab.pendingUrl === url);
}