/******/ (function() { // webpackBootstrap /******/ "use strict"; /******/ var __webpack_modules__ = ({ /***/ 90495: /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); // extracted by mini-css-extract-plugin /***/ }) /******/ }); /************************************************************************/ /******/ // The module cache /******/ var __webpack_module_cache__ = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ var cachedModule = __webpack_module_cache__[moduleId]; /******/ if (cachedModule !== undefined) { /******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = __webpack_module_cache__[moduleId] = { /******/ // no module.id needed /******/ // no module.loaded needed /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /************************************************************************/ /******/ /* webpack/runtime/make namespace object */ /******/ !function() { /******/ // define __esModule on exports /******/ __webpack_require__.r = function(exports) { /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); /******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ }(); /******/ /************************************************************************/ var __webpack_exports__ = {}; ;// ../../libs/common/src/autofill/constants/match-patterns.ts const CardExpiryDateDelimiters = ["/", "-", ".", " "]; // `CardExpiryDateDelimiters` is not intended solely for regex consumption, // so we need to format it here const ExpiryDateDelimitersPattern = "\\" + CardExpiryDateDelimiters.join("\\") // replace space character with the regex whitespace character class .replace(" ", "s"); const MonthPattern = "(([1]{1}[0-2]{1})|(0?[1-9]{1}))"; // Because we're dealing with expiry dates, we assume the year will be in current or next century (as of 2024) const ExpiryFullYearPattern = "2[0-1]{1}\\d{2}"; const DelimiterPatternExpression = new RegExp(`[${ExpiryDateDelimitersPattern}]`, "g"); const IrrelevantExpiryCharactersPatternExpression = new RegExp( // "nor digits" to ensure numbers are removed from guidance pattern, which aren't covered by ^\w `[^\\d${ExpiryDateDelimitersPattern}]`, "g"); const MonthPatternExpression = new RegExp(`^${MonthPattern}$`); const ExpiryFullYearPatternExpression = new RegExp(`^${ExpiryFullYearPattern}$`); ;// ../../libs/common/src/autofill/constants/index.ts const TYPE_CHECK = { FUNCTION: "function", NUMBER: "number", STRING: "string", }; const EVENTS = { CHANGE: "change", INPUT: "input", KEYDOWN: "keydown", KEYPRESS: "keypress", KEYUP: "keyup", BLUR: "blur", CLICK: "click", FOCUS: "focus", FOCUSIN: "focusin", FOCUSOUT: "focusout", SCROLL: "scroll", RESIZE: "resize", DOMCONTENTLOADED: "DOMContentLoaded", LOAD: "load", MESSAGE: "message", VISIBILITYCHANGE: "visibilitychange", MOUSEENTER: "mouseenter", MOUSELEAVE: "mouseleave", MOUSEUP: "mouseup", MOUSEOUT: "mouseout", SUBMIT: "submit", }; const ClearClipboardDelay = { Never: null, TenSeconds: 10, TwentySeconds: 20, ThirtySeconds: 30, OneMinute: 60, TwoMinutes: 120, FiveMinutes: 300, }; /* Ids for context menu items and messaging events */ const AUTOFILL_CARD_ID = "autofill-card"; const AUTOFILL_ID = "autofill"; const SHOW_AUTOFILL_BUTTON = "show-autofill-button"; const AUTOFILL_IDENTITY_ID = "autofill-identity"; const COPY_IDENTIFIER_ID = "copy-identifier"; const COPY_PASSWORD_ID = "copy-password"; const COPY_USERNAME_ID = "copy-username"; const COPY_VERIFICATION_CODE_ID = "copy-totp"; const CREATE_CARD_ID = "create-card"; const CREATE_IDENTITY_ID = "create-identity"; const CREATE_LOGIN_ID = "create-login"; const GENERATE_PASSWORD_ID = "generate-password"; const NOOP_COMMAND_SUFFIX = "noop"; const ROOT_ID = "root"; const SEPARATOR_ID = "separator"; const UPDATE_PASSWORD = "update-password"; const NOTIFICATION_BAR_LIFESPAN_MS = 150000; // 150 seconds const AUTOFILL_OVERLAY_HANDLE_REPOSITION = "autofill-overlay-handle-reposition-event"; const AUTOFILL_OVERLAY_HANDLE_SCROLL = "autofill-overlay-handle-scroll-event"; const UPDATE_PASSKEYS_HEADINGS_ON_SCROLL = "update-passkeys-headings-on-scroll"; const AUTOFILL_TRIGGER_FORM_FIELD_SUBMIT = "autofill-trigger-form-field-submit"; const AutofillOverlayVisibility = { Off: 0, OnButtonClick: 1, OnFieldFocus: 2, }; const BrowserClientVendors = { Chrome: "Chrome", Opera: "Opera", Edge: "Edge", Vivaldi: "Vivaldi", Unknown: "Unknown", }; const BrowserShortcutsUris = { Chrome: "chrome://extensions/shortcuts", Opera: "opera://extensions/shortcuts", Edge: "edge://extensions/shortcuts", Vivaldi: "vivaldi://extensions/shortcuts", Unknown: "https://bitwarden.com/help/keyboard-shortcuts", }; const DisablePasswordManagerUris = { Chrome: "chrome://settings/autofill", Opera: "opera://settings/autofill", Edge: "edge://settings/passwords", Vivaldi: "vivaldi://settings/autofill", Unknown: "https://bitwarden.com/help/disable-browser-autofill/", }; const ExtensionCommand = { AutofillCommand: "autofill_cmd", AutofillCard: "autofill_card", AutofillIdentity: "autofill_identity", AutofillLogin: "autofill_login", OpenAutofillOverlay: "open_autofill_overlay", GeneratePassword: "generate_password", OpenPopup: "open_popup", LockVault: "lock_vault", NoopCommand: "noop", }; const CLEAR_NOTIFICATION_LOGIN_DATA_DURATION = (/* unused pure expression or super */ null && (60 * 1000)); // 1 minute const MAX_DEEP_QUERY_RECURSION_DEPTH = 4; ;// ../../node_modules/tslib/tslib.es6.mjs /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** *//* global Reflect, Promise, SuppressedError, Symbol, Iterator */var extendStatics=function(d,b){extendStatics=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(d,b){d.__proto__=b;}||function(d,b){for(var p in b)if(Object.prototype.hasOwnProperty.call(b,p))d[p]=b[p];};return extendStatics(d,b);};function __extends(d,b){if(typeof b!=="function"&&b!==null)throw new TypeError("Class extends value "+String(b)+" is not a constructor or null");extendStatics(d,b);function __(){this.constructor=d;}d.prototype=b===null?Object.create(b):(__.prototype=b.prototype,new __());}var __assign=function(){__assign=Object.assign||function __assign(t){for(var s,i=1,n=arguments.length;i=0;i--)if(d=decorators[i])r=(c<3?d(r):c>3?d(target,key,r):d(target,key))||r;return c>3&&r&&Object.defineProperty(target,key,r),r;}function __param(paramIndex,decorator){return function(target,key){decorator(target,key,paramIndex);};}function __esDecorate(ctor,descriptorIn,decorators,contextIn,initializers,extraInitializers){function accept(f){if(f!==void 0&&typeof f!=="function")throw new TypeError("Function expected");return f;}var kind=contextIn.kind,key=kind==="getter"?"get":kind==="setter"?"set":"value";var target=!descriptorIn&&ctor?contextIn["static"]?ctor:ctor.prototype:null;var descriptor=descriptorIn||(target?Object.getOwnPropertyDescriptor(target,contextIn.name):{});var _,done=false;for(var i=decorators.length-1;i>=0;i--){var context={};for(var p in contextIn)context[p]=p==="access"?{}:contextIn[p];for(var p in contextIn.access)context.access[p]=contextIn.access[p];context.addInitializer=function(f){if(done)throw new TypeError("Cannot add initializers after decoration has completed");extraInitializers.push(accept(f||null));};var result=(0,decorators[i])(kind==="accessor"?{get:descriptor.get,set:descriptor.set}:descriptor[key],context);if(kind==="accessor"){if(result===void 0)continue;if(result===null||typeof result!=="object")throw new TypeError("Object expected");if(_=accept(result.get))descriptor.get=_;if(_=accept(result.set))descriptor.set=_;if(_=accept(result.init))initializers.unshift(_);}else if(_=accept(result)){if(kind==="field")initializers.unshift(_);else descriptor[key]=_;}}if(target)Object.defineProperty(target,contextIn.name,descriptor);done=true;};function __runInitializers(thisArg,initializers,value){var useValue=arguments.length>2;for(var i=0;i0&&t[t.length-1])&&(op[0]===6||op[0]===2)){_=0;continue;}if(op[0]===3&&(!t||op[1]>t[0]&&op[1]=o.length)o=void 0;return{value:o&&o[i++],done:!o};}};throw new TypeError(s?"Object is not iterable.":"Symbol.iterator is not defined.");}function __read(o,n){var m=typeof Symbol==="function"&&o[Symbol.iterator];if(!m)return o;var i=m.call(o),r,ar=[],e;try{while((n===void 0||n-->0)&&!(r=i.next()).done)ar.push(r.value);}catch(error){e={error:error};}finally{try{if(r&&!r.done&&(m=i["return"]))m.call(i);}finally{if(e)throw e.error;}}return ar;}/** @deprecated */function __spread(){for(var ar=[],i=0;i1||resume(n,v);});};if(f)i[n]=f(i[n]);}}function resume(n,v){try{step(g[n](v));}catch(e){settle(q[0][3],e);}}function step(r){r.value instanceof __await?Promise.resolve(r.value.v).then(fulfill,reject):settle(q[0][2],r);}function fulfill(value){resume("next",value);}function reject(value){resume("throw",value);}function settle(f,v){if(f(v),q.shift(),q.length)resume(q[0][0],q[0][1]);}}function __asyncDelegator(o){var i,p;return i={},verb("next"),verb("throw",function(e){throw e;}),verb("return"),i[Symbol.iterator]=function(){return this;},i;function verb(n,f){i[n]=o[n]?function(v){return(p=!p)?{value:__await(o[n](v)),done:false}:f?f(v):v;}:f;}}function __asyncValues(o){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var m=o[Symbol.asyncIterator],i;return m?m.call(o):(o=typeof __values==="function"?__values(o):o[Symbol.iterator](),i={},verb("next"),verb("throw"),verb("return"),i[Symbol.asyncIterator]=function(){return this;},i);function verb(n){i[n]=o[n]&&function(v){return new Promise(function(resolve,reject){v=o[n](v),settle(resolve,reject,v.done,v.value);});};}function settle(resolve,reject,d,v){Promise.resolve(v).then(function(v){resolve({value:v,done:d});},reject);}}function __makeTemplateObject(cooked,raw){if(Object.defineProperty){Object.defineProperty(cooked,"raw",{value:raw});}else{cooked.raw=raw;}return cooked;};var __setModuleDefault=Object.create?function(o,v){Object.defineProperty(o,"default",{enumerable:true,value:v});}:function(o,v){o["default"]=v;};var ownKeys=function(o){ownKeys=Object.getOwnPropertyNames||function(o){var ar=[];for(var k in o)if(Object.prototype.hasOwnProperty.call(o,k))ar[ar.length]=k;return ar;};return ownKeys(o);};function __importStar(mod){if(mod&&mod.__esModule)return mod;var result={};if(mod!=null)for(var k=ownKeys(mod),i=0;i { * await this.runEvent(); * }); * * messageListener.messages$(MY_MESSAGE).pipe( * debounceTime( * 10 * 1000, * toScheduler(taskScheduler, ShedulerTaskNames.myTaskName), * ), * switchMap(() => this.runEvent()), * ) * } * } * ``` * * @param taskScheduler The task scheduler service to use to shedule RXJS work. * @param taskName The name of the task that the handler should be registered and scheduled based on. * @returns A SchedulerLike object that can be passed in to RXJS operators like `delay` and `timeout`. */ function toScheduler(taskScheduler, taskName) { return new TaskSchedulerSheduler(taskScheduler, taskName); } class TaskSchedulerSheduler { constructor(taskSchedulerService, taskName) { this.taskSchedulerService = taskSchedulerService; this.taskName = taskName; } schedule(work, delay, state) { return this.taskSchedulerService.setTimeout(this.taskName, delay !== null && delay !== void 0 ? delay : 0); } now() { return asyncScheduler.now(); } } class TaskSchedulerService { } ;// ../../libs/common/src/platform/scheduling/default-task-scheduler.service.ts var default_task_scheduler_service_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; class DefaultTaskSchedulerService extends TaskSchedulerService { constructor(logService) { super(); this.logService = logService; this.taskHandlers = new Map(); } /** * Sets a timeout and returns the timeout id. * * @param taskName - The name of the task. Unused in the base implementation. * @param delayInMs - The delay in milliseconds. */ setTimeout(taskName, delayInMs) { this.validateRegisteredTask(taskName); const timeoutHandle = globalThis.setTimeout(() => this.triggerTask(taskName), delayInMs); return new Subscription(() => globalThis.clearTimeout(timeoutHandle)); } /** * Sets an interval and returns the interval id. * * @param taskName - The name of the task. Unused in the base implementation. * @param intervalInMs - The interval in milliseconds. * @param _initialDelayInMs - The initial delay in milliseconds. Unused in the base implementation. */ setInterval(taskName, intervalInMs, _initialDelayInMs) { this.validateRegisteredTask(taskName); const intervalHandle = globalThis.setInterval(() => this.triggerTask(taskName), intervalInMs); return new Subscription(() => globalThis.clearInterval(intervalHandle)); } /** * Registers a task handler. * * @param taskName - The name of the task. * @param handler - The task handler. */ registerTaskHandler(taskName, handler) { const existingHandler = this.taskHandlers.get(taskName); if (existingHandler) { this.logService.warning(`Task handler for ${taskName} already exists. Overwriting.`); this.unregisterTaskHandler(taskName); } this.taskHandlers.set(taskName, handler); } /** * Unregisters a task handler. * * @param taskName - The name of the task. */ unregisterTaskHandler(taskName) { this.taskHandlers.delete(taskName); } /** * Triggers a task. * * @param taskName - The name of the task. * @param _periodInMinutes - The period in minutes. Unused in the base implementation. */ triggerTask(taskName, _periodInMinutes) { return default_task_scheduler_service_awaiter(this, void 0, void 0, function* () { const handler = this.taskHandlers.get(taskName); if (handler) { handler(); } }); } /** * Validates that a task handler is registered. * * @param taskName - The name of the task. */ validateRegisteredTask(taskName) { if (!this.taskHandlers.has(taskName)) { throw new Error(`Task handler for ${taskName} not registered. Unable to schedule task.`); } } } ;// ../../libs/common/src/platform/scheduling/scheduled-task-name.enum.ts const scheduled_task_name_enum_ScheduledTaskNames = { generatePasswordClearClipboardTimeout: "generatePasswordClearClipboardTimeout", systemClearClipboardTimeout: "systemClearClipboardTimeout", loginStrategySessionTimeout: "loginStrategySessionTimeout", notificationsReconnectTimeout: "notificationsReconnectTimeout", fido2ClientAbortTimeout: "fido2ClientAbortTimeout", scheduleNextSyncInterval: "scheduleNextSyncInterval", eventUploadsInterval: "eventUploadsInterval", vaultTimeoutCheckInterval: "vaultTimeoutCheckInterval", clearPopupViewCache: "clearPopupViewCache", phishingDomainUpdate: "phishingDomainUpdate", }; ;// ../../libs/common/src/platform/scheduling/index.ts ;// ../../libs/platform/src/background-sync/background-sync.service.ts /** * The default interval between background syncs. * 300,000ms = 5 minutes */ const DEFAULT_SYNC_INTERVAL_MS = 300000; /** * Service responsible for registering and managing background synchronization for the browser extension. * Handles scheduling of periodic sync operations using the task scheduler infrastructure. */ class BackgroundSyncService { /** * Creates a new instance of BackgroundSyncService. * @param taskSchedulerService - Service that handles scheduling and execution of periodic tasks */ constructor(taskSchedulerService) { this.taskSchedulerService = taskSchedulerService; } /** * Registers a callback function to be executed when the sync interval task is triggered. * This associates the sync task name with the provided callback in the task scheduler. * * @param syncCallback - The function to execute when the sync task is triggered */ register(syncCallback) { this.taskSchedulerService.registerTaskHandler(ScheduledTaskNames.scheduleNextSyncInterval, syncCallback); } /** * Initializes the background sync service by scheduling the sync interval task. * This sets up a recurring timer that triggers the registered sync callback at regular intervals. * * @param intervalMs - The interval in milliseconds between sync operations (defaults to 300000ms/5 minutes) */ init(intervalMs = DEFAULT_SYNC_INTERVAL_MS) { intervalMs = intervalMs < 1 ? DEFAULT_SYNC_INTERVAL_MS : intervalMs; this.taskSchedulerService.setInterval(ScheduledTaskNames.scheduleNextSyncInterval, intervalMs); } } ;// ../../libs/platform/src/background-sync/index.ts ;// ../../libs/platform/src/index.ts ;// ./src/browser/safariApp.ts class SafariApp { static sendMessageToApp(command, data = null, resolveNow = false) { if (!BrowserApi.isSafariApi) { return Promise.resolve(null); } return new Promise((resolve) => { const now = new Date(); const messageId = now.getTime().toString() + "_" + Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); browser.runtime.sendNativeMessage("com.bitwarden.desktop", { id: messageId, command: command, data: data, responseData: null, }, (response) => { resolve(response); }); }); } } ;// ../../libs/logging/src/log-level.ts // FIXME: update to use a const object instead of a typescript enum // eslint-disable-next-line @bitwarden/platform/no-enums var LogLevel; (function (LogLevel) { LogLevel[LogLevel["Debug"] = 0] = "Debug"; LogLevel[LogLevel["Info"] = 1] = "Info"; LogLevel[LogLevel["Warning"] = 2] = "Warning"; LogLevel[LogLevel["Error"] = 3] = "Error"; })(LogLevel || (LogLevel = {})); ;// ../../libs/logging/src/console-log.service.ts class ConsoleLogService { constructor(isDev, filter = null) { this.isDev = isDev; this.filter = filter; this.timersMap = new Map(); } debug(message, ...optionalParams) { if (!this.isDev) { return; } this.write(LogLevel.Debug, message, ...optionalParams); } info(message, ...optionalParams) { this.write(LogLevel.Info, message, ...optionalParams); } warning(message, ...optionalParams) { this.write(LogLevel.Warning, message, ...optionalParams); } error(message, ...optionalParams) { this.write(LogLevel.Error, message, ...optionalParams); } write(level, message, ...optionalParams) { if (this.filter != null && this.filter(level)) { return; } switch (level) { case LogLevel.Debug: // eslint-disable-next-line console.log(message, ...optionalParams); break; case LogLevel.Info: // eslint-disable-next-line console.log(message, ...optionalParams); break; case LogLevel.Warning: // eslint-disable-next-line console.warn(message, ...optionalParams); break; case LogLevel.Error: // eslint-disable-next-line console.error(message, ...optionalParams); break; default: break; } } measure(start, trackGroup, track, name, properties) { const measureName = `[${track}]: ${name}`; const measure = performance.measure(measureName, { start: start, detail: { devtools: { dataType: "track-entry", track, trackGroup, properties, }, }, }); this.info(`${measureName} took ${measure.duration}`, properties); return measure; } mark(name) { const mark = performance.mark(name, { detail: { devtools: { dataType: "marker", }, }, }); this.info(mark.name, new Date().toISOString()); return mark; } } ;// ../../libs/logging/src/index.ts ;// ../../libs/common/src/platform/services/console-log.service.ts ;// ./src/platform/services/browser-clipboard.service.ts var browser_clipboard_service_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; class BrowserClipboardService { /** * Copies the given text to the user's clipboard. * * @param globalContext - The global window context. * @param text - The text to copy. */ static copy(globalContext, text) { return browser_clipboard_service_awaiter(this, void 0, void 0, function* () { if (!BrowserClipboardService.isClipboardApiSupported(globalContext, "writeText")) { this.useLegacyCopyMethod(globalContext, text); return; } try { yield globalContext.navigator.clipboard.writeText(text); } catch (error) { BrowserClipboardService.consoleLogService.debug(`Error copying to clipboard using the clipboard API, attempting legacy method: ${error}`); this.useLegacyCopyMethod(globalContext, text); } }); } /** * Reads the user's clipboard and returns the text. * * @param globalContext - The global window context. */ static read(globalContext) { return browser_clipboard_service_awaiter(this, void 0, void 0, function* () { if (!BrowserClipboardService.isClipboardApiSupported(globalContext, "readText")) { return this.useLegacyReadMethod(globalContext); } try { return yield globalContext.navigator.clipboard.readText(); } catch (error) { BrowserClipboardService.consoleLogService.debug(`Error reading from clipboard using the clipboard API, attempting legacy method: ${error}`); return this.useLegacyReadMethod(globalContext); } }); } /** * Copies the given text to the user's clipboard using the legacy `execCommand` method. This * method is used as a fallback when the clipboard API is not supported or fails. * * @param globalContext - The global window context. * @param text - The text to copy. */ static useLegacyCopyMethod(globalContext, text) { if (!BrowserClipboardService.isLegacyClipboardMethodSupported(globalContext, "copy")) { BrowserClipboardService.consoleLogService.warning("Legacy copy method not supported"); return; } const textareaElement = globalContext.document.createElement("textarea"); textareaElement.textContent = !text ? " " : text; textareaElement.style.position = "fixed"; globalContext.document.body.appendChild(textareaElement); textareaElement.select(); try { globalContext.document.execCommand("copy"); } catch (error) { BrowserClipboardService.consoleLogService.warning(`Error writing to clipboard: ${error}`); } finally { globalContext.document.body.removeChild(textareaElement); } } /** * Reads the user's clipboard using the legacy `execCommand` method. This method is used as a * fallback when the clipboard API is not supported or fails. * * @param globalContext - The global window context. */ static useLegacyReadMethod(globalContext) { if (!BrowserClipboardService.isLegacyClipboardMethodSupported(globalContext, "paste")) { BrowserClipboardService.consoleLogService.warning("Legacy paste method not supported"); return ""; } const textareaElement = globalContext.document.createElement("textarea"); textareaElement.style.position = "fixed"; globalContext.document.body.appendChild(textareaElement); textareaElement.focus(); try { return globalContext.document.execCommand("paste") ? textareaElement.value : ""; } catch (error) { BrowserClipboardService.consoleLogService.warning(`Error reading from clipboard: ${error}`); } finally { globalContext.document.body.removeChild(textareaElement); } return ""; } /** * Checks if the clipboard API is supported in the current environment. * * @param globalContext - The global window context. * @param method - The clipboard API method to check for support. */ static isClipboardApiSupported(globalContext, method) { return "clipboard" in globalContext.navigator && method in globalContext.navigator.clipboard; } /** * Checks if the legacy clipboard method is supported in the current environment. * * @param globalContext - The global window context. * @param method - The legacy clipboard method to check for support. */ static isLegacyClipboardMethodSupported(globalContext, method) { return ("queryCommandSupported" in globalContext.document && globalContext.document.queryCommandSupported(method)); } } BrowserClipboardService.consoleLogService = new ConsoleLogService(false); /* harmony default export */ var browser_clipboard_service = (BrowserClipboardService); ;// ./src/platform/services/platform-utils/browser-platform-utils.service.ts var browser_platform_utils_service_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore class BrowserPlatformUtilsService { constructor(clipboardWriteCallback, globalContext, offscreenDocumentService) { this.clipboardWriteCallback = clipboardWriteCallback; this.globalContext = globalContext; this.offscreenDocumentService = offscreenDocumentService; } static getDevice(globalContext) { if (this.deviceCache) { return this.deviceCache; } // ORDERING MATTERS HERE // Ordered from most specific to least specific. We try to discern the greatest detail // for the type of extension the user is on by checking specific cases first and as we go down // the list we hope to catch all by the most generic clients they could be on. if (BrowserPlatformUtilsService.isFirefox()) { this.deviceCache = DeviceType.FirefoxExtension; } else if (BrowserPlatformUtilsService.isOpera(globalContext)) { this.deviceCache = DeviceType.OperaExtension; } else if (BrowserPlatformUtilsService.isEdge()) { this.deviceCache = DeviceType.EdgeExtension; } else if (BrowserPlatformUtilsService.isVivaldi()) { this.deviceCache = DeviceType.VivaldiExtension; } else if (BrowserPlatformUtilsService.isChrome(globalContext)) { this.deviceCache = DeviceType.ChromeExtension; } else if (BrowserPlatformUtilsService.isSafari(globalContext)) { this.deviceCache = DeviceType.SafariExtension; } return this.deviceCache; } getDevice() { return BrowserPlatformUtilsService.getDevice(this.globalContext); } getDeviceString() { const device = DeviceType[this.getDevice()].toLowerCase(); return device.replace("extension", ""); } getClientType() { return ClientType.Browser; } static isFirefox() { return (navigator.userAgent.indexOf(" Firefox/") !== -1 || navigator.userAgent.indexOf(" Gecko/") !== -1); } isFirefox() { return this.getDevice() === DeviceType.FirefoxExtension; } static isChrome(globalContext) { return globalContext.chrome && navigator.userAgent.indexOf(" Chrome/") !== -1; } isChrome() { return this.getDevice() === DeviceType.ChromeExtension; } static isEdge() { return navigator.userAgent.indexOf(" Edg/") !== -1; } isEdge() { return this.getDevice() === DeviceType.EdgeExtension; } static isOpera(globalContext) { var _a; return (!!((_a = globalContext.opr) === null || _a === void 0 ? void 0 : _a.addons) || !!globalContext.opera || navigator.userAgent.indexOf(" OPR/") >= 0); } isOpera() { return this.getDevice() === DeviceType.OperaExtension; } static isVivaldi() { return navigator.userAgent.indexOf(" Vivaldi/") !== -1; } isVivaldi() { return this.getDevice() === DeviceType.VivaldiExtension; } static isSafari(globalContext) { // Opera masquerades as Safari, so make sure we're not there first return (!BrowserPlatformUtilsService.isOpera(globalContext) && navigator.userAgent.indexOf(" Safari/") !== -1); } static safariVersion() { var _a; return (_a = navigator.userAgent.match("Version/([0-9.]*)")) === null || _a === void 0 ? void 0 : _a[1]; } isSafari() { return this.getDevice() === DeviceType.SafariExtension; } isChromium() { return this.isChrome() || this.isEdge() || this.isOpera() || this.isVivaldi(); } /** * Safari previous to version 16.1 had a bug which caused artifacts on hover in large extension popups. * https://bugs.webkit.org/show_bug.cgi?id=218704 */ static shouldApplySafariHeightFix(globalContext) { var _a; if (BrowserPlatformUtilsService.getDevice(globalContext) !== DeviceType.SafariExtension) { return false; } const version = BrowserPlatformUtilsService.safariVersion(); const parts = (_a = version === null || version === void 0 ? void 0 : version.split(".")) === null || _a === void 0 ? void 0 : _a.map((v) => Number(v)); return (parts === null || parts === void 0 ? void 0 : parts[0]) < 16 || ((parts === null || parts === void 0 ? void 0 : parts[0]) === 16 && (parts === null || parts === void 0 ? void 0 : parts[1]) === 0); } isIE() { return false; } isMacAppStore() { return false; } /** * Identifies if the vault popup is currently open. This is done by sending a * message to the popup and waiting for a response. If a response is received, * the view is open. */ isPopupOpen() { return browser_platform_utils_service_awaiter(this, void 0, void 0, function* () { if (this.isSafari()) { // Query views on safari since chrome.runtime.sendMessage does not timeout and will hang. return BrowserApi.isPopupOpen(); } return new Promise((resolve, reject) => { chrome.runtime.sendMessage({ command: "checkVaultPopupHeartbeat" }, (response) => { if (chrome.runtime.lastError != null) { // This error means that nothing was there to listen to the message, // meaning the view is not open. if (chrome.runtime.lastError.message === "Could not establish connection. Receiving end does not exist.") { resolve(false); return; } // All unhandled errors still reject reject(chrome.runtime.lastError); return; } resolve(Boolean(response)); }); }); }); } lockTimeout() { return null; } launchUri(uri, options) { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises BrowserApi.createNewTab(uri, options && options.extensionPage === true); } getApplicationVersion() { var _a; const manifest = chrome.runtime.getManifest(); return Promise.resolve((_a = manifest.version_name) !== null && _a !== void 0 ? _a : manifest.version); } getApplicationVersionNumber() { const manifest = chrome.runtime.getManifest(); return Promise.resolve(manifest.version.split(RegExp("[+|-]"))[0].trim()); } supportsWebAuthn(win) { return typeof PublicKeyCredential !== "undefined"; } supportsDuo() { return true; } supportsAutofill() { return true; } supportsFileDownloads() { return false; } isDev() { return "production" === "development"; } isSelfHost() { return false; } /** * Copies the passed text to the clipboard. For Safari, this will use * the native messaging API to send the text to the Bitwarden app. If * the extension is using manifest v3, the offscreen document API will * be used to copy the text to the clipboard. Otherwise, the browser's * clipboard API will be used. * * @param text - The text to copy to the clipboard. * @param options - Options for the clipboard operation. */ copyToClipboard(text, options) { const windowContext = (options === null || options === void 0 ? void 0 : options.window) || this.globalContext; const clearing = Boolean(options === null || options === void 0 ? void 0 : options.clearing); const clearMs = (options === null || options === void 0 ? void 0 : options.clearMs) || null; const handleClipboardWriteCallback = () => { if (!clearing && this.clipboardWriteCallback != null) { this.clipboardWriteCallback(text, clearMs); } }; if (this.isSafari()) { void SafariApp.sendMessageToApp("copyToClipboard", text).then(handleClipboardWriteCallback); return; } if (this.isChrome() && text === "") { text = "\u0000"; } if (BrowserApi.isManifestVersion(3) && this.offscreenDocumentService.offscreenApiSupported()) { void this.triggerOffscreenCopyToClipboard(text).then(handleClipboardWriteCallback); return; } void browser_clipboard_service.copy(windowContext, text).then(handleClipboardWriteCallback); } /** * Reads the text from the clipboard. For Safari, this will use the * native messaging API to request the text from the Bitwarden app. If * the extension is using manifest v3, the offscreen document API will * be used to read the text from the clipboard. Otherwise, the browser's * clipboard API will be used. * * @param options - Options for the clipboard operation. */ readFromClipboard(options) { return browser_platform_utils_service_awaiter(this, void 0, void 0, function* () { const windowContext = (options === null || options === void 0 ? void 0 : options.window) || this.globalContext; if (this.isSafari()) { return yield SafariApp.sendMessageToApp("readFromClipboard"); } if (BrowserApi.isManifestVersion(3) && this.offscreenDocumentService.offscreenApiSupported()) { return yield this.triggerOffscreenReadFromClipboard(); } return yield browser_clipboard_service.read(windowContext); }); } supportsSecureStorage() { return false; } getAutofillKeyboardShortcut() { return browser_platform_utils_service_awaiter(this, void 0, void 0, function* () { let autofillCommand; // You can not change the command in Safari or obtain it programmatically if (this.isSafari()) { autofillCommand = "Cmd+Shift+L"; } else if (this.isFirefox()) { autofillCommand = (yield browser.commands.getAll()).find((c) => c.name === ExtensionCommand.AutofillLogin).shortcut; // Firefox is returning Ctrl instead of Cmd for the modifier key on macOS if // the command is the default one set on installation. if ((yield browser.runtime.getPlatformInfo()).os === "mac" && autofillCommand === "Ctrl+Shift+L") { autofillCommand = "Cmd+Shift+L"; } } else { yield new Promise((resolve) => chrome.commands.getAll((c) => resolve((autofillCommand = c.find((c) => c.name === ExtensionCommand.AutofillLogin).shortcut)))); } return autofillCommand; }); } packageType() { return browser_platform_utils_service_awaiter(this, void 0, void 0, function* () { switch (this.getDevice()) { case DeviceType.ChromeExtension: return "Chrome Extension"; case DeviceType.FirefoxExtension: return "Firefox Extension"; case DeviceType.OperaExtension: return "Opera Extension"; case DeviceType.EdgeExtension: return "Edge Extension"; case DeviceType.VivaldiExtension: return "Vivaldi Extension"; case DeviceType.SafariExtension: return "Safari Extension"; default: return "Unknown Browser Extension"; } }); } /** * Triggers the offscreen document API to copy the text to the clipboard. */ triggerOffscreenCopyToClipboard(text) { return browser_platform_utils_service_awaiter(this, void 0, void 0, function* () { yield this.offscreenDocumentService.withDocument([chrome.offscreen.Reason.CLIPBOARD], "Write text to the clipboard.", () => browser_platform_utils_service_awaiter(this, void 0, void 0, function* () { yield BrowserApi.sendMessageWithResponse("offscreenCopyToClipboard", { text }); })); }); } /** * Triggers the offscreen document API to read the text from the clipboard. */ triggerOffscreenReadFromClipboard() { return browser_platform_utils_service_awaiter(this, void 0, void 0, function* () { const response = yield this.offscreenDocumentService.withDocument([chrome.offscreen.Reason.CLIPBOARD], "Read text from the clipboard.", () => browser_platform_utils_service_awaiter(this, void 0, void 0, function* () { return yield BrowserApi.sendMessageWithResponse("offscreenReadFromClipboard"); })); if (typeof response === "string") { return response; } return ""; }); } } BrowserPlatformUtilsService.deviceCache = null; ;// ./src/platform/browser/browser-api.register-content-scripts-polyfill.ts var browser_api_register_content_scripts_polyfill_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore /** * MIT License * * Copyright (c) Federico Brigante (https://fregante.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * @see https://github.com/fregante/content-scripts-register-polyfill * @version 4.0.2 */ let registerContentScripts; function registerContentScriptsPolyfill(contentScriptOptions, callback) { return browser_api_register_content_scripts_polyfill_awaiter(this, void 0, void 0, function* () { if (!registerContentScripts) { registerContentScripts = buildRegisterContentScriptsPolyfill(); } return registerContentScripts(contentScriptOptions, callback); }); } function buildRegisterContentScriptsPolyfill() { var _a, _b; const logService = new ConsoleLogService(false); const chromeProxy = globalThis.chrome && NestedProxy(globalThis.chrome); const patternValidationRegex = /^(https?|wss?|file|ftp|\*):\/\/(\*|\*\.[^*/]+|[^*/]+)\/.*$|^file:\/\/\/.*$|^resource:\/\/(\*|\*\.[^*/]+|[^*/]+)\/.*$|^about:/; const isFirefox = (_a = globalThis.navigator) === null || _a === void 0 ? void 0 : _a.userAgent.includes("Firefox/"); const gotScripting = Boolean((_b = globalThis.chrome) === null || _b === void 0 ? void 0 : _b.scripting); const gotNavigation = typeof chrome === "object" && "webNavigation" in chrome; function NestedProxy(target) { return new Proxy(target, { get(target, prop) { if (!target[prop]) { return; } if (typeof target[prop] !== "function") { return NestedProxy(target[prop]); } return (...arguments_) => new Promise((resolve, reject) => { target[prop](...arguments_, (result) => { if (chrome.runtime.lastError) { reject(new Error(chrome.runtime.lastError.message)); } else { resolve(result); } }); }); }, }); } function assertValidPattern(matchPattern) { if (!isValidPattern(matchPattern)) { throw new Error(`${matchPattern} is an invalid pattern, it must match ${String(patternValidationRegex)}`); } } function isValidPattern(matchPattern) { return matchPattern === "" || patternValidationRegex.test(matchPattern); } function getRawPatternRegex(matchPattern) { assertValidPattern(matchPattern); let [, protocol, host = "", pathname] = matchPattern.split(/(^[^:]+:[/][/])([^/]+)?/); protocol = protocol .replace("*", isFirefox ? "(https?|wss?)" : "https?") .replaceAll(/[/]/g, "[/]"); if (host === "*") { host = "[^/]+"; } else if (host) { host = host .replace(/^[*][.]/, "([^/]+.)*") .replaceAll(/[.]/g, "[.]") .replace(/[*]$/, "[^.]+"); } pathname = pathname .replaceAll(/[/]/g, "[/]") .replaceAll(/[.]/g, "[.]") .replaceAll(/[*]/g, ".*"); return "^" + protocol + host + "(" + pathname + ")?$"; } function patternToRegex(...matchPatterns) { if (matchPatterns.length === 0) { return /$./; } if (matchPatterns.includes("")) { // regex return /^(https?|file|ftp):[/]+/; } if (matchPatterns.includes("*://*/*")) { // all stars regex return isFirefox ? /^(https?|wss?):[/][/][^/]+([/].*)?$/ : /^https?:[/][/][^/]+([/].*)?$/; } return new RegExp(matchPatterns.map((x) => getRawPatternRegex(x)).join("|")); } function castAllFramesTarget(target) { if (typeof target === "object") { return Object.assign(Object.assign({}, target), { allFrames: false }); } return { tabId: target, frameId: undefined, allFrames: true, }; } function castArray(possibleArray) { if (Array.isArray(possibleArray)) { return possibleArray; } return [possibleArray]; } function createTarget(tabId, frameId, allFrames) { if (frameId === undefined) { return { tabId, frameIds: undefined, allFrames: allFrames, }; } else { return { tabId, frameIds: [frameId], allFrames: undefined, }; } } function insertCSS(_a) { return browser_api_register_content_scripts_polyfill_awaiter(this, arguments, void 0, function* ({ tabId, frameId, files, allFrames, matchAboutBlank, runAt, }, { ignoreTargetErrors } = {}) { const everyInsertion = Promise.all(files.map((content) => browser_api_register_content_scripts_polyfill_awaiter(this, void 0, void 0, function* () { if (typeof content === "string") { content = { file: content }; } if (gotScripting) { if ("file" in content) { return chrome.scripting.insertCSS({ target: createTarget(tabId, frameId, allFrames), files: [content.file], }); } else { return chrome.scripting.insertCSS({ target: createTarget(tabId, frameId, allFrames), css: content.code, }); } } return chromeProxy.tabs.insertCSS(tabId, Object.assign(Object.assign({}, content), { matchAboutBlank, allFrames, frameId, runAt: runAt !== null && runAt !== void 0 ? runAt : "document_start" })); }))); if (ignoreTargetErrors) { yield catchTargetInjectionErrors(everyInsertion); } else { yield everyInsertion; } }); } function assertNoCode(files) { if (files.some((content) => "code" in content)) { throw new Error("chrome.scripting does not support injecting strings of `code`"); } } function executeScript(_a) { return browser_api_register_content_scripts_polyfill_awaiter(this, arguments, void 0, function* ({ tabId, frameId, files, allFrames, matchAboutBlank, runAt, }, { ignoreTargetErrors } = {}) { const normalizedFiles = files.map((file) => (typeof file === "string" ? { file } : file)); if (gotScripting) { assertNoCode(normalizedFiles); const injection = chrome.scripting.executeScript({ target: createTarget(tabId, frameId, allFrames), files: normalizedFiles.map(({ file }) => file), }); if (ignoreTargetErrors) { yield catchTargetInjectionErrors(injection); } else { yield injection; } return; } const executions = []; for (const content of normalizedFiles) { if ("code" in content) { yield executions.at(-1); } executions.push(chromeProxy.tabs.executeScript(tabId, Object.assign(Object.assign({}, content), { matchAboutBlank, allFrames, frameId, runAt }))); } if (ignoreTargetErrors) { yield catchTargetInjectionErrors(Promise.all(executions)); } else { yield Promise.all(executions); } }); } function injectContentScript(where_1, scripts_1) { return browser_api_register_content_scripts_polyfill_awaiter(this, arguments, void 0, function* (where, scripts, options = {}) { const targets = castArray(where); yield Promise.all(targets.map((target) => browser_api_register_content_scripts_polyfill_awaiter(this, void 0, void 0, function* () { return injectContentScriptInSpecificTarget(castAllFramesTarget(target), scripts, options); }))); }); } function injectContentScriptInSpecificTarget(_a, scripts_1) { return browser_api_register_content_scripts_polyfill_awaiter(this, arguments, void 0, function* ({ frameId, tabId, allFrames }, scripts, options = {}) { const injections = castArray(scripts).flatMap((script) => { var _a, _b, _c, _d, _e, _f; return [ insertCSS({ tabId, frameId, allFrames, files: (_a = script.css) !== null && _a !== void 0 ? _a : [], matchAboutBlank: (_b = script.matchAboutBlank) !== null && _b !== void 0 ? _b : script.match_about_blank, runAt: (_c = script.runAt) !== null && _c !== void 0 ? _c : script.run_at, }, options), executeScript({ tabId, frameId, allFrames, files: (_d = script.js) !== null && _d !== void 0 ? _d : [], matchAboutBlank: (_e = script.matchAboutBlank) !== null && _e !== void 0 ? _e : script.match_about_blank, runAt: (_f = script.runAt) !== null && _f !== void 0 ? _f : script.run_at, }, options), ]; }); yield Promise.all(injections); }); } function catchTargetInjectionErrors(promise) { return browser_api_register_content_scripts_polyfill_awaiter(this, void 0, void 0, function* () { try { yield promise; } catch (error) { const targetErrors = /^No frame with id \d+ in tab \d+.$|^No tab with id: \d+.$|^The tab was closed.$|^The frame was removed.$/; if (!targetErrors.test(error === null || error === void 0 ? void 0 : error.message)) { throw error; } } }); } function isOriginPermitted(url) { return browser_api_register_content_scripts_polyfill_awaiter(this, void 0, void 0, function* () { return chromeProxy.permissions.contains({ origins: [new URL(url).origin + "/*"], }); }); } return (contentScriptOptions, callback) => browser_api_register_content_scripts_polyfill_awaiter(this, void 0, void 0, function* () { const { js = [], css = [], matchAboutBlank, matches = [], excludeMatches, runAt, } = contentScriptOptions; let { allFrames } = contentScriptOptions; if (gotNavigation) { allFrames = false; } else if (allFrames) { logService.warning("`allFrames: true` requires the `webNavigation` permission to work correctly: https://github.com/fregante/content-scripts-register-polyfill#permissions"); } if (matches.length === 0) { throw new Error("Type error for parameter contentScriptOptions (Error processing matches: Array requires at least 1 items; you have 0) for contentScripts.register."); } yield Promise.all(matches.map((pattern) => browser_api_register_content_scripts_polyfill_awaiter(this, void 0, void 0, function* () { if (!(yield chromeProxy.permissions.contains({ origins: [pattern] }))) { throw new Error(`Permission denied to register a content script for ${pattern}`); } }))); const matchesRegex = patternToRegex(...matches); const excludeMatchesRegex = patternToRegex(...(excludeMatches !== null && excludeMatches !== void 0 ? excludeMatches : [])); const inject = (url_1, tabId_1, ...args_1) => browser_api_register_content_scripts_polyfill_awaiter(this, [url_1, tabId_1, ...args_1], void 0, function* (url, tabId, frameId = 0) { if (!matchesRegex.test(url) || excludeMatchesRegex.test(url) || !(yield isOriginPermitted(url))) { return; } yield injectContentScript({ tabId, frameId }, { css, js, matchAboutBlank, runAt }, { ignoreTargetErrors: true }); }); const tabListener = (tabId_1, _a, _b) => browser_api_register_content_scripts_polyfill_awaiter(this, [tabId_1, _a, _b], void 0, function* (tabId, { status }, { url }) { if (status === "loading" && url) { void inject(url, tabId); } }); const navListener = (_a) => browser_api_register_content_scripts_polyfill_awaiter(this, [_a], void 0, function* ({ tabId, frameId, url, }) { void inject(url, tabId, frameId); }); if (gotNavigation) { BrowserApi.addListener(chrome.webNavigation.onCommitted, navListener); } else { BrowserApi.addListener(chrome.tabs.onUpdated, tabListener); } const registeredContentScript = { unregister() { return browser_api_register_content_scripts_polyfill_awaiter(this, void 0, void 0, function* () { if (gotNavigation) { chrome.webNavigation.onCommitted.removeListener(navListener); } else { chrome.tabs.onUpdated.removeListener(tabListener); } }); }, }; if (typeof callback === "function") { callback(registeredContentScript); } return registeredContentScript; }); } ;// ./src/platform/browser/browser-api.ts var browser_api_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore class BrowserApi { static get manifestVersion() { return chrome.runtime.getManifest().manifest_version; } /** * Determines if the extension manifest version is the given version. * * @param expectedVersion - The expected manifest version to check against. */ static isManifestVersion(expectedVersion) { return BrowserApi.manifestVersion === expectedVersion; } /** * Helper method that attempts to distinguish whether a message sender is internal to the extension or not. * * Currently this is done through source origin matching, and frameId checking (only top-level frames are internal). * @param sender a message sender * @param logger an optional logger to log validation results * @returns whether or not the sender appears to be internal to the extension */ static senderIsInternal(sender, logger) { var _a, _b; if (!(sender === null || sender === void 0 ? void 0 : sender.origin)) { logger === null || logger === void 0 ? void 0 : logger.warning("[BrowserApi] Message sender has no origin"); return false; } const extensionUrl = (typeof chrome !== "undefined" && ((_a = chrome.runtime) === null || _a === void 0 ? void 0 : _a.getURL(""))) || (typeof browser !== "undefined" && ((_b = browser.runtime) === null || _b === void 0 ? void 0 : _b.getURL(""))) || ""; if (!extensionUrl) { logger === null || logger === void 0 ? void 0 : logger.warning("[BrowserApi] Unable to determine extension URL"); return false; } // Normalize both URLs by removing trailing slashes const normalizedOrigin = sender.origin.replace(/\/$/, "").toLowerCase(); const normalizedExtensionUrl = extensionUrl.replace(/\/$/, "").toLowerCase(); if (!normalizedOrigin.startsWith(normalizedExtensionUrl)) { logger === null || logger === void 0 ? void 0 : logger.warning(`[BrowserApi] Message sender origin (${normalizedOrigin}) does not match extension URL (${normalizedExtensionUrl})`); return false; } // We only send messages from the top-level frame, but frameId is only set if tab is set, which for popups it is not. if ("frameId" in sender && sender.frameId !== 0) { logger === null || logger === void 0 ? void 0 : logger.warning("[BrowserApi] Message sender is not from the top-level frame"); return false; } logger === null || logger === void 0 ? void 0 : logger.info("[BrowserApi] Message sender appears to be internal"); return true; } /** * Gets all open browser windows, including their tabs. * * @returns A promise that resolves to an array of browser windows. */ static getWindows() { return browser_api_awaiter(this, void 0, void 0, function* () { return new Promise((resolve) => chrome.windows.getAll({ populate: true }, resolve)); }); } /** * Gets the current window or the window with the given id. * * @param windowId - The id of the window to get. If not provided, the current window is returned. */ static getWindow(windowId) { return browser_api_awaiter(this, void 0, void 0, function* () { if (!windowId) { return BrowserApi.getCurrentWindow(); } return yield BrowserApi.getWindowById(windowId); }); } /** * Gets the currently active browser window. */ static getCurrentWindow() { return browser_api_awaiter(this, void 0, void 0, function* () { return new Promise((resolve) => chrome.windows.getCurrent({ populate: true }, resolve)); }); } /** * Gets the window with the given id. * * @param windowId - The id of the window to get. */ static getWindowById(windowId) { return browser_api_awaiter(this, void 0, void 0, function* () { return new Promise((resolve) => chrome.windows.get(windowId, { populate: true }, resolve)); }); } static createWindow(options) { return browser_api_awaiter(this, void 0, void 0, function* () { return new Promise((resolve) => { chrome.windows.create(options, (newWindow) => browser_api_awaiter(this, void 0, void 0, function* () { if (!BrowserApi.isSafariApi) { return resolve(newWindow); } // Safari doesn't close the default extension popup when a new window is created so we need to // manually trigger the close by focusing the main window after the new window is created const allWindows = yield new Promise((resolve) => { chrome.windows.getAll({ windowTypes: ["normal"] }, (windows) => resolve(windows)); }); const mainWindow = allWindows.find((window) => window.id !== newWindow.id); // No main window found, resolve the new window if (mainWindow == null || !mainWindow.id) { return resolve(newWindow); } // Focus the main window to close the extension popup chrome.windows.update(mainWindow.id, { focused: true }, () => { // Refocus the newly created window chrome.windows.update(newWindow.id, { focused: true }, () => { resolve(newWindow); }); }); })); }); }); } /** * Removes the window with the given id. * * @param windowId - The id of the window to remove. */ static removeWindow(windowId) { return browser_api_awaiter(this, void 0, void 0, function* () { return new Promise((resolve) => chrome.windows.remove(windowId, () => resolve())); }); } /** * Updates the properties of the window with the given id. * * @param windowId - The id of the window to update. * @param options - The window properties to update. */ static updateWindowProperties(windowId, options) { return browser_api_awaiter(this, void 0, void 0, function* () { return new Promise((resolve) => chrome.windows.update(windowId, options, () => { resolve(); })); }); } /** * Focuses the window with the given id. * * @param windowId - The id of the window to focus. */ static focusWindow(windowId) { return browser_api_awaiter(this, void 0, void 0, function* () { yield BrowserApi.updateWindowProperties(windowId, { focused: true }); }); } static getTabFromCurrentWindowId() { return browser_api_awaiter(this, void 0, void 0, function* () { return yield BrowserApi.tabsQueryFirstCurrentWindowForSafari({ active: true, windowId: chrome.windows.WINDOW_ID_CURRENT, }); }); } static getBrowserClientVendor(clientWindow) { const device = BrowserPlatformUtilsService.getDevice(clientWindow); switch (device) { case DeviceType.ChromeExtension: case DeviceType.ChromeBrowser: return BrowserClientVendors.Chrome; case DeviceType.OperaExtension: case DeviceType.OperaBrowser: return BrowserClientVendors.Opera; case DeviceType.EdgeExtension: case DeviceType.EdgeBrowser: return BrowserClientVendors.Edge; case DeviceType.VivaldiExtension: case DeviceType.VivaldiBrowser: return BrowserClientVendors.Vivaldi; default: return BrowserClientVendors.Unknown; } } /** * Gets the tab with the given id. * * @param tabId - The id of the tab to get. */ static getTab(tabId) { return browser_api_awaiter(this, void 0, void 0, function* () { if (!tabId) { return null; } if (BrowserApi.isManifestVersion(3)) { return yield chrome.tabs.get(tabId); } return new Promise((resolve) => chrome.tabs.get(tabId, (tab) => { resolve(tab); })); }); } static getTabFromCurrentWindow() { return browser_api_awaiter(this, void 0, void 0, function* () { return yield BrowserApi.tabsQueryFirstCurrentWindowForSafari({ active: true, currentWindow: true, }); }); } static getActiveTabs() { return browser_api_awaiter(this, void 0, void 0, function* () { return yield BrowserApi.tabsQuery({ active: true, }); }); } /** * Fetch the currently open browser tab */ static getCurrentTab() { return browser_api_awaiter(this, void 0, void 0, function* () { if (BrowserApi.isManifestVersion(3)) { return yield chrome.tabs.getCurrent(); } return new Promise((resolve) => chrome.tabs.getCurrent((tab) => { resolve(tab); })); }); } /** * Closes a browser tab with the given id * * @param tabId The id of the tab to close */ static closeTab(tabId) { return browser_api_awaiter(this, void 0, void 0, function* () { if (tabId) { if (BrowserApi.isWebExtensionsApi) { yield browser.tabs.remove(tabId).catch((error) => { throw new Error("[BrowserApi] Failed to remove current tab: " + error.message); }); } else if (BrowserApi.isChromeApi) { yield chrome.tabs.remove(tabId).catch((error) => { throw new Error("[BrowserApi] Failed to remove current tab: " + error.message); }); } } }); } /** * Navigates a browser tab to the given URL * * @param tabId The id of the tab to navigate * @param url The URL to navigate to */ static navigateTabToUrl(tabId, url) { return browser_api_awaiter(this, void 0, void 0, function* () { if (tabId) { if (BrowserApi.isWebExtensionsApi) { yield browser.tabs.update(tabId, { url: url.href }).catch((error) => { throw new Error("Failed to navigate tab to URL: " + error.message); }); } else if (BrowserApi.isChromeApi) { chrome.tabs.update(tabId, { url: url.href }, () => { if (chrome.runtime.lastError) { throw new Error("Failed to navigate tab to URL: " + chrome.runtime.lastError.message); } }); } } }); } static tabsQuery(options) { return browser_api_awaiter(this, void 0, void 0, function* () { return new Promise((resolve) => { chrome.tabs.query(options, (tabs) => { resolve(tabs); }); }); }); } static tabsQueryFirst(options) { return browser_api_awaiter(this, void 0, void 0, function* () { const tabs = yield BrowserApi.tabsQuery(options); if (tabs.length > 0) { return tabs[0]; } return null; }); } /** * Drop-in replacement for {@link BrowserApi.tabsQueryFirst}. * * Safari sometimes returns >1 tabs unexpectedly even when * specifying a `windowId` or `currentWindow: true` query option. * * For all of these calls, * ``` * await chrome.tabs.query({active: true, currentWindow: true}) * await chrome.tabs.query({active: true, windowId: chrome.windows.WINDOW_ID_CURRENT}) * await chrome.tabs.query({active: true, windowId: 10}) * ``` * * Safari could return: * ``` * [ * {windowId: 2, pinned: true, title: "Incorrect tab in another window", …}, * {windowId: 10, title: "Correct tab in foreground", …}, * ] * ``` * * This function captures the current window ID manually before running the query, * then finds and returns the tab with the matching window ID. * * See the `SafariTabsQuery` tests in `browser-api.spec.ts`. * * This workaround can be removed when Safari fixes this bug. */ static tabsQueryFirstCurrentWindowForSafari(options) { return browser_api_awaiter(this, void 0, void 0, function* () { var _a; if (!BrowserApi.isSafariApi) { return yield BrowserApi.tabsQueryFirst(options); } const currentWindowId = (yield BrowserApi.getCurrentWindow()).id; const tabs = yield BrowserApi.tabsQuery(options); if (tabs.length <= 1 || currentWindowId == null) { return tabs[0]; } return (_a = tabs.find((t) => t.windowId === currentWindowId)) !== null && _a !== void 0 ? _a : tabs[0]; }); } static tabSendMessageData(tab, command, data = null) { const obj = { command: command, }; if (data != null) { obj.data = data; } return BrowserApi.tabSendMessage(tab, obj); } static tabSendMessage(tab_1, obj_1) { return browser_api_awaiter(this, arguments, void 0, function* (tab, obj, options = null, rejectOnError = false) { if (!tab || !tab.id) { return; } return new Promise((resolve, reject) => { chrome.tabs.sendMessage(tab.id, obj, options, (response) => { if (chrome.runtime.lastError && rejectOnError) { // Some error happened reject(); } resolve(response); }); }); }); } static sendTabsMessage(tabId, message, options, responseCallback) { chrome.tabs.sendMessage(tabId, message, options, responseCallback); } static getRuntimeURL(path) { if (BrowserApi.isWebExtensionsApi) { return browser.runtime.getURL(path); } else if (BrowserApi.isChromeApi) { return chrome.runtime.getURL(path); } } static onWindowCreated(callback) { return browser_api_awaiter(this, void 0, void 0, function* () { // FIXME: Make sure that is does not cause a memory leak in Safari or use BrowserApi.AddListener // and test that it doesn't break. // eslint-disable-next-line no-restricted-syntax return chrome.windows.onCreated.addListener(callback); }); } /** * Gets the background page for the extension. This method is * not valid within manifest v3 background service workers. As * a result, it will return null when called from that context. */ static getBackgroundPage() { if (typeof chrome.extension.getBackgroundPage === "undefined") { return null; } return chrome.extension.getBackgroundPage(); } /** * Accepts a window object and determines if it is * associated with the background page of the extension. * * @param window - The window to check. */ static isBackgroundPage(window) { return typeof window !== "undefined" && window === BrowserApi.getBackgroundPage(); } /** * Gets the extension views that match the given properties. This method is not * available within background service worker. As a result, it will return an * empty array when called from that context. * * @param fetchProperties - The properties used to filter extension views. */ static getExtensionViews(fetchProperties) { if (typeof chrome.extension.getViews === "undefined") { return []; } return chrome.extension.getViews(fetchProperties); } /** * Queries all extension views that are of type `popup` * and returns whether any are currently open. */ static isPopupOpen() { return browser_api_awaiter(this, void 0, void 0, function* () { return Promise.resolve(BrowserApi.getExtensionViews({ type: "popup" }).length > 0); }); } static createNewTab(url, active = true) { return new Promise((resolve) => chrome.tabs.create({ url: url, active: active }, (tab) => resolve(tab))); } /** * Gathers the details for a specified sub-frame of a tab. * * @param details - The details of the frame to get. */ static getFrameDetails(details) { return browser_api_awaiter(this, void 0, void 0, function* () { return new Promise((resolve) => chrome.webNavigation.getFrame(details, resolve)); }); } /** * Gets all frames associated with a tab. * * @param tabId - The id of the tab to get the frames for. */ static getAllFrameDetails(tabId) { return browser_api_awaiter(this, void 0, void 0, function* () { return new Promise((resolve) => chrome.webNavigation.getAllFrames({ tabId }, resolve)); }); } static messageListener(name, callback) { BrowserApi.addListener(chrome.runtime.onMessage, callback); } static messageListener$() { return new Observable((subscriber) => { const handler = (message) => { subscriber.next(message); }; BrowserApi.addListener(chrome.runtime.onMessage, handler); return () => BrowserApi.removeListener(chrome.runtime.onMessage, handler); }); } static storageChangeListener(callback) { BrowserApi.addListener(chrome.storage.onChanged, callback); } /** * Adds a callback to the given chrome event in a cross-browser platform manner. * * **Important:** All event listeners in the browser extension popup context must * use this instead of the native APIs to handle unsubscribing from Safari properly. * * @param event - The event in which to add the listener to. * @param callback - The callback you want registered onto the event. */ static addListener(event, callback) { event.addListener(callback); if (BrowserApi.isSafariApi && !BrowserApi.isBackgroundPage(self)) { BrowserApi.trackedChromeEventListeners.push([event, callback]); BrowserApi.setupUnloadListeners(); } } /** * Removes a callback from the given chrome event in a cross-browser platform manner. * @param event - The event in which to remove the listener from. * @param callback - The callback you want removed from the event. */ static removeListener(event, callback) { event.removeListener(callback); if (BrowserApi.isSafariApi && !BrowserApi.isBackgroundPage(self)) { const index = BrowserApi.trackedChromeEventListeners.findIndex(([_event, eventListener]) => { return eventListener == callback; }); if (index !== -1) { BrowserApi.trackedChromeEventListeners.splice(index, 1); } } } // Setup the event to destroy all the listeners when the popup gets unloaded in Safari, otherwise we get a memory leak static setupUnloadListeners() { // The MDN recommend using 'visibilitychange' but that event is fired any time the popup window is obscured as well // 'pagehide' works just like 'unload' but is compatible with the back/forward cache, so we prefer using that one self.addEventListener("pagehide", () => { for (const [event, callback] of BrowserApi.trackedChromeEventListeners) { event.removeListener(callback); } }); } static sendMessage(subscriber, arg = {}) { const message = Object.assign({}, { command: subscriber }, arg); return chrome.runtime.sendMessage(message); } static sendMessageWithResponse(subscriber, arg = {}) { const message = Object.assign({}, { command: subscriber }, arg); return new Promise((resolve) => chrome.runtime.sendMessage(message, resolve)); } static focusTab(tabId) { return browser_api_awaiter(this, void 0, void 0, function* () { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises chrome.tabs.update(tabId, { active: true, highlighted: true }); }); } static closePopup(win) { if (BrowserApi.isWebExtensionsApi && BrowserApi.isFirefoxOnAndroid) { // Reactivating the active tab dismisses the popup tab. The promise final // condition is only called if the popup wasn't already dismissed (future proofing). // ref: https://bugzilla.mozilla.org/show_bug.cgi?id=1433604 // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises browser.tabs.update({ active: true }).finally(win.close); } else { win.close(); } } static gaFilter() { return "production" !== "production"; } static getUILanguage() { return chrome.i18n.getUILanguage(); } /** * Handles reloading the extension using the underlying functionality exposed by the browser API. */ static reloadExtension() { // If we do `chrome.runtime.reload` on safari they will send an onInstalled reason of install // and that prompts us to show a new tab, this apparently doesn't happen on sideloaded // extensions and only shows itself production scenarios. See: https://bitwarden.atlassian.net/browse/PM-12298 if (this.isSafariApi) { return self.location.reload(); } return chrome.runtime.reload(); } /** * Reloads all open extension views, except the background page. Will also * skip reloading the current window location if exemptCurrentHref is true. * * @param exemptCurrentHref - Whether to exempt the current window location from the reload. */ static reloadOpenWindows(exemptCurrentHref = false) { const views = BrowserApi.getExtensionViews(); if (!views.length) { return; } const currentHref = self.location.href; views .filter((w) => w.location.href != null && !w.location.href.includes("background.html")) .filter((w) => !exemptCurrentHref || w.location.href !== currentHref) .forEach((w) => w.location.reload()); } static connectNative(application) { if (BrowserApi.isWebExtensionsApi) { return browser.runtime.connectNative(application); } else if (BrowserApi.isChromeApi) { return chrome.runtime.connectNative(application); } } static requestPermission(permission) { if (BrowserApi.isWebExtensionsApi) { return browser.permissions.request(permission); } return new Promise((resolve) => { chrome.permissions.request(permission, resolve); }); } /** * Checks if the user has provided the given permissions to the extension. * * @param permissions - The permissions to check. */ static permissionsGranted(permissions) { return browser_api_awaiter(this, void 0, void 0, function* () { return new Promise((resolve) => chrome.permissions.contains({ permissions }, (result) => resolve(result))); }); } static getPlatformInfo() { if (BrowserApi.isWebExtensionsApi) { return browser.runtime.getPlatformInfo(); } return new Promise((resolve) => { chrome.runtime.getPlatformInfo(resolve); }); } /** * Returns the supported BrowserAction API based on the manifest version. */ static getBrowserAction() { return BrowserApi.isManifestVersion(3) ? chrome.action : chrome.browserAction; } static getSidebarAction(win) { var _a; const deviceType = BrowserPlatformUtilsService.getDevice(win); if (deviceType === DeviceType.FirefoxExtension) { return browser.sidebarAction; } if (deviceType === DeviceType.OperaExtension) { return (_a = win.opr) === null || _a === void 0 ? void 0 : _a.sidebarAction; } return null; } static captureVisibleTab() { return new Promise((resolve) => { chrome.tabs.captureVisibleTab(null, { format: "png" }, resolve); }); } /** * Extension API helper method used to execute a script in a tab. * * @see https://developer.chrome.com/docs/extensions/reference/tabs/#method-executeScript * @param tabId - The id of the tab to execute the script in. * @param details {@link "InjectDetails" https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/extensionTypes/InjectDetails} * @param scriptingApiDetails {@link "ExecutionWorld" https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/ExecutionWorld} */ static executeScriptInTab(tabId, details, scriptingApiDetails) { if (BrowserApi.isManifestVersion(3)) { let target; if (typeof details.frameId === "number") { target = { tabId, frameIds: [details.frameId] }; } else if (details.allFrames) { target = { tabId, allFrames: true }; } else { target = { tabId }; } return chrome.scripting.executeScript({ target, files: details.file ? [details.file] : null, injectImmediately: details.runAt === "document_start", world: (scriptingApiDetails === null || scriptingApiDetails === void 0 ? void 0 : scriptingApiDetails.world) || chrome.scripting.ExecutionWorld.ISOLATED, }); } return new Promise((resolve) => { chrome.tabs.executeScript(tabId, details, (result) => { resolve(result); }); }); } /** * Identifies if the browser autofill settings are overridden by the extension. */ static browserAutofillSettingsOverridden() { return browser_api_awaiter(this, void 0, void 0, function* () { if (!(yield BrowserApi.permissionsGranted(["privacy"]))) { return false; } const checkOverrideStatus = (details) => details.levelOfControl === "controlled_by_this_extension" && !details.value; const autofillAddressOverridden = yield new Promise((resolve) => chrome.privacy.services.autofillAddressEnabled.get({}, (details) => resolve(checkOverrideStatus(details)))); const autofillCreditCardOverridden = yield new Promise((resolve) => chrome.privacy.services.autofillCreditCardEnabled.get({}, (details) => resolve(checkOverrideStatus(details)))); const passwordSavingOverridden = yield new Promise((resolve) => chrome.privacy.services.passwordSavingEnabled.get({}, (details) => resolve(checkOverrideStatus(details)))); return autofillAddressOverridden && autofillCreditCardOverridden && passwordSavingOverridden; }); } /** * Updates the browser autofill settings to the given value. * * @param value - Determines whether to enable or disable the autofill settings. */ static updateDefaultBrowserAutofillSettings(value) { return browser_api_awaiter(this, void 0, void 0, function* () { yield chrome.privacy.services.autofillAddressEnabled.set({ value }); yield chrome.privacy.services.autofillCreditCardEnabled.set({ value }); yield chrome.privacy.services.passwordSavingEnabled.set({ value }); }); } /** * Handles registration of static content scripts within manifest v2. * * @param contentScriptOptions - Details of the registered content scripts */ static registerContentScriptsMv2(contentScriptOptions) { return browser_api_awaiter(this, void 0, void 0, function* () { var _a; if (typeof browser !== "undefined" && !!((_a = browser.contentScripts) === null || _a === void 0 ? void 0 : _a.register)) { return yield browser.contentScripts.register(contentScriptOptions); } return yield registerContentScriptsPolyfill(contentScriptOptions); }); } /** * Handles registration of static content scripts within manifest v3. * * @param scripts - Details of the registered content scripts */ static registerContentScriptsMv3(scripts) { return browser_api_awaiter(this, void 0, void 0, function* () { yield chrome.scripting.registerContentScripts(scripts); }); } /** * Handles unregistering of static content scripts within manifest v3. * * @param filter - Optional filter to unregister content scripts. Passing an empty object will unregister all content scripts. */ static unregisterContentScriptsMv3(filter) { return browser_api_awaiter(this, void 0, void 0, function* () { yield chrome.scripting.unregisterContentScripts(filter); }); } } BrowserApi.isWebExtensionsApi = typeof browser !== "undefined"; BrowserApi.isSafariApi = isBrowserSafariApi(); BrowserApi.isChromeApi = !BrowserApi.isSafariApi && typeof chrome !== "undefined"; BrowserApi.isFirefoxOnAndroid = navigator.userAgent.indexOf("Firefox/") !== -1 && navigator.userAgent.indexOf("Android") !== -1; // Keep track of all the events registered in a Safari popup so we can remove // them when the popup gets unloaded, otherwise we cause a memory leak BrowserApi.trackedChromeEventListeners = []; ;// ./src/autofill/enums/autofill-port.enum.ts const autofill_port_enum_AutofillPort = { InjectedScript: "autofill-injected-script-port", }; ;// ./src/autofill/utils/index.ts var utils_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; /** * Generates a random string of characters. * * @param length - The length of the random string to generate. */ function generateRandomChars(length) { const chars = "abcdefghijklmnopqrstuvwxyz"; const randomChars = []; const randomBytes = new Uint8Array(length); globalThis.crypto.getRandomValues(randomBytes); for (let byteIndex = 0; byteIndex < randomBytes.length; byteIndex++) { const byte = randomBytes[byteIndex]; randomChars.push(chars[byte % chars.length]); } return randomChars.join(""); } /** * Polyfills the requestIdleCallback API with a setTimeout fallback. * * @param callback - The callback function to run when the browser is idle. * @param options - The options to pass to the requestIdleCallback function. */ function requestIdleCallbackPolyfill(callback, options) { if ("requestIdleCallback" in globalThis) { return globalThis.requestIdleCallback(() => callback(), options); } return globalThis.setTimeout(() => callback(), 1); } /** * Polyfills the cancelIdleCallback API with a clearTimeout fallback. * * @param id - The ID of the idle callback to cancel. */ function cancelIdleCallbackPolyfill(id) { if ("cancelIdleCallback" in globalThis) { return globalThis.cancelIdleCallback(id); } return globalThis.clearTimeout(id); } /** * Generates a random string of characters that formatted as a custom element name. */ function generateRandomCustomElementName() { const length = Math.floor(Math.random() * 5) + 8; // Between 8 and 12 characters const numHyphens = Math.min(Math.max(Math.floor(Math.random() * 4), 1), length - 1); // At least 1, maximum of 3 hyphens const hyphenIndices = []; while (hyphenIndices.length < numHyphens) { const index = Math.floor(Math.random() * (length - 1)) + 1; if (!hyphenIndices.includes(index)) { hyphenIndices.push(index); } } hyphenIndices.sort((a, b) => a - b); let randomString = ""; let prevIndex = 0; for (let index = 0; index < hyphenIndices.length; index++) { const hyphenIndex = hyphenIndices[index]; randomString = randomString + generateRandomChars(hyphenIndex - prevIndex) + "-"; prevIndex = hyphenIndex; } randomString += generateRandomChars(length - prevIndex); return randomString; } /** * Builds a DOM element from an SVG string. * * @param svgString - The SVG string to build the DOM element from. * @param ariaHidden - Determines whether the SVG should be hidden from screen readers. */ function buildSvgDomElement(svgString, ariaHidden = true) { const domParser = new DOMParser(); const svgDom = domParser.parseFromString(svgString, "image/svg+xml"); const domElement = svgDom.documentElement; domElement.setAttribute("aria-hidden", `${ariaHidden}`); return domElement; } /** * Sends a message to the extension. * * @param command - The command to send. * @param options - The options to send with the command. */ function sendExtensionMessage(command_1) { return utils_awaiter(this, arguments, void 0, function* (command, options = {}) { if (typeof browser !== "undefined" && typeof browser.runtime !== "undefined" && typeof browser.runtime.sendMessage !== "undefined") { return browser.runtime.sendMessage(Object.assign({ command }, options)); } return new Promise((resolve) => chrome.runtime.sendMessage(Object.assign({ command }, options), (response) => { if (chrome.runtime.lastError) { resolve(null); } resolve(response); })); }); } /** * Sets CSS styles on an element. * * @param element - The element to set the styles on. * @param styles - The styles to set on the element. * @param priority - Determines whether the styles should be set as important. */ function setElementStyles(element, styles, priority) { if (!element || !styles || !Object.keys(styles).length) { return; } for (const styleProperty in styles) { element.style.setProperty(styleProperty.replace(/([a-z])([A-Z])/g, "$1-$2"), // Convert camelCase to kebab-case styles[styleProperty], priority ? "important" : undefined); } } /** * Sets up a long-lived connection with the extension background * and triggers an onDisconnect event if the extension context * is invalidated. * * @param callback - Callback export function to run when the extension disconnects */ function setupExtensionDisconnectAction(callback) { const port = chrome.runtime.connect({ name: AutofillPort.InjectedScript }); const onDisconnectCallback = (disconnectedPort) => { callback(disconnectedPort); port.onDisconnect.removeListener(onDisconnectCallback); }; port.onDisconnect.addListener(onDisconnectCallback); } /** * Handles setup of the extension disconnect action for the autofill init class * in both instances where the overlay might or might not be initialized. * * @param windowContext - The global window context */ function setupAutofillInitDisconnectAction(windowContext) { if (!windowContext.bitwardenAutofillInit) { return; } const onDisconnectCallback = () => { windowContext.bitwardenAutofillInit.destroy(); delete windowContext.bitwardenAutofillInit; }; setupExtensionDisconnectAction(onDisconnectCallback); } /** * Identifies whether an element is a fillable form field. * This is determined by whether the element is a form field and not a span. * * @param formFieldElement - The form field element to check. */ function elementIsFillableFormField(formFieldElement) { return !elementIsSpanElement(formFieldElement); } /** * Identifies whether an element is an instance of a specific tag name. * * @param element - The element to check. * @param tagName - The tag name to check against. */ function elementIsInstanceOf(element, tagName) { return nodeIsElement(element) && element.tagName.toLowerCase() === tagName; } /** * Identifies whether an element is a span element. * * @param element - The element to check. */ function elementIsSpanElement(element) { return elementIsInstanceOf(element, "span"); } /** * Identifies whether an element is an input field. * * @param element - The element to check. */ function elementIsInputElement(element) { return elementIsInstanceOf(element, "input"); } /** * Identifies whether an element is a select field. * * @param element - The element to check. */ function elementIsSelectElement(element) { return elementIsInstanceOf(element, "select"); } /** * Identifies whether an element is a textarea field. * * @param element - The element to check. */ function elementIsTextAreaElement(element) { return elementIsInstanceOf(element, "textarea"); } /** * Identifies whether an element is a form element. * * @param element - The element to check. */ function elementIsFormElement(element) { return elementIsInstanceOf(element, "form"); } /** * Identifies whether an element is a label element. * * @param element - The element to check. */ function elementIsLabelElement(element) { return elementIsInstanceOf(element, "label"); } /** * Identifies whether an element is a description details `dd` element. * * @param element - The element to check. */ function elementIsDescriptionDetailsElement(element) { return elementIsInstanceOf(element, "dd"); } /** * Identifies whether an element is a description term `dt` element. * * @param element - The element to check. */ function elementIsDescriptionTermElement(element) { return elementIsInstanceOf(element, "dt"); } /** * Identifies whether a node is an HTML element. * * @param node - The node to check. */ function nodeIsElement(node) { if (!node) { return false; } return (node === null || node === void 0 ? void 0 : node.nodeType) === Node.ELEMENT_NODE; } /** * Identifies whether a node is an input element. * * @param node - The node to check. */ function nodeIsInputElement(node) { return nodeIsElement(node) && elementIsInputElement(node); } /** * Identifies whether a node is a form element. * * @param node - The node to check. */ function nodeIsFormElement(node) { return nodeIsElement(node) && elementIsFormElement(node); } function nodeIsTypeSubmitElement(node) { return nodeIsElement(node) && getPropertyOrAttribute(node, "type") === "submit"; } function nodeIsButtonElement(node) { return (nodeIsElement(node) && (elementIsInstanceOf(node, "button") || getPropertyOrAttribute(node, "type") === "button")); } function nodeIsAnchorElement(node) { return nodeIsElement(node) && elementIsInstanceOf(node, "a"); } /** * Returns a boolean representing the attribute value of an element. * * @param element * @param attributeName * @param checkString */ function getAttributeBoolean(element, attributeName, checkString = false) { if (checkString) { return getPropertyOrAttribute(element, attributeName) === "true"; } return Boolean(getPropertyOrAttribute(element, attributeName)); } /** * Get the value of a property or attribute from a FormFieldElement. * * @param element * @param attributeName */ function getPropertyOrAttribute(element, attributeName) { if (attributeName in element) { return element[attributeName]; } return element.getAttribute(attributeName); } /** * Throttles a callback function to run at most once every `limit` milliseconds. * * @param callback - The callback function to throttle. * @param limit - The time in milliseconds to throttle the callback. */ function throttle(callback, limit) { let waitingDelay = false; return function (...args) { if (!waitingDelay) { callback.apply(this, args); waitingDelay = true; globalThis.setTimeout(() => (waitingDelay = false), limit); } }; } /** * Debounces a callback function to run after a delay of `delay` milliseconds. * * @param callback - The callback function to debounce. * @param delay - The time in milliseconds to debounce the callback. * @param immediate - Determines whether the callback should run immediately. */ function debounce(callback, delay, immediate) { let timeout; return function (...args) { const callImmediately = !!immediate && !timeout; if (timeout) { globalThis.clearTimeout(timeout); } timeout = globalThis.setTimeout(() => { timeout = null; if (!callImmediately) { callback.apply(this, args); } }, delay); if (callImmediately) { callback.apply(this, args); } }; } /** * Gathers and normalizes keywords from a potential submit button element. Used * to verify if the element submits a login or change password form. * * @param element - The element to gather keywords from. */ function getSubmitButtonKeywordsSet(element) { const keywords = [ element.textContent, element.getAttribute("type"), element.getAttribute("value"), element.getAttribute("aria-label"), element.getAttribute("aria-labelledby"), element.getAttribute("aria-describedby"), element.getAttribute("title"), element.getAttribute("id"), element.getAttribute("name"), element.getAttribute("class"), ]; const keywordsSet = new Set(); for (let i = 0; i < keywords.length; i++) { if (typeof keywords[i] === "string") { // Iterate over all keywords metadata and split them by non-letter characters. // This ensures we check against individual words and not the entire string. keywords[i] .toLowerCase() .replace(/[-\s]/g, "") .split(/[^\p{L}]+/gu) .forEach((keyword) => { if (keyword) { keywordsSet.add(keyword); } }); } } return keywordsSet; } /** * Generates the origin and subdomain match patterns for the URL. * * @param url - The URL of the tab */ function generateDomainMatchPatterns(url) { try { const extensionUrlPattern = /^(chrome|chrome-extension|moz-extension|safari-web-extension):\/\/\/?/; if (extensionUrlPattern.test(url)) { return []; } // Add protocol to URL if it is missing to allow for parsing the hostname correctly const urlPattern = /^(https?|file):\/\/\/?/; if (!urlPattern.test(url)) { url = `https://${url}`; } let protocolGlob = "*://"; if (url.startsWith("file:///")) { protocolGlob = "*:///"; // File URLs require three slashes to be a valid match pattern } const parsedUrl = new URL(url); const originMatchPattern = `${protocolGlob}${parsedUrl.hostname}/*`; const splitHost = parsedUrl.hostname.split("."); const domain = splitHost.slice(-2).join("."); const subDomainMatchPattern = `${protocolGlob}*.${domain}/*`; return [originMatchPattern, subDomainMatchPattern]; } catch (_a) { return []; } } /** * Determines if the status code of the web response is invalid. An invalid status code is * any status code that is not in the 200-299 range. * * @param statusCode - The status code of the web response */ function isInvalidResponseStatusCode(statusCode) { return statusCode < 200 || statusCode >= 300; } /** * Determines if the current context is within a sandboxed iframe. */ function currentlyInSandboxedIframe() { var _a, _b; if (String(self.origin).toLowerCase() === "null" || globalThis.location.hostname === "") { return true; } const sandbox = (_b = (_a = globalThis.frameElement) === null || _a === void 0 ? void 0 : _a.getAttribute) === null || _b === void 0 ? void 0 : _b.call(_a, "sandbox"); // No frameElement or sandbox attribute means not sandboxed if (sandbox === null || sandbox === undefined) { return false; } // An empty string means fully sandboxed if (sandbox === "") { return true; } const tokens = new Set(sandbox.toLowerCase().split(" ")); return !["allow-scripts", "allow-same-origin"].every((token) => tokens.has(token)); } /** * This object allows us to map a special character to a key name. The key name is used * in gathering the i18n translation of the written version of the special character. */ const specialCharacterToKeyMap = { " ": "spaceCharacterDescriptor", "~": "tildeCharacterDescriptor", "`": "backtickCharacterDescriptor", "!": "exclamationCharacterDescriptor", "@": "atSignCharacterDescriptor", "#": "hashSignCharacterDescriptor", $: "dollarSignCharacterDescriptor", "%": "percentSignCharacterDescriptor", "^": "caretCharacterDescriptor", "&": "ampersandCharacterDescriptor", "*": "asteriskCharacterDescriptor", "(": "parenLeftCharacterDescriptor", ")": "parenRightCharacterDescriptor", "-": "hyphenCharacterDescriptor", _: "underscoreCharacterDescriptor", "+": "plusCharacterDescriptor", "=": "equalsCharacterDescriptor", "{": "braceLeftCharacterDescriptor", "}": "braceRightCharacterDescriptor", "[": "bracketLeftCharacterDescriptor", "]": "bracketRightCharacterDescriptor", "|": "pipeCharacterDescriptor", "\\": "backSlashCharacterDescriptor", ":": "colonCharacterDescriptor", ";": "semicolonCharacterDescriptor", '"': "doubleQuoteCharacterDescriptor", "'": "singleQuoteCharacterDescriptor", "<": "lessThanCharacterDescriptor", ">": "greaterThanCharacterDescriptor", ",": "commaCharacterDescriptor", ".": "periodCharacterDescriptor", "?": "questionCharacterDescriptor", "/": "forwardSlashCharacterDescriptor", }; /** * Determines if the current rect values are not all 0. */ function rectHasSize(rect) { if (rect.right > 0 && rect.left > 0 && rect.top > 0 && rect.bottom > 0) { return true; } return false; } /** * Checks if all the values corresponding to the specified keys in an object are null. * If no keys are specified, checks all keys in the object. * * @param obj - The object to check. * @param keys - An optional array of keys to check in the object. Defaults to all keys. * @returns Returns true if all values for the specified keys (or all keys if none are provided) are null; otherwise, false. */ function areKeyValuesNull(obj, keys) { const keysToCheck = keys && keys.length > 0 ? keys : Object.keys(obj); return keysToCheck.every((key) => obj[key] == null); } ;// ./src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.ts /** * Allowlist of commands that can be sent to the background script. */ const ALLOWED_BG_COMMANDS = new Set([ "addNewVaultItem", "autofillInlineMenuBlurred", "autofillInlineMenuButtonClicked", "checkAutofillInlineMenuButtonFocused", "checkInlineMenuButtonFocused", "fillAutofillInlineMenuCipher", "fillGeneratedPassword", "redirectAutofillInlineMenuFocusOut", "refreshGeneratedPassword", "refreshOverlayCiphers", "triggerDelayedAutofillInlineMenuClosure", "updateAutofillInlineMenuColorScheme", "updateAutofillInlineMenuListHeight", "unlockVault", "viewSelectedCipher", ]); class AutofillInlineMenuContainer { constructor() { var _a; this.setElementStyles = setElementStyles; this.port = null; this.isInitialized = false; this.iframeStyles = { all: "initial", position: "fixed", top: "0", left: "0", width: "100%", height: "100%", display: "block", zIndex: "2147483647", lineHeight: "0", overflow: "hidden", visibility: "visible", clipPath: "none", pointerEvents: "auto", margin: "0", padding: "0", colorScheme: "normal", }; this.defaultIframeAttributes = { src: "", title: "", sandbox: "allow-scripts", allowtransparency: "true", tabIndex: "-1", }; this.windowMessageHandlers = { initAutofillInlineMenuButton: (message) => this.handleInitInlineMenuIframe(message), initAutofillInlineMenuList: (message) => this.handleInitInlineMenuIframe(message), }; /** * Sets up the port message listener for the inline menu page. * * @param message - The message containing the port name. */ this.setupPortMessageListener = (message) => { this.port = chrome.runtime.connect({ name: this.portName }); const initMessage = Object.assign(Object.assign({}, message), { token: this.token }); this.postMessageToInlineMenuPageUnsafe(initMessage); }; /** * Handles window messages, routing them to the appropriate handler. * * @param event - The message event. */ this.handleWindowMessage = (event) => { const message = event.data; if (!(message === null || message === void 0 ? void 0 : message.command)) { return; } if (this.isForeignWindowMessage(event)) { return; } if (this.windowMessageHandlers[message.command]) { // only accept init messages from extension origin or parent window if ((message.command === "initAutofillInlineMenuButton" || message.command === "initAutofillInlineMenuList") && !this.isMessageFromExtensionOrigin(event) && !this.isMessageFromParentWindow(event)) { return; } this.windowMessageHandlers[message.command](message); return; } if (this.isMessageFromParentWindow(event)) { // messages from parent window are trusted and forwarded to iframe this.postMessageToInlineMenuPage(message); return; } // messages from iframe to background require object identity verification with a contentWindow check and token auth if (this.isMessageFromInlineMenuPageIframe(event)) { if (this.isValidSessionToken(message)) { this.postMessageToBackground(message); } return; } }; this.token = generateRandomChars(32); this.extensionOrigin = (_a = BrowserApi.getRuntimeURL("")) === null || _a === void 0 ? void 0 : _a.slice(0, -1); globalThis.addEventListener("message", this.handleWindowMessage); } /** * Handles initialization of the iframe used to display the inline menu. * * @param message - The message containing the iframe url and page title. */ handleInitInlineMenuIframe(message) { if (this.isInitialized) { return; } const expectedOrigin = message.extensionOrigin || this.extensionOrigin; if (!this.isExtensionUrlWithOrigin(message.iframeUrl, expectedOrigin)) { return; } if (message.styleSheetUrl && !this.isExtensionUrlWithOrigin(message.styleSheetUrl)) { return; } this.defaultIframeAttributes.src = message.iframeUrl; this.defaultIframeAttributes.title = message.pageTitle; this.portName = message.portName; this.isInitialized = true; this.inlineMenuPageIframe = globalThis.document.createElement("iframe"); this.setElementStyles(this.inlineMenuPageIframe, this.iframeStyles, true); for (const [attribute, value] of Object.entries(this.defaultIframeAttributes)) { this.inlineMenuPageIframe.setAttribute(attribute, value); } const handleInlineMenuPageIframeLoad = () => { this.inlineMenuPageIframe.removeEventListener(EVENTS.LOAD, handleInlineMenuPageIframeLoad); this.setupPortMessageListener(message); }; this.inlineMenuPageIframe.addEventListener(EVENTS.LOAD, handleInlineMenuPageIframeLoad); globalThis.document.body.appendChild(this.inlineMenuPageIframe); } /** * Validates that a URL uses an extension protocol and matches the expected extension origin. * If no expectedOrigin is provided, validates against the URL's own origin. * * @param url - The URL to validate. */ isExtensionUrlWithOrigin(url, expectedOrigin) { if (!url) { return false; } try { const urlObj = new URL(url); const extensionProtocols = new Set([ "chrome-extension:", "moz-extension:", "safari-web-extension:", ]); const isExtensionProtocol = extensionProtocols.has(urlObj.protocol); if (!isExtensionProtocol) { return false; } const originToValidate = expectedOrigin !== null && expectedOrigin !== void 0 ? expectedOrigin : urlObj.origin; return urlObj.origin === originToValidate || urlObj.href.startsWith(originToValidate + "/"); } catch (_a) { return false; } } /** * Posts a message to the inline menu page iframe. * * @param message - The message to post. */ postMessageToInlineMenuPage(message) { var _a; if ((_a = this.inlineMenuPageIframe) === null || _a === void 0 ? void 0 : _a.contentWindow) { const messageWithToken = Object.assign(Object.assign({}, message), { token: this.token }); this.postMessageToInlineMenuPageUnsafe(messageWithToken); } } /** * Posts a message to the inline menu page iframe without token validation. * * UNSAFE: Bypasses token authentication and sends raw messages. Only use internally * when sending trusted messages (e.g., initialization) or when token validation * would create circular dependencies. External callers should use postMessageToInlineMenuPage(). * * @param message - The message to post. */ postMessageToInlineMenuPageUnsafe(message) { var _a; if ((_a = this.inlineMenuPageIframe) === null || _a === void 0 ? void 0 : _a.contentWindow) { this.inlineMenuPageIframe.contentWindow.postMessage(message, "*"); } } /** * Posts a message from the inline menu iframe to the background script. * * @param message - The message to post. */ postMessageToBackground(message) { if (!this.port) { return; } if (message.command && !ALLOWED_BG_COMMANDS.has(message.command)) { return; } this.port.postMessage(message); } /** * Identifies if the message is from a foreign window. A foreign window message is * considered as any message that does not have a portKey, is not from the parent window, * or is not from the inline menu page iframe. * * @param event - The message event. */ isForeignWindowMessage(event) { var _a; if (!((_a = event.data) === null || _a === void 0 ? void 0 : _a.portKey)) { return true; } if (this.isMessageFromParentWindow(event)) { return false; } return !this.isMessageFromInlineMenuPageIframe(event); } /** * Identifies if the message is from the parent window. * * @param event - The message event. */ isMessageFromParentWindow(event) { return globalThis.parent === event.source; } /** * Identifies if the message is from the inline menu page iframe. * * @param event - The message event. */ isMessageFromInlineMenuPageIframe(event) { if (!this.inlineMenuPageIframe) { return false; } // only trust the specific iframe we created return this.inlineMenuPageIframe.contentWindow === event.source; } /** * Validates that the message contains a valid session token. * The session token is generated when the container is created and is refreshed * every time the inline menu container is recreated. * */ isValidSessionToken(message) { if (!this.token || !(message === null || message === void 0 ? void 0 : message.token) || !(message === null || message === void 0 ? void 0 : message.token.length)) { return false; } return message.token === this.token; } /** * Validates that a message event originates from the extension. * * @param event - The message event to validate. * @returns True if the message is from the extension origin. */ isMessageFromExtensionOrigin(event) { try { if (event.origin === "null") { return false; } return event.origin === this.extensionOrigin; } catch (_a) { return false; } } } ;// ./src/autofill/overlay/inline-menu/pages/menu-container/bootstrap-autofill-inline-menu-container.ts // FIXME: Remove when updating file. Eslint update // eslint-disable-next-line @typescript-eslint/no-require-imports __webpack_require__(90495); (() => new AutofillInlineMenuContainer())(); /******/ })() ;