add: .config

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Melvin Ragusa
2026-02-02 14:58:13 +01:00
parent 2bfdc582b0
commit d3d7242351
1121 changed files with 617892 additions and 844 deletions

View File

@@ -0,0 +1,43 @@
@-webkit-keyframes bitwardenfill {
0% {
-webkit-transform: scale(1, 1);
}
50% {
-webkit-transform: scale(1.2, 1.2);
}
100% {
-webkit-transform: scale(1, 1);
}
}
@-moz-keyframes bitwardenfill {
0% {
transform: scale(1, 1);
}
50% {
transform: scale(1.2, 1.2);
}
100% {
transform: scale(1, 1);
}
}
span[data-bwautofill].com-bitwarden-browser-animated-fill {
display: inline-block;
}
.com-bitwarden-browser-animated-fill {
animation: bitwardenfill 200ms ease-in-out 0ms 1;
-webkit-animation: bitwardenfill 200ms ease-in-out 0ms 1;
}
@media (prefers-reduced-motion) {
.com-bitwarden-browser-animated-fill {
animation: none;
-webkit-animation: none;
}
}

View File

@@ -0,0 +1,575 @@
/******/ (function() { // webpackBootstrap
/******/ "use strict";
;// ./src/autofill/enums/autofill-port.enum.ts
const AutofillPort = {
InjectedScript: "autofill-injected-script-port",
};
;// ./src/autofill/utils/index.ts
var __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 __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/content/autofiller.ts
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", loadAutofiller);
}
else {
loadAutofiller();
}
function loadAutofiller() {
let pageHref = null;
let filledThisHref = false;
let delayFillTimeout;
let doFillInterval;
const handleExtensionDisconnect = () => {
clearDoFillInterval();
clearDelayFillTimeout();
};
const handleExtensionMessage = (message) => {
if (message.command === "fillForm" && pageHref === message.url) {
filledThisHref = true;
}
};
setupExtensionEventListeners();
triggerUserFillOnLoad();
function triggerUserFillOnLoad() {
clearDoFillInterval();
doFillInterval = setInterval(() => doFillIfNeeded(), 500);
}
function doFillIfNeeded(force = false) {
if (force || pageHref !== window.location.href) {
if (!force) {
// Some websites are slow and rendering all page content. Try to fill again later
// if we haven't already.
filledThisHref = false;
clearDelayFillTimeout();
delayFillTimeout = window.setTimeout(() => {
if (!filledThisHref) {
doFillIfNeeded(true);
}
}, 1500);
}
pageHref = window.location.href;
const msg = {
command: "bgCollectPageDetails",
sender: "autofiller",
};
void chrome.runtime.sendMessage(msg);
}
}
function clearDoFillInterval() {
if (doFillInterval) {
window.clearInterval(doFillInterval);
}
}
function clearDelayFillTimeout() {
if (delayFillTimeout) {
window.clearTimeout(delayFillTimeout);
}
}
function setupExtensionEventListeners() {
setupExtensionDisconnectAction(handleExtensionDisconnect);
chrome.runtime.onMessage.addListener(handleExtensionMessage);
}
}
/******/ })()
;

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,177 @@
/******/ (function() { // webpackBootstrap
/******/ "use strict";
;// ../../libs/common/src/vault/enums/vault-messages.enum.ts
const VaultMessages = {
HasBwInstalled: "hasBwInstalled",
checkBwInstalled: "checkIfBWExtensionInstalled",
/** @deprecated use {@link OpenBrowserExtensionToUrl} */
OpenAtRiskPasswords: "openAtRiskPasswords",
OpenBrowserExtensionToUrl: "openBrowserExtensionToUrl",
PopupOpened: "popupOpened",
};
;// ./src/autofill/content/content-message-handler.ts
var __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());
});
};
/**
* Handlers for window messages from the content script.
* NOTE: These handlers should be above the event listener to ensure they are defined before being used.
*/
const windowMessageHandlers = {
authResult: ({ data, referrer }) => handleAuthResultMessage(data, referrer),
webAuthnResult: ({ data, referrer }) => handleWebAuthnResultMessage(data, referrer),
[VaultMessages.checkBwInstalled]: () => handleExtensionInstallCheck(),
duoResult: ({ data, referrer }) => handleDuoResultMessage(data, referrer),
[VaultMessages.OpenAtRiskPasswords]: () => handleOpenAtRiskPasswordsMessage(),
[VaultMessages.OpenBrowserExtensionToUrl]: ({ data }) => handleOpenBrowserExtensionToUrlMessage(data),
};
/**
* IMPORTANT: Safari seems to have a bug where it doesn't properly handle
* window message events from content scripts when the listener these events
* is registered within a class. This is why these listeners are registered
* at the top level of this file.
*/
window.addEventListener("message", handleWindowMessageEvent, false);
chrome.runtime.onMessage.addListener(handleExtensionMessage);
setupExtensionDisconnectAction(() => {
window.removeEventListener("message", handleWindowMessageEvent);
chrome.runtime.onMessage.removeListener(handleExtensionMessage);
});
/**
* Handles the post to the web vault showing the extension has been installed
*/
function handleExtensionInstallCheck() {
window.postMessage({ command: VaultMessages.HasBwInstalled });
}
/**
* Handles the auth result message from the window.
*
* @param data - Data from the window message
* @param referrer - The referrer of the window
*/
function handleAuthResultMessage(data, referrer) {
const { command, lastpass, code, state } = data;
sendExtensionRuntimeMessage({ command, code, state, lastpass, referrer });
}
/**
* Handles the Duo 2FA result message from the window.
*
* @param data - Data from the window message
* @param referrer - The referrer of the window
*/
function handleDuoResultMessage(data, referrer) {
return __awaiter(this, void 0, void 0, function* () {
const { command, code, state } = data;
sendExtensionRuntimeMessage({ command, code, state, referrer });
});
}
/**
* Handles the webauthn result message from the window.
*
* @param data - Data from the window message
* @param referrer - The referrer of the window
*/
function handleWebAuthnResultMessage(data, referrer) {
const { command, remember } = data;
sendExtensionRuntimeMessage({ command, data: data.data, remember, referrer });
}
/** @deprecated use {@link handleOpenBrowserExtensionToUrlMessage} */
function handleOpenAtRiskPasswordsMessage() {
sendExtensionRuntimeMessage({ command: VaultMessages.OpenAtRiskPasswords });
}
function handleOpenBrowserExtensionToUrlMessage({ url }) {
sendExtensionRuntimeMessage({ command: VaultMessages.OpenBrowserExtensionToUrl, url });
}
/**
* Handles window message events, validating source and extracting referrer for security.
*
* @param event - The window message event
*/
function handleWindowMessageEvent(event) {
const { source, data, origin } = event;
if (source !== window || !(data === null || data === void 0 ? void 0 : data.command)) {
return;
}
// Extract hostname from event.origin for secure referrer validation in background script
let referrer;
// Sandboxed iframe or opaque origin support
if (origin === "null") {
referrer = "null";
}
else {
try {
const originUrl = new URL(origin);
referrer = originUrl.hostname;
}
catch (_a) {
return;
}
}
const handler = windowMessageHandlers[data.command];
if (handler) {
handler({ data, referrer });
}
}
/**
* Commands to forward from this script to the extension background.
*/
const forwardCommands = new Set([
"bgUnlockPopoutOpened",
"addToLockedVaultPendingNotifications",
"unlockCompleted",
"addedCipher",
]);
/**
* Handles messages from the extension. Currently, this is
* used to forward messages from the background context to
* other scripts within the extension.
*
* @param message - The message from the extension
*/
function handleExtensionMessage(message) {
if (forwardCommands.has(message.command)) {
sendExtensionRuntimeMessage(message);
}
}
/**
* Sends a message to the extension runtime, and ignores
* any potential promises that should be handled using
* the `void` operator.
*
* @param message - The message to send to the extension runtime
*/
function sendExtensionRuntimeMessage(message) {
void chrome.runtime.sendMessage(message);
}
/**
* Duplicate implementation of the same named method within `apps/browser/src/autofill/utils/index.ts`.
* This is done due to some strange observed compilation behavior present when importing the method from
* the utils file.
*
* TODO: Investigate why webpack tree shaking is not removing other methods when importing from the utils file.
* Possible cause can be seen below:
* @see https://stackoverflow.com/questions/71679366/webpack5-does-not-seem-to-tree-shake-unused-exports
*
* @param callback - Callback function to run when the extension disconnects
*/
function setupExtensionDisconnectAction(callback) {
const port = chrome.runtime.connect({ name: "autofill-injected-script-port" });
const onDisconnectCallback = (disconnectedPort) => {
callback(disconnectedPort);
port.onDisconnect.removeListener(onDisconnectCallback);
};
port.onDisconnect.addListener(onDisconnectCallback);
}
/******/ })()
;

View File

@@ -0,0 +1,68 @@
/******/ (function() { // webpackBootstrap
const inputTags = ["input", "textarea", "select"];
const labelTags = ["label", "span"];
const attributeKeys = ["id", "name", "label-aria", "placeholder"];
const invalidElement = chrome.i18n.getMessage("copyCustomFieldNameInvalidElement");
const noUniqueIdentifier = chrome.i18n.getMessage("copyCustomFieldNameNotUnique");
let clickedElement = null;
// Find the best attribute to be used as the Name for an element in a custom field.
function getClickedElementIdentifier() {
var _a, _b;
if (clickedElement == null) {
return invalidElement;
}
const clickedTag = clickedElement.nodeName.toLowerCase();
let inputElement = null;
// Try to identify the input element (which may not be the clicked element)
if (labelTags.includes(clickedTag)) {
let inputId;
if (clickedTag === "label") {
inputId = clickedElement.getAttribute("for");
}
else {
inputId = (_a = clickedElement.closest("label")) === null || _a === void 0 ? void 0 : _a.getAttribute("for");
}
if (inputId) {
inputElement = document.getElementById(inputId);
}
}
else {
inputElement = clickedElement;
}
if (inputElement == null || !inputTags.includes(inputElement.nodeName.toLowerCase())) {
return invalidElement;
}
for (const attributeKey of attributeKeys) {
const attributeValue = inputElement.getAttribute(attributeKey);
const selector = "[" + attributeKey + '="' + attributeValue + '"]';
if (!isNullOrEmpty(attributeValue) && ((_b = document.querySelectorAll(selector)) === null || _b === void 0 ? void 0 : _b.length) === 1) {
return attributeValue;
}
}
return noUniqueIdentifier;
}
function isNullOrEmpty(s) {
return s == null || s === "";
}
// We only have access to the element that's been clicked when the context menu is first opened.
// Remember it for use later.
document.addEventListener("contextmenu", (event) => {
clickedElement = event.target;
});
// Runs when the 'Copy Custom Field Name' context menu item is actually clicked.
chrome.runtime.onMessage.addListener((event, _sender, sendResponse) => {
if (event.command === "getClickedElement") {
const identifier = getClickedElementIdentifier();
if (sendResponse) {
sendResponse(identifier);
}
void chrome.runtime.sendMessage({
command: "getClickedElementResponse",
sender: "contextMenuHandler",
identifier: identifier,
});
}
});
/******/ })()
;

View File

@@ -0,0 +1,773 @@
/******/ (function() { // webpackBootstrap
/******/ "use strict";
;// ./src/autofill/enums/autofill-port.enum.ts
const autofill_port_enum_AutofillPort = {
InjectedScript: "autofill-injected-script-port",
};
;// ./src/autofill/utils/index.ts
var __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 __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/fido2/enums/fido2-port-name.enum.ts
const Fido2PortName = {
InjectedScript: "fido2-injected-content-script-port",
};
;// ./src/autofill/fido2/content/messaging/message.ts
const MessageTypes = {
CredentialCreationRequest: 0,
CredentialCreationResponse: 1,
CredentialGetRequest: 2,
CredentialGetResponse: 3,
AbortRequest: 4,
DisconnectRequest: 5,
ReconnectRequest: 6,
AbortResponse: 7,
ErrorResponse: 8,
};
;// ./src/autofill/fido2/content/messaging/messenger.ts
var messenger_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
const SENDER = "bitwarden-webauthn";
/**
* A class that handles communication between the page and content script. It converts
* the browser's broadcasting API into a request/response API with support for seamlessly
* handling aborts and exceptions across separate execution contexts.
*/
class Messenger {
/**
* Creates a messenger that uses the browser's `window.postMessage` API to initiate
* requests in the content script. Every request will then create it's own
* `MessageChannel` through which all subsequent communication will be sent through.
*
* @param window the window object to use for communication
* @returns a `Messenger` instance
*/
static forDOMCommunication(window) {
const windowOrigin = window.location.origin;
return new Messenger({
postMessage: (message, port) => window.postMessage(message, windowOrigin, [port]),
addEventListener: (listener) => window.addEventListener("message", listener),
removeEventListener: (listener) => window.removeEventListener("message", listener),
});
}
constructor(broadcastChannel) {
this.broadcastChannel = broadcastChannel;
this.messageEventListener = null;
this.onDestroy = new EventTarget();
this.messengerId = this.generateUniqueId();
this.messageEventListener = this.createMessageEventListener();
this.broadcastChannel.addEventListener(this.messageEventListener);
}
/**
* Sends a request to the content script and returns the response.
* AbortController signals will be forwarded to the content script.
*
* @param request data to send to the content script
* @param abortSignal the abort controller that might be used to abort the request
* @returns the response from the content script
*/
request(request, abortSignal) {
return messenger_awaiter(this, void 0, void 0, function* () {
const requestChannel = new MessageChannel();
const { port1: localPort, port2: remotePort } = requestChannel;
try {
const promise = new Promise((resolve) => {
localPort.onmessage = (event) => resolve(event.data);
});
const abortListener = () => localPort.postMessage({
metadata: { SENDER },
type: MessageTypes.AbortRequest,
});
abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.addEventListener("abort", abortListener);
this.broadcastChannel.postMessage(Object.assign(Object.assign({}, request), { SENDER, senderId: this.messengerId }), remotePort);
const response = yield promise;
abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.removeEventListener("abort", abortListener);
if (response.type === MessageTypes.ErrorResponse) {
const error = new Error();
Object.assign(error, JSON.parse(response.error));
throw error;
}
return response;
}
finally {
localPort.close();
}
});
}
createMessageEventListener() {
return (event) => messenger_awaiter(this, void 0, void 0, function* () {
var _a;
const windowOrigin = window.location.origin;
if (event.origin !== windowOrigin || !this.handler) {
return;
}
const message = event.data;
const port = (_a = event.ports) === null || _a === void 0 ? void 0 : _a[0];
if ((message === null || message === void 0 ? void 0 : message.SENDER) !== SENDER || message.senderId == this.messengerId || port == null) {
return;
}
const abortController = new AbortController();
port.onmessage = (event) => {
if (event.data.type === MessageTypes.AbortRequest) {
abortController.abort();
}
};
const onDestroyListener = () => abortController.abort();
this.onDestroy.addEventListener("destroy", onDestroyListener);
try {
const handlerResponse = yield this.handler(message, abortController);
port.postMessage(Object.assign(Object.assign({}, handlerResponse), { SENDER }));
}
catch (error) {
port.postMessage({
SENDER,
type: MessageTypes.ErrorResponse,
error: JSON.stringify(error, Object.getOwnPropertyNames(error)),
});
}
finally {
this.onDestroy.removeEventListener("destroy", onDestroyListener);
port.close();
}
});
}
/**
* Cleans up the messenger by removing the message event listener
*/
destroy() {
return messenger_awaiter(this, void 0, void 0, function* () {
this.onDestroy.dispatchEvent(new Event("destroy"));
if (this.messageEventListener) {
yield this.sendDisconnectCommand();
this.broadcastChannel.removeEventListener(this.messageEventListener);
this.messageEventListener = null;
}
});
}
sendDisconnectCommand() {
return messenger_awaiter(this, void 0, void 0, function* () {
yield this.request({ type: MessageTypes.DisconnectRequest });
});
}
generateUniqueId() {
return Date.now().toString(36) + Math.random().toString(36).substring(2);
}
}
;// ./src/autofill/fido2/content/fido2-content-script.ts
var fido2_content_script_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());
});
};
(function (globalContext) {
const shouldExecuteContentScript = globalContext.document.contentType === "text/html" &&
(globalContext.document.location.protocol === "https:" ||
(globalContext.document.location.protocol === "http:" &&
globalContext.document.location.hostname === "localhost"));
if (!shouldExecuteContentScript) {
return;
}
// Initialization logic, set up the messenger and connect a port to the background script.
const messenger = Messenger.forDOMCommunication(globalContext.window);
messenger.handler = handleFido2Message;
const port = chrome.runtime.connect({ name: Fido2PortName.InjectedScript });
port.onDisconnect.addListener(handlePortOnDisconnect);
/**
* Handles FIDO2 credential requests and returns the result.
*
* @param message - The message to handle.
* @param abortController - The abort controller used to handle exit conditions from the FIDO2 request.
*/
function handleFido2Message(message, abortController) {
return fido2_content_script_awaiter(this, void 0, void 0, function* () {
const requestId = Date.now().toString();
const abortHandler = () => sendExtensionMessage("fido2AbortRequest", { abortedRequestId: requestId });
abortController.signal.addEventListener("abort", abortHandler);
try {
if (message.type === MessageTypes.CredentialCreationRequest) {
return handleCredentialCreationRequestMessage(requestId, message.data);
}
if (message.type === MessageTypes.CredentialGetRequest) {
return handleCredentialGetRequestMessage(requestId, message.data);
}
if (message.type === MessageTypes.AbortRequest) {
return sendExtensionMessage("fido2AbortRequest", { abortedRequestId: requestId });
}
}
finally {
abortController.signal.removeEventListener("abort", abortHandler);
}
});
}
/**
* Handles the credential creation request message and returns the result.
*
* @param requestId - The request ID of the message.
* @param data - Data associated with the credential request.
*/
function handleCredentialCreationRequestMessage(requestId, data) {
return fido2_content_script_awaiter(this, void 0, void 0, function* () {
return respondToCredentialRequest("fido2RegisterCredentialRequest", MessageTypes.CredentialCreationResponse, requestId, data);
});
}
/**
* Handles the credential get request message and returns the result.
*
* @param requestId - The request ID of the message.
* @param data - Data associated with the credential request.
*/
function handleCredentialGetRequestMessage(requestId, data) {
return fido2_content_script_awaiter(this, void 0, void 0, function* () {
return respondToCredentialRequest("fido2GetCredentialRequest", MessageTypes.CredentialGetResponse, requestId, data);
});
}
/**
* Sends a message to the extension to handle the
* credential request and returns the result.
*
* @param command - The command to send to the extension.
* @param type - The type of message, either CredentialCreationResponse or CredentialGetResponse.
* @param requestId - The request ID of the message.
* @param messageData - Data associated with the credential request.
*/
function respondToCredentialRequest(command, type, requestId, messageData) {
return fido2_content_script_awaiter(this, void 0, void 0, function* () {
const data = Object.assign(Object.assign({}, messageData), { origin: globalContext.location.origin, sameOriginWithAncestors: globalContext.self === globalContext.top });
const result = yield sendExtensionMessage(command, { data, requestId });
if (result && result.error !== undefined) {
return Promise.reject(result.error);
}
return Promise.resolve({ type, result });
});
}
/**
* Handles the disconnect event of the port. Calls
* to the messenger to destroy and tear down the
* implemented page-script.js logic.
*/
function handlePortOnDisconnect() {
void messenger.destroy();
}
})(globalThis);
/******/ })()
;

View File

@@ -0,0 +1,640 @@
/******/ (function() { // webpackBootstrap
/******/ "use strict";
;// ../../libs/common/src/platform/services/fido2/fido2-utils.ts
// @ts-strict-ignore
class Fido2Utils {
static createResultToJson(result) {
return {
id: result.credentialId,
rawId: result.credentialId,
response: {
clientDataJSON: result.clientDataJSON,
authenticatorData: result.authData,
transports: result.transports,
publicKey: result.publicKey,
publicKeyAlgorithm: result.publicKeyAlgorithm,
attestationObject: result.attestationObject,
},
authenticatorAttachment: "platform",
clientExtensionResults: result.extensions,
type: "public-key",
};
}
static getResultToJson(result) {
return {
id: result.credentialId,
rawId: result.credentialId,
response: {
clientDataJSON: result.clientDataJSON,
authenticatorData: result.authenticatorData,
signature: result.signature,
userHandle: result.userHandle,
},
authenticatorAttachment: "platform",
clientExtensionResults: {},
type: "public-key",
};
}
static bufferToString(bufferSource) {
return Fido2Utils.fromBufferToB64(Fido2Utils.bufferSourceToUint8Array(bufferSource))
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=/g, "");
}
static stringToBuffer(str) {
return Fido2Utils.fromB64ToArray(Fido2Utils.fromUrlB64ToB64(str)).buffer;
}
static bufferSourceToUint8Array(bufferSource) {
if (Fido2Utils.isArrayBuffer(bufferSource)) {
return new Uint8Array(bufferSource);
}
else {
return new Uint8Array(bufferSource.buffer, bufferSource.byteOffset, bufferSource.byteLength);
}
}
/** Utility function to identify type of bufferSource. Necessary because of differences between runtimes */
static isArrayBuffer(bufferSource) {
return bufferSource instanceof ArrayBuffer || bufferSource.buffer === undefined;
}
static fromB64toUrlB64(b64Str) {
return b64Str.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
}
static fromBufferToB64(buffer) {
if (buffer == null) {
return null;
}
let binary = "";
const bytes = new Uint8Array(buffer);
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return globalThis.btoa(binary);
}
static fromB64ToArray(str) {
if (str == null) {
return null;
}
const binaryString = globalThis.atob(str);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
}
static fromUrlB64ToB64(urlB64Str) {
let output = urlB64Str.replace(/-/g, "+").replace(/_/g, "/");
switch (output.length % 4) {
case 0:
break;
case 2:
output += "==";
break;
case 3:
output += "=";
break;
default:
throw new Error("Illegal base64url string!");
}
return output;
}
/**
* This methods returns true if a cipher either has no passkeys, or has a passkey matching with userHandle
* @param userHandle
*/
static cipherHasNoOtherPasskeys(cipher, userHandle) {
if (cipher.login.fido2Credentials == null || cipher.login.fido2Credentials.length === 0) {
return true;
}
return cipher.login.fido2Credentials.some((passkey) => passkey.userHandle === userHandle);
}
}
;// ./src/autofill/fido2/utils/webauthn-utils.ts
class WebauthnUtils {
static mapCredentialCreationOptions(options, fallbackSupported) {
var _a, _b, _c, _d, _e;
const keyOptions = options.publicKey;
if (keyOptions == undefined) {
throw new Error("Public-key options not found");
}
return {
attestation: keyOptions.attestation,
authenticatorSelection: {
requireResidentKey: (_a = keyOptions.authenticatorSelection) === null || _a === void 0 ? void 0 : _a.requireResidentKey,
residentKey: (_b = keyOptions.authenticatorSelection) === null || _b === void 0 ? void 0 : _b.residentKey,
userVerification: (_c = keyOptions.authenticatorSelection) === null || _c === void 0 ? void 0 : _c.userVerification,
},
challenge: Fido2Utils.bufferToString(keyOptions.challenge),
excludeCredentials: (_d = keyOptions.excludeCredentials) === null || _d === void 0 ? void 0 : _d.map((credential) => ({
id: Fido2Utils.bufferToString(credential.id),
transports: credential.transports,
type: credential.type,
})),
extensions: {
credProps: (_e = keyOptions.extensions) === null || _e === void 0 ? void 0 : _e.credProps,
},
pubKeyCredParams: keyOptions.pubKeyCredParams
.map((params) => ({
// Fix for spec-deviation: Sites using KeycloakJS send `kp.alg` as a string
alg: Number(params.alg),
type: params.type,
}))
.filter((params) => !isNaN(params.alg)),
rp: {
id: keyOptions.rp.id,
name: keyOptions.rp.name,
},
user: {
id: Fido2Utils.bufferToString(keyOptions.user.id),
displayName: keyOptions.user.displayName,
name: keyOptions.user.name,
},
timeout: keyOptions.timeout,
fallbackSupported,
};
}
static mapCredentialRegistrationResult(result) {
const credential = {
id: result.credentialId,
rawId: Fido2Utils.stringToBuffer(result.credentialId),
type: "public-key",
authenticatorAttachment: "platform",
response: {
clientDataJSON: Fido2Utils.stringToBuffer(result.clientDataJSON),
attestationObject: Fido2Utils.stringToBuffer(result.attestationObject),
getAuthenticatorData() {
return Fido2Utils.stringToBuffer(result.authData);
},
getPublicKey() {
return Fido2Utils.stringToBuffer(result.publicKey);
},
getPublicKeyAlgorithm() {
return result.publicKeyAlgorithm;
},
getTransports() {
return result.transports;
},
},
getClientExtensionResults: () => ({
credProps: result.extensions.credProps,
}),
toJSON: () => Fido2Utils.createResultToJson(result),
};
// Modify prototype chains to fix `instanceof` calls.
// This makes these objects indistinguishable from the native classes.
// Unfortunately PublicKeyCredential does not have a javascript constructor so `extends` does not work here.
Object.setPrototypeOf(credential.response, AuthenticatorAttestationResponse.prototype);
Object.setPrototypeOf(credential, PublicKeyCredential.prototype);
return credential;
}
static mapCredentialRequestOptions(options, fallbackSupported) {
var _a, _b;
const keyOptions = options.publicKey;
if (keyOptions == undefined) {
throw new Error("Public-key options not found");
}
return {
allowedCredentialIds: (_b = (_a = keyOptions.allowCredentials) === null || _a === void 0 ? void 0 : _a.map((c) => Fido2Utils.bufferToString(c.id))) !== null && _b !== void 0 ? _b : [],
challenge: Fido2Utils.bufferToString(keyOptions.challenge),
rpId: keyOptions.rpId,
userVerification: keyOptions.userVerification,
timeout: keyOptions.timeout,
mediation: options.mediation,
fallbackSupported,
};
}
static mapCredentialAssertResult(result) {
const credential = {
id: result.credentialId,
rawId: Fido2Utils.stringToBuffer(result.credentialId),
type: "public-key",
response: {
authenticatorData: Fido2Utils.stringToBuffer(result.authenticatorData),
clientDataJSON: Fido2Utils.stringToBuffer(result.clientDataJSON),
signature: Fido2Utils.stringToBuffer(result.signature),
userHandle: Fido2Utils.stringToBuffer(result.userHandle),
},
getClientExtensionResults: () => ({}),
authenticatorAttachment: "platform",
toJSON: () => Fido2Utils.getResultToJson(result),
};
// Modify prototype chains to fix `instanceof` calls.
// This makes these objects indistinguishable from the native classes.
// Unfortunately PublicKeyCredential does not have a javascript constructor so `extends` does not work here.
Object.setPrototypeOf(credential.response, AuthenticatorAssertionResponse.prototype);
Object.setPrototypeOf(credential, PublicKeyCredential.prototype);
return credential;
}
}
;// ./src/autofill/fido2/content/messaging/message.ts
const MessageTypes = {
CredentialCreationRequest: 0,
CredentialCreationResponse: 1,
CredentialGetRequest: 2,
CredentialGetResponse: 3,
AbortRequest: 4,
DisconnectRequest: 5,
ReconnectRequest: 6,
AbortResponse: 7,
ErrorResponse: 8,
};
;// ./src/autofill/fido2/content/messaging/messenger.ts
var __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
const SENDER = "bitwarden-webauthn";
/**
* A class that handles communication between the page and content script. It converts
* the browser's broadcasting API into a request/response API with support for seamlessly
* handling aborts and exceptions across separate execution contexts.
*/
class Messenger {
/**
* Creates a messenger that uses the browser's `window.postMessage` API to initiate
* requests in the content script. Every request will then create it's own
* `MessageChannel` through which all subsequent communication will be sent through.
*
* @param window the window object to use for communication
* @returns a `Messenger` instance
*/
static forDOMCommunication(window) {
const windowOrigin = window.location.origin;
return new Messenger({
postMessage: (message, port) => window.postMessage(message, windowOrigin, [port]),
addEventListener: (listener) => window.addEventListener("message", listener),
removeEventListener: (listener) => window.removeEventListener("message", listener),
});
}
constructor(broadcastChannel) {
this.broadcastChannel = broadcastChannel;
this.messageEventListener = null;
this.onDestroy = new EventTarget();
this.messengerId = this.generateUniqueId();
this.messageEventListener = this.createMessageEventListener();
this.broadcastChannel.addEventListener(this.messageEventListener);
}
/**
* Sends a request to the content script and returns the response.
* AbortController signals will be forwarded to the content script.
*
* @param request data to send to the content script
* @param abortSignal the abort controller that might be used to abort the request
* @returns the response from the content script
*/
request(request, abortSignal) {
return __awaiter(this, void 0, void 0, function* () {
const requestChannel = new MessageChannel();
const { port1: localPort, port2: remotePort } = requestChannel;
try {
const promise = new Promise((resolve) => {
localPort.onmessage = (event) => resolve(event.data);
});
const abortListener = () => localPort.postMessage({
metadata: { SENDER },
type: MessageTypes.AbortRequest,
});
abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.addEventListener("abort", abortListener);
this.broadcastChannel.postMessage(Object.assign(Object.assign({}, request), { SENDER, senderId: this.messengerId }), remotePort);
const response = yield promise;
abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.removeEventListener("abort", abortListener);
if (response.type === MessageTypes.ErrorResponse) {
const error = new Error();
Object.assign(error, JSON.parse(response.error));
throw error;
}
return response;
}
finally {
localPort.close();
}
});
}
createMessageEventListener() {
return (event) => __awaiter(this, void 0, void 0, function* () {
var _a;
const windowOrigin = window.location.origin;
if (event.origin !== windowOrigin || !this.handler) {
return;
}
const message = event.data;
const port = (_a = event.ports) === null || _a === void 0 ? void 0 : _a[0];
if ((message === null || message === void 0 ? void 0 : message.SENDER) !== SENDER || message.senderId == this.messengerId || port == null) {
return;
}
const abortController = new AbortController();
port.onmessage = (event) => {
if (event.data.type === MessageTypes.AbortRequest) {
abortController.abort();
}
};
const onDestroyListener = () => abortController.abort();
this.onDestroy.addEventListener("destroy", onDestroyListener);
try {
const handlerResponse = yield this.handler(message, abortController);
port.postMessage(Object.assign(Object.assign({}, handlerResponse), { SENDER }));
}
catch (error) {
port.postMessage({
SENDER,
type: MessageTypes.ErrorResponse,
error: JSON.stringify(error, Object.getOwnPropertyNames(error)),
});
}
finally {
this.onDestroy.removeEventListener("destroy", onDestroyListener);
port.close();
}
});
}
/**
* Cleans up the messenger by removing the message event listener
*/
destroy() {
return __awaiter(this, void 0, void 0, function* () {
this.onDestroy.dispatchEvent(new Event("destroy"));
if (this.messageEventListener) {
yield this.sendDisconnectCommand();
this.broadcastChannel.removeEventListener(this.messageEventListener);
this.messageEventListener = null;
}
});
}
sendDisconnectCommand() {
return __awaiter(this, void 0, void 0, function* () {
yield this.request({ type: MessageTypes.DisconnectRequest });
});
}
generateUniqueId() {
return Date.now().toString(36) + Math.random().toString(36).substring(2);
}
}
;// ./src/autofill/fido2/content/fido2-page-script.ts
var fido2_page_script_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
(function (globalContext) {
if (globalContext.document.currentScript) {
globalContext.document.currentScript.parentNode.removeChild(globalContext.document.currentScript);
}
const shouldExecuteContentScript = globalContext.document.contentType === "text/html" &&
(globalContext.document.location.protocol === "https:" ||
(globalContext.document.location.protocol === "http:" &&
globalContext.document.location.hostname === "localhost"));
if (!shouldExecuteContentScript) {
return;
}
const BrowserPublicKeyCredential = globalContext.PublicKeyCredential;
const BrowserNavigatorCredentials = navigator.credentials;
const BrowserAuthenticatorAttestationResponse = globalContext.AuthenticatorAttestationResponse;
const browserNativeWebauthnSupport = globalContext.PublicKeyCredential != undefined;
let browserNativeWebauthnPlatformAuthenticatorSupport = false;
if (!browserNativeWebauthnSupport) {
// Polyfill webauthn support
try {
// credentials are read-only if supported, use type-casting to force assignment
navigator.credentials = {
create() {
return fido2_page_script_awaiter(this, void 0, void 0, function* () {
throw new Error("Webauthn not supported in this browser.");
});
},
get() {
return fido2_page_script_awaiter(this, void 0, void 0, function* () {
throw new Error("Webauthn not supported in this browser.");
});
},
};
globalContext.PublicKeyCredential = class PolyfillPublicKeyCredential {
static isUserVerifyingPlatformAuthenticatorAvailable() {
return Promise.resolve(true);
}
};
globalContext.AuthenticatorAttestationResponse =
class PolyfillAuthenticatorAttestationResponse {
};
}
catch (_a) {
/* empty */
}
}
else {
void BrowserPublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable().then((available) => {
browserNativeWebauthnPlatformAuthenticatorSupport = available;
if (!available) {
// Polyfill platform authenticator support
globalContext.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable = () => Promise.resolve(true);
}
});
}
const browserCredentials = {
create: navigator.credentials.create.bind(navigator.credentials),
get: navigator.credentials.get.bind(navigator.credentials),
};
const messenger = Messenger.forDOMCommunication(window);
let waitForFocusTimeout;
let focusListenerHandler;
navigator.credentials.create = createWebAuthnCredential;
navigator.credentials.get = getWebAuthnCredential;
/**
* Creates a new webauthn credential.
*
* @param options Options for creating new credentials.
* @returns Promise that resolves to the new credential object.
*/
function createWebAuthnCredential(options) {
return fido2_page_script_awaiter(this, void 0, void 0, function* () {
var _a, _b;
if (!isWebauthnCall(options)) {
return yield browserCredentials.create(options);
}
const authenticatorAttachmentIsPlatform = ((_b = (_a = options === null || options === void 0 ? void 0 : options.publicKey) === null || _a === void 0 ? void 0 : _a.authenticatorSelection) === null || _b === void 0 ? void 0 : _b.authenticatorAttachment) === "platform";
const fallbackSupported = (authenticatorAttachmentIsPlatform && browserNativeWebauthnPlatformAuthenticatorSupport) ||
(!authenticatorAttachmentIsPlatform && browserNativeWebauthnSupport);
try {
const response = yield messenger.request({
type: MessageTypes.CredentialCreationRequest,
data: WebauthnUtils.mapCredentialCreationOptions(options, fallbackSupported),
}, options === null || options === void 0 ? void 0 : options.signal);
if (response.type !== MessageTypes.CredentialCreationResponse) {
throw new Error("Something went wrong.");
}
return WebauthnUtils.mapCredentialRegistrationResult(response.result);
}
catch (error) {
if (error && error.fallbackRequested && fallbackSupported) {
yield waitForFocus();
return yield browserCredentials.create(options);
}
throw error;
}
});
}
/**
* Retrieves a webauthn credential.
*
* @param options Options for creating new credentials.
* @returns Promise that resolves to the new credential object.
*/
function getWebAuthnCredential(options) {
return fido2_page_script_awaiter(this, void 0, void 0, function* () {
if (!isWebauthnCall(options)) {
return yield browserCredentials.get(options);
}
const abortSignal = (options === null || options === void 0 ? void 0 : options.signal) || new AbortController().signal;
const fallbackSupported = browserNativeWebauthnSupport;
if ((options === null || options === void 0 ? void 0 : options.mediation) && options.mediation === "conditional") {
const internalAbortControllers = [new AbortController(), new AbortController()];
const bitwardenResponse = (internalAbortController) => fido2_page_script_awaiter(this, void 0, void 0, function* () {
try {
const abortListener = () => messenger.request({
type: MessageTypes.AbortRequest,
abortedRequestId: abortSignal.toString(),
});
internalAbortController.signal.addEventListener("abort", abortListener);
const response = yield messenger.request({
type: MessageTypes.CredentialGetRequest,
data: WebauthnUtils.mapCredentialRequestOptions(options, fallbackSupported),
}, internalAbortController.signal);
internalAbortController.signal.removeEventListener("abort", abortListener);
if (response.type !== MessageTypes.CredentialGetResponse) {
throw new Error("Something went wrong.");
}
return WebauthnUtils.mapCredentialAssertResult(response.result);
}
catch (_a) {
// Ignoring error
}
});
const browserResponse = (internalAbortController) => browserCredentials.get(Object.assign(Object.assign({}, options), { signal: internalAbortController.signal }));
const abortListener = () => {
internalAbortControllers.forEach((controller) => controller.abort());
};
abortSignal.addEventListener("abort", abortListener);
const response = yield Promise.race([
bitwardenResponse(internalAbortControllers[0]),
browserResponse(internalAbortControllers[1]),
]);
abortSignal.removeEventListener("abort", abortListener);
internalAbortControllers.forEach((controller) => controller.abort());
return response;
}
try {
const response = yield messenger.request({
type: MessageTypes.CredentialGetRequest,
data: WebauthnUtils.mapCredentialRequestOptions(options, fallbackSupported),
}, options === null || options === void 0 ? void 0 : options.signal);
if (response.type !== MessageTypes.CredentialGetResponse) {
throw new Error("Something went wrong.");
}
return WebauthnUtils.mapCredentialAssertResult(response.result);
}
catch (error) {
if (error && error.fallbackRequested && fallbackSupported) {
yield waitForFocus();
return yield browserCredentials.get(options);
}
throw error;
}
});
}
function isWebauthnCall(options) {
return options && "publicKey" in options;
}
/**
* Wait for window to be focused.
* Safari doesn't allow scripts to trigger webauthn when window is not focused.
*
* @param fallbackWait How long to wait when the script is not able to add event listeners to `window.top`. Defaults to 500ms.
* @param timeout Maximum time to wait for focus in milliseconds. Defaults to 5 minutes.
* @returns Promise that resolves when window is focused, or rejects if timeout is reached.
*/
function waitForFocus() {
return fido2_page_script_awaiter(this, arguments, void 0, function* (fallbackWait = 500, timeout = 5 * 60 * 1000) {
try {
if (globalContext.top.document.hasFocus()) {
return;
}
}
catch (_a) {
// Cannot access window.top due to cross-origin frame, fallback to waiting
return yield new Promise((resolve) => globalContext.setTimeout(resolve, fallbackWait));
}
const focusPromise = new Promise((resolve) => {
focusListenerHandler = () => resolve();
globalContext.top.addEventListener("focus", focusListenerHandler);
});
const timeoutPromise = new Promise((_, reject) => {
waitForFocusTimeout = globalContext.setTimeout(() => reject(new DOMException("The operation either timed out or was not allowed.", "AbortError")), timeout);
});
try {
yield Promise.race([focusPromise, timeoutPromise]);
}
finally {
clearWaitForFocus();
}
});
}
function clearWaitForFocus() {
globalContext.top.removeEventListener("focus", focusListenerHandler);
if (waitForFocusTimeout) {
globalContext.clearTimeout(waitForFocusTimeout);
}
}
function destroy() {
try {
if (browserNativeWebauthnSupport) {
navigator.credentials.create = browserCredentials.create;
navigator.credentials.get = browserCredentials.get;
}
else {
navigator.credentials = BrowserNavigatorCredentials;
globalContext.PublicKeyCredential = BrowserPublicKeyCredential;
globalContext.AuthenticatorAttestationResponse = BrowserAuthenticatorAttestationResponse;
}
clearWaitForFocus();
void messenger.destroy();
}
catch (_a) {
/** empty */
}
}
/**
* Sets up a listener to handle cleanup or reconnection when the extension's
* context changes due to being reloaded or unloaded.
*/
messenger.handler = (message) => {
const type = message.type;
// Handle cleanup for disconnect request
if (type === MessageTypes.DisconnectRequest) {
destroy();
}
};
})(globalThis);
/******/ })()
;

View File

@@ -0,0 +1,52 @@
/******/ (function() { // webpackBootstrap
/******/ "use strict";
// UNUSED EXPORTS: sendExtensionMessage
;// ../../libs/common/src/platform/ipc/ipc-message.ts
function isIpcMessage(message) {
return message.type === "bitwarden-ipc-message";
}
;// ./src/platform/ipc/content/ipc-content-script.ts
// TODO: This content script should be dynamically reloaded when the extension is updated,
// to avoid "Extension context invalidated." errors.
// Web -> Background
function sendExtensionMessage(message) {
if (typeof browser !== "undefined" &&
typeof browser.runtime !== "undefined" &&
typeof browser.runtime.sendMessage !== "undefined") {
void browser.runtime.sendMessage(message);
return;
}
void chrome.runtime.sendMessage(message);
}
window.addEventListener("message", (event) => {
if (event.origin !== window.origin) {
return;
}
if (isIpcMessage(event.data)) {
sendExtensionMessage(event.data);
}
});
// Background -> Web
function setupMessageListener() {
function listener(message) {
if (isIpcMessage(message)) {
void window.postMessage(message);
}
}
if (typeof browser !== "undefined" &&
typeof browser.runtime !== "undefined" &&
typeof browser.runtime.onMessage !== "undefined") {
browser.runtime.onMessage.addListener(listener);
return;
}
// eslint-disable-next-line no-restricted-syntax -- This doesn't run in the popup but in the content script
chrome.runtime.onMessage.addListener(listener);
}
setupMessageListener();
/******/ })()
;

View File

@@ -0,0 +1,22 @@
/******/ (function() { // webpackBootstrap
/******/ "use strict";
;// ../../libs/common/src/vault/enums/vault-messages.enum.ts
const VaultMessages = {
HasBwInstalled: "hasBwInstalled",
checkBwInstalled: "checkIfBWExtensionInstalled",
/** @deprecated use {@link OpenBrowserExtensionToUrl} */
OpenAtRiskPasswords: "openAtRiskPasswords",
OpenBrowserExtensionToUrl: "openBrowserExtensionToUrl",
PopupOpened: "popupOpened",
};
;// ./src/vault/content/send-on-installed-message.ts
(function (globalContext) {
globalContext.postMessage({ command: VaultMessages.HasBwInstalled });
})(window);
/******/ })()
;

View File

@@ -0,0 +1,23 @@
/******/ (function() { // webpackBootstrap
/******/ "use strict";
;// ../../libs/common/src/vault/enums/vault-messages.enum.ts
const VaultMessages = {
HasBwInstalled: "hasBwInstalled",
checkBwInstalled: "checkIfBWExtensionInstalled",
/** @deprecated use {@link OpenBrowserExtensionToUrl} */
OpenAtRiskPasswords: "openAtRiskPasswords",
OpenBrowserExtensionToUrl: "openBrowserExtensionToUrl",
PopupOpened: "popupOpened",
};
;// ./src/vault/content/send-popup-open-message.ts
(function (globalContext) {
// Send a message to the window that the popup opened
globalContext.postMessage({ command: VaultMessages.PopupOpened });
})(window);
/******/ })()
;

View File

@@ -0,0 +1,7 @@
/******/ (function() { // webpackBootstrap
(function () {
void chrome.runtime.sendMessage({ command: "triggerAutofillScriptInjection" });
})();
/******/ })()
;