Skip to content

build(extension): add TARGET-driven dual builds with Firefox manifest - #2000

Open
ManthanNimodiya wants to merge 3 commits into
CapSoftware:mainfrom
ManthanNimodiya:feat/extension-firefox-build
Open

build(extension): add TARGET-driven dual builds with Firefox manifest#2000
ManthanNimodiya wants to merge 3 commits into
CapSoftware:mainfrom
ManthanNimodiya:feat/extension-firefox-build

Conversation

@ManthanNimodiya

@ManthanNimodiyaManthanNimodiya commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Stacked on #1998, only the last commit(s) are new here, the earlier ones will disappear once the base PR merges.

TARGET=chrome|firefox vite builds → dist/chrome/dist/firefox.
Manifests move to manifests/manifest.{chrome,firefox}.json (vite copies the right one per target; a unit test keeps them in lockstep).

Firefox manifest: event-page background, options_ui, no offscreen/tabCapture, browser_specific_settings.gecko (id, min 128). New scripts: build:firefox, dev:firefox, run:firefox/lint:firefox (web-ext). e2e stays Chromium-only against dist/chrome. web-ext lint: 0 errors.

Greptile Summary

This PR adds target-specific extension builds for Chrome and Firefox. The main changes are:

  • Chrome and Firefox builds now write to separate dist subdirectories.
  • Browser manifests moved into manifests/ with a new Firefox manifest.
  • The recorder page replaces the old offscreen page name.
  • Vite config now injects a build target and copies the selected manifest.
  • Firefox helper scripts and manifest parity tests were added.

Confidence Score: 4/5

The Chrome release package and Firefox recorder startup paths can break with the new target layout.

  • The default build now creates two target folders, while Chrome packaging still treats dist as one extension root.
  • The Chrome release workflow still reads the manifest from the old public path.
  • Firefox runtime paths can still call Chrome-only offscreen APIs from the shared recorder host.

apps/chrome-extension/package.json, apps/chrome-extension/manifests/manifest.chrome.json, apps/chrome-extension/src/background/recorder-host.ts

Important Files Changed

FilenameOverview
apps/chrome-extension/package.jsonAdds dual-target build scripts, but the default build now changes the shape consumed by Chrome release packaging.
apps/chrome-extension/manifests/manifest.chrome.jsonMoves the Chrome manifest into the new manifest directory while existing release automation still expects the old public path.
apps/chrome-extension/manifests/manifest.firefox.jsonAdds the Firefox manifest with event-page background settings and Chrome-only permissions removed.
apps/chrome-extension/src/background/recorder-host.tsExtracts recorder host lifecycle code, but the shared helper still uses Chrome-only offscreen APIs.
apps/chrome-extension/src/background/service-worker.tsSwitches recorder messaging to the new host helper and uses the computed extension protocol for sender checks.
apps/chrome-extension/src/platform/capabilities.tsAdds target feature flags for Chrome and Firefox capabilities.
apps/chrome-extension/src/platform/extension-protocol.tsAdds a small helper for Chrome versus Firefox extension URL protocols.
apps/chrome-extension/src/recorder/recorder.tsUpdates recorder pipeline selection for streaming uploads and resumes the audio context after creation.
apps/chrome-extension/vite.config.tsAdds target-aware output directories, manifest copying, and the renamed recorder entry.
apps/chrome-extension/vite.shared.tsCentralizes target parsing, output directory selection, and Vite define injection.

Comments Outside Diff (1)

  1. apps/chrome-extension/manifests/manifest.chrome.json, line 1 (link)

    P1Publish Workflow Reads Old Manifest

    Moving the Chrome manifest out of public/manifest.json leaves the Chrome release workflow's version read pointing at a path that no longer exists. The package step fails before upload when it tries to read the removed public manifest.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/chrome-extension/manifests/manifest.chrome.json
    Line: 1
    Comment:
    **Publish Workflow Reads Old Manifest**
    Moving the Chrome manifest out of `public/manifest.json` leaves the Chrome release workflow's version read pointing at a path that no longer exists. The package step fails before upload when it tries to read the removed public manifest.
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.
---### Issue 1 of 3
apps/chrome-extension/package.json:7
**Chrome Package Contains Both Targets**
When the Chrome publish workflow runs the default `build`, this script now creates both `dist/chrome` and `dist/firefox`. The existing package step zips `dist` as the extension root, so the upload contains target subdirectories and Firefox artifacts instead of a valid Chrome extension layout.
### Issue 2 of 3
apps/chrome-extension/manifests/manifest.chrome.json:1
**Publish Workflow Reads Old Manifest**
Moving the Chrome manifest out of `public/manifest.json` leaves the Chrome release workflow's version read pointing at a path that no longer exists. The package step fails before upload when it tries to read the removed public manifest.
### Issue 3 of 3
apps/chrome-extension/src/background/recorder-host.ts:9-16
**Firefox Reaches Offscreen APIs**
The shared background script imports this helper for both targets, but `hasRecorderHost()` and `ensureRecorderHost()` call Chrome-only offscreen APIs without checking the target. In the Firefox build, normal paths like starting a recording, probing the mic, enumerating devices, or connecting camera preview can reach `chrome.runtime.getContexts` or `chrome.offscreen.createDocument`, producing a runtime error instead of opening a Firefox recorder page.

Reviews (1): Last reviewed commit: "build(extension): add TARGET-driven dual..." | Re-trigger Greptile

Greptile also left 2 inline comments on this PR.

Context used:

  • Context used - CLAUDE.md (source)
  • Context used - AGENTS.md (source)

"scripts": {
"build": "rm -rf dist && vite build && vite build --config vite.content.config.ts && vite build --config vite.content-overlay.config.ts",
"dev": "rm -rf dist && (trap 'kill 0' INT TERM; vite build --watch --config vite.content.config.ts --mode development & vite build --watch --config vite.content-overlay.config.ts --mode development & vite build --watch --mode development & wait)",
"build": "pnpm build:chrome && pnpm build:firefox",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1Chrome Package Contains Both Targets

When the Chrome publish workflow runs the default build, this script now creates both dist/chrome and dist/firefox. The existing package step zips dist as the extension root, so the upload contains target subdirectories and Firefox artifacts instead of a valid Chrome extension layout.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/chrome-extension/package.json
Line: 7
Comment:
**Chrome Package Contains Both Targets**
When the Chrome publish workflow runs the default `build`, this script now creates both `dist/chrome` and `dist/firefox`. The existing package step zips `dist` as the extension root, so the upload contains target subdirectories and Firefox artifacts instead of a valid Chrome extension layout.
How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +9 to +16
const getRecorderContexts = async () => {
const recorderUrl = chrome.runtime.getURL(RECORDER_URL);
return new Promise<Array<{ documentUrl?: string }>>((resolve) => {
chrome.runtime.getContexts(
{
contextTypes: [chrome.runtime.ContextType.OFFSCREEN_DOCUMENT],
documentUrls: [recorderUrl],
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1Firefox Reaches Offscreen APIs

The shared background script imports this helper for both targets, but hasRecorderHost() and ensureRecorderHost() call Chrome-only offscreen APIs without checking the target. In the Firefox build, normal paths like starting a recording, probing the mic, enumerating devices, or connecting camera preview can reach chrome.runtime.getContexts or chrome.offscreen.createDocument, producing a runtime error instead of opening a Firefox recorder page.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/chrome-extension/src/background/recorder-host.ts
Line: 9-16
Comment:
**Firefox Reaches Offscreen APIs**
The shared background script imports this helper for both targets, but `hasRecorderHost()` and `ensureRecorderHost()` call Chrome-only offscreen APIs without checking the target. In the Firefox build, normal paths like starting a recording, probing the mic, enumerating devices, or connecting camera preview can reach `chrome.runtime.getContexts` or `chrome.offscreen.createDocument`, producing a runtime error instead of opening a Firefox recorder page.
How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +1 to +46
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { describe, expect, it } from "vitest";

// The two manifests are maintained by hand; these assertions keep the parts
// that must not drift (version, entry points, content scripts, resources) in
// lockstep and pin the deliberate per-browser differences.

type Manifest = {
manifest_version: number;
name: string;
short_name: string;
version: string;
homepage_url: string;
icons: Record<string, string>;
action: unknown;
background: {
service_worker?: string;
scripts?: string[];
type?: string;
};
permissions: string[];
host_permissions: string[];
content_scripts: unknown[];
web_accessible_resources: Array<{
resources: string[];
matches: string[];
use_dynamic_url?: boolean;
}>;
options_page?: string;
options_ui?: { page: string };
browser_specific_settings?: {
gecko?: {
id?: string;
strict_min_version?: string;
};
};
};

const loadManifest = (target: "chrome" | "firefox"): Manifest =>
JSON.parse(
readFileSync(
resolve(__dirname, `../../manifests/manifest.${target}.json`),
"utf8",
),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apps/chrome-extension is type: module, so __dirname won’t exist if Vitest executes this test as ESM. Safer to derive it from import.meta.url.

Suggested change
import{readFileSync}from"node:fs";
import{resolve}from"node:path";
import{describe,expect,it}from"vitest";
// The two manifests are maintained by hand; these assertions keep the parts
// that must not drift (version, entry points, content scripts, resources) in
// lockstep and pin the deliberate per-browser differences.
typeManifest={
manifest_version: number;
name: string;
short_name: string;
version: string;
homepage_url: string;
icons: Record<string,string>;
action: unknown;
background: {
service_worker?: string;
scripts?: string[];
type?: string;
};
permissions: string[];
host_permissions: string[];
content_scripts: unknown[];
web_accessible_resources: Array<{
resources: string[];
matches: string[];
use_dynamic_url?: boolean;
}>;
options_page?: string;
options_ui?: {page: string};
browser_specific_settings?: {
gecko?: {
id?: string;
strict_min_version?: string;
};
};
};
constloadManifest=(target: "chrome"|"firefox"): Manifest=>
JSON.parse(
readFileSync(
resolve(__dirname,`../../manifests/manifest.${target}.json`),
"utf8",
),
);
import{readFileSync}from"node:fs";
import{dirname,resolve}from"node:path";
import{fileURLToPath}from"node:url";
import{describe,expect,it}from"vitest";
// The two manifests are maintained by hand; these assertions keep the parts
// that must not drift (version, entry points, content scripts, resources) in
// lockstep and pin the deliberate per-browser differences.
const__dirname=dirname(fileURLToPath(import.meta.url));
typeManifest={
manifest_version: number;
name: string;
short_name: string;
version: string;
homepage_url: string;
icons: Record<string,string>;
action: unknown;
background: {
service_worker?: string;
scripts?: string[];
type?: string;
};
permissions: string[];
host_permissions: string[];
content_scripts: unknown[];
web_accessible_resources: Array<{
resources: string[];
matches: string[];
use_dynamic_url?: boolean;
}>;
options_page?: string;
options_ui?: {page: string};
browser_specific_settings?: {
gecko?: {
id?: string;
strict_min_version?: string;
};
};
};
constloadManifest=(target: "chrome"|"firefox"): Manifest=>
JSON.parse(
readFileSync(
resolve(__dirname,`../../manifests/manifest.${target}.json`),
"utf8",
),
);

const getRecorderContexts = async () => {
const recorderUrl = chrome.runtime.getURL(RECORDER_URL);
return new Promise<Array<{ documentUrl?: string }>>((resolve) => {
chrome.runtime.getContexts(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file assumes chrome.runtime.getContexts + chrome.offscreen.createDocument exist. Since this PR adds a Firefox build (and the manifest drops the offscreen permission), it might be worth adding a Firefox-specific implementation here (or at least a feature-detect + clearer error) so sendOffscreen(...) doesn’t end up throwing a TypeError at runtime.

@ManthanNimodiya
ManthanNimodiyaforce-pushed the feat/extension-firefox-build branch from 92c255e to 718b032CompareJuly 21, 2026 16:13
@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

DiffPackageSupply Chain
Security
VulnerabilityQualityMaintenanceLicense
Addednpm/​web-ext@​10.5.0961001009370

View full report

@socket-security

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

ActionSeverityAlert (click "▶" to expand/collapse)
WarnHigh
Obfuscated code: npm @fregante/relaxed-json is 90.0% likely obfuscated

Confidence: 0.90

Location:Package overview

From:pnpm-lock.yamlnpm/web-ext@10.5.0npm/@fregante/relaxed-json@2.0.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@fregante/relaxed-json@2.0.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

WarnHigh
Obfuscated code: npm @pnpm/network.ca-file is 90.0% likely obfuscated

Confidence: 0.90

Location:Package overview

From:pnpm-lock.yamlnpm/web-ext@10.5.0npm/@pnpm/network.ca-file@1.0.2

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@pnpm/network.ca-file@1.0.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

WarnHigh
Obfuscated code: npm cheerio is 90.0% likely obfuscated

Confidence: 0.90

Location:Package overview

From:pnpm-lock.yamlnpm/web-ext@10.5.0npm/cheerio@1.2.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/cheerio@1.2.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

WarnHigh
Obfuscated code: npm css-tree is 90.0% likely obfuscated

Confidence: 0.90

Location:Package overview

From:pnpm-lock.yamlnpm/web-ext@10.5.0npm/css-tree@3.2.1

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/css-tree@3.2.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@ManthanNimodiya