diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml
index 43661d5091..823cb7e25e 100644
--- a/.github/workflows/build-browser.yml
+++ b/.github/workflows/build-browser.yml
@@ -123,11 +123,20 @@ jobs:
build-source:
- name: Build browser source
+ name: Build browser source - ${{matrix.license_type.readable}}
runs-on: ubuntu-22.04
needs:
- setup
- locales-test
+ strategy:
+ matrix:
+ license_type:
+ - include_bitwarden_license_folder: false
+ archive_name_prefix: ""
+ readable: "open source license"
+ - include_bitwarden_license_folder: true
+ archive_name_prefix: "bit-"
+ readable: "commercial license"
env:
_BUILD_NUMBER: ${{ needs.setup.outputs.adj_build_number }}
_NODE_VERSION: ${{ needs.setup.outputs.node_version }}
@@ -166,6 +175,12 @@ jobs:
mkdir -p browser-source/apps/browser
cp -r apps/browser/* browser-source/apps/browser
+ # Copy bitwarden_license/bit-browser to the Browser source directory
+ if [[ ${{matrix.license_type.include_bitwarden_license_folder}} == "true" ]]; then
+ mkdir -p browser-source/bitwarden_license/bit-browser
+ cp -r bitwarden_license/bit-browser/* browser-source/bitwarden_license/bit-browser
+ fi
+
# Copy libs to Browser source directory
mkdir browser-source/libs
cp -r libs/* browser-source/libs
@@ -175,13 +190,13 @@ jobs:
- name: Upload browser source
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
- name: browser-source-${{ env._BUILD_NUMBER }}.zip
+ name: ${{matrix.license_type.archive_name_prefix}}browser-source-${{ env._BUILD_NUMBER }}.zip
path: browser-source.zip
if-no-files-found: error
build:
- name: Build
+ name: Build ${{ matrix.browser.name }} - ${{ matrix.license_type.readable }}
runs-on: ubuntu-22.04
needs:
- setup
@@ -192,25 +207,38 @@ jobs:
_NODE_VERSION: ${{ needs.setup.outputs.node_version }}
strategy:
matrix:
- include:
+ license_type:
+ - build_prefix: ""
+ artifact_prefix: ""
+ source_archive_name_prefix: ""
+ archive_name_prefix: ""
+ npm_command_prefix: "dist:"
+ readable: "open source license"
+ - build_prefix: "bit-"
+ artifact_prefix: "bit-"
+ source_archive_name_prefix: "bit-"
+ archive_name_prefix: "bit-"
+ npm_command_prefix: "dist:bit:"
+ readable: "commercial license"
+ browser:
- name: "chrome"
- npm_command: "dist:chrome"
+ npm_command_suffix: "chrome"
archive_name: "dist-chrome.zip"
artifact_name: "dist-chrome-MV3"
- name: "edge"
- npm_command: "dist:edge"
+ npm_command_suffix: "edge"
archive_name: "dist-edge.zip"
artifact_name: "dist-edge-MV3"
- name: "firefox"
- npm_command: "dist:firefox"
+ npm_command_suffix: "firefox"
archive_name: "dist-firefox.zip"
artifact_name: "dist-firefox"
- name: "firefox-mv3"
- npm_command: "dist:firefox:mv3"
+ npm_command_suffix: "firefox:mv3"
archive_name: "dist-firefox.zip"
artifact_name: "DO-NOT-USE-FOR-PROD-dist-firefox-MV3"
- name: "opera-mv3"
- npm_command: "dist:opera:mv3"
+ npm_command_suffix: "opera:mv3"
archive_name: "dist-opera.zip"
artifact_name: "dist-opera-MV3"
steps:
@@ -234,7 +262,7 @@ jobs:
- name: Download browser source
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
- name: browser-source-${{ env._BUILD_NUMBER }}.zip
+ name: ${{matrix.license_type.source_archive_name_prefix}}browser-source-${{ env._BUILD_NUMBER }}.zip
- name: Unzip browser source artifact
run: |
@@ -264,7 +292,7 @@ jobs:
run: npm link ../sdk-internal
- name: Check source file size
- if: ${{ startsWith(matrix.name, 'firefox') }}
+ if: ${{ startsWith(matrix.browser.name, 'firefox') }}
run: |
# Declare variable as indexed array
declare -a FILES
@@ -287,19 +315,19 @@ jobs:
fi
- name: Build extension
- run: npm run ${{ matrix.npm_command }}
+ run: npm run ${{matrix.license_type.npm_command_prefix}}${{ matrix.browser.npm_command_suffix }}
working-directory: browser-source/apps/browser
- name: Upload extension artifact
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
- name: ${{ matrix.artifact_name }}-${{ env._BUILD_NUMBER }}.zip
- path: browser-source/apps/browser/dist/${{ matrix.archive_name }}
+ name: ${{ matrix.license_type.artifact_prefix }}${{ matrix.browser.artifact_name }}-${{ env._BUILD_NUMBER }}.zip
+ path: browser-source/apps/browser/dist/${{matrix.license_type.archive_name_prefix}}${{ matrix.browser.archive_name }}
if-no-files-found: error
build-safari:
- name: Build Safari
+ name: Build Safari - ${{ matrix.license_type.readable }}
runs-on: macos-13
permissions:
contents: read
@@ -308,6 +336,19 @@ jobs:
- setup
- locales-test
if: ${{ needs.setup.outputs.has_secrets == 'true' }}
+ strategy:
+ matrix:
+ license_type:
+ - build_prefix: ""
+ artifact_prefix: ""
+ archive_name_prefix: ""
+ npm_command_prefix: "dist:"
+ readable: "open source license"
+ - build_prefix: "bit-"
+ artifact_prefix: "bit-"
+ archive_name_prefix: "bit-"
+ npm_command_prefix: "dist:bit:"
+ readable: "commercial license"
env:
_BUILD_NUMBER: ${{ needs.setup.outputs.adj_build_number }}
_NODE_VERSION: ${{ needs.setup.outputs.node_version }}
@@ -433,21 +474,21 @@ jobs:
npm link ../sdk-internal
- name: Build Safari extension
- run: npm run dist:safari
+ run: npm run ${{matrix.license_type.npm_command_prefix}}safari
working-directory: apps/browser
- name: Zip Safari build artifact
run: |
cd apps/browser/dist
- zip dist-safari.zip ./Safari/**/build/Release/safari.appex -r
+ zip ${{matrix.license_type.archive_name_prefix }}dist-safari.zip ./Safari/**/build/Release/safari.appex -r
pwd
ls -la
- name: Upload Safari artifact
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
- name: dist-safari-${{ env._BUILD_NUMBER }}.zip
- path: apps/browser/dist/dist-safari.zip
+ name: ${{matrix.license_type.archive_name_prefix}}dist-safari-${{ env._BUILD_NUMBER }}.zip
+ path: apps/browser/dist/${{matrix.license_type.archive_name_prefix}}dist-safari.zip
if-no-files-found: error
crowdin-push:
diff --git a/apps/browser/package.json b/apps/browser/package.json
index 3cfc437722..bfa152f236 100644
--- a/apps/browser/package.json
+++ b/apps/browser/package.json
@@ -3,32 +3,58 @@
"version": "2025.8.2",
"scripts": {
"build": "npm run build:chrome",
+ "build:bit": "npm run build:bit:chrome",
"build:chrome": "cross-env BROWSER=chrome MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack",
+ "build:bit:chrome": "cross-env BROWSER=chrome MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-browser/webpack.config.js",
"build:edge": "cross-env BROWSER=edge MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack",
+ "build:bit:edge": "cross-env BROWSER=edge MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-browser/webpack.config.js",
"build:firefox": "cross-env BROWSER=firefox NODE_OPTIONS=\"--max-old-space-size=8192\" webpack",
+ "build:bit:firefox": "cross-env BROWSER=firefox NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-browser/webpack.config.js",
"build:opera": "cross-env BROWSER=opera MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack",
+ "build:bit:opera": "cross-env BROWSER=opera MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-browser/webpack.config.js",
"build:safari": "cross-env BROWSER=safari NODE_OPTIONS=\"--max-old-space-size=8192\" webpack",
+ "build:bit:safari": "cross-env BROWSER=safari NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-browser/webpack.config.js",
"build:watch": "npm run build:watch:chrome",
"build:watch:chrome": "npm run build:chrome -- --watch",
+ "build:bit:watch:chrome": "npm run build:bit:chrome -- --watch",
"build:watch:edge": "npm run build:edge -- --watch",
+ "build:bit:watch:edge": "npm run build:bit:edge -- --watch",
"build:watch:firefox": "npm run build:firefox -- --watch",
+ "build:bit:watch:firefox": "npm run build:bit:firefox -- --watch",
"build:watch:opera": "npm run build:opera -- --watch",
+ "build:bit:watch:opera": "npm run build:bit:opera -- --watch",
"build:watch:safari": "npm run build:safari -- --watch",
- "build:watch:firefox:mv3": "cross-env MANIFEST_VERSION=3 npm run build:firefox -- --watch",
- "build:watch:safari:mv3": "cross-env MANIFEST_VERSION=3 npm run build:safari -- --watch",
+ "build:bit:watch:safari": "npm run build:bit:safari -- --watch",
+ "build:watch:firefox:mv3": "cross-env MANIFEST_VERSION=3 npm run build:watch:firefox",
+ "build:bit:watch:firefox:mv3": "cross-env MANIFEST_VERSION=3 npm run build:bit:watch:firefox",
+ "build:watch:safari:mv3": "cross-env MANIFEST_VERSION=3 npm run build:watch:safari",
+ "build:bit:watch:safari:mv3": "cross-env MANIFEST_VERSION=3 npm run build:bit:watch:safari",
"build:prod:chrome": "cross-env NODE_ENV=production npm run build:chrome",
+ "build:bit:prod:chrome": "cross-env NODE_ENV=production npm run build:bit:chrome",
"build:prod:edge": "cross-env NODE_ENV=production npm run build:edge",
+ "build:bit:prod:edge": "cross-env NODE_ENV=production npm run build:bit:edge",
"build:prod:firefox": "cross-env NODE_ENV=production npm run build:firefox",
+ "build:bit:prod:firefox": "cross-env NODE_ENV=production npm run build:bit:firefox",
"build:prod:opera": "cross-env NODE_ENV=production npm run build:opera",
+ "build:bit:prod:opera": "cross-env NODE_ENV=production npm run build:bit:opera",
"build:prod:safari": "cross-env NODE_ENV=production npm run build:safari",
+ "build:bit:prod:safari": "cross-env NODE_ENV=production npm run build:bit:safari",
"dist:chrome": "npm run build:prod:chrome && mkdir -p dist && ./scripts/compress.sh dist-chrome.zip",
+ "dist:bit:chrome": "npm run build:bit:prod:chrome && mkdir -p dist && ./scripts/compress.sh bit-dist-chrome.zip",
"dist:edge": "npm run build:prod:edge && mkdir -p dist && ./scripts/compress.sh dist-edge.zip",
+ "dist:bit:edge": "npm run build:bit:prod:edge && mkdir -p dist && ./scripts/compress.sh bit-dist-edge.zip",
"dist:firefox": "npm run build:prod:firefox && mkdir -p dist && ./scripts/compress.sh dist-firefox.zip",
+ "dist:bit:firefox": "npm run build:bit:prod:firefox && mkdir -p dist && ./scripts/compress.sh bit-dist-firefox.zip",
"dist:opera": "npm run build:prod:opera && mkdir -p dist && ./scripts/compress.sh dist-opera.zip",
+ "dist:bit:opera": "npm run build:bit:prod:opera && mkdir -p dist && ./scripts/compress.sh bit-dist-opera.zip",
"dist:safari": "npm run build:prod:safari && ./scripts/package-safari.ps1",
+ "dist:bit:safari": "npm run build:bit:prod:safari && ./scripts/package-safari.ps1",
"dist:firefox:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:firefox",
+ "dist:bit:firefox:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:bit:firefox",
"dist:opera:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:opera",
+ "dist:bit:opera:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:bit:opera",
"dist:safari:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:safari",
+ "dist:bit:safari:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:bit:safari",
"test": "jest",
"test:watch": "jest --watch",
"test:watch:all": "jest --watchAll",
diff --git a/apps/browser/src/popup/app.component.html b/apps/browser/src/popup/app.component.html
new file mode 100644
index 0000000000..3d81354ca3
--- /dev/null
+++ b/apps/browser/src/popup/app.component.html
@@ -0,0 +1,20 @@
+@if (showSdkWarning | async) {
+
+} @else {
+
+
+
+
+}
diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts
index ee75dbaf7a..4f46f889ea 100644
--- a/apps/browser/src/popup/app.component.ts
+++ b/apps/browser/src/popup/app.component.ts
@@ -57,28 +57,7 @@ import { DesktopSyncVerificationDialogComponent } from "./components/desktop-syn
selector: "app-root",
styles: [],
animations: [routerTransition],
- template: `
- @if (showSdkWarning | async) {
-
- } @else {
-
-
-
-
- }
- `,
+ templateUrl: "app.component.html",
standalone: false,
})
export class AppComponent implements OnInit, OnDestroy {
diff --git a/apps/browser/src/popup/main.ts b/apps/browser/src/popup/main.ts
index bb975f48e5..fa6a07d031 100644
--- a/apps/browser/src/popup/main.ts
+++ b/apps/browser/src/popup/main.ts
@@ -4,13 +4,10 @@ import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { PopupSizeService } from "../platform/popup/layout/popup-size.service";
import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service";
-// eslint-disable-next-line @typescript-eslint/no-require-imports
-require("./scss/popup.scss");
-// eslint-disable-next-line @typescript-eslint/no-require-imports
-require("./scss/tailwind.css");
-
import { AppModule } from "./app.module";
+import "./scss";
+
// We put these first to minimize the delay in window changing.
PopupSizeService.initBodyWidthFromLocalStorage();
// Should be removed once we deprecate support for Safari 16.0 and older. See Jira ticket [PM-1861]
diff --git a/apps/browser/src/popup/scss/index.ts b/apps/browser/src/popup/scss/index.ts
new file mode 100644
index 0000000000..abb62fa0dd
--- /dev/null
+++ b/apps/browser/src/popup/scss/index.ts
@@ -0,0 +1,4 @@
+// eslint-disable-next-line @typescript-eslint/no-require-imports
+require("./popup.scss");
+// eslint-disable-next-line @typescript-eslint/no-require-imports
+require("./tailwind.css");
diff --git a/apps/browser/webpack.base.js b/apps/browser/webpack.base.js
new file mode 100644
index 0000000000..872da6600b
--- /dev/null
+++ b/apps/browser/webpack.base.js
@@ -0,0 +1,443 @@
+const path = require("path");
+const webpack = require("webpack");
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+const CopyWebpackPlugin = require("copy-webpack-plugin");
+const MiniCssExtractPlugin = require("mini-css-extract-plugin");
+const { AngularWebpackPlugin } = require("@ngtools/webpack");
+const TerserPlugin = require("terser-webpack-plugin");
+const { TsconfigPathsPlugin } = require("tsconfig-paths-webpack-plugin");
+const configurator = require("./config/config");
+const manifest = require("./webpack/manifest");
+const AngularCheckPlugin = require("./webpack/angular-check");
+
+module.exports.getEnv = function getEnv() {
+ const ENV = (process.env.ENV = process.env.NODE_ENV);
+ const manifestVersion = process.env.MANIFEST_VERSION == 3 ? 3 : 2;
+ const browser = process.env.BROWSER ?? "chrome";
+
+ return { ENV, manifestVersion, browser };
+};
+
+/**
+ * @param {{
+ * configName: string;
+ * popup: {
+ * entry: string;
+ * entryModule: string;
+ * };
+ * background: {
+ * entry: string;
+ * };
+ * tsConfig: string;
+ * additionalEntries?: { [outputPath: string]: string }
+ * }} params - The input parameters for building the config.
+ */
+module.exports.buildConfig = function buildConfig(params) {
+ if (process.env.NODE_ENV == null) {
+ process.env.NODE_ENV = "development";
+ }
+
+ const { ENV, manifestVersion, browser } = module.exports.getEnv();
+
+ console.log(`Building Manifest Version ${manifestVersion} app - ${params.configName} version`);
+
+ const envConfig = configurator.load(ENV);
+ configurator.log(envConfig);
+
+ const moduleRules = [
+ {
+ test: /\.(html)$/,
+ loader: "html-loader",
+ },
+ {
+ test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
+ exclude: /loading.svg/,
+ generator: {
+ filename: "popup/fonts/[name].[contenthash][ext]",
+ },
+ type: "asset/resource",
+ },
+ {
+ test: /\.(jpe?g|png|gif|svg)$/i,
+ exclude: /.*(bwi-font|glyphicons-halflings-regular)\.svg/,
+ generator: {
+ filename: "popup/images/[name][ext]",
+ },
+ type: "asset/resource",
+ },
+ {
+ test: /\.css$/,
+ use: [
+ {
+ loader: MiniCssExtractPlugin.loader,
+ },
+ "css-loader",
+ "resolve-url-loader",
+ {
+ loader: "postcss-loader",
+ options: {
+ sourceMap: true,
+ },
+ },
+ ],
+ },
+ {
+ test: /\.scss$/,
+ use: [
+ {
+ loader: MiniCssExtractPlugin.loader,
+ },
+ "css-loader",
+ "resolve-url-loader",
+ {
+ loader: "sass-loader",
+ options: {
+ sourceMap: true,
+ },
+ },
+ ],
+ },
+ {
+ test: /\.[cm]?js$/,
+ use: [
+ {
+ loader: "babel-loader",
+ options: {
+ configFile: "../../babel.config.json",
+ cacheDirectory: ENV === "development",
+ compact: ENV !== "development",
+ },
+ },
+ ],
+ },
+ {
+ test: /\.[jt]sx?$/,
+ loader: "@ngtools/webpack",
+ },
+ ];
+
+ const requiredPlugins = [
+ new webpack.DefinePlugin({
+ "process.env": {
+ ENV: JSON.stringify(ENV),
+ },
+ }),
+ new webpack.EnvironmentPlugin({
+ FLAGS: envConfig.flags,
+ DEV_FLAGS: ENV === "development" ? envConfig.devFlags : {},
+ }),
+ ];
+
+ const plugins = [
+ new HtmlWebpackPlugin({
+ template: "./src/popup/index.ejs",
+ filename: "popup/index.html",
+ chunks: ["popup/polyfills", "popup/vendor-angular", "popup/vendor", "popup/main"],
+ browser: browser,
+ }),
+ new HtmlWebpackPlugin({
+ template: "./src/autofill/notification/bar.html",
+ filename: "notification/bar.html",
+ chunks: ["notification/bar"],
+ }),
+ new HtmlWebpackPlugin({
+ template: "./src/autofill/overlay/inline-menu/pages/button/button.html",
+ filename: "overlay/menu-button.html",
+ chunks: ["overlay/menu-button"],
+ }),
+ new HtmlWebpackPlugin({
+ template: "./src/autofill/overlay/inline-menu/pages/list/list.html",
+ filename: "overlay/menu-list.html",
+ chunks: ["overlay/menu-list"],
+ }),
+ new HtmlWebpackPlugin({
+ template: "./src/autofill/overlay/inline-menu/pages/menu-container/menu-container.html",
+ filename: "overlay/menu.html",
+ chunks: ["overlay/menu"],
+ }),
+ new CopyWebpackPlugin({
+ patterns: [
+ {
+ from: manifestVersion == 3 ? "./src/manifest.v3.json" : "./src/manifest.json",
+ to: "manifest.json",
+ transform: manifest.transform(browser),
+ },
+ { from: "./src/managed_schema.json", to: "managed_schema.json" },
+ { from: "./src/_locales", to: "_locales" },
+ { from: "./src/images", to: "images" },
+ { from: "./src/popup/images", to: "popup/images" },
+ { from: "./src/autofill/content/autofill.css", to: "content" },
+ ],
+ }),
+ new MiniCssExtractPlugin({
+ filename: "[name].css",
+ chunkFilename: "chunk-[id].css",
+ }),
+ new AngularWebpackPlugin({
+ tsconfig: params.tsConfig,
+ entryModule: params.popup.entryModule,
+ sourceMap: true,
+ }),
+ new webpack.ProvidePlugin({
+ process: "process/browser.js",
+ }),
+ new webpack.SourceMapDevToolPlugin({
+ exclude: [/content\/.*/, /notification\/.*/, /overlay\/.*/],
+ filename: "[file].map",
+ }),
+ ...requiredPlugins,
+ ];
+
+ /**
+ * @type {import("webpack").Configuration}
+ * This config compiles everything but the background
+ */
+ const mainConfig = {
+ name: "main",
+ mode: ENV,
+ devtool: false,
+ entry: {
+ "popup/polyfills": "./src/popup/polyfills.ts",
+ "popup/main": params.popup.entry,
+ "content/trigger-autofill-script-injection":
+ "./src/autofill/content/trigger-autofill-script-injection.ts",
+ "content/bootstrap-autofill": "./src/autofill/content/bootstrap-autofill.ts",
+ "content/bootstrap-autofill-overlay": "./src/autofill/content/bootstrap-autofill-overlay.ts",
+ "content/bootstrap-autofill-overlay-menu":
+ "./src/autofill/content/bootstrap-autofill-overlay-menu.ts",
+ "content/bootstrap-autofill-overlay-notifications":
+ "./src/autofill/content/bootstrap-autofill-overlay-notifications.ts",
+ "content/autofiller": "./src/autofill/content/autofiller.ts",
+ "content/auto-submit-login": "./src/autofill/content/auto-submit-login.ts",
+ "content/contextMenuHandler": "./src/autofill/content/context-menu-handler.ts",
+ "content/content-message-handler": "./src/autofill/content/content-message-handler.ts",
+ "content/fido2-content-script": "./src/autofill/fido2/content/fido2-content-script.ts",
+ "content/fido2-page-script": "./src/autofill/fido2/content/fido2-page-script.ts",
+ "content/ipc-content-script": "./src/platform/ipc/content/ipc-content-script.ts",
+ "notification/bar": "./src/autofill/notification/bar.ts",
+ "overlay/menu-button":
+ "./src/autofill/overlay/inline-menu/pages/button/bootstrap-autofill-inline-menu-button.ts",
+ "overlay/menu-list":
+ "./src/autofill/overlay/inline-menu/pages/list/bootstrap-autofill-inline-menu-list.ts",
+ "overlay/menu":
+ "./src/autofill/overlay/inline-menu/pages/menu-container/bootstrap-autofill-inline-menu-container.ts",
+ "content/send-on-installed-message": "./src/vault/content/send-on-installed-message.ts",
+ "content/send-popup-open-message": "./src/vault/content/send-popup-open-message.ts",
+ ...params.additionalEntries,
+ },
+ cache:
+ ENV !== "development"
+ ? false
+ : {
+ type: "filesystem",
+ name: "main-cache",
+ cacheDirectory: path.resolve(
+ __dirname,
+ "../../node_modules/.cache/webpack-browser-main",
+ ),
+ buildDependencies: {
+ config: [__filename],
+ },
+ },
+ snapshot: {
+ unmanagedPaths: [path.resolve(__dirname, "../../node_modules/@bitwarden/")],
+ },
+ optimization: {
+ minimize: ENV !== "development",
+ minimizer: [
+ new TerserPlugin({
+ exclude: [/content\/.*/, /notification\/.*/, /overlay\/.*/],
+ terserOptions: {
+ // Replicate Angular CLI behaviour
+ compress: {
+ global_defs: {
+ ngDevMode: false,
+ ngI18nClosureMode: false,
+ },
+ },
+ },
+ }),
+ ],
+ splitChunks: {
+ cacheGroups: {
+ commons: {
+ test(module, chunks) {
+ return (
+ module.resource != null &&
+ module.resource.includes(`${path.sep}node_modules${path.sep}`) &&
+ !module.resource.includes(`${path.sep}node_modules${path.sep}@angular${path.sep}`)
+ );
+ },
+ name: "popup/vendor",
+ chunks: (chunk) => {
+ return chunk.name === "popup/main";
+ },
+ },
+ angular: {
+ test(module, chunks) {
+ return (
+ module.resource != null &&
+ module.resource.includes(`${path.sep}node_modules${path.sep}@angular${path.sep}`)
+ );
+ },
+ name: "popup/vendor-angular",
+ chunks: (chunk) => {
+ return chunk.name === "popup/main";
+ },
+ },
+ },
+ },
+ },
+ resolve: {
+ extensions: [".ts", ".js"],
+ symlinks: false,
+ modules: [path.resolve("../../node_modules")],
+ fallback: {
+ assert: false,
+ buffer: require.resolve("buffer/"),
+ util: require.resolve("util/"),
+ url: require.resolve("url/"),
+ fs: false,
+ path: require.resolve("path-browserify"),
+ },
+ cache: true,
+ },
+ output: {
+ filename: "[name].js",
+ chunkFilename: "assets/[name].js",
+ webassemblyModuleFilename: "assets/[modulehash].wasm",
+ path: path.resolve(__dirname, "build"),
+ clean: true,
+ },
+ module: {
+ rules: moduleRules,
+ },
+ experiments: {
+ asyncWebAssembly: true,
+ },
+ plugins: plugins,
+ };
+
+ /**
+ * @type {import("webpack").Configuration[]}
+ */
+ const configs = [];
+
+ if (manifestVersion == 2) {
+ mainConfig.optimization.splitChunks.cacheGroups.commons2 = {
+ test: /[\\/]node_modules[\\/]/,
+ name: "vendor",
+ chunks: (chunk) => {
+ return chunk.name === "background";
+ },
+ };
+
+ // Manifest V2 uses Background Pages which requires a html page.
+ mainConfig.plugins.push(
+ new HtmlWebpackPlugin({
+ template: "./src/platform/background.html",
+ filename: "background.html",
+ chunks: ["vendor", "background"],
+ }),
+ );
+
+ // Manifest V2 background pages can be run through the regular build pipeline.
+ // Since it's a standard webpage.
+ mainConfig.entry.background = params.background.entry;
+ mainConfig.entry["content/fido2-page-script-delay-append-mv2"] =
+ "./src/autofill/fido2/content/fido2-page-script-delay-append.mv2.ts";
+
+ configs.push(mainConfig);
+ } else {
+ // Firefox does not use the offscreen API
+ if (browser !== "firefox") {
+ mainConfig.entry["offscreen-document/offscreen-document"] =
+ "./src/platform/offscreen-document/offscreen-document.ts";
+
+ mainConfig.plugins.push(
+ new HtmlWebpackPlugin({
+ template: "./src/platform/offscreen-document/index.html",
+ filename: "offscreen-document/index.html",
+ chunks: ["offscreen-document/offscreen-document"],
+ }),
+ );
+ }
+
+ const target = browser === "firefox" ? "web" : "webworker";
+
+ /**
+ * @type {import("webpack").Configuration}
+ */
+ const backgroundConfig = {
+ name: "background",
+ mode: ENV,
+ devtool: false,
+ entry: params.background.entry,
+ target: target,
+ output: {
+ filename: "background.js",
+ path: path.resolve(__dirname, "build"),
+ },
+ module: {
+ rules: [
+ {
+ test: /\.tsx?$/,
+ loader: "ts-loader",
+ },
+ ],
+ },
+ cache:
+ ENV !== "development"
+ ? false
+ : {
+ type: "filesystem",
+ name: "background-cache",
+ cacheDirectory: path.resolve(
+ __dirname,
+ "../../node_modules/.cache/webpack-browser-background",
+ ),
+ buildDependencies: {
+ config: [__filename],
+ },
+ },
+ snapshot: {
+ unmanagedPaths: [path.resolve(__dirname, "../../node_modules/@bitwarden/")],
+ },
+ experiments: {
+ asyncWebAssembly: true,
+ },
+ resolve: {
+ extensions: [".ts", ".js"],
+ symlinks: false,
+ modules: [path.resolve("../../node_modules")],
+ plugins: [new TsconfigPathsPlugin()],
+ fallback: {
+ fs: false,
+ path: require.resolve("path-browserify"),
+ },
+ cache: true,
+ },
+ dependencies: ["main"],
+ plugins: [...requiredPlugins, new AngularCheckPlugin()],
+ };
+
+ // Safari's desktop build process requires a background.html and vendor.js file to exist
+ // within the root of the extension. This is a workaround to allow us to build Safari
+ // for manifest v2 and v3 without modifying the desktop project structure.
+ if (browser === "safari") {
+ backgroundConfig.plugins.push(
+ new CopyWebpackPlugin({
+ patterns: [
+ { from: "./src/safari/mv3/fake-background.html", to: "background.html" },
+ { from: "./src/safari/mv3/fake-vendor.js", to: "vendor.js" },
+ ],
+ }),
+ );
+ }
+
+ configs.push(mainConfig);
+ configs.push(backgroundConfig);
+ }
+
+ return configs;
+};
diff --git a/apps/browser/webpack.config.js b/apps/browser/webpack.config.js
index e62f90354d..9eac990ab6 100644
--- a/apps/browser/webpack.config.js
+++ b/apps/browser/webpack.config.js
@@ -1,416 +1,13 @@
-const path = require("path");
-const webpack = require("webpack");
-const HtmlWebpackPlugin = require("html-webpack-plugin");
-const CopyWebpackPlugin = require("copy-webpack-plugin");
-const MiniCssExtractPlugin = require("mini-css-extract-plugin");
-const { AngularWebpackPlugin } = require("@ngtools/webpack");
-const TerserPlugin = require("terser-webpack-plugin");
-const { TsconfigPathsPlugin } = require("tsconfig-paths-webpack-plugin");
-const configurator = require("./config/config");
-const manifest = require("./webpack/manifest");
-const AngularCheckPlugin = require("./webpack/angular-check");
+const { buildConfig } = require("./webpack.base");
-if (process.env.NODE_ENV == null) {
- process.env.NODE_ENV = "development";
-}
-const ENV = (process.env.ENV = process.env.NODE_ENV);
-const manifestVersion = process.env.MANIFEST_VERSION == 3 ? 3 : 2;
-const browser = process.env.BROWSER ?? "chrome";
-
-console.log(`Building Manifest Version ${manifestVersion} app`);
-
-const envConfig = configurator.load(ENV);
-configurator.log(envConfig);
-
-const moduleRules = [
- {
- test: /\.(html)$/,
- loader: "html-loader",
- },
- {
- test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
- exclude: /loading.svg/,
- generator: {
- filename: "popup/fonts/[name].[contenthash][ext]",
- },
- type: "asset/resource",
- },
- {
- test: /\.(jpe?g|png|gif|svg)$/i,
- exclude: /.*(bwi-font|glyphicons-halflings-regular)\.svg/,
- generator: {
- filename: "popup/images/[name][ext]",
- },
- type: "asset/resource",
- },
- {
- test: /\.css$/,
- use: [
- {
- loader: MiniCssExtractPlugin.loader,
- },
- "css-loader",
- "resolve-url-loader",
- {
- loader: "postcss-loader",
- options: {
- sourceMap: true,
- },
- },
- ],
- },
- {
- test: /\.scss$/,
- use: [
- {
- loader: MiniCssExtractPlugin.loader,
- },
- "css-loader",
- "resolve-url-loader",
- {
- loader: "sass-loader",
- options: {
- sourceMap: true,
- },
- },
- ],
- },
- {
- test: /\.[cm]?js$/,
- use: [
- {
- loader: "babel-loader",
- options: {
- configFile: "../../babel.config.json",
- cacheDirectory: ENV === "development",
- compact: ENV !== "development",
- },
- },
- ],
- },
- {
- test: /\.[jt]sx?$/,
- loader: "@ngtools/webpack",
- },
-];
-
-const requiredPlugins = [
- new webpack.DefinePlugin({
- "process.env": {
- ENV: JSON.stringify(ENV),
- },
- }),
- new webpack.EnvironmentPlugin({
- FLAGS: envConfig.flags,
- DEV_FLAGS: ENV === "development" ? envConfig.devFlags : {},
- }),
-];
-
-const plugins = [
- new HtmlWebpackPlugin({
- template: "./src/popup/index.ejs",
- filename: "popup/index.html",
- chunks: ["popup/polyfills", "popup/vendor-angular", "popup/vendor", "popup/main"],
- browser: browser,
- }),
- new HtmlWebpackPlugin({
- template: "./src/autofill/notification/bar.html",
- filename: "notification/bar.html",
- chunks: ["notification/bar"],
- }),
- new HtmlWebpackPlugin({
- template: "./src/autofill/overlay/inline-menu/pages/button/button.html",
- filename: "overlay/menu-button.html",
- chunks: ["overlay/menu-button"],
- }),
- new HtmlWebpackPlugin({
- template: "./src/autofill/overlay/inline-menu/pages/list/list.html",
- filename: "overlay/menu-list.html",
- chunks: ["overlay/menu-list"],
- }),
- new HtmlWebpackPlugin({
- template: "./src/autofill/overlay/inline-menu/pages/menu-container/menu-container.html",
- filename: "overlay/menu.html",
- chunks: ["overlay/menu"],
- }),
- new CopyWebpackPlugin({
- patterns: [
- {
- from: manifestVersion == 3 ? "./src/manifest.v3.json" : "./src/manifest.json",
- to: "manifest.json",
- transform: manifest.transform(browser),
- },
- { from: "./src/managed_schema.json", to: "managed_schema.json" },
- { from: "./src/_locales", to: "_locales" },
- { from: "./src/images", to: "images" },
- { from: "./src/popup/images", to: "popup/images" },
- { from: "./src/autofill/content/autofill.css", to: "content" },
- ],
- }),
- new MiniCssExtractPlugin({
- filename: "[name].css",
- chunkFilename: "chunk-[id].css",
- }),
- new AngularWebpackPlugin({
- tsConfigPath: "tsconfig.json",
+module.exports = buildConfig({
+ configName: "OSS",
+ popup: {
+ entry: "./src/popup/main.ts",
entryModule: "src/popup/app.module#AppModule",
- sourceMap: true,
- }),
- new webpack.ProvidePlugin({
- process: "process/browser.js",
- }),
- new webpack.SourceMapDevToolPlugin({
- exclude: [/content\/.*/, /notification\/.*/, /overlay\/.*/],
- filename: "[file].map",
- }),
- ...requiredPlugins,
-];
-
-/**
- * @type {import("webpack").Configuration}
- * This config compiles everything but the background
- */
-const mainConfig = {
- name: "main",
- mode: ENV,
- devtool: false,
- entry: {
- "popup/polyfills": "./src/popup/polyfills.ts",
- "popup/main": "./src/popup/main.ts",
- "content/trigger-autofill-script-injection":
- "./src/autofill/content/trigger-autofill-script-injection.ts",
- "content/bootstrap-autofill": "./src/autofill/content/bootstrap-autofill.ts",
- "content/bootstrap-autofill-overlay": "./src/autofill/content/bootstrap-autofill-overlay.ts",
- "content/bootstrap-autofill-overlay-menu":
- "./src/autofill/content/bootstrap-autofill-overlay-menu.ts",
- "content/bootstrap-autofill-overlay-notifications":
- "./src/autofill/content/bootstrap-autofill-overlay-notifications.ts",
- "content/autofiller": "./src/autofill/content/autofiller.ts",
- "content/auto-submit-login": "./src/autofill/content/auto-submit-login.ts",
- "content/contextMenuHandler": "./src/autofill/content/context-menu-handler.ts",
- "content/content-message-handler": "./src/autofill/content/content-message-handler.ts",
- "content/fido2-content-script": "./src/autofill/fido2/content/fido2-content-script.ts",
- "content/fido2-page-script": "./src/autofill/fido2/content/fido2-page-script.ts",
- "content/ipc-content-script": "./src/platform/ipc/content/ipc-content-script.ts",
- "notification/bar": "./src/autofill/notification/bar.ts",
- "overlay/menu-button":
- "./src/autofill/overlay/inline-menu/pages/button/bootstrap-autofill-inline-menu-button.ts",
- "overlay/menu-list":
- "./src/autofill/overlay/inline-menu/pages/list/bootstrap-autofill-inline-menu-list.ts",
- "overlay/menu":
- "./src/autofill/overlay/inline-menu/pages/menu-container/bootstrap-autofill-inline-menu-container.ts",
- "content/send-on-installed-message": "./src/vault/content/send-on-installed-message.ts",
- "content/send-popup-open-message": "./src/vault/content/send-popup-open-message.ts",
},
- cache:
- ENV !== "development"
- ? false
- : {
- type: "filesystem",
- name: "main-cache",
- cacheDirectory: path.resolve(__dirname, "../../node_modules/.cache/webpack-browser-main"),
- buildDependencies: {
- config: [__filename],
- },
- },
- snapshot: {
- unmanagedPaths: [path.resolve(__dirname, "../../node_modules/@bitwarden/")],
- },
- optimization: {
- minimize: ENV !== "development",
- minimizer: [
- new TerserPlugin({
- exclude: [/content\/.*/, /notification\/.*/, /overlay\/.*/],
- terserOptions: {
- // Replicate Angular CLI behaviour
- compress: {
- global_defs: {
- ngDevMode: false,
- ngI18nClosureMode: false,
- },
- },
- },
- }),
- ],
- splitChunks: {
- cacheGroups: {
- commons: {
- test(module, chunks) {
- return (
- module.resource != null &&
- module.resource.includes(`${path.sep}node_modules${path.sep}`) &&
- !module.resource.includes(`${path.sep}node_modules${path.sep}@angular${path.sep}`)
- );
- },
- name: "popup/vendor",
- chunks: (chunk) => {
- return chunk.name === "popup/main";
- },
- },
- angular: {
- test(module, chunks) {
- return (
- module.resource != null &&
- module.resource.includes(`${path.sep}node_modules${path.sep}@angular${path.sep}`)
- );
- },
- name: "popup/vendor-angular",
- chunks: (chunk) => {
- return chunk.name === "popup/main";
- },
- },
- },
- },
- },
- resolve: {
- extensions: [".ts", ".js"],
- symlinks: false,
- modules: [path.resolve("../../node_modules")],
- fallback: {
- assert: false,
- buffer: require.resolve("buffer/"),
- util: require.resolve("util/"),
- url: require.resolve("url/"),
- fs: false,
- path: require.resolve("path-browserify"),
- },
- cache: true,
- },
- output: {
- filename: "[name].js",
- chunkFilename: "assets/[name].js",
- webassemblyModuleFilename: "assets/[modulehash].wasm",
- path: path.resolve(__dirname, "build"),
- clean: true,
- },
- module: {
- rules: moduleRules,
- },
- experiments: {
- asyncWebAssembly: true,
- },
- plugins: plugins,
-};
-
-/**
- * @type {import("webpack").Configuration[]}
- */
-const configs = [];
-
-if (manifestVersion == 2) {
- mainConfig.optimization.splitChunks.cacheGroups.commons2 = {
- test: /[\\/]node_modules[\\/]/,
- name: "vendor",
- chunks: (chunk) => {
- return chunk.name === "background";
- },
- };
-
- // Manifest V2 uses Background Pages which requires a html page.
- mainConfig.plugins.push(
- new HtmlWebpackPlugin({
- template: "./src/platform/background.html",
- filename: "background.html",
- chunks: ["vendor", "background"],
- }),
- );
-
- // Manifest V2 background pages can be run through the regular build pipeline.
- // Since it's a standard webpage.
- mainConfig.entry.background = "./src/platform/background.ts";
- mainConfig.entry["content/fido2-page-script-delay-append-mv2"] =
- "./src/autofill/fido2/content/fido2-page-script-delay-append.mv2.ts";
-
- configs.push(mainConfig);
-} else {
- // Firefox does not use the offscreen API
- if (browser !== "firefox") {
- mainConfig.entry["offscreen-document/offscreen-document"] =
- "./src/platform/offscreen-document/offscreen-document.ts";
-
- mainConfig.plugins.push(
- new HtmlWebpackPlugin({
- template: "./src/platform/offscreen-document/index.html",
- filename: "offscreen-document/index.html",
- chunks: ["offscreen-document/offscreen-document"],
- }),
- );
- }
-
- const target = browser === "firefox" ? "web" : "webworker";
-
- /**
- * @type {import("webpack").Configuration}
- */
- const backgroundConfig = {
- name: "background",
- mode: ENV,
- devtool: false,
+ background: {
entry: "./src/platform/background.ts",
- target: target,
- output: {
- filename: "background.js",
- path: path.resolve(__dirname, "build"),
- },
- module: {
- rules: [
- {
- test: /\.tsx?$/,
- loader: "ts-loader",
- },
- ],
- },
- cache:
- ENV !== "development"
- ? false
- : {
- type: "filesystem",
- name: "background-cache",
- cacheDirectory: path.resolve(
- __dirname,
- "../../node_modules/.cache/webpack-browser-background",
- ),
- buildDependencies: {
- config: [__filename],
- },
- },
- snapshot: {
- unmanagedPaths: [path.resolve(__dirname, "../../node_modules/@bitwarden/")],
- },
- experiments: {
- asyncWebAssembly: true,
- },
- resolve: {
- extensions: [".ts", ".js"],
- symlinks: false,
- modules: [path.resolve("../../node_modules")],
- plugins: [new TsconfigPathsPlugin()],
- fallback: {
- fs: false,
- path: require.resolve("path-browserify"),
- },
- cache: true,
- },
- dependencies: ["main"],
- plugins: [...requiredPlugins, new AngularCheckPlugin()],
- };
-
- // Safari's desktop build process requires a background.html and vendor.js file to exist
- // within the root of the extension. This is a workaround to allow us to build Safari
- // for manifest v2 and v3 without modifying the desktop project structure.
- if (browser === "safari") {
- backgroundConfig.plugins.push(
- new CopyWebpackPlugin({
- patterns: [
- { from: "./src/safari/mv3/fake-background.html", to: "background.html" },
- { from: "./src/safari/mv3/fake-vendor.js", to: "vendor.js" },
- ],
- }),
- );
- }
-
- configs.push(mainConfig);
- configs.push(backgroundConfig);
-}
-
-module.exports = configs;
+ },
+ tsConfig: "tsconfig.json",
+});
diff --git a/bitwarden_license/bit-browser/jest.config.js b/bitwarden_license/bit-browser/jest.config.js
new file mode 100644
index 0000000000..5451c15e47
--- /dev/null
+++ b/bitwarden_license/bit-browser/jest.config.js
@@ -0,0 +1,22 @@
+const { pathsToModuleNameMapper } = require("ts-jest");
+
+const { compilerOptions } = require("../../tsconfig.base");
+
+const sharedConfig = require("../../libs/shared/jest.config.angular");
+
+/** @type {import('jest').Config} */
+module.exports = {
+ ...sharedConfig,
+ setupFilesAfterEnv: ["../../apps/browser/test.setup.ts"],
+ moduleNameMapper: pathsToModuleNameMapper(
+ {
+ "@bitwarden/common/spec": ["libs/common/spec"],
+ "@bitwarden/common": ["libs/common/src/*"],
+ "@bitwarden/admin-console/common": ["libs/admin-console/src/common"],
+ ...(compilerOptions?.paths ?? {}),
+ },
+ {
+ prefix: "/../../",
+ },
+ ),
+};
diff --git a/bitwarden_license/bit-browser/src/background/main.background.ts b/bitwarden_license/bit-browser/src/background/main.background.ts
new file mode 100644
index 0000000000..48efc8099e
--- /dev/null
+++ b/bitwarden_license/bit-browser/src/background/main.background.ts
@@ -0,0 +1,9 @@
+import OssMainBackground from "@bitwarden/browser/background/main.background";
+
+export default class MainBackground {
+ private ossMain = new OssMainBackground();
+
+ async bootstrap() {
+ await this.ossMain.bootstrap();
+ }
+}
diff --git a/bitwarden_license/bit-browser/src/platform/background.ts b/bitwarden_license/bit-browser/src/platform/background.ts
new file mode 100644
index 0000000000..0cd9b3285e
--- /dev/null
+++ b/bitwarden_license/bit-browser/src/platform/background.ts
@@ -0,0 +1,7 @@
+import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
+
+import MainBackground from "../background/main.background";
+
+const logService = new ConsoleLogService(false);
+const bitwardenMain = ((self as any).bitwardenMain = new MainBackground());
+bitwardenMain.bootstrap().catch((error) => logService.error(error));
diff --git a/bitwarden_license/bit-browser/src/popup/app-routing.module.ts b/bitwarden_license/bit-browser/src/popup/app-routing.module.ts
new file mode 100644
index 0000000000..7cabfb68e8
--- /dev/null
+++ b/bitwarden_license/bit-browser/src/popup/app-routing.module.ts
@@ -0,0 +1,10 @@
+import { NgModule } from "@angular/core";
+import { RouterModule, Routes } from "@angular/router";
+
+const routes: Routes = [];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule],
+})
+export class AppRoutingModule {}
diff --git a/bitwarden_license/bit-browser/src/popup/app.component.ts b/bitwarden_license/bit-browser/src/popup/app.component.ts
new file mode 100644
index 0000000000..339681d66d
--- /dev/null
+++ b/bitwarden_license/bit-browser/src/popup/app.component.ts
@@ -0,0 +1,14 @@
+import { Component, OnInit } from "@angular/core";
+
+import { AppComponent as BaseAppComponent } from "@bitwarden/browser/popup/app.component";
+
+@Component({
+ selector: "app-root",
+ templateUrl: "../../../../apps/browser/src/popup/app.component.html",
+ standalone: false,
+})
+export class AppComponent extends BaseAppComponent implements OnInit {
+ ngOnInit() {
+ return super.ngOnInit();
+ }
+}
diff --git a/bitwarden_license/bit-browser/src/popup/app.module.ts b/bitwarden_license/bit-browser/src/popup/app.module.ts
new file mode 100644
index 0000000000..589d779472
--- /dev/null
+++ b/bitwarden_license/bit-browser/src/popup/app.module.ts
@@ -0,0 +1,39 @@
+import { OverlayModule } from "@angular/cdk/overlay";
+import { CommonModule } from "@angular/common";
+import { NgModule } from "@angular/core";
+import { RouterModule } from "@angular/router";
+
+import { JslibModule } from "@bitwarden/angular/jslib.module";
+// import { AppRoutingAnimationsModule } from "@bitwarden/browser/popup/app-routing-animations";
+import { AppRoutingModule as OssRoutingModule } from "@bitwarden/browser/popup/app-routing.module";
+import { AppModule as OssModule } from "@bitwarden/browser/popup/app.module";
+// import { WildcardRoutingModule } from "@bitwarden/browser/popup/wildcard-routing.module";
+
+import { AppRoutingModule } from "./app-routing.module";
+import { AppComponent } from "./app.component";
+/**
+ * This is the AppModule for the commercial version of Bitwarden.
+ * `apps/browser/app.module.ts` contains the OSS version.
+ *
+ * You probably do not want to modify this file. Consider editing `oss.module.ts` instead.
+ */
+@NgModule({
+ imports: [
+ CommonModule,
+ OverlayModule,
+ OssModule,
+ JslibModule,
+ // BrowserAnimationsModule,
+ // FormsModule,
+ // ReactiveFormsModule,
+ // CoreModule,
+ // DragDropModule,
+ AppRoutingModule,
+ OssRoutingModule,
+ RouterModule,
+ // WildcardRoutingModule, // Needs to be last to catch all non-existing routes
+ ],
+ declarations: [AppComponent],
+ bootstrap: [AppComponent],
+})
+export class AppModule {}
diff --git a/bitwarden_license/bit-browser/src/popup/main.ts b/bitwarden_license/bit-browser/src/popup/main.ts
new file mode 100644
index 0000000000..2c8c68e1a2
--- /dev/null
+++ b/bitwarden_license/bit-browser/src/popup/main.ts
@@ -0,0 +1,26 @@
+import { enableProdMode } from "@angular/core";
+import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
+
+import { PopupSizeService } from "@bitwarden/browser/platform/popup/layout/popup-size.service";
+import { BrowserPlatformUtilsService } from "@bitwarden/browser/platform/services/platform-utils/browser-platform-utils.service";
+
+import { AppModule } from "./app.module";
+
+import "@bitwarden/browser/popup/scss";
+
+// We put these first to minimize the delay in window changing.
+PopupSizeService.initBodyWidthFromLocalStorage();
+// Should be removed once we deprecate support for Safari 16.0 and older. See Jira ticket [PM-1861]
+if (BrowserPlatformUtilsService.shouldApplySafariHeightFix(window)) {
+ document.documentElement.classList.add("safari_height_fix");
+}
+
+if (process.env.ENV === "production") {
+ enableProdMode();
+}
+
+function init() {
+ void platformBrowserDynamic().bootstrapModule(AppModule);
+}
+
+init();
diff --git a/bitwarden_license/bit-browser/tsconfig.json b/bitwarden_license/bit-browser/tsconfig.json
new file mode 100644
index 0000000000..c3420e280b
--- /dev/null
+++ b/bitwarden_license/bit-browser/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "extends": "../../apps/browser/tsconfig",
+ "include": [
+ "src",
+
+ "../../apps/browser/src/**/*.d.ts",
+
+ "../../apps/browser/src/autofill/content/*.ts",
+ "../../apps/browser/src/autofill/fido2/content/*.ts",
+ "../../apps/browser/src/autofill/notification/bar.ts",
+ "../../apps/browser/src/autofill/overlay/inline-menu/**/*.ts",
+ "../../apps/browser/src/platform/ipc/content/*.ts",
+ "../../apps/browser/src/platform/offscreen-document/offscreen-document.ts",
+ "../../apps/browser/src/popup/polyfills.ts",
+ "../../apps/browser/src/vault/content/*.ts",
+
+ "../../libs/common/src/autofill/constants",
+ "../../libs/common/custom-matchers.d.ts"
+ ]
+}
diff --git a/bitwarden_license/bit-browser/tsconfig.spec.json b/bitwarden_license/bit-browser/tsconfig.spec.json
new file mode 100644
index 0000000000..968309bca6
--- /dev/null
+++ b/bitwarden_license/bit-browser/tsconfig.spec.json
@@ -0,0 +1,8 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "isolatedModules": true,
+ "emitDecoratorMetadata": false
+ },
+ "files": ["../../apps/browser/test.setup.ts"]
+}
diff --git a/bitwarden_license/bit-browser/webpack.config.js b/bitwarden_license/bit-browser/webpack.config.js
new file mode 100644
index 0000000000..294e2f1625
--- /dev/null
+++ b/bitwarden_license/bit-browser/webpack.config.js
@@ -0,0 +1,13 @@
+const { buildConfig } = require("../../apps/browser/webpack.base");
+
+module.exports = buildConfig({
+ configName: "Commercial",
+ popup: {
+ entry: "../../bitwarden_license/bit-browser/src/popup/main.ts",
+ entryModule: "../../bitwarden_license/bit-browser/src/popup/app.module#AppModule",
+ },
+ background: {
+ entry: "../../bitwarden_license/bit-browser/src/platform/background.ts",
+ },
+ tsConfig: "../../bitwarden_license/bit-browser/tsconfig.json",
+});
diff --git a/jest.config.js b/jest.config.js
index 3e4fb665a8..9be54e9999 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -20,9 +20,10 @@ module.exports = {
"/apps/cli/jest.config.js",
"/apps/desktop/jest.config.js",
"/apps/web/jest.config.js",
- "/bitwarden_license/bit-web/jest.config.js",
+ "/bitwarden_license/bit-browser/jest.config.js",
"/bitwarden_license/bit-cli/jest.config.js",
"/bitwarden_license/bit-common/jest.config.js",
+ "/bitwarden_license/bit-web/jest.config.js",
"/libs/admin-console/jest.config.js",
"/libs/angular/jest.config.js",
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 3d38f0f821..3d1d2915f6 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -26,6 +26,7 @@
"@bitwarden/auth/common": ["./libs/auth/src/common"],
"@bitwarden/billing": ["./libs/billing/src"],
"@bitwarden/bit-common/*": ["./bitwarden_license/bit-common/src/*"],
+ "@bitwarden/browser/*": ["./apps/browser/src/*"],
"@bitwarden/cli/*": ["./apps/cli/src/*"],
"@bitwarden/client-type": ["libs/client-type/src/index.ts"],
"@bitwarden/common/*": ["./libs/common/src/*"],