From 276c9a7dcb4f11d9f68fb83135f6d825b4f7f1fd Mon Sep 17 00:00:00 2001 From: Shane Osbourne Date: Thu, 7 Mar 2024 12:02:38 +0000 Subject: [PATCH] fixing flaky tests --- dist/autofill-debug.js | 31310 +++++++--------- dist/autofill.js | 23005 +++++------- .../helpers/pages/emailAutofillPage.js | 2 +- integration-test/helpers/pages/loginPage.js | 6 +- integration-test/helpers/test-context.js | 5 + .../tests/incontext-signup.extension.spec.js | 2 +- .../tests/login-form.macos.spec.js | 2 +- package.json | 2 +- .../Resources/assets/autofill-debug.js | 31310 +++++++--------- swift-package/Resources/assets/autofill.js | 23005 +++++------- 10 files changed, 47236 insertions(+), 61413 deletions(-) diff --git a/dist/autofill-debug.js b/dist/autofill-debug.js index d789ba540..48a8197b8 100644 --- a/dist/autofill-debug.js +++ b/dist/autofill-debug.js @@ -1,14934 +1,12988 @@ -(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i 1 && !unprotectedDomain) { - const partialDomain = domainParts.join('.'); - unprotectedDomain = featureList.filter(domain => domain.domain === partialDomain).length > 0; - domainParts.shift(); - } - return unprotectedDomain; -} -function processConfig(data, userList, preferences) { - const topLevelUrl = getTopLevelURL(); - const allowlisted = userList.filter(domain => domain === topLevelUrl.host).length > 0; - const enabledFeatures = Object.keys(data.features).filter(featureName => { - const feature = data.features[featureName]; - return feature.state === 'enabled' && !isUnprotectedDomain(topLevelUrl, feature.exceptions); - }); - const isBroken = isUnprotectedDomain(topLevelUrl, data.unprotectedTemporary); - preferences.site = { - domain: topLevelUrl.hostname, - isBroken, - allowlisted, - enabledFeatures +(() => { + var __defProp = Object.defineProperty; + var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __publicField = (obj, key, value) => { + __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); + return value; + }; + var __accessCheck = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); + }; + var __privateGet = (obj, member, getter) => { + __accessCheck(obj, member, "read from private field"); + return getter ? getter.call(obj) : member.get(obj); + }; + var __privateAdd = (obj, member, value) => { + if (member.has(obj)) + throw TypeError("Cannot add the same private member more than once"); + member instanceof WeakSet ? member.add(obj) : member.set(obj, value); + }; + var __privateSet = (obj, member, value, setter) => { + __accessCheck(obj, member, "write to private field"); + setter ? setter.call(obj, value) : member.set(obj, value); + return value; }; - // TODO - preferences.cookie = {}; - return preferences; -} -},{}],2:[function(require,module,exports){ -"use strict"; + // src/requestIdleCallback.js + /*! + * Copyright 2015 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + window.requestIdleCallback = window.requestIdleCallback || function(cb) { + return setTimeout(function() { + const start = Date.now(); + cb({ + didTimeout: false, + timeRemaining: function() { + return Math.max(0, 50 - (Date.now() - start)); + } + }); + }, 1); + }; + window.cancelIdleCallback = window.cancelIdleCallback || function(id) { + clearTimeout(id); + }; -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.ZodError = exports.quotelessJson = exports.ZodIssueCode = void 0; -const util_1 = require("./helpers/util"); -exports.ZodIssueCode = util_1.util.arrayToEnum(["invalid_type", "invalid_literal", "custom", "invalid_union", "invalid_union_discriminator", "invalid_enum_value", "unrecognized_keys", "invalid_arguments", "invalid_return_type", "invalid_date", "invalid_string", "too_small", "too_big", "invalid_intersection_types", "not_multiple_of", "not_finite"]); -const quotelessJson = obj => { - const json = JSON.stringify(obj, null, 2); - return json.replace(/"([^"]+)":/g, "$1:"); -}; -exports.quotelessJson = quotelessJson; -class ZodError extends Error { - constructor(issues) { - var _this; - super(); - _this = this; - this.issues = []; - this.addIssue = sub => { - this.issues = [...this.issues, sub]; - }; - this.addIssues = function () { - let subs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; - _this.issues = [..._this.issues, ...subs]; - }; - const actualProto = new.target.prototype; - if (Object.setPrototypeOf) { - // eslint-disable-next-line ban/ban - Object.setPrototypeOf(this, actualProto); - } else { - this.__proto__ = actualProto; - } - this.name = "ZodError"; - this.issues = issues; - } - get errors() { - return this.issues; - } - format(_mapper) { - const mapper = _mapper || function (issue) { - return issue.message; + // src/config.js + var DDG_DOMAIN_REGEX = new RegExp(/^https:\/\/(([a-z0-9-_]+?)\.)?duckduckgo\.com\/email/); + function createGlobalConfig(overrides) { + let isApp = false; + let isTopFrame = false; + let supportsTopFrame = false; + let hasModernWebkitAPI = false; + // INJECT isApp HERE + // INJECT isTopFrame HERE + // INJECT supportsTopFrame HERE + // INJECT hasModernWebkitAPI HERE + let isWindows = false; + // INJECT isWindows HERE + let webkitMessageHandlerNames = []; + // INJECT webkitMessageHandlerNames HERE + let isDDGTestMode = false; + isDDGTestMode = true; + let contentScope = null; + let userUnprotectedDomains = null; + let userPreferences = null; + // INJECT contentScope HERE + // INJECT userUnprotectedDomains HERE + // INJECT userPreferences HERE + let availableInputTypes = null; + // INJECT availableInputTypes HERE + let secret = "PLACEHOLDER_SECRET"; + const isAndroid = userPreferences?.platform.name === "android"; + const isDDGApp = ["ios", "android", "macos", "windows"].includes(userPreferences?.platform.name) || isWindows; + const isMobileApp = ["ios", "android"].includes(userPreferences?.platform.name); + const isFirefox = navigator.userAgent.includes("Firefox"); + const isDDGDomain = Boolean(window.location.href.match(DDG_DOMAIN_REGEX)); + const isExtension = false; + const config = { + isApp, + isDDGApp, + isAndroid, + isFirefox, + isMobileApp, + isExtension, + isTopFrame, + isWindows, + secret, + supportsTopFrame, + hasModernWebkitAPI, + contentScope, + userUnprotectedDomains, + userPreferences, + isDDGTestMode, + isDDGDomain, + availableInputTypes, + webkitMessageHandlerNames, + ...overrides }; - const fieldErrors = { - _errors: [] - }; - const processError = error => { - for (const issue of error.issues) { - if (issue.code === "invalid_union") { - issue.unionErrors.map(processError); - } else if (issue.code === "invalid_return_type") { - processError(issue.returnTypeError); - } else if (issue.code === "invalid_arguments") { - processError(issue.argumentsError); - } else if (issue.path.length === 0) { - fieldErrors._errors.push(mapper(issue)); - } else { - let curr = fieldErrors; - let i = 0; - while (i < issue.path.length) { - const el = issue.path[i]; - const terminal = i === issue.path.length - 1; - if (!terminal) { - curr[el] = curr[el] || { - _errors: [] - }; - // if (typeof el === "string") { - // curr[el] = curr[el] || { _errors: [] }; - // } else if (typeof el === "number") { - // const errorArray: any = []; - // errorArray._errors = []; - // curr[el] = curr[el] || errorArray; - // } - } else { - curr[el] = curr[el] || { - _errors: [] - }; - curr[el]._errors.push(mapper(issue)); - } - curr = curr[el]; - i++; - } + return config; + } + + // src/constants.js + var constants = { + ATTR_INPUT_TYPE: "data-ddg-inputType", + ATTR_AUTOFILL: "data-ddg-autofill", + TEXT_LENGTH_CUTOFF: 100, + MAX_INPUTS_PER_PAGE: 100, + MAX_FORMS_PER_PAGE: 30, + MAX_INPUTS_PER_FORM: 80, + MAX_FORM_RESCANS: 50 + }; + + // src/Form/label-util.js + var EXCLUDED_TAGS = ["BR", "SCRIPT", "NOSCRIPT", "OPTION", "STYLE"]; + var extractElementStrings = (element) => { + const strings = /* @__PURE__ */ new Set(); + const _extractElementStrings = (el) => { + if (EXCLUDED_TAGS.includes(el.tagName)) { + return; + } + if (el.nodeType === el.TEXT_NODE || !el.childNodes.length) { + let trimmedText = removeExcessWhitespace(el.textContent); + if (trimmedText) { + strings.add(trimmedText); } + return; } - }; - processError(this); - return fieldErrors; - } - toString() { - return this.message; - } - get message() { - return JSON.stringify(this.issues, util_1.util.jsonStringifyReplacer, 2); - } - get isEmpty() { - return this.issues.length === 0; - } - flatten() { - let mapper = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : issue => issue.message; - const fieldErrors = {}; - const formErrors = []; - for (const sub of this.issues) { - if (sub.path.length > 0) { - fieldErrors[sub.path[0]] = fieldErrors[sub.path[0]] || []; - fieldErrors[sub.path[0]].push(mapper(sub)); - } else { - formErrors.push(mapper(sub)); + for (let node of el.childNodes) { + let nodeType = node.nodeType; + if (nodeType !== node.ELEMENT_NODE && nodeType !== node.TEXT_NODE) { + continue; + } + _extractElementStrings(node); } - } - return { - formErrors, - fieldErrors }; - } - get formErrors() { - return this.flatten(); - } -} -exports.ZodError = ZodError; -ZodError.create = issues => { - const error = new ZodError(issues); - return error; -}; - -},{"./helpers/util":8}],3:[function(require,module,exports){ -"use strict"; - -var __importDefault = void 0 && (void 0).__importDefault || function (mod) { - return mod && mod.__esModule ? mod : { - "default": mod + _extractElementStrings(element); + return [...strings]; }; -}; -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.getErrorMap = exports.setErrorMap = exports.defaultErrorMap = void 0; -const en_1 = __importDefault(require("./locales/en")); -exports.defaultErrorMap = en_1.default; -let overrideErrorMap = en_1.default; -function setErrorMap(map) { - overrideErrorMap = map; -} -exports.setErrorMap = setErrorMap; -function getErrorMap() { - return overrideErrorMap; -} -exports.getErrorMap = getErrorMap; - -},{"./locales/en":10}],4:[function(require,module,exports){ -"use strict"; -var __createBinding = void 0 && (void 0).__createBinding || (Object.create ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { - enumerable: true, - get: function () { - return m[k]; + // src/Form/matching-config/__generated__/compiled-matching-config.js + var matchingConfiguration = { + matchers: { + fields: { + unknown: { type: "unknown", strategies: { ddgMatcher: "unknown" } }, + emailAddress: { + type: "emailAddress", + strategies: { + cssSelector: "emailAddress", + ddgMatcher: "emailAddress", + vendorRegex: "email" + } + }, + password: { + type: "password", + strategies: { cssSelector: "password", ddgMatcher: "password" } + }, + username: { + type: "username", + strategies: { cssSelector: "username", ddgMatcher: "username" } + }, + firstName: { + type: "firstName", + strategies: { + cssSelector: "firstName", + ddgMatcher: "firstName", + vendorRegex: "given-name" + } + }, + middleName: { + type: "middleName", + strategies: { + cssSelector: "middleName", + ddgMatcher: "middleName", + vendorRegex: "additional-name" + } + }, + lastName: { + type: "lastName", + strategies: { + cssSelector: "lastName", + ddgMatcher: "lastName", + vendorRegex: "family-name" + } + }, + fullName: { + type: "fullName", + strategies: { + cssSelector: "fullName", + ddgMatcher: "fullName", + vendorRegex: "name" + } + }, + phone: { + type: "phone", + strategies: { + cssSelector: "phone", + ddgMatcher: "phone", + vendorRegex: "tel" + } + }, + addressStreet: { + type: "addressStreet", + strategies: { + cssSelector: "addressStreet", + ddgMatcher: "addressStreet", + vendorRegex: "address-line1" + } + }, + addressStreet2: { + type: "addressStreet2", + strategies: { + cssSelector: "addressStreet2", + ddgMatcher: "addressStreet2", + vendorRegex: "address-line2" + } + }, + addressCity: { + type: "addressCity", + strategies: { + cssSelector: "addressCity", + ddgMatcher: "addressCity", + vendorRegex: "address-level2" + } + }, + addressProvince: { + type: "addressProvince", + strategies: { + cssSelector: "addressProvince", + ddgMatcher: "addressProvince", + vendorRegex: "address-level1" + } + }, + addressPostalCode: { + type: "addressPostalCode", + strategies: { + cssSelector: "addressPostalCode", + ddgMatcher: "addressPostalCode", + vendorRegex: "postal-code" + } + }, + addressCountryCode: { + type: "addressCountryCode", + strategies: { + cssSelector: "addressCountryCode", + ddgMatcher: "addressCountryCode", + vendorRegex: "country" + } + }, + birthdayDay: { + type: "birthdayDay", + strategies: { cssSelector: "birthdayDay", ddgMatcher: "birthdayDay" } + }, + birthdayMonth: { + type: "birthdayMonth", + strategies: { cssSelector: "birthdayMonth", ddgMatcher: "birthdayMonth" } + }, + birthdayYear: { + type: "birthdayYear", + strategies: { cssSelector: "birthdayYear", ddgMatcher: "birthdayYear" } + }, + cardName: { + type: "cardName", + strategies: { + cssSelector: "cardName", + ddgMatcher: "cardName", + vendorRegex: "cc-name" + } + }, + cardNumber: { + type: "cardNumber", + strategies: { + cssSelector: "cardNumber", + ddgMatcher: "cardNumber", + vendorRegex: "cc-number" + } + }, + cardSecurityCode: { + type: "cardSecurityCode", + strategies: { + cssSelector: "cardSecurityCode", + ddgMatcher: "cardSecurityCode" + } + }, + expirationMonth: { + type: "expirationMonth", + strategies: { + cssSelector: "expirationMonth", + ddgMatcher: "expirationMonth", + vendorRegex: "cc-exp-month" + } + }, + expirationYear: { + type: "expirationYear", + strategies: { + cssSelector: "expirationYear", + ddgMatcher: "expirationYear", + vendorRegex: "cc-exp-year" + } + }, + expiration: { + type: "expiration", + strategies: { + cssSelector: "expiration", + ddgMatcher: "expiration", + vendorRegex: "cc-exp" + } + } + }, + lists: { + unknown: ["unknown"], + emailAddress: ["emailAddress"], + password: ["password"], + username: ["username"], + cc: [ + "cardName", + "cardNumber", + "cardSecurityCode", + "expirationMonth", + "expirationYear", + "expiration" + ], + id: [ + "firstName", + "middleName", + "lastName", + "fullName", + "phone", + "addressStreet", + "addressStreet2", + "addressCity", + "addressProvince", + "addressPostalCode", + "addressCountryCode", + "birthdayDay", + "birthdayMonth", + "birthdayYear" + ] + } + }, + strategies: { + cssSelector: { + selectors: { + genericTextField: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month])', + submitButtonSelector: 'input[type=submit], input[type=button], input[type=image], button:not([role=switch]):not([role=link]), [role=button], a[href="#"][id*=button i], a[href="#"][id*=btn i]', + formInputsSelector: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month]),[autocomplete=username],select', + safeUniversalSelector: "*:not(select):not(option):not(script):not(noscript):not(style):not(br)", + emailAddress: 'input:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]), input[type=""][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([type=tel]), input[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]):not([name*=code i]), input:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]), input[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=""][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=email], input[type=text][aria-label*=email i]:not([aria-label*=search i]), input:not([type])[aria-label*=email i]:not([aria-label*=search i]), input[name=username][type=email], input[autocomplete=username][type=email], input[autocomplete=username][placeholder*=email i], input[autocomplete=email],input[name="mail_tel" i],input[value=email i]', + username: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month])[autocomplete^=user i],input[name=username i],input[name="loginId" i],input[name="userid" i],input[id="userid" i],input[name="user_id" i],input[name="user-id" i],input[id="login-id" i],input[id="login_id" i],input[id="loginid" i],input[name="login" i],input[name=accountname i],input[autocomplete=username i],input[name*=accountid i],input[name="j_username" i],input[id="j_username" i],input[name="uwinid" i],input[name="livedoor_id" i],input[name="ssousername" i],input[name="j_userlogin_pwd" i],input[name="user[login]" i],input[name="user" i],input[name$="_username" i],input[id="lmSsoinput" i],input[name="account_subdomain" i],input[name="masterid" i],input[name="tridField" i],input[id="signInName" i],input[id="w3c_accountsbundle_accountrequeststep1_login" i],input[id="username" i],input[name="_user" i],input[name="login_username" i],input[name^="login-user-account" i],input[id="loginusuario" i],input[name="usuario" i],input[id="UserLoginFormUsername" i],input[id="nw_username" i],input[can-field="accountName"],input[placeholder^="username" i]', + password: "input[type=password]:not([autocomplete*=cc]):not([autocomplete=one-time-code]):not([name*=answer i]):not([name*=mfa i]):not([name*=tin i]):not([name*=card i]):not([name*=cvv i]),input.js-cloudsave-phrase", + cardName: 'input[autocomplete="cc-name" i], input[autocomplete="ccname" i], input[name="ccname" i], input[name="cc-name" i], input[name="ppw-accountHolderName" i], input[id*=cardname i], input[id*=card-name i], input[id*=card_name i]', + cardNumber: 'input[autocomplete="cc-number" i], input[autocomplete="ccnumber" i], input[autocomplete="cardnumber" i], input[autocomplete="card-number" i], input[name="ccnumber" i], input[name="cc-number" i], input[name*=card i][name*=number i], input[name*=cardnumber i], input[id*=cardnumber i], input[id*=card-number i], input[id*=card_number i]', + cardSecurityCode: 'input[autocomplete="cc-csc" i], input[autocomplete="csc" i], input[autocomplete="cc-cvc" i], input[autocomplete="cvc" i], input[name="cvc" i], input[name="cc-cvc" i], input[name="cc-csc" i], input[name="csc" i], input[name*=security i][name*=code i]', + expirationMonth: '[autocomplete="cc-exp-month" i], [autocomplete="cc_exp_month" i], [name="ccmonth" i], [name="ppw-expirationDate_month" i], [name=cardExpiryMonth i], [name*=ExpDate_Month i], [name*=expiration i][name*=month i], [id*=expiration i][id*=month i], [name*=cc-exp-month i], [name*="card_exp-month" i], [name*=cc_exp_month i], [id*=cc-month i]', + expirationYear: '[autocomplete="cc-exp-year" i], [autocomplete="cc_exp_year" i], [name="ccyear" i], [name="ppw-expirationDate_year" i], [name=cardExpiryYear i], [name*=ExpDate_Year i], [name*=expiration i][name*=year i], [id*=expiration i][id*=year i], [name*="cc-exp-year" i], [name*="card_exp-year" i], [name*=cc_exp_year i], [id*=cc-year i]', + expiration: '[autocomplete="cc-exp" i], [name="cc-exp" i], [name="exp-date" i], [name="expirationDate" i], input[id*=expiration i]', + firstName: "[name*=fname i], [autocomplete*=given-name i], [name*=firstname i], [autocomplete*=firstname i], [name*=first-name i], [autocomplete*=first-name i], [name*=first_name i], [autocomplete*=first_name i], [name*=givenname i], [autocomplete*=givenname i], [name*=given-name i], [name*=given_name i], [autocomplete*=given_name i], [name*=forename i], [autocomplete*=forename i]", + middleName: "[name*=mname i], [autocomplete*=additional-name i], [name*=middlename i], [autocomplete*=middlename i], [name*=middle-name i], [autocomplete*=middle-name i], [name*=middle_name i], [autocomplete*=middle_name i], [name*=additionalname i], [autocomplete*=additionalname i], [name*=additional-name i], [name*=additional_name i], [autocomplete*=additional_name i]", + lastName: "[name=lname], [autocomplete*=family-name i], [name*=lastname i], [autocomplete*=lastname i], [name*=last-name i], [autocomplete*=last-name i], [name*=last_name i], [autocomplete*=last_name i], [name*=familyname i], [autocomplete*=familyname i], [name*=family-name i], [name*=family_name i], [autocomplete*=family_name i], [name*=surname i], [autocomplete*=surname i]", + fullName: "[autocomplete=name], [name*=fullname i], [autocomplete*=fullname i], [name*=full-name i], [autocomplete*=full-name i], [name*=full_name i], [autocomplete*=full_name i], [name*=your-name i], [autocomplete*=your-name i]", + phone: '[name*=phone i]:not([name*=extension i]):not([name*=type i]):not([name*=country i]), [name*=mobile i]:not([name*=type i]), [autocomplete=tel], [autocomplete="tel-national"], [placeholder*="phone number" i]', + addressStreet: "[name=address i], [autocomplete=street-address i], [autocomplete=address-line1 i], [name=street i], [name=ppw-line1 i], [name*=addressLine1 i]", + addressStreet2: "[name=address2 i], [autocomplete=address-line2 i], [name=ppw-line2 i], [name*=addressLine2 i]", + addressCity: "[name=city i], [autocomplete=address-level2 i], [name=ppw-city i], [name*=addressCity i]", + addressProvince: "[name=province i], [name=state i], [autocomplete=address-level1 i]", + addressPostalCode: "[name=zip i], [name=zip2 i], [name=postal i], [autocomplete=postal-code i], [autocomplete=zip-code i], [name*=postalCode i], [name*=zipcode i]", + addressCountryCode: "[name=country i], [autocomplete=country i], [name*=countryCode i], [name*=country-code i], [name*=countryName i], [name*=country-name i],select.idms-address-country", + birthdayDay: '[name=bday-day i], [name*=birthday_day i], [name*=birthday-day i], [name=date_of_birth_day i], [name=date-of-birth-day i], [name^=birthdate_d i], [name^=birthdate-d i], [aria-label="birthday" i][placeholder="day" i]', + birthdayMonth: '[name=bday-month i], [name*=birthday_month i], [name*=birthday-month i], [name=date_of_birth_month i], [name=date-of-birth-month i], [name^=birthdate_m i], [name^=birthdate-m i], select[name="mm" i]', + birthdayYear: '[name=bday-year i], [name*=birthday_year i], [name*=birthday-year i], [name=date_of_birth_year i], [name=date-of-birth-year i], [name^=birthdate_y i], [name^=birthdate-y i], [aria-label="birthday" i][placeholder="year" i]' + } + }, + ddgMatcher: { + matchers: { + unknown: { + match: /search|filter|subject|title|captcha|mfa|2fa|(two|2).?factor|one-time|otp|cerca|filtr|oggetto|titolo|(due|2|più).?fattori|suche|filtern|betreff|zoeken|filter|onderwerp|titel|chercher|filtrer|objet|titre|authentification multifacteur|double authentification|à usage unique|busca|busqueda|filtra|dos pasos|un solo uso|sök|filter|ämne|multifaktorsautentisering|tvåfaktorsautentisering|två.?faktor|engångs/iu, + skip: /phone|mobile|email|password/iu + }, + emailAddress: { + match: /.mail\b|apple.?id|posta elettronica|e.?mailadres|correo electr|correo-e|^correo$|\be.?post|e.?postadress/iu, + skip: /phone|(first.?|last.?)name|number|code/iu, + forceUnknown: /search|filter|subject|title|\btab\b|otp/iu + }, + password: { + match: /password|passwort|kennwort|wachtwoord|mot de passe|clave|contraseña|lösenord/iu, + skip: /email|one-time|error|hint/iu, + forceUnknown: /captcha|mfa|2fa|two factor|otp|pin/iu + }, + newPassword: { match: /new|re.?(enter|type)|repeat|update|reset\b/iu }, + currentPassword: { match: /current|old|previous|expired|existing/iu }, + username: { + match: /(user|account|online.?id|log(i|o)n|net)((.)?(name|i.?d.?|log(i|o)n).?)?(.?((or|\/).+|\*|:)( required)?)?$|(nome|id|login).?utente|(nome|id) (dell.)?account|codice cliente|nutzername|anmeldename|gebruikersnaam|nom d.utilisateur|identifiant|pseudo|usuari|cuenta|identificador|apodo|\bdni\b|\bnie\b| del? documento|documento de identidad|användarnamn|kontonamn|användar-id/iu, + skip: /phone/iu, + forceUnknown: /search|policy|choose a user\b/iu + }, + cardName: { + match: /(card.*name|name.*card)|(card.*holder|holder.*card)|(card.*owner|owner.*card)/iu + }, + cardNumber: { + match: /card.*number|number.*card/iu, + skip: /phone/iu, + forceUnknown: /plus/iu + }, + cardSecurityCode: { + match: /security.?code|card.?verif|cvv|csc|cvc|cv2|card id/iu + }, + expirationMonth: { + match: /(card|\bcc\b)?.?(exp(iry|iration)?)?.?(month|\bmm\b(?![.\s/-]yy))/iu, + skip: /mm[/\s.\-_—–]|check/iu + }, + expirationYear: { + match: /(card|\bcc\b)?.?(exp(iry|iration)?)?.?(year|yy)/iu, + skip: /mm[/\s.\-_—–]|check/iu + }, + expiration: { + match: /(\bmm\b|\b\d\d\b)[/\s.\-_—–](\byy|\bjj|\baa|\b\d\d)|\bexp|\bvalid(idity| through| until)/iu, + skip: /invalid|^dd\/|check/iu + }, + firstName: { + match: /(first|given|fore).?name|\bnome/iu, + skip: /last|cognome|completo/iu + }, + middleName: { match: /(middle|additional).?name/iu }, + lastName: { + match: /(last|family|sur)[^i]?name|cognome/iu, + skip: /first|\bnome/iu + }, + fullName: { + match: /^(full.?|whole\s|first.*last\s|real\s|contact.?)?name\b|\bnome/iu, + forceUnknown: /company|org|item/iu + }, + phone: { + match: /phone|mobile|telefono|cellulare/iu, + skip: /code|pass|country/iu, + forceUnknown: /ext|type|otp/iu + }, + addressStreet: { + match: /address/iu, + forceUnknown: /\bip\b|duck|web|url/iu, + skip: /address.*(2|two|3|three)|email|log.?in|sign.?in|civico/iu + }, + addressStreet2: { + match: /address.*(2|two)|apartment|\bapt\b|\bflat\b|\bline.*(2|two)/iu, + forceUnknown: /\bip\b|duck/iu, + skip: /email|log.?in|sign.?in/iu + }, + addressCity: { + match: /city|town|città|comune/iu, + skip: /\bzip\b|\bcap\b/iu, + forceUnknown: /vatican/iu + }, + addressProvince: { + match: /state|province|region|county|provincia|regione/iu, + forceUnknown: /united/iu, + skip: /country/iu + }, + addressPostalCode: { + match: /\bzip\b|postal\b|post.?code|\bcap\b|codice postale/iu + }, + addressCountryCode: { match: /country|\bnation\b|nazione|paese/iu }, + birthdayDay: { match: /(birth.*day|day.*birth)/iu, skip: /month|year/iu }, + birthdayMonth: { match: /(birth.*month|month.*birth)/iu, skip: /year/iu }, + birthdayYear: { match: /(birth.*year|year.*birth)/iu }, + loginRegex: { + match: /sign(ing)?.?[io]n(?!g)|log.?[io]n|log.?out|unsubscri|(forgot(ten)?|reset) (your )?password|password (forgotten|lost)|mfa-submit-form|unlock|logged in as|entra|accedi|accesso|resetta password|password dimenticata|dimenticato la password|recuper[ao] password|(ein|aus)loggen|anmeld(eformular|ung|efeld)|abmelden|passwort (vergessen|verloren)|zugang| zugangsformular|einwahl|inloggen|se (dé)?connecter|(dé)?connexion|récupérer ((mon|ton|votre|le) )?mot de passe|mot de passe (oublié|perdu)|clave(?! su)|olvidó su (clave|contraseña)|.*sesión|conect(arse|ado)|conéctate|acce(de|so)|entrar|logga (in|ut)|avprenumerera|avregistrera|glömt lösenord|återställ lösenord/iu + }, + signupRegex: { + match: /sign(ing)?.?up|join|\bregist(er|ration)|newsletter|\bsubscri(be|ption)|contact|create|start|enroll|settings|preferences|profile|update|checkout|purchase|buy|order|schedule|estimate|request|new.?customer|(confirm|re.?(type|enter)|repeat) password|password confirm|iscri(viti|zione)|registra(ti|zione)|(?:nuovo|crea(?:zione)?) account|contatt(?:ac)i|sottoscriv|sottoscrizione|compra|acquist(a|o)|ordin[aeio]|richie(?:di|sta)|(?:conferma|ripeti) password|inizia|nuovo cliente|impostazioni|preferenze|profilo|aggiorna|paga|registrier(ung|en)|profil (anlegen|erstellen)| nachrichten|verteiler|neukunde|neuer (kunde|benutzer|nutzer)|passwort wiederholen|anmeldeseite|nieuwsbrief|aanmaken|profiel|s.inscrire|inscription|s.abonner|créer|préférences|profil|mise à jour|payer|ach(eter|at)| nouvel utilisateur|(confirmer|réessayer) ((mon|ton|votre|le) )?mot de passe|regis(trarse|tro)|regístrate|inscr(ibirse|ipción|íbete)|solicitar|crea(r cuenta)?|nueva cuenta|nuevo (cliente|usuario)|preferencias|perfil|lista de correo|registrer(a|ing)|(nytt|öppna) konto|nyhetsbrev|prenumer(era|ation)|kontakt|skapa|starta|inställningar|min (sida|kundvagn)|uppdatera|till kassan|gäst|köp|beställ|schemalägg|ny kund|(repetera|bekräfta) lösenord/iu + }, + conservativeSignupRegex: { + match: /sign.?up|join|register|enroll|(create|new).+account|newsletter|subscri(be|ption)|settings|preferences|profile|update|iscri(viti|zione)|registra(ti|zione)|(?:nuovo|crea(?:zione)?) account|contatt(?:ac)?i|sottoscriv|sottoscrizione|impostazioni|preferenze|aggiorna|anmeld(en|ung)|registrier(en|ung)|neukunde|neuer (kunde|benutzer|nutzer)|registreren|eigenschappen|profiel|bijwerken|s.inscrire|inscription|s.abonner|abonnement|préférences|profil|créer un compte|regis(trarse|tro)|regístrate|inscr(ibirse|ipción|íbete)|crea(r cuenta)?|nueva cuenta|nuevo (cliente|usuario)|preferencias|perfil|lista de correo|registrer(a|ing)|(nytt|öppna) konto|nyhetsbrev|prenumer(era|ation)|kontakt|skapa|starta|inställningar|min (sida|kundvagn)|uppdatera/iu + }, + resetPasswordLink: { + match: /(forgot(ten)?|reset|don't remember) (your )?password|password forgotten|password dimenticata|reset(?:ta) password|recuper[ao] password|(vergessen|verloren|verlegt|wiederherstellen) passwort|wachtwoord (vergeten|reset)|(oublié|récupérer) ((mon|ton|votre|le) )?mot de passe|mot de passe (oublié|perdu)|re(iniciar|cuperar) (contraseña|clave)|olvid(ó su|aste tu|é mi) (contraseña|clave)|recordar( su)? (contraseña|clave)|glömt lösenord|återställ lösenord/iu + }, + loginProvidersRegex: { match: / with | con | mit | met | avec /iu }, + submitButtonRegex: { + match: /submit|send|confirm|save|continue|next|sign|log.?([io])n|buy|purchase|check.?out|subscribe|donate|update|\bset\b|invia|conferma|salva|continua|entra|acced|accesso|compra|paga|sottoscriv|registra|dona|senden|\bja\b|bestätigen|weiter|nächste|kaufen|bezahlen|spenden|versturen|verzenden|opslaan|volgende|koop|kopen|voeg toe|aanmelden|envoyer|confirmer|sauvegarder|continuer|suivant|signer|connexion|acheter|payer|s.abonner|donner|enviar|confirmar|registrarse|continuar|siguiente|comprar|donar|skicka|bekräfta|spara|fortsätt|nästa|logga in|köp|handla|till kassan|registrera|donera/iu + }, + submitButtonUnlikelyRegex: { + match: /facebook|twitter|google|apple|cancel|show|toggle|reveal|hide|print|back|already|annulla|mostra|nascondi|stampa|indietro|già|abbrechen|passwort|zeigen|verbergen|drucken|zurück|annuleer|wachtwoord|toon|vorige|annuler|mot de passe|montrer|cacher|imprimer|retour|déjà|anular|cancelar|imprimir|cerrar|avbryt|lösenord|visa|dölj|skirv ut|tillbaka|redan/iu + } + } + }, + vendorRegex: { + rules: { + email: /((^e-?mail$)|(^email-?address$))|(e.?mail|courriel|correo.*electr(o|ó)nico|メールアドレス|электронной.?почты|邮件|邮箱|電郵地址|ഇ-മെയില്‍|ഇലക്ട്രോണിക്.?മെയിൽ|ایمیل|پست.*الکترونیک|ईमेल|इलॅक्ट्रॉनिक.?मेल|(\b|_)eposta(\b|_)|(?:이메일|전자.?우편|[ee]-?mail)(.?주소)?)/iu, + tel: /((^phone$)|(^mobile$)|(^mobile-?phone$)|(^tel$)|(^telephone$)|(^phone-?number$))|(phone|mobile|contact.?number|telefonnummer|telefono|teléfono|telfixe|電話|telefone|telemovel|телефон|मोबाइल|(\b|_|\*)telefon(\b|_|\*)|电话|മൊബൈല്‍|(?:전화|핸드폰|휴대폰|휴대전화)(?:.?번호)?)/iu, + organization: /((^company$)|(^company-?name$)|(^organization$)|(^organization-?name$))|(company|business|organization|organisation|empresa|societe|société|ragione.?sociale|会社|название.?компании|单位|公司|شرکت|회사|직장)/iu, + "street-address": /((^address$)|(^street-?address$)|(^addr$)|(^street$)|(^mailing-?addr(ess)?$)|(^billing-?addr(ess)?$)|(^mail-?addr(ess)?$)|(^bill-?addr(ess)?$))|(streetaddress|street-address)/iu, + "address-line1": /(addrline1|address_1)|((^address-?1$)|(^address-?line-?1$)|(^addr-?1$)|(^street-?1$))|(^address$|address[_-]?line[_-]?(1|one)|address1|addr1|street|(?:shipping|billing)address$|strasse|straße|hausnummer|housenumber|house.?name|direccion|dirección|adresse|indirizzo|^住所$|住所1|адрес|地址|(\b|_)adres(?! (başlığı(nız)?|tarifi))(\b|_)|^주소.?$|주소.?1)/iu, + "address-line2": /(addrline2|address_2)|((^address-?2$)|(^address-?line-?2$)|(^addr-?2$)|(^street-?2$))|(address[_-]?line(2|two)|address2|addr2|street|suite|unit(?!e)|adresszusatz|ergänzende.?angaben|direccion2|colonia|adicional|addresssuppl|complementnom|appartement|indirizzo2|住所2|complemento|addrcomplement|улица|地址2|주소.?2)/iu, + "address-line3": /(addrline3|address_3)|((^address-?3$)|(^address-?line-?3$)|(^addr-?3$)|(^street-?3$))|(address[_-]?line(3|three)|address3|addr3|street|suite|unit(?!e)|adresszusatz|ergänzende.?angaben|direccion3|colonia|adicional|addresssuppl|complementnom|appartement|indirizzo3|住所3|complemento|addrcomplement|улица|地址3|주소.?3)/iu, + "address-level2": /((^city$)|(^town$)|(^address-?level-?2$)|(^address-?city$)|(^address-?town$))|(city|town|\bort\b|stadt|suburb|ciudad|provincia|localidad|poblacion|ville|commune|localit(a|à)|citt(a|à)|市区町村|cidade|город|市|分區|شهر|शहर|ग्राम|गाँव|നഗരം|ഗ്രാമം|((\b|_|\*)([i̇ii̇]l[cç]e(miz|niz)?)(\b|_|\*))|^시[^도·・]|시[·・]?군[·・]?구)/iu, + "address-level1": /(land)|((^state$)|(^province$)|(^provence$)|(^address-?level-?1$)|(^address-?state$)|(^address-?province$))|(county|region|province|county|principality|都道府県|estado|provincia|область|省|地區|സംസ്ഥാനം|استان|राज्य|((\b|_|\*)(eyalet|[şs]ehir|[i̇ii̇]limiz|kent)(\b|_|\*))|^시[·・]?도)/iu, + "postal-code": /((^postal$)|(^zip$)|(^zip2$)|(^zip-?code$)|(^postal-?code$)|(^post-?code$)|(^address-?zip$)|(^address-?postal$)|(^address-?code$)|(^address-?postal-?code$)|(^address-?zip-?code$))|(zip|postal|post.*code|pcode|pin.?code|postleitzahl|\bcp\b|\bcdp\b|\bcap\b|郵便番号|codigo|codpos|\bcep\b|почтовый.?индекс|पिन.?कोड|പിന്‍കോഡ്|邮政编码|邮编|郵遞區號|(\b|_)posta kodu(\b|_)|우편.?번호)/iu, + country: /((^country$)|(^country-?code$)|(^country-?name$)|(^address-?country$)|(^address-?country-?name$)|(^address-?country-?code$))|(country|countries|país|pais|(\b|_)land(\b|_)(?!.*(mark.*))|国家|국가|나라|(\b|_)(ülke|ulce|ulke)(\b|_)|کشور)/iu, + "cc-name": /(accountholdername|titulaire)|(cc-?name|card-?name|cardholder-?name|cardholder|(^nom$))|(card.?(?:holder|owner)|name.*(\b)?on(\b)?.*card|(?:card|cc).?name|cc.?full.?name|karteninhaber|nombre.*tarjeta|nom.*carte|nome.*cart|名前|имя.*карты|信用卡开户名|开户名|持卡人姓名|持卡人姓名)/iu, + name: /((^name$)|full-?name|your-?name)|(^name|full.?name|your.?name|customer.?name|bill.?name|ship.?name|name.*first.*last|firstandlastname|nombre.*y.*apellidos|^nom(?!bre)\b|お名前|氏名|^nome|نام.*نام.*خانوادگی|姓名|(\b|_|\*)ad[ı]? soyad[ı]?(\b|_|\*)|성명)/iu, + "given-name": /((^f-?name$)|(^first-?name$)|(^given-?name$)|(^first-?n$))|(first.*name|initials|fname|first$|given.*name|vorname|nombre|forename|prénom|prenom|名|\bnome|имя|نام|이름|പേര്|(\b|_|\*)(isim|ad|ad(i|ı|iniz|ınız)?)(\b|_|\*)|नाम)/iu, + "additional-name": /(apellido.?materno|lastlastname)|((^m-?name$)|(^middle-?name$)|(^additional-?name$)|(^middle-?initial$)|(^middle-?n$)|(^middle-?i$))|(middle.*name|mname|middle$|middle.*initial|m\.i\.|mi$|\bmi\b)/iu, + "family-name": /((^l-?name$)|(^last-?name$)|(^s-?name$)|(^surname$)|(^family-?name$)|(^family-?n$)|(^last-?n$))|(last.*name|lname|surname|last$|secondname|family.*name|nachname|apellidos?|famille|^nom(?!bre)|cognome|姓|apelidos|surename|sobrenome|фамилия|نام.*خانوادگی|उपनाम|മറുപേര്|(\b|_|\*)(soyisim|soyad(i|ı|iniz|ınız)?)(\b|_|\*)|\b성(?:[^명]|\b))/iu, + "cc-number": /((cc|kk)nr)|(cc-?number|cc-?num|card-?number|card-?num|(^number$)|(^cc$)|cc-?no|card-?no|(^credit-?card$)|numero-?carte|(^carte$)|(^carte-?credit$)|num-?carte|cb-?num)|((add)?(?:card|cc|acct).?(?:number|#|no|num|field)|カード番号|номер.*карты|信用卡号|信用卡号码|信用卡卡號|카드|(numero|número|numéro)(?!.*(document|fono|phone|réservation)))/iu, + "cc-exp-month": /((cc|kk)month)|((^exp-?month$)|(^cc-?exp-?month$)|(^cc-?month$)|(^card-?month$)|(^cc-?mo$)|(^card-?mo$)|(^exp-?mo$)|(^card-?exp-?mo$)|(^cc-?exp-?mo$)|(^card-?expiration-?month$)|(^expiration-?month$)|(^cc-?mm$)|(^cc-?m$)|(^card-?mm$)|(^card-?m$)|(^card-?exp-?mm$)|(^cc-?exp-?mm$)|(^exp-?mm$)|(^exp-?m$)|(^expire-?month$)|(^expire-?mo$)|(^expiry-?month$)|(^expiry-?mo$)|(^card-?expire-?month$)|(^card-?expire-?mo$)|(^card-?expiry-?month$)|(^card-?expiry-?mo$)|(^mois-?validite$)|(^mois-?expiration$)|(^m-?validite$)|(^m-?expiration$)|(^expiry-?date-?field-?month$)|(^expiration-?date-?month$)|(^expiration-?date-?mm$)|(^exp-?mon$)|(^validity-?mo$)|(^exp-?date-?mo$)|(^cb-?date-?mois$)|(^date-?m$))|(gueltig|gültig|monat|fecha|date.*exp|scadenza|有効期限|validade|срок действия карты|月)/iu, + "cc-exp-year": /((cc|kk)year)|((^exp-?year$)|(^cc-?exp-?year$)|(^cc-?year$)|(^card-?year$)|(^cc-?yr$)|(^card-?yr$)|(^exp-?yr$)|(^card-?exp-?yr$)|(^cc-?exp-?yr$)|(^card-?expiration-?year$)|(^expiration-?year$)|(^cc-?yy$)|(^cc-?y$)|(^card-?yy$)|(^card-?y$)|(^card-?exp-?yy$)|(^cc-?exp-?yy$)|(^exp-?yy$)|(^exp-?y$)|(^cc-?yyyy$)|(^card-?yyyy$)|(^card-?exp-?yyyy$)|(^cc-?exp-?yyyy$)|(^expire-?year$)|(^expire-?yr$)|(^expiry-?year$)|(^expiry-?yr$)|(^card-?expire-?year$)|(^card-?expire-?yr$)|(^card-?expiry-?year$)|(^card-?expiry-?yr$)|(^an-?validite$)|(^an-?expiration$)|(^annee-?validite$)|(^annee-?expiration$)|(^expiry-?date-?field-?year$)|(^expiration-?date-?year$)|(^cb-?date-?ann$)|(^expiration-?date-?yy$)|(^expiration-?date-?yyyy$)|(^validity-?year$)|(^exp-?date-?year$)|(^date-?y$))|(ablaufdatum|gueltig|gültig|jahr|fecha|scadenza|有効期限|validade|срок действия карты|年|有效期)/iu, + "cc-exp": /((^cc-?exp$)|(^card-?exp$)|(^cc-?expiration$)|(^card-?expiration$)|(^cc-?ex$)|(^card-?ex$)|(^card-?expire$)|(^card-?expiry$)|(^validite$)|(^expiration$)|(^expiry$)|mm-?yy|mm-?yyyy|yy-?mm|yyyy-?mm|expiration-?date|payment-?card-?expiration|(^payment-?cc-?date$))|(expir|exp.*date|^expfield$|gueltig|gültig|fecha|date.*exp|scadenza|有効期限|validade|срок действия карты)/iu, + "cc-type": /(type|kartenmarke)|((^cc-?type$)|(^card-?type$)|(^card-?brand$)|(^cc-?brand$)|(^cb-?type$))/iu + }, + ruleSets: [ + { + "address-line1": "addrline1|address_1", + "address-line2": "addrline2|address_2", + "address-line3": "addrline3|address_3", + "address-level1": "land", + "additional-name": "apellido.?materno|lastlastname", + "cc-name": "accountholdername|titulaire", + "cc-number": "(cc|kk)nr", + "cc-exp-month": "(cc|kk)month", + "cc-exp-year": "(cc|kk)year", + "cc-type": "type|kartenmarke" + }, + { + email: "(^e-?mail$)|(^email-?address$)", + tel: "(^phone$)|(^mobile$)|(^mobile-?phone$)|(^tel$)|(^telephone$)|(^phone-?number$)", + organization: "(^company$)|(^company-?name$)|(^organization$)|(^organization-?name$)", + "street-address": "(^address$)|(^street-?address$)|(^addr$)|(^street$)|(^mailing-?addr(ess)?$)|(^billing-?addr(ess)?$)|(^mail-?addr(ess)?$)|(^bill-?addr(ess)?$)", + "address-line1": "(^address-?1$)|(^address-?line-?1$)|(^addr-?1$)|(^street-?1$)", + "address-line2": "(^address-?2$)|(^address-?line-?2$)|(^addr-?2$)|(^street-?2$)", + "address-line3": "(^address-?3$)|(^address-?line-?3$)|(^addr-?3$)|(^street-?3$)", + "address-level2": "(^city$)|(^town$)|(^address-?level-?2$)|(^address-?city$)|(^address-?town$)", + "address-level1": "(^state$)|(^province$)|(^provence$)|(^address-?level-?1$)|(^address-?state$)|(^address-?province$)", + "postal-code": "(^postal$)|(^zip$)|(^zip2$)|(^zip-?code$)|(^postal-?code$)|(^post-?code$)|(^address-?zip$)|(^address-?postal$)|(^address-?code$)|(^address-?postal-?code$)|(^address-?zip-?code$)", + country: "(^country$)|(^country-?code$)|(^country-?name$)|(^address-?country$)|(^address-?country-?name$)|(^address-?country-?code$)", + name: "(^name$)|full-?name|your-?name", + "given-name": "(^f-?name$)|(^first-?name$)|(^given-?name$)|(^first-?n$)", + "additional-name": "(^m-?name$)|(^middle-?name$)|(^additional-?name$)|(^middle-?initial$)|(^middle-?n$)|(^middle-?i$)", + "family-name": "(^l-?name$)|(^last-?name$)|(^s-?name$)|(^surname$)|(^family-?name$)|(^family-?n$)|(^last-?n$)", + "cc-name": "cc-?name|card-?name|cardholder-?name|cardholder|(^nom$)", + "cc-number": "cc-?number|cc-?num|card-?number|card-?num|(^number$)|(^cc$)|cc-?no|card-?no|(^credit-?card$)|numero-?carte|(^carte$)|(^carte-?credit$)|num-?carte|cb-?num", + "cc-exp": "(^cc-?exp$)|(^card-?exp$)|(^cc-?expiration$)|(^card-?expiration$)|(^cc-?ex$)|(^card-?ex$)|(^card-?expire$)|(^card-?expiry$)|(^validite$)|(^expiration$)|(^expiry$)|mm-?yy|mm-?yyyy|yy-?mm|yyyy-?mm|expiration-?date|payment-?card-?expiration|(^payment-?cc-?date$)", + "cc-exp-month": "(^exp-?month$)|(^cc-?exp-?month$)|(^cc-?month$)|(^card-?month$)|(^cc-?mo$)|(^card-?mo$)|(^exp-?mo$)|(^card-?exp-?mo$)|(^cc-?exp-?mo$)|(^card-?expiration-?month$)|(^expiration-?month$)|(^cc-?mm$)|(^cc-?m$)|(^card-?mm$)|(^card-?m$)|(^card-?exp-?mm$)|(^cc-?exp-?mm$)|(^exp-?mm$)|(^exp-?m$)|(^expire-?month$)|(^expire-?mo$)|(^expiry-?month$)|(^expiry-?mo$)|(^card-?expire-?month$)|(^card-?expire-?mo$)|(^card-?expiry-?month$)|(^card-?expiry-?mo$)|(^mois-?validite$)|(^mois-?expiration$)|(^m-?validite$)|(^m-?expiration$)|(^expiry-?date-?field-?month$)|(^expiration-?date-?month$)|(^expiration-?date-?mm$)|(^exp-?mon$)|(^validity-?mo$)|(^exp-?date-?mo$)|(^cb-?date-?mois$)|(^date-?m$)", + "cc-exp-year": "(^exp-?year$)|(^cc-?exp-?year$)|(^cc-?year$)|(^card-?year$)|(^cc-?yr$)|(^card-?yr$)|(^exp-?yr$)|(^card-?exp-?yr$)|(^cc-?exp-?yr$)|(^card-?expiration-?year$)|(^expiration-?year$)|(^cc-?yy$)|(^cc-?y$)|(^card-?yy$)|(^card-?y$)|(^card-?exp-?yy$)|(^cc-?exp-?yy$)|(^exp-?yy$)|(^exp-?y$)|(^cc-?yyyy$)|(^card-?yyyy$)|(^card-?exp-?yyyy$)|(^cc-?exp-?yyyy$)|(^expire-?year$)|(^expire-?yr$)|(^expiry-?year$)|(^expiry-?yr$)|(^card-?expire-?year$)|(^card-?expire-?yr$)|(^card-?expiry-?year$)|(^card-?expiry-?yr$)|(^an-?validite$)|(^an-?expiration$)|(^annee-?validite$)|(^annee-?expiration$)|(^expiry-?date-?field-?year$)|(^expiration-?date-?year$)|(^cb-?date-?ann$)|(^expiration-?date-?yy$)|(^expiration-?date-?yyyy$)|(^validity-?year$)|(^exp-?date-?year$)|(^date-?y$)", + "cc-type": "(^cc-?type$)|(^card-?type$)|(^card-?brand$)|(^cc-?brand$)|(^cb-?type$)" + }, + { + email: "e.?mail|courriel|correo.*electr(o|\xF3)nico|\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9|\u042D\u043B\u0435\u043A\u0442\u0440\u043E\u043D\u043D\u043E\u0439.?\u041F\u043E\u0447\u0442\u044B|\u90AE\u4EF6|\u90AE\u7BB1|\u96FB\u90F5\u5730\u5740|\u0D07-\u0D2E\u0D46\u0D2F\u0D3F\u0D32\u0D4D\u200D|\u0D07\u0D32\u0D15\u0D4D\u0D1F\u0D4D\u0D30\u0D4B\u0D23\u0D3F\u0D15\u0D4D.?\u0D2E\u0D46\u0D2F\u0D3F\u0D7D|\u0627\u06CC\u0645\u06CC\u0644|\u067E\u0633\u062A.*\u0627\u0644\u06A9\u062A\u0631\u0648\u0646\u06CC\u06A9|\u0908\u092E\u0947\u0932|\u0907\u0932\u0945\u0915\u094D\u091F\u094D\u0930\u0949\u0928\u093F\u0915.?\u092E\u0947\u0932|(\\b|_)eposta(\\b|_)|(?:\uC774\uBA54\uC77C|\uC804\uC790.?\uC6B0\uD3B8|[Ee]-?mail)(.?\uC8FC\uC18C)?", + tel: "phone|mobile|contact.?number|telefonnummer|telefono|tel\xE9fono|telfixe|\u96FB\u8A71|telefone|telemovel|\u0442\u0435\u043B\u0435\u0444\u043E\u043D|\u092E\u094B\u092C\u093E\u0907\u0932|(\\b|_|\\*)telefon(\\b|_|\\*)|\u7535\u8BDD|\u0D2E\u0D4A\u0D2C\u0D48\u0D32\u0D4D\u200D|(?:\uC804\uD654|\uD578\uB4DC\uD3F0|\uD734\uB300\uD3F0|\uD734\uB300\uC804\uD654)(?:.?\uBC88\uD638)?", + organization: "company|business|organization|organisation|empresa|societe|soci\xE9t\xE9|ragione.?sociale|\u4F1A\u793E|\u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435.?\u043A\u043E\u043C\u043F\u0430\u043D\u0438\u0438|\u5355\u4F4D|\u516C\u53F8|\u0634\u0631\u06A9\u062A|\uD68C\uC0AC|\uC9C1\uC7A5", + "street-address": "streetaddress|street-address", + "address-line1": "^address$|address[_-]?line[_-]?(1|one)|address1|addr1|street|(?:shipping|billing)address$|strasse|stra\xDFe|hausnummer|housenumber|house.?name|direccion|direcci\xF3n|adresse|indirizzo|^\u4F4F\u6240$|\u4F4F\u62401|\u0410\u0434\u0440\u0435\u0441|\u5730\u5740|(\\b|_)adres(?! (ba\u015Fl\u0131\u011F\u0131(n\u0131z)?|tarifi))(\\b|_)|^\uC8FC\uC18C.?$|\uC8FC\uC18C.?1", + "address-line2": "address[_-]?line(2|two)|address2|addr2|street|suite|unit(?!e)|adresszusatz|erg\xE4nzende.?angaben|direccion2|colonia|adicional|addresssuppl|complementnom|appartement|indirizzo2|\u4F4F\u62402|complemento|addrcomplement|\u0423\u043B\u0438\u0446\u0430|\u5730\u57402|\uC8FC\uC18C.?2", + "address-line3": "address[_-]?line(3|three)|address3|addr3|street|suite|unit(?!e)|adresszusatz|erg\xE4nzende.?angaben|direccion3|colonia|adicional|addresssuppl|complementnom|appartement|indirizzo3|\u4F4F\u62403|complemento|addrcomplement|\u0423\u043B\u0438\u0446\u0430|\u5730\u57403|\uC8FC\uC18C.?3", + "address-level2": "city|town|\\bort\\b|stadt|suburb|ciudad|provincia|localidad|poblacion|ville|commune|localit(a|\xE0)|citt(a|\xE0)|\u5E02\u533A\u753A\u6751|cidade|\u0413\u043E\u0440\u043E\u0434|\u5E02|\u5206\u5340|\u0634\u0647\u0631|\u0936\u0939\u0930|\u0917\u094D\u0930\u093E\u092E|\u0917\u093E\u0901\u0935|\u0D28\u0D17\u0D30\u0D02|\u0D17\u0D4D\u0D30\u0D3E\u0D2E\u0D02|((\\b|_|\\*)([\u0130ii\u0307]l[c\xE7]e(miz|niz)?)(\\b|_|\\*))|^\uC2DC[^\uB3C4\xB7\u30FB]|\uC2DC[\xB7\u30FB]?\uAD70[\xB7\u30FB]?\uAD6C", + "address-level1": "county|region|province|county|principality|\u90FD\u9053\u5E9C\u770C|estado|provincia|\u043E\u0431\u043B\u0430\u0441\u0442\u044C|\u7701|\u5730\u5340|\u0D38\u0D02\u0D38\u0D4D\u0D25\u0D3E\u0D28\u0D02|\u0627\u0633\u062A\u0627\u0646|\u0930\u093E\u091C\u094D\u092F|((\\b|_|\\*)(eyalet|[\u015Fs]ehir|[\u0130ii\u0307]limiz|kent)(\\b|_|\\*))|^\uC2DC[\xB7\u30FB]?\uB3C4", + "postal-code": "zip|postal|post.*code|pcode|pin.?code|postleitzahl|\\bcp\\b|\\bcdp\\b|\\bcap\\b|\u90F5\u4FBF\u756A\u53F7|codigo|codpos|\\bcep\\b|\u041F\u043E\u0447\u0442\u043E\u0432\u044B\u0439.?\u0418\u043D\u0434\u0435\u043A\u0441|\u092A\u093F\u0928.?\u0915\u094B\u0921|\u0D2A\u0D3F\u0D28\u0D4D\u200D\u0D15\u0D4B\u0D21\u0D4D|\u90AE\u653F\u7F16\u7801|\u90AE\u7F16|\u90F5\u905E\u5340\u865F|(\\b|_)posta kodu(\\b|_)|\uC6B0\uD3B8.?\uBC88\uD638", + country: "country|countries|pa\xEDs|pais|(\\b|_)land(\\b|_)(?!.*(mark.*))|\u56FD\u5BB6|\uAD6D\uAC00|\uB098\uB77C|(\\b|_)(\xFClke|ulce|ulke)(\\b|_)|\u06A9\u0634\u0648\u0631", + "cc-name": "card.?(?:holder|owner)|name.*(\\b)?on(\\b)?.*card|(?:card|cc).?name|cc.?full.?name|karteninhaber|nombre.*tarjeta|nom.*carte|nome.*cart|\u540D\u524D|\u0418\u043C\u044F.*\u043A\u0430\u0440\u0442\u044B|\u4FE1\u7528\u5361\u5F00\u6237\u540D|\u5F00\u6237\u540D|\u6301\u5361\u4EBA\u59D3\u540D|\u6301\u5361\u4EBA\u59D3\u540D", + name: "^name|full.?name|your.?name|customer.?name|bill.?name|ship.?name|name.*first.*last|firstandlastname|nombre.*y.*apellidos|^nom(?!bre)\\b|\u304A\u540D\u524D|\u6C0F\u540D|^nome|\u0646\u0627\u0645.*\u0646\u0627\u0645.*\u062E\u0627\u0646\u0648\u0627\u062F\u06AF\u06CC|\u59D3\u540D|(\\b|_|\\*)ad[\u0131]? soyad[\u0131]?(\\b|_|\\*)|\uC131\uBA85", + "given-name": "first.*name|initials|fname|first$|given.*name|vorname|nombre|forename|pr\xE9nom|prenom|\u540D|\\bnome|\u0418\u043C\u044F|\u0646\u0627\u0645|\uC774\uB984|\u0D2A\u0D47\u0D30\u0D4D|(\\b|_|\\*)(isim|ad|ad(i|\u0131|iniz|\u0131n\u0131z)?)(\\b|_|\\*)|\u0928\u093E\u092E", + "additional-name": "middle.*name|mname|middle$|middle.*initial|m\\.i\\.|mi$|\\bmi\\b", + "family-name": "last.*name|lname|surname|last$|secondname|family.*name|nachname|apellidos?|famille|^nom(?!bre)|cognome|\u59D3|apelidos|surename|sobrenome|\u0424\u0430\u043C\u0438\u043B\u0438\u044F|\u0646\u0627\u0645.*\u062E\u0627\u0646\u0648\u0627\u062F\u06AF\u06CC|\u0909\u092A\u0928\u093E\u092E|\u0D2E\u0D31\u0D41\u0D2A\u0D47\u0D30\u0D4D|(\\b|_|\\*)(soyisim|soyad(i|\u0131|iniz|\u0131n\u0131z)?)(\\b|_|\\*)|\\b\uC131(?:[^\uBA85]|\\b)", + "cc-number": "(add)?(?:card|cc|acct).?(?:number|#|no|num|field)|\u30AB\u30FC\u30C9\u756A\u53F7|\u041D\u043E\u043C\u0435\u0440.*\u043A\u0430\u0440\u0442\u044B|\u4FE1\u7528\u5361\u53F7|\u4FE1\u7528\u5361\u53F7\u7801|\u4FE1\u7528\u5361\u5361\u865F|\uCE74\uB4DC|(numero|n\xFAmero|num\xE9ro)(?!.*(document|fono|phone|r\xE9servation))", + "cc-exp-month": "gueltig|g\xFCltig|monat|fecha|date.*exp|scadenza|\u6709\u52B9\u671F\u9650|validade|\u0421\u0440\u043E\u043A \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044F \u043A\u0430\u0440\u0442\u044B|\u6708", + "cc-exp-year": "ablaufdatum|gueltig|g\xFCltig|jahr|fecha|scadenza|\u6709\u52B9\u671F\u9650|validade|\u0421\u0440\u043E\u043A \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044F \u043A\u0430\u0440\u0442\u044B|\u5E74|\u6709\u6548\u671F", + "cc-exp": "expir|exp.*date|^expfield$|gueltig|g\xFCltig|fecha|date.*exp|scadenza|\u6709\u52B9\u671F\u9650|validade|\u0421\u0440\u043E\u043A \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044F \u043A\u0430\u0440\u0442\u044B" + } + ] + } } - }); -} : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -}); -var __exportStar = void 0 && (void 0).__exportStar || function (m, exports) { - for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); -}; -Object.defineProperty(exports, "__esModule", { - value: true -}); -__exportStar(require("./errors"), exports); -__exportStar(require("./helpers/parseUtil"), exports); -__exportStar(require("./helpers/typeAliases"), exports); -__exportStar(require("./helpers/util"), exports); -__exportStar(require("./types"), exports); -__exportStar(require("./ZodError"), exports); - -},{"./ZodError":2,"./errors":3,"./helpers/parseUtil":6,"./helpers/typeAliases":7,"./helpers/util":8,"./types":11}],5:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.errorUtil = void 0; -var errorUtil; -(function (errorUtil) { - errorUtil.errToObj = message => typeof message === "string" ? { - message - } : message || {}; - errorUtil.toString = message => typeof message === "string" ? message : message === null || message === void 0 ? void 0 : message.message; -})(errorUtil = exports.errorUtil || (exports.errorUtil = {})); - -},{}],6:[function(require,module,exports){ -"use strict"; - -var __importDefault = void 0 && (void 0).__importDefault || function (mod) { - return mod && mod.__esModule ? mod : { - "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.isAsync = exports.isValid = exports.isDirty = exports.isAborted = exports.OK = exports.DIRTY = exports.INVALID = exports.ParseStatus = exports.addIssueToContext = exports.EMPTY_PATH = exports.makeIssue = void 0; -const errors_1 = require("../errors"); -const en_1 = __importDefault(require("../locales/en")); -const makeIssue = params => { - const { - data, - path, - errorMaps, - issueData - } = params; - const fullPath = [...path, ...(issueData.path || [])]; - const fullIssue = { - ...issueData, - path: fullPath - }; - let errorMessage = ""; - const maps = errorMaps.filter(m => !!m).slice().reverse(); - for (const map of maps) { - errorMessage = map(fullIssue, { - data, - defaultError: errorMessage - }).message; - } - return { - ...issueData, - path: fullPath, - message: issueData.message || errorMessage + + // src/Form/matching-utils.js + function logMatching(el, matchingResult) { + if (!shouldLog()) + return; + const fieldIdentifier = getInputIdentifier(el); + console.group(fieldIdentifier); + console.log(el); + const { strategyName, matchedString, matchedFrom, matcherType } = matchingResult; + const verb = getVerb(matchingResult); + let stringToLog = `${verb} for "${matcherType}" with "${strategyName}"`; + if (matchedString && matchedFrom) { + stringToLog += ` +String: "${matchedString}" +Source: "${matchedFrom}"`; + } + console.log(stringToLog); + console.groupEnd(); + } + function getVerb(matchingResult) { + if (matchingResult.matched) + return "Matched"; + if (matchingResult.proceed === false) + return "Matched forceUnknown"; + if (matchingResult.skip) + return "Skipped"; + return ""; + } + function getInputIdentifier(el) { + const label = getExplicitLabelsText(el); + const placeholder = el instanceof HTMLInputElement && el.placeholder ? `${el.placeholder}` : ""; + const name = el.name ? `${el.name}` : ""; + const id = el.id ? `#${el.id}` : ""; + return "Field: " + (label || placeholder || name || id); + } + function logUnmatched(el, allStrings) { + if (!shouldLog()) + return; + const fieldIdentifier = getInputIdentifier(el); + console.group(fieldIdentifier); + console.log(el); + const stringToLog = "Field not matched."; + console.log(stringToLog, allStrings); + console.groupEnd(); + } + + // src/Form/matching.js + var { TEXT_LENGTH_CUTOFF, ATTR_INPUT_TYPE } = constants; + var dimensionBounds = { + emailAddress: { minWidth: 35 } }; -}; -exports.makeIssue = makeIssue; -exports.EMPTY_PATH = []; -function addIssueToContext(ctx, issueData) { - const issue = (0, exports.makeIssue)({ - issueData: issueData, - data: ctx.data, - path: ctx.path, - errorMaps: [ctx.common.contextualErrorMap, ctx.schemaErrorMap, (0, errors_1.getErrorMap)(), en_1.default // then global default map - ].filter(x => !!x) - }); - ctx.common.issues.push(issue); -} -exports.addIssueToContext = addIssueToContext; -class ParseStatus { - constructor() { - this.value = "valid"; - } - dirty() { - if (this.value === "valid") this.value = "dirty"; - } - abort() { - if (this.value !== "aborted") this.value = "aborted"; - } - static mergeArray(status, results) { - const arrayValue = []; - for (const s of results) { - if (s.status === "aborted") return exports.INVALID; - if (s.status === "dirty") status.dirty(); - arrayValue.push(s.value); - } - return { - status: status.value, - value: arrayValue - }; - } - static async mergeObjectAsync(status, pairs) { - const syncPairs = []; - for (const pair of pairs) { - syncPairs.push({ - key: await pair.key, - value: await pair.value + var _config, _cssSelectors, _ddgMatchers, _vendorRegexRules, _matcherLists, _defaultStrategyOrder; + var Matching = class { + /** + * @param {MatchingConfiguration} config + */ + constructor(config) { + /** @type {MatchingConfiguration} */ + __privateAdd(this, _config, void 0); + /** @type {CssSelectorConfiguration['selectors']} */ + __privateAdd(this, _cssSelectors, void 0); + /** @type {Record} */ + __privateAdd(this, _ddgMatchers, void 0); + /** + * This acts as an internal cache for the larger vendorRegexes + * @type {VendorRegexConfiguration['rules']} + */ + __privateAdd(this, _vendorRegexRules, void 0); + /** @type {MatcherLists} */ + __privateAdd(this, _matcherLists, void 0); + /** @type {Array} */ + __privateAdd(this, _defaultStrategyOrder, ["cssSelector", "ddgMatcher", "vendorRegex"]); + /** @type {Record} */ + __publicField(this, "activeElementStrings", { + nameAttr: "", + labelText: "", + placeholderAttr: "", + relatedText: "", + id: "" }); - } - return ParseStatus.mergeObjectSync(status, syncPairs); - } - static mergeObjectSync(status, pairs) { - const finalObject = {}; - for (const pair of pairs) { - const { - key, - value - } = pair; - if (key.status === "aborted") return exports.INVALID; - if (value.status === "aborted") return exports.INVALID; - if (key.status === "dirty") status.dirty(); - if (value.status === "dirty") status.dirty(); - if (key.value !== "__proto__" && (typeof value.value !== "undefined" || pair.alwaysSet)) { - finalObject[key.value] = value.value; + /** + * Yield strings in the order in which they should be checked against. + * + * Note: some strategies may not want to accept all strings, which is + * where `matchableStrings` helps. It defaults to when you see below but can + * be overridden. + * + * For example, `nameAttr` is first, since this has the highest chance of matching + * and then the rest are in decreasing order of value vs cost + * + * A generator function is used here to prevent any potentially expensive + * lookups occurring if they are rare. For example if 90% of all matching never needs + * to look at the output from `relatedText`, then the cost of computing it will be avoided. + * + * @param {HTMLInputElement|HTMLSelectElement} el + * @param {HTMLElement} form + * @returns {Record} + */ + __publicField(this, "_elementStringCache", /* @__PURE__ */ new WeakMap()); + __privateSet(this, _config, config); + __privateSet(this, _vendorRegexRules, __privateGet(this, _config).strategies.vendorRegex.rules); + __privateSet(this, _cssSelectors, __privateGet(this, _config).strategies.cssSelector.selectors); + __privateSet(this, _ddgMatchers, __privateGet(this, _config).strategies.ddgMatcher.matchers); + __privateSet(this, _matcherLists, { + unknown: [], + cc: [], + id: [], + password: [], + username: [], + emailAddress: [] + }); + for (let [listName, matcherNames] of Object.entries(__privateGet(this, _config).matchers.lists)) { + for (let fieldName of matcherNames) { + if (!__privateGet(this, _matcherLists)[listName]) { + __privateGet(this, _matcherLists)[listName] = []; + } + __privateGet(this, _matcherLists)[listName].push(__privateGet(this, _config).matchers.fields[fieldName]); + } } } - return { - status: status.value, - value: finalObject - }; - } -} -exports.ParseStatus = ParseStatus; -exports.INVALID = Object.freeze({ - status: "aborted" -}); -const DIRTY = value => ({ - status: "dirty", - value -}); -exports.DIRTY = DIRTY; -const OK = value => ({ - status: "valid", - value -}); -exports.OK = OK; -const isAborted = x => x.status === "aborted"; -exports.isAborted = isAborted; -const isDirty = x => x.status === "dirty"; -exports.isDirty = isDirty; -const isValid = x => x.status === "valid"; -exports.isValid = isValid; -const isAsync = x => typeof Promise !== "undefined" && x instanceof Promise; -exports.isAsync = isAsync; - -},{"../errors":3,"../locales/en":10}],7:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -},{}],8:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.getParsedType = exports.ZodParsedType = exports.objectUtil = exports.util = void 0; -var util; -(function (util) { - util.assertEqual = val => val; - function assertIs(_arg) {} - util.assertIs = assertIs; - function assertNever(_x) { - throw new Error(); - } - util.assertNever = assertNever; - util.arrayToEnum = items => { - const obj = {}; - for (const item of items) { - obj[item] = item; + /** + * @param {HTMLInputElement|HTMLSelectElement} input + * @param {HTMLElement} formEl + */ + setActiveElementStrings(input, formEl) { + this.activeElementStrings = this.getElementStrings(input, formEl); } - return obj; - }; - util.getValidEnumValues = obj => { - const validKeys = util.objectKeys(obj).filter(k => typeof obj[obj[k]] !== "number"); - const filtered = {}; - for (const k of validKeys) { - filtered[k] = obj[k]; + /** + * Try to access a 'vendor regex' by name + * @param {string} regexName + * @returns {RegExp | undefined} + */ + vendorRegex(regexName) { + const match = __privateGet(this, _vendorRegexRules)[regexName]; + if (!match) { + console.warn("Vendor Regex not found for", regexName); + return void 0; + } + return match; } - return util.objectValues(filtered); - }; - util.objectValues = obj => { - return util.objectKeys(obj).map(function (e) { - return obj[e]; - }); - }; - util.objectKeys = typeof Object.keys === "function" // eslint-disable-line ban/ban - ? obj => Object.keys(obj) // eslint-disable-line ban/ban - : object => { - const keys = []; - for (const key in object) { - if (Object.prototype.hasOwnProperty.call(object, key)) { - keys.push(key); + /** + * Strategies can have different lookup names. This returns the correct one + * @param {MatcherTypeNames} matcherName + * @param {StrategyNames} vendorRegex + * @returns {MatcherTypeNames} + */ + getStrategyLookupByType(matcherName, vendorRegex) { + return __privateGet(this, _config).matchers.fields[matcherName]?.strategies[vendorRegex]; + } + /** + * Try to access a 'css selector' by name from configuration + * @param {RequiredCssSelectors | string} selectorName + * @returns {string}; + */ + cssSelector(selectorName) { + const match = __privateGet(this, _cssSelectors)[selectorName]; + if (!match) { + console.warn("CSS selector not found for %s, using a default value", selectorName); + return ""; } + return match; } - return keys; - }; - util.find = (arr, checker) => { - for (const item of arr) { - if (checker(item)) return item; + /** + * Try to access a 'ddg matcher' by name from configuration + * @param {MatcherTypeNames | string} matcherName + * @returns {DDGMatcher | undefined} + */ + ddgMatcher(matcherName) { + const match = __privateGet(this, _ddgMatchers)[matcherName]; + if (!match) { + console.warn("DDG matcher not found for", matcherName); + return void 0; + } + return match; } - return undefined; - }; - util.isInteger = typeof Number.isInteger === "function" ? val => Number.isInteger(val) // eslint-disable-line ban/ban - : val => typeof val === "number" && isFinite(val) && Math.floor(val) === val; - function joinValues(array) { - let separator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : " | "; - return array.map(val => typeof val === "string" ? `'${val}'` : val).join(separator); - } - util.joinValues = joinValues; - util.jsonStringifyReplacer = (_, value) => { - if (typeof value === "bigint") { - return value.toString(); + /** + * Returns the RegExp for the given matcherName, with proper flags + * @param {AllDDGMatcherNames} matcherName + * @returns {RegExp|undefined} + */ + getDDGMatcherRegex(matcherName) { + const matcher = this.ddgMatcher(matcherName); + if (!matcher || !matcher.match) { + console.warn("DDG matcher has unexpected format"); + return void 0; + } + return matcher?.match; } - return value; - }; -})(util = exports.util || (exports.util = {})); -var objectUtil; -(function (objectUtil) { - objectUtil.mergeShapes = (first, second) => { - return { - ...first, - ...second // second overwrites first - }; - }; -})(objectUtil = exports.objectUtil || (exports.objectUtil = {})); -exports.ZodParsedType = util.arrayToEnum(["string", "nan", "number", "integer", "float", "boolean", "date", "bigint", "symbol", "function", "undefined", "null", "array", "object", "unknown", "promise", "void", "never", "map", "set"]); -const getParsedType = data => { - const t = typeof data; - switch (t) { - case "undefined": - return exports.ZodParsedType.undefined; - case "string": - return exports.ZodParsedType.string; - case "number": - return isNaN(data) ? exports.ZodParsedType.nan : exports.ZodParsedType.number; - case "boolean": - return exports.ZodParsedType.boolean; - case "function": - return exports.ZodParsedType.function; - case "bigint": - return exports.ZodParsedType.bigint; - case "symbol": - return exports.ZodParsedType.symbol; - case "object": - if (Array.isArray(data)) { - return exports.ZodParsedType.array; - } - if (data === null) { - return exports.ZodParsedType.null; - } - if (data.then && typeof data.then === "function" && data.catch && typeof data.catch === "function") { - return exports.ZodParsedType.promise; - } - if (typeof Map !== "undefined" && data instanceof Map) { - return exports.ZodParsedType.map; - } - if (typeof Set !== "undefined" && data instanceof Set) { - return exports.ZodParsedType.set; - } - if (typeof Date !== "undefined" && data instanceof Date) { - return exports.ZodParsedType.date; - } - return exports.ZodParsedType.object; - default: - return exports.ZodParsedType.unknown; - } -}; -exports.getParsedType = getParsedType; - -},{}],9:[function(require,module,exports){ -"use strict"; - -var __createBinding = void 0 && (void 0).__createBinding || (Object.create ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { - enumerable: true, - get: function () { - return m[k]; + /** + * Try to access a list of matchers by name - these are the ones collected in the constructor + * @param {keyof MatcherLists} listName + * @return {Matcher[]} + */ + matcherList(listName) { + const matcherList = __privateGet(this, _matcherLists)[listName]; + if (!matcherList) { + console.warn("MatcherList not found for ", listName); + return []; + } + return matcherList; } - }); -} : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -}); -var __setModuleDefault = void 0 && (void 0).__setModuleDefault || (Object.create ? function (o, v) { - Object.defineProperty(o, "default", { - enumerable: true, - value: v - }); -} : function (o, v) { - o["default"] = v; -}); -var __importStar = void 0 && (void 0).__importStar || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -var __exportStar = void 0 && (void 0).__exportStar || function (m, exports) { - for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); -}; -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.z = void 0; -const z = __importStar(require("./external")); -exports.z = z; -__exportStar(require("./external"), exports); -exports.default = z; - -},{"./external":4}],10:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -const util_1 = require("../helpers/util"); -const ZodError_1 = require("../ZodError"); -const errorMap = (issue, _ctx) => { - let message; - switch (issue.code) { - case ZodError_1.ZodIssueCode.invalid_type: - if (issue.received === util_1.ZodParsedType.undefined) { - message = "Required"; - } else { - message = `Expected ${issue.expected}, received ${issue.received}`; - } - break; - case ZodError_1.ZodIssueCode.invalid_literal: - message = `Invalid literal value, expected ${JSON.stringify(issue.expected, util_1.util.jsonStringifyReplacer)}`; - break; - case ZodError_1.ZodIssueCode.unrecognized_keys: - message = `Unrecognized key(s) in object: ${util_1.util.joinValues(issue.keys, ", ")}`; - break; - case ZodError_1.ZodIssueCode.invalid_union: - message = `Invalid input`; - break; - case ZodError_1.ZodIssueCode.invalid_union_discriminator: - message = `Invalid discriminator value. Expected ${util_1.util.joinValues(issue.options)}`; - break; - case ZodError_1.ZodIssueCode.invalid_enum_value: - message = `Invalid enum value. Expected ${util_1.util.joinValues(issue.options)}, received '${issue.received}'`; - break; - case ZodError_1.ZodIssueCode.invalid_arguments: - message = `Invalid function arguments`; - break; - case ZodError_1.ZodIssueCode.invalid_return_type: - message = `Invalid function return type`; - break; - case ZodError_1.ZodIssueCode.invalid_date: - message = `Invalid date`; - break; - case ZodError_1.ZodIssueCode.invalid_string: - if (typeof issue.validation === "object") { - if ("includes" in issue.validation) { - message = `Invalid input: must include "${issue.validation.includes}"`; - if (typeof issue.validation.position === "number") { - message = `${message} at one or more positions greater than or equal to ${issue.validation.position}`; + /** + * Convert a list of matchers into a single CSS selector. + * + * This will consider all matchers in the list and if it + * contains a CSS Selector it will be added to the final output + * + * @param {keyof MatcherLists} listName + * @returns {string | undefined} + */ + joinCssSelectors(listName) { + const matcherList = this.matcherList(listName); + if (!matcherList) { + console.warn("Matcher list not found for", listName); + return void 0; + } + const selectors = []; + for (let matcher of matcherList) { + if (matcher.strategies.cssSelector) { + const css = this.cssSelector(matcher.strategies.cssSelector); + if (css) { + selectors.push(css); } - } else if ("startsWith" in issue.validation) { - message = `Invalid input: must start with "${issue.validation.startsWith}"`; - } else if ("endsWith" in issue.validation) { - message = `Invalid input: must end with "${issue.validation.endsWith}"`; - } else { - util_1.util.assertNever(issue.validation); } - } else if (issue.validation !== "regex") { - message = `Invalid ${issue.validation}`; - } else { - message = "Invalid"; - } - break; - case ZodError_1.ZodIssueCode.too_small: - if (issue.type === "array") message = `Array must contain ${issue.exact ? "exactly" : issue.inclusive ? `at least` : `more than`} ${issue.minimum} element(s)`;else if (issue.type === "string") message = `String must contain ${issue.exact ? "exactly" : issue.inclusive ? `at least` : `over`} ${issue.minimum} character(s)`;else if (issue.type === "number") message = `Number must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${issue.minimum}`;else if (issue.type === "date") message = `Date must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${new Date(Number(issue.minimum))}`;else message = "Invalid input"; - break; - case ZodError_1.ZodIssueCode.too_big: - if (issue.type === "array") message = `Array must contain ${issue.exact ? `exactly` : issue.inclusive ? `at most` : `less than`} ${issue.maximum} element(s)`;else if (issue.type === "string") message = `String must contain ${issue.exact ? `exactly` : issue.inclusive ? `at most` : `under`} ${issue.maximum} character(s)`;else if (issue.type === "number") message = `Number must be ${issue.exact ? `exactly` : issue.inclusive ? `less than or equal to` : `less than`} ${issue.maximum}`;else if (issue.type === "bigint") message = `BigInt must be ${issue.exact ? `exactly` : issue.inclusive ? `less than or equal to` : `less than`} ${issue.maximum}`;else if (issue.type === "date") message = `Date must be ${issue.exact ? `exactly` : issue.inclusive ? `smaller than or equal to` : `smaller than`} ${new Date(Number(issue.maximum))}`;else message = "Invalid input"; - break; - case ZodError_1.ZodIssueCode.custom: - message = `Invalid input`; - break; - case ZodError_1.ZodIssueCode.invalid_intersection_types: - message = `Intersection results could not be merged`; - break; - case ZodError_1.ZodIssueCode.not_multiple_of: - message = `Number must be a multiple of ${issue.multipleOf}`; - break; - case ZodError_1.ZodIssueCode.not_finite: - message = "Number must be finite"; - break; - default: - message = _ctx.defaultError; - util_1.util.assertNever(issue); - } - return { - message - }; -}; -exports.default = errorMap; - -},{"../ZodError":2,"../helpers/util":8}],11:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.date = exports.boolean = exports.bigint = exports.array = exports.any = exports.coerce = exports.ZodFirstPartyTypeKind = exports.late = exports.ZodSchema = exports.Schema = exports.custom = exports.ZodReadonly = exports.ZodPipeline = exports.ZodBranded = exports.BRAND = exports.ZodNaN = exports.ZodCatch = exports.ZodDefault = exports.ZodNullable = exports.ZodOptional = exports.ZodTransformer = exports.ZodEffects = exports.ZodPromise = exports.ZodNativeEnum = exports.ZodEnum = exports.ZodLiteral = exports.ZodLazy = exports.ZodFunction = exports.ZodSet = exports.ZodMap = exports.ZodRecord = exports.ZodTuple = exports.ZodIntersection = exports.ZodDiscriminatedUnion = exports.ZodUnion = exports.ZodObject = exports.ZodArray = exports.ZodVoid = exports.ZodNever = exports.ZodUnknown = exports.ZodAny = exports.ZodNull = exports.ZodUndefined = exports.ZodSymbol = exports.ZodDate = exports.ZodBoolean = exports.ZodBigInt = exports.ZodNumber = exports.ZodString = exports.ZodType = void 0; -exports.NEVER = exports.void = exports.unknown = exports.union = exports.undefined = exports.tuple = exports.transformer = exports.symbol = exports.string = exports.strictObject = exports.set = exports.record = exports.promise = exports.preprocess = exports.pipeline = exports.ostring = exports.optional = exports.onumber = exports.oboolean = exports.object = exports.number = exports.nullable = exports.null = exports.never = exports.nativeEnum = exports.nan = exports.map = exports.literal = exports.lazy = exports.intersection = exports.instanceof = exports.function = exports.enum = exports.effect = exports.discriminatedUnion = void 0; -const errors_1 = require("./errors"); -const errorUtil_1 = require("./helpers/errorUtil"); -const parseUtil_1 = require("./helpers/parseUtil"); -const util_1 = require("./helpers/util"); -const ZodError_1 = require("./ZodError"); -class ParseInputLazyPath { - constructor(parent, value, path, key) { - this._cachedPath = []; - this.parent = parent; - this.data = value; - this._path = path; - this._key = key; - } - get path() { - if (!this._cachedPath.length) { - if (this._key instanceof Array) { - this._cachedPath.push(...this._path, ...this._key); - } else { - this._cachedPath.push(...this._path, this._key); } + return selectors.join(", "); } - return this._cachedPath; - } -} -const handleResult = (ctx, result) => { - if ((0, parseUtil_1.isValid)(result)) { - return { - success: true, - data: result.value - }; - } else { - if (!ctx.common.issues.length) { - throw new Error("Validation failed but no issues detected."); + /** + * Returns true if the field is visible and large enough + * @param {keyof MatcherLists} matchedType + * @param {HTMLInputElement} input + * @returns {boolean} + */ + isInputLargeEnough(matchedType, input) { + const expectedDimensionBounds = dimensionBounds[matchedType]; + if (!expectedDimensionBounds) + return true; + const width = input.offsetWidth; + const height = input.offsetHeight; + const isHidden = height === 0 && width === 0; + if (isHidden) + return true; + return width >= expectedDimensionBounds.minWidth; } - return { - success: false, - get error() { - if (this._error) return this._error; - const error = new ZodError_1.ZodError(ctx.common.issues); - this._error = error; - return this._error; + /** + * Tries to infer the input type for an input + * + * @param {HTMLInputElement|HTMLSelectElement} input + * @param {HTMLElement} formEl + * @param {SetInputTypeOpts} [opts] + * @returns {SupportedTypes} + */ + inferInputType(input, formEl, opts = {}) { + const presetType = getInputType(input); + if (presetType !== "unknown") { + return presetType; } - }; - } -}; -function processCreateParams(params) { - if (!params) return {}; - const { - errorMap, - invalid_type_error, - required_error, - description - } = params; - if (errorMap && (invalid_type_error || required_error)) { - throw new Error(`Can't use "invalid_type_error" or "required_error" in conjunction with custom error map.`); - } - if (errorMap) return { - errorMap: errorMap, - description - }; - const customMap = (iss, ctx) => { - if (iss.code !== "invalid_type") return { - message: ctx.defaultError - }; - if (typeof ctx.data === "undefined") { + this.setActiveElementStrings(input, formEl); + if (this.subtypeFromMatchers("unknown", input)) + return "unknown"; + if (opts.isCCForm) { + const subtype = this.subtypeFromMatchers("cc", input); + if (subtype && isValidCreditCardSubtype(subtype)) { + return `creditCards.${subtype}`; + } + } + if (input instanceof HTMLInputElement) { + if (this.subtypeFromMatchers("password", input)) { + if (["password", "text"].includes(input.type) && input.name !== "email" && // pcsretirement.com, improper use of the for attribute + input.name !== "Username") { + return this.inferPasswordVariant(input, opts); + } + } + if (this.subtypeFromMatchers("emailAddress", input)) { + if (!this.isInputLargeEnough("emailAddress", input)) { + if (shouldLog()) { + console.log("Field matched for Email Address, but discarded because too small when scanned"); + } + return "unknown"; + } + if (opts.isLogin || opts.isHybrid) { + return "credentials.username"; + } + if (window.location.href.includes("https://accounts.google.com/v3/signin/identifier") && input.matches("[type=email][autocomplete=username]")) { + return "credentials.username"; + } + return "identities.emailAddress"; + } + if (this.subtypeFromMatchers("username", input)) { + return "credentials.username"; + } + } + const idSubtype = this.subtypeFromMatchers("id", input); + if (idSubtype && isValidIdentitiesSubtype(idSubtype)) { + return `identities.${idSubtype}`; + } + logUnmatched(input, this.activeElementStrings); + return "unknown"; + } + /** + * @typedef {{ + * isLogin?: boolean, + * isHybrid?: boolean, + * isCCForm?: boolean, + * hasCredentials?: boolean, + * supportsIdentitiesAutofill?: boolean + * }} SetInputTypeOpts + */ + /** + * Sets the input type as a data attribute to the element and returns it + * @param {HTMLInputElement} input + * @param {HTMLElement} formEl + * @param {SetInputTypeOpts} [opts] + * @returns {SupportedSubTypes | string} + */ + setInputType(input, formEl, opts = {}) { + const type = this.inferInputType(input, formEl, opts); + input.setAttribute(ATTR_INPUT_TYPE, type); + return type; + } + /** + * Tries to infer input subtype, with checks in decreasing order of reliability + * @param {keyof MatcherLists} listName + * @param {HTMLInputElement|HTMLSelectElement} el + * @return {MatcherTypeNames|undefined} + */ + subtypeFromMatchers(listName, el) { + const matchers = this.matcherList(listName); + for (let strategyName of __privateGet(this, _defaultStrategyOrder)) { + let result; + for (let matcher of matchers) { + const lookup = matcher.strategies[strategyName]; + if (!lookup) + continue; + if (strategyName === "cssSelector") { + result = this.execCssSelector(lookup, el); + } + if (strategyName === "ddgMatcher") { + result = this.execDDGMatcher(lookup); + } + if (strategyName === "vendorRegex") { + result = this.execVendorRegex(lookup); + } + if (result?.matched) { + logMatching(el, result); + return matcher.type; + } + if (!result?.matched && result?.proceed === false) { + logMatching(el, result); + return void 0; + } + } + if (result?.skip) { + logMatching(el, result); + break; + } + } + return void 0; + } + /** + * Returns the password type string including the variant + * @param {HTMLInputElement} input + * @param opts + * @returns {'credentials.password.new'|'credentials.password.current'} + */ + inferPasswordVariant(input, opts) { + const attrsToCheck = [input.autocomplete, input.name, input.id]; + if (opts.isSignup && attrsToCheck.some((str) => safeRegexTest(/new.?password|password.?new/i, str))) { + return "credentials.password.new"; + } + if ((opts.isLogin || opts.isHybrid) && attrsToCheck.some((str) => safeRegexTest(/(current|old|previous).?password|password.?(current|old|previous)/i, str))) { + return "credentials.password.current"; + } + const newPasswordMatch = this.execDDGMatcher("newPassword"); + if (newPasswordMatch.matched) { + return "credentials.password.new"; + } + const currentPasswordMatch = this.execDDGMatcher("currentPassword"); + if (currentPasswordMatch.matched) { + return "credentials.password.current"; + } + if (opts.isLogin || opts.isHybrid) { + return "credentials.password.current"; + } + return "credentials.password.new"; + } + /** + * CSS selector matching just leverages the `.matches` method on elements + * + * @param {MatcherTypeNames} lookup + * @param {HTMLInputElement|HTMLSelectElement} el + * @returns {MatchingResult} + */ + execCssSelector(lookup, el) { + const selector = this.cssSelector(lookup); return { - message: required_error !== null && required_error !== void 0 ? required_error : ctx.defaultError + matched: el.matches(selector), + strategyName: "cssSelector", + matcherType: lookup }; } - return { - message: invalid_type_error !== null && invalid_type_error !== void 0 ? invalid_type_error : ctx.defaultError - }; + /** + * A DDG Matcher can have a `match` regex along with a `not` regex. This is done + * to allow it to be driven by configuration as it avoids needing to invoke custom functions. + * + * todo: maxDigits was added as an edge-case when converting this over to be declarative, but I'm + * unsure if it's actually needed. It's not urgent, but we should consider removing it if that's the case + * + * @param {MatcherTypeNames} lookup + * @returns {MatchingResult} + */ + execDDGMatcher(lookup) { + const defaultResult = { matched: false, strategyName: "ddgMatcher", matcherType: lookup }; + const ddgMatcher = this.ddgMatcher(lookup); + if (!ddgMatcher || !ddgMatcher.match) { + return defaultResult; + } + let matchRexExp = this.getDDGMatcherRegex(lookup); + if (!matchRexExp) { + return defaultResult; + } + let requiredScore = ["match", "forceUnknown", "maxDigits"].filter((ddgMatcherProp) => ddgMatcherProp in ddgMatcher).length; + const matchableStrings = ddgMatcher.matchableStrings || ["labelText", "placeholderAttr", "relatedText"]; + for (let stringName of matchableStrings) { + let elementString = this.activeElementStrings[stringName]; + if (!elementString) + continue; + let score = 0; + const result = { + ...defaultResult, + matchedString: elementString, + matchedFrom: stringName + }; + if (ddgMatcher.forceUnknown) { + let notRegex = ddgMatcher.forceUnknown; + if (!notRegex) { + return { ...result, matched: false }; + } + if (safeRegexTest(notRegex, elementString)) { + return { ...result, matched: false, proceed: false }; + } else { + score++; + } + } + if (ddgMatcher.skip) { + let skipRegex = ddgMatcher.skip; + if (!skipRegex) { + return { ...result, matched: false }; + } + if (safeRegexTest(skipRegex, elementString)) { + return { ...result, matched: false, skip: true }; + } + } + if (!safeRegexTest(matchRexExp, elementString)) { + continue; + } + score++; + if (ddgMatcher.maxDigits) { + const digitLength = elementString.replace(/[^0-9]/g, "").length; + if (digitLength > ddgMatcher.maxDigits) { + return { ...result, matched: false }; + } else { + score++; + } + } + if (score === requiredScore) { + return { ...result, matched: true }; + } + } + return defaultResult; + } + /** + * If we get here, a firefox/vendor regex was given and we can execute it on the element + * strings + * @param {MatcherTypeNames} lookup + * @return {MatchingResult} + */ + execVendorRegex(lookup) { + const defaultResult = { matched: false, strategyName: "vendorRegex", matcherType: lookup }; + const regex = this.vendorRegex(lookup); + if (!regex) { + return defaultResult; + } + const stringsToMatch = ["placeholderAttr", "nameAttr", "labelText", "id", "relatedText"]; + for (let stringName of stringsToMatch) { + let elementString = this.activeElementStrings[stringName]; + if (!elementString) + continue; + if (safeRegexTest(regex, elementString)) { + return { + ...defaultResult, + matched: true, + matchedString: elementString, + matchedFrom: stringName + }; + } + } + return defaultResult; + } + getElementStrings(el, form) { + if (this._elementStringCache.has(el)) { + return this._elementStringCache.get(el); + } + const explicitLabelsText = getExplicitLabelsText(el); + const next = { + nameAttr: el.name, + labelText: explicitLabelsText, + placeholderAttr: el.placeholder || "", + id: el.id, + relatedText: explicitLabelsText ? "" : getRelatedText(el, form, this.cssSelector("formInputsSelector")) + }; + this._elementStringCache.set(el, next); + return next; + } + clear() { + this._elementStringCache = /* @__PURE__ */ new WeakMap(); + } + /** + * Only used for testing + * @param {HTMLInputElement|HTMLSelectElement} input + * @param {HTMLElement} form + * @returns {Matching} + */ + forInput(input, form) { + this.setActiveElementStrings(input, form); + return this; + } }; - return { - errorMap: customMap, - description + _config = new WeakMap(); + _cssSelectors = new WeakMap(); + _ddgMatchers = new WeakMap(); + _vendorRegexRules = new WeakMap(); + _matcherLists = new WeakMap(); + _defaultStrategyOrder = new WeakMap(); + /** + * @type {MatchingConfiguration} + */ + __publicField(Matching, "emptyConfig", { + matchers: { + lists: {}, + fields: {} + }, + strategies: { + "vendorRegex": { + rules: {}, + ruleSets: [] + }, + "ddgMatcher": { + matchers: {} + }, + "cssSelector": { + selectors: {} + } + } + }); + function getInputType(input) { + const attr = input?.getAttribute(ATTR_INPUT_TYPE); + if (isValidSupportedType(attr)) { + return attr; + } + return "unknown"; + } + function getMainTypeFromType(type) { + const mainType = type.split(".")[0]; + switch (mainType) { + case "credentials": + case "creditCards": + case "identities": + return mainType; + } + return "unknown"; + } + var getInputMainType = (input) => getMainTypeFromType(getInputType(input)); + var supportedIdentitiesSubtypes = ( + /** @type {const} */ + [ + "emailAddress", + "firstName", + "middleName", + "lastName", + "fullName", + "phone", + "addressStreet", + "addressStreet2", + "addressCity", + "addressProvince", + "addressPostalCode", + "addressCountryCode", + "birthdayDay", + "birthdayMonth", + "birthdayYear" + ] + ); + function isValidIdentitiesSubtype(supportedType) { + return supportedIdentitiesSubtypes.includes(supportedType); + } + var supportedCreditCardSubtypes = ( + /** @type {const} */ + [ + "cardName", + "cardNumber", + "cardSecurityCode", + "expirationMonth", + "expirationYear", + "expiration" + ] + ); + function isValidCreditCardSubtype(supportedType) { + return supportedCreditCardSubtypes.includes(supportedType); + } + var supportedCredentialsSubtypes = ( + /** @type {const} */ + [ + "password", + "password.new", + "password.current", + "username" + ] + ); + var supportedVariants = ( + /** @type {const} */ + [ + "new", + "current" + ] + ); + function isValidCredentialsSubtype(supportedType) { + return supportedCredentialsSubtypes.includes(supportedType); + } + var supportedTypes = [ + ...supportedIdentitiesSubtypes.map((type) => `identities.${type}`), + ...supportedCreditCardSubtypes.map((type) => `creditCards.${type}`), + ...supportedCredentialsSubtypes.map((type) => `credentials.${type}`) + ]; + function getSubtypeFromType(type) { + const subType = type?.split(".")[1]; + const validType = isValidSubtype(subType); + return validType ? subType : "unknown"; + } + function getVariantFromType(type) { + const variant = type?.split(".")[2]; + const validVariant = isValidVariant(variant); + return validVariant ? variant : ""; + } + function isValidSubtype(supportedSubType) { + return isValidIdentitiesSubtype(supportedSubType) || isValidCreditCardSubtype(supportedSubType) || isValidCredentialsSubtype(supportedSubType); + } + function isValidSupportedType(supportedType) { + return supportedTypes.includes(supportedType); + } + function isValidVariant(supportedVariant) { + return supportedVariants.includes(supportedVariant); + } + function getInputSubtype(input) { + const type = getInputType(input); + return getSubtypeFromType(type); + } + function getInputVariant(input) { + const type = getInputType(input); + return getVariantFromType(type); + } + var removeExcessWhitespace = (string = "") => { + string = string?.trim() || ""; + if (!string || string.length > TEXT_LENGTH_CUTOFF + 50) + return ""; + return string.replace(/\n/g, " ").replace(/\s{2,}/g, " "); }; -} -class ZodType { - constructor(def) { - /** Alias of safeParseAsync */ - this.spa = this.safeParseAsync; - this._def = def; - this.parse = this.parse.bind(this); - this.safeParse = this.safeParse.bind(this); - this.parseAsync = this.parseAsync.bind(this); - this.safeParseAsync = this.safeParseAsync.bind(this); - this.spa = this.spa.bind(this); - this.refine = this.refine.bind(this); - this.refinement = this.refinement.bind(this); - this.superRefine = this.superRefine.bind(this); - this.optional = this.optional.bind(this); - this.nullable = this.nullable.bind(this); - this.nullish = this.nullish.bind(this); - this.array = this.array.bind(this); - this.promise = this.promise.bind(this); - this.or = this.or.bind(this); - this.and = this.and.bind(this); - this.transform = this.transform.bind(this); - this.brand = this.brand.bind(this); - this.default = this.default.bind(this); - this.catch = this.catch.bind(this); - this.describe = this.describe.bind(this); - this.pipe = this.pipe.bind(this); - this.readonly = this.readonly.bind(this); - this.isNullable = this.isNullable.bind(this); - this.isOptional = this.isOptional.bind(this); - } - get description() { - return this._def.description; - } - _getType(input) { - return (0, util_1.getParsedType)(input.data); - } - _getOrReturnCtx(input, ctx) { - return ctx || { - common: input.parent.common, - data: input.data, - parsedType: (0, util_1.getParsedType)(input.data), - schemaErrorMap: this._def.errorMap, - path: input.path, - parent: input.parent - }; - } - _processInputParams(input) { - return { - status: new parseUtil_1.ParseStatus(), - ctx: { - common: input.parent.common, - data: input.data, - parsedType: (0, util_1.getParsedType)(input.data), - schemaErrorMap: this._def.errorMap, - path: input.path, - parent: input.parent + var getExplicitLabelsText = (el) => { + const labelTextCandidates = []; + for (let label of el.labels || []) { + labelTextCandidates.push(...extractElementStrings(label)); + } + if (el.hasAttribute("aria-label")) { + labelTextCandidates.push(removeExcessWhitespace(el.getAttribute("aria-label"))); + } + const ariaLabelAttr = removeExcessWhitespace(el.getAttribute("aria-labelled") || el.getAttribute("aria-labelledby")); + if (ariaLabelAttr) { + const labelledByElement = document.getElementById(ariaLabelAttr); + if (labelledByElement) { + labelTextCandidates.push(...extractElementStrings(labelledByElement)); } - }; - } - _parseSync(input) { - const result = this._parse(input); - if ((0, parseUtil_1.isAsync)(result)) { - throw new Error("Synchronous parse encountered promise."); } - return result; - } - _parseAsync(input) { - const result = this._parse(input); - return Promise.resolve(result); - } - parse(data, params) { - const result = this.safeParse(data, params); - if (result.success) return result.data; - throw result.error; + const filteredLabels = labelTextCandidates.filter((string) => string.length < 65); + if (filteredLabels.length > 0) { + return filteredLabels.join(" "); + } + return ""; + }; + var recursiveGetPreviousElSibling = (el) => { + const previousEl = el.previousElementSibling; + if (!previousEl) + return null; + if (EXCLUDED_TAGS.includes(previousEl.tagName)) { + return recursiveGetPreviousElSibling(previousEl); + } + return previousEl; + }; + var getRelatedText = (el, form, cssSelector) => { + let scope = getLargestMeaningfulContainer(el, form, cssSelector); + if (scope === el) { + let previousEl = recursiveGetPreviousElSibling(el); + if (previousEl instanceof HTMLElement) { + scope = previousEl; + } + if (scope === el || scope instanceof HTMLSelectElement) { + if (el.previousSibling instanceof Text) { + return removeExcessWhitespace(el.previousSibling.textContent); + } + return ""; + } + } + if (scope === el || scope instanceof HTMLSelectElement) { + if (el.previousSibling instanceof Text) { + return removeExcessWhitespace(el.previousSibling.textContent); + } + return ""; + } + let trimmedText = ""; + const label = scope.querySelector("label"); + if (label) { + trimmedText = getTextShallow(label); + } else { + trimmedText = extractElementStrings(scope).join(" "); + } + if (trimmedText.length < TEXT_LENGTH_CUTOFF) + return trimmedText; + return ""; + }; + var getLargestMeaningfulContainer = (el, form, cssSelector) => { + const parentElement = el.parentElement; + if (!parentElement || el === form || !cssSelector) + return el; + const inputsInParentsScope = parentElement.querySelectorAll(cssSelector); + if (inputsInParentsScope.length === 1) { + return getLargestMeaningfulContainer(parentElement, form, cssSelector); + } + return el; + }; + var matchInPlaceholderAndLabels = (input, regex, form, cssSelector) => { + return input.placeholder?.match(regex) || getExplicitLabelsText(input).match(regex) || getRelatedText(input, form, cssSelector).match(regex); + }; + var checkPlaceholderAndLabels = (input, regex, form, cssSelector) => { + return !!matchInPlaceholderAndLabels(input, regex, form, cssSelector); + }; + function createMatching() { + return new Matching(matchingConfiguration); } - safeParse(data, params) { - var _a; - const ctx = { - common: { - issues: [], - async: (_a = params === null || params === void 0 ? void 0 : params.async) !== null && _a !== void 0 ? _a : false, - contextualErrorMap: params === null || params === void 0 ? void 0 : params.errorMap - }, - path: (params === null || params === void 0 ? void 0 : params.path) || [], - schemaErrorMap: this._def.errorMap, - parent: null, - data, - parsedType: (0, util_1.getParsedType)(data) + + // node_modules/@duckduckgo/content-scope-scripts/src/apple-utils.js + function getTopLevelURL() { + try { + if (window.location !== window.parent.location) { + return new URL(window.location.href !== "about:blank" ? document.referrer : window.parent.location.href); + } else { + return new URL(window.location.href); + } + } catch (error) { + return new URL(location.href); + } + } + function isUnprotectedDomain(topLevelUrl, featureList) { + let unprotectedDomain = false; + const domainParts = topLevelUrl && topLevelUrl.host ? topLevelUrl.host.split(".") : []; + while (domainParts.length > 1 && !unprotectedDomain) { + const partialDomain = domainParts.join("."); + unprotectedDomain = featureList.filter((domain) => domain.domain === partialDomain).length > 0; + domainParts.shift(); + } + return unprotectedDomain; + } + function processConfig(data, userList, preferences) { + const topLevelUrl = getTopLevelURL(); + const allowlisted = userList.filter((domain) => domain === topLevelUrl.host).length > 0; + const enabledFeatures = Object.keys(data.features).filter((featureName) => { + const feature = data.features[featureName]; + return feature.state === "enabled" && !isUnprotectedDomain(topLevelUrl, feature.exceptions); + }); + const isBroken = isUnprotectedDomain(topLevelUrl, data.unprotectedTemporary); + preferences.site = { + domain: topLevelUrl.hostname, + isBroken, + allowlisted, + enabledFeatures }; - const result = this._parseSync({ - data, - path: ctx.path, - parent: ctx - }); - return handleResult(ctx, result); + preferences.cookie = {}; + return preferences; } - async parseAsync(data, params) { - const result = await this.safeParseAsync(data, params); - if (result.success) return result.data; - throw result.error; - } - async safeParseAsync(data, params) { - const ctx = { - common: { - issues: [], - contextualErrorMap: params === null || params === void 0 ? void 0 : params.errorMap, - async: true - }, - path: (params === null || params === void 0 ? void 0 : params.path) || [], - schemaErrorMap: this._def.errorMap, - parent: null, - data, - parsedType: (0, util_1.getParsedType)(data) - }; - const maybeAsyncResult = this._parse({ - data, - path: ctx.path, - parent: ctx + + // src/autofill-utils.js + var SIGN_IN_MSG = { signMeIn: true }; + var notifyWebApp = (message) => { + window.postMessage(message, window.origin); + }; + var sendAndWaitForAnswer = (msgOrFn, expectedResponse) => { + if (typeof msgOrFn === "function") { + msgOrFn(); + } else { + window.postMessage(msgOrFn, window.origin); + } + return new Promise((resolve) => { + const handler = (e) => { + if (e.origin !== window.origin) + return; + if (!e.data || e.data && !(e.data[expectedResponse] || e.data.type === expectedResponse)) + return; + resolve(e.data); + window.removeEventListener("message", handler); + }; + window.addEventListener("message", handler); }); - const result = await ((0, parseUtil_1.isAsync)(maybeAsyncResult) ? maybeAsyncResult : Promise.resolve(maybeAsyncResult)); - return handleResult(ctx, result); - } - refine(check, message) { - const getIssueProperties = val => { - if (typeof message === "string" || typeof message === "undefined") { - return { - message - }; - } else if (typeof message === "function") { - return message(val); - } else { - return message; + }; + var autofillEnabled = (globalConfig) => { + if (!globalConfig.contentScope) { + return true; + } + if ("site" in globalConfig.contentScope) { + const enabled = isAutofillEnabledFromProcessedConfig(globalConfig.contentScope); + return enabled; + } + const { contentScope, userUnprotectedDomains, userPreferences } = globalConfig; + const processedConfig = processConfig(contentScope, userUnprotectedDomains, userPreferences); + return isAutofillEnabledFromProcessedConfig(processedConfig); + }; + var isAutofillEnabledFromProcessedConfig = (processedConfig) => { + const site = processedConfig.site; + if (site.isBroken || !site.enabledFeatures.includes("autofill")) { + if (shouldLog()) { + console.log("\u26A0\uFE0F Autofill disabled by remote config"); } - }; - return this._refinement((val, ctx) => { - const result = check(val); - const setError = () => ctx.addIssue({ - code: ZodError_1.ZodIssueCode.custom, - ...getIssueProperties(val) - }); - if (typeof Promise !== "undefined" && result instanceof Promise) { - return result.then(data => { - if (!data) { - setError(); - return false; - } else { - return true; - } - }); + return false; + } + return true; + }; + var isIncontextSignupEnabledFromProcessedConfig = (processedConfig) => { + const site = processedConfig.site; + if (site.isBroken || !site.enabledFeatures.includes("incontextSignup")) { + if (shouldLog()) { + console.log("\u26A0\uFE0F In-context signup disabled by remote config"); } - if (!result) { - setError(); - return false; - } else { + return false; + } + return true; + }; + var originalSet = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set; + var setValueForInput = (el, val, config) => { + if (!config?.isAndroid) { + el.focus(); + } + el.dispatchEvent(new Event("keydown", { bubbles: true })); + originalSet?.call(el, val); + const events = [ + new Event("input", { bubbles: true }), + // todo(Shane): Not sending a 'key' property on these events can cause exceptions on 3rd party listeners that expect it + new Event("keyup", { bubbles: true }), + new Event("change", { bubbles: true }) + ]; + events.forEach((ev) => el.dispatchEvent(ev)); + originalSet?.call(el, val); + events.forEach((ev) => el.dispatchEvent(ev)); + el.blur(); + return true; + }; + var fireEventsOnSelect = (el) => { + const events = [ + new Event("mousedown", { bubbles: true }), + new Event("mouseup", { bubbles: true }), + new Event("click", { bubbles: true }), + new Event("change", { bubbles: true }) + ]; + events.forEach((ev) => el.dispatchEvent(ev)); + events.forEach((ev) => el.dispatchEvent(ev)); + el.blur(); + }; + var setValueForSelect = (el, val) => { + const subtype = getInputSubtype(el); + const isMonth = subtype.includes("Month"); + const isZeroBasedNumber = isMonth && el.options[0].value === "0" && el.options.length === 12; + const stringVal = String(val); + const numberVal = Number(val); + for (const option of el.options) { + let value = option.value; + if (isZeroBasedNumber) { + value = `${Number(value) + 1}`; + } + if (value === stringVal || Number(value) === numberVal) { + if (option.selected) + return false; + option.selected = true; + fireEventsOnSelect(el); return true; } - }); - } - refinement(check, refinementData) { - return this._refinement((val, ctx) => { - if (!check(val)) { - ctx.addIssue(typeof refinementData === "function" ? refinementData(val, ctx) : refinementData); - return false; - } else { + } + for (const option of el.options) { + if (option.innerText === stringVal || Number(option.innerText) === numberVal) { + if (option.selected) + return false; + option.selected = true; + fireEventsOnSelect(el); return true; } - }); - } - _refinement(refinement) { - return new ZodEffects({ - schema: this, - typeName: ZodFirstPartyTypeKind.ZodEffects, - effect: { - type: "refinement", - refinement + } + return false; + }; + var setValue = (el, val, config) => { + if (el instanceof HTMLInputElement) + return setValueForInput(el, val, config); + if (el instanceof HTMLSelectElement) + return setValueForSelect(el, val); + return false; + }; + var safeExecute = (el, fn, _opts = {}) => { + const intObs = new IntersectionObserver((changes) => { + for (const change of changes) { + if (typeof change.isVisible === "undefined") { + change.isVisible = true; + } + if (change.isIntersecting) { + fn(); + } } - }); + intObs.disconnect(); + }, { trackVisibility: true, delay: 100 }); + intObs.observe(el); + }; + var isPotentiallyViewable = (el) => { + const computedStyle = window.getComputedStyle(el); + const opacity = parseFloat(computedStyle.getPropertyValue("opacity") || "1"); + const visibility = computedStyle.getPropertyValue("visibility"); + const opacityThreshold = 0.6; + return el.clientWidth !== 0 && el.clientHeight !== 0 && opacity > opacityThreshold && visibility !== "hidden"; + }; + var getDaxBoundingBox = (input) => { + const { right: inputRight, top: inputTop, height: inputHeight } = input.getBoundingClientRect(); + const inputRightPadding = parseInt(getComputedStyle(input).paddingRight); + const width = 30; + const height = 30; + const top = inputTop + (inputHeight - height) / 2; + const right = inputRight - inputRightPadding; + const left = right - width; + const bottom = top + height; + return { bottom, height, left, right, top, width, x: left, y: top }; + }; + var isEventWithinDax = (e, input) => { + const { left, right, top, bottom } = getDaxBoundingBox(input); + const withinX = e.clientX >= left && e.clientX <= right; + const withinY = e.clientY >= top && e.clientY <= bottom; + return withinX && withinY; + }; + var addInlineStyles = (el, styles) => Object.entries(styles).forEach(([property, val]) => el.style.setProperty(property, val, "important")); + var removeInlineStyles = (el, styles) => Object.keys(styles).forEach((property) => el.style.removeProperty(property)); + var ADDRESS_DOMAIN = "@duck.com"; + var formatDuckAddress = (address) => address + ADDRESS_DOMAIN; + function escapeXML(str) { + const replacements = { "&": "&", '"': """, "'": "'", "<": "<", ">": ">", "/": "/" }; + return String(str).replace(/[&"'<>/]/g, (m) => replacements[m]); + } + var isLikelyASubmitButton = (el, matching) => { + const text = getTextShallow(el); + const ariaLabel = el.getAttribute("aria-label") || ""; + const dataTestId = el.getAttribute("data-test-id") || ""; + if ((el.getAttribute("type") === "submit" || // is explicitly set as "submit" + el.getAttribute("name") === "submit") && // is called "submit" + !safeRegexTest(matching.getDDGMatcherRegex("submitButtonUnlikelyRegex"), text + " " + ariaLabel)) + return true; + return (safeRegexTest(/primary|submit/i, el.className) || // has high-signal submit classes + safeRegexTest(/submit/i, dataTestId) || safeRegexTest(matching.getDDGMatcherRegex("submitButtonRegex"), text) || // has high-signal text + el.offsetHeight * el.offsetWidth >= 1e4 && !safeRegexTest(/secondary/i, el.className)) && el.offsetHeight * el.offsetWidth >= 2e3 && // it's not a very small button like inline links and such + !safeRegexTest(matching.getDDGMatcherRegex("submitButtonUnlikelyRegex"), text + " " + ariaLabel); + }; + var buttonMatchesFormType = (el, formObj) => { + if (formObj.isLogin) { + return !safeRegexTest(/sign.?up|register|join/i, el.textContent || ""); + } else if (formObj.isSignup) { + return !safeRegexTest(/(log|sign).?([io])n/i, el.textContent || ""); + } else { + return true; + } + }; + var buttonInputTypes = ["submit", "button"]; + var getTextShallow = (el) => { + if (el instanceof HTMLButtonElement) + return removeExcessWhitespace(el.textContent); + if (el instanceof HTMLInputElement) { + if (buttonInputTypes.includes(el.type)) { + return el.value; + } + if (el.type === "image") { + return removeExcessWhitespace(el.alt || el.value || el.title || el.name); + } + } + let text = ""; + for (const childNode of el.childNodes) { + if (childNode instanceof Text) { + text += " " + childNode.textContent; + } + } + return removeExcessWhitespace(text); + }; + function isLocalNetwork(hostname = window.location.hostname) { + return ["localhost", "", "::1"].includes(hostname) || hostname.includes("127.0.0.1") || hostname.includes("192.168.") || hostname.startsWith("10.0.") || hostname.endsWith(".local") || hostname.endsWith(".internal"); } - superRefine(refinement) { - return this._refinement(refinement); + var tldrs = /\.(?:c(?:o(?:m|op)?|at?|[iykgdmnxruhcfzvl])|o(?:rg|m)|n(?:et?|a(?:me)?|[ucgozrfpil])|e(?:d?u|[gechstr])|i(?:n(?:t|fo)?|[stqldroem])|m(?:o(?:bi)?|u(?:seum)?|i?l|[mcyvtsqhaerngxzfpwkd])|g(?:ov|[glqeriabtshdfmuywnp])|b(?:iz?|[drovfhtaywmzjsgbenl])|t(?:r(?:avel)?|[ncmfzdvkopthjwg]|e?l)|k[iemygznhwrp]|s[jtvberindlucygkhaozm]|u[gymszka]|h[nmutkr]|r[owesu]|d[kmzoej]|a(?:e(?:ro)?|r(?:pa)?|[qofiumsgzlwcnxdt])|p(?:ro?|[sgnthfymakwle])|v[aegiucn]|l[sayuvikcbrt]|j(?:o(?:bs)?|[mep])|w[fs]|z[amw]|f[rijkom]|y[eut]|qa)$/i; + function isValidTLD(hostname = window.location.hostname) { + return tldrs.test(hostname) || hostname === "fill.dev"; } - optional() { - return ZodOptional.create(this, this._def); + var wasAutofilledByChrome = (input) => { + try { + return input.matches("input:-internal-autofill-selected"); + } catch (e) { + return false; + } + }; + function shouldLog() { + return readDebugSetting("ddg-autofill-debug"); } - nullable() { - return ZodNullable.create(this, this._def); + function shouldLogPerformance() { + return readDebugSetting("ddg-autofill-perf"); } - nullish() { - return this.nullable().optional(); + function readDebugSetting(setting) { + try { + return window.sessionStorage?.getItem(setting) === "true"; + } catch (e) { + return false; + } } - array() { - return ZodArray.create(this, this._def); + function logPerformance(markName) { + if (shouldLogPerformance()) { + const measurement = window.performance?.measure(`${markName}:init`, `${markName}:init:start`, `${markName}:init:end`); + console.log(`${markName} took ${Math.round(measurement?.duration)}ms`); + window.performance?.clearMarks(); + } } - promise() { - return ZodPromise.create(this, this._def); + function whenIdle(callback) { + let timer; + return (...args) => { + cancelIdleCallback(timer); + timer = requestIdleCallback(() => callback.apply(this, args)); + }; } - or(option) { - return ZodUnion.create([this, option], this._def); + function truncateFromMiddle(string, totalLength = 30) { + if (totalLength < 4) { + throw new Error("Do not use with strings shorter than 4"); + } + if (string.length <= totalLength) + return string; + const truncated = string.slice(0, totalLength / 2).concat("\u2026", string.slice(totalLength / -2)); + return truncated; } - and(incoming) { - return ZodIntersection.create(this, incoming, this._def); + function isFormLikelyToBeUsedAsPageWrapper(form) { + if (form.parentElement !== document.body) + return false; + const formChildren = form.querySelectorAll("*").length; + if (formChildren < 100) + return false; + const bodyChildren = document.body.querySelectorAll("*").length; + const formChildrenPercentage = formChildren * 100 / bodyChildren; + return formChildrenPercentage > 50; } - transform(transform) { - return new ZodEffects({ - ...processCreateParams(this._def), - schema: this, - typeName: ZodFirstPartyTypeKind.ZodEffects, - effect: { - type: "transform", - transform - } - }); - } - default(def) { - const defaultValueFunc = typeof def === "function" ? def : () => def; - return new ZodDefault({ - ...processCreateParams(this._def), - innerType: this, - defaultValue: defaultValueFunc, - typeName: ZodFirstPartyTypeKind.ZodDefault - }); - } - brand() { - return new ZodBranded({ - typeName: ZodFirstPartyTypeKind.ZodBranded, - type: this, - ...processCreateParams(this._def) - }); - } - catch(def) { - const catchValueFunc = typeof def === "function" ? def : () => def; - return new ZodCatch({ - ...processCreateParams(this._def), - innerType: this, - catchValue: catchValueFunc, - typeName: ZodFirstPartyTypeKind.ZodCatch - }); - } - describe(description) { - const This = this.constructor; - return new This({ - ...this._def, - description - }); - } - pipe(target) { - return ZodPipeline.create(this, target); - } - readonly() { - return ZodReadonly.create(this); - } - isOptional() { - return this.safeParse(undefined).success; - } - isNullable() { - return this.safeParse(null).success; - } -} -exports.ZodType = ZodType; -exports.Schema = ZodType; -exports.ZodSchema = ZodType; -const cuidRegex = /^c[^\s-]{8,}$/i; -const cuid2Regex = /^[a-z][a-z0-9]*$/; -const ulidRegex = /^[0-9A-HJKMNP-TV-Z]{26}$/; -// const uuidRegex = -// /^([a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}|00000000-0000-0000-0000-000000000000)$/i; -const uuidRegex = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/i; -// from https://stackoverflow.com/a/46181/1550155 -// old version: too slow, didn't support unicode -// const emailRegex = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i; -//old email regex -// const emailRegex = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@((?!-)([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{1,})[^-<>()[\].,;:\s@"]$/i; -// eslint-disable-next-line -// const emailRegex = -// /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\])|(\[IPv6:(([a-f0-9]{1,4}:){7}|::([a-f0-9]{1,4}:){0,6}|([a-f0-9]{1,4}:){1}:([a-f0-9]{1,4}:){0,5}|([a-f0-9]{1,4}:){2}:([a-f0-9]{1,4}:){0,4}|([a-f0-9]{1,4}:){3}:([a-f0-9]{1,4}:){0,3}|([a-f0-9]{1,4}:){4}:([a-f0-9]{1,4}:){0,2}|([a-f0-9]{1,4}:){5}:([a-f0-9]{1,4}:){0,1})([a-f0-9]{1,4}|(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2})))\])|([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])*(\.[A-Za-z]{2,})+))$/; -// const emailRegex = -// /^[a-zA-Z0-9\.\!\#\$\%\&\'\*\+\/\=\?\^\_\`\{\|\}\~\-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; -// const emailRegex = -// /^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$/i; -const emailRegex = /^(?!\.)(?!.*\.\.)([A-Z0-9_+-\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i; -// const emailRegex = -// /^[a-z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-z0-9-]+(?:\.[a-z0-9\-]+)*$/i; -// from https://thekevinscott.com/emojis-in-javascript/#writing-a-regular-expression -const _emojiRegex = `^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$`; -let emojiRegex; -const ipv4Regex = /^(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))$/; -const ipv6Regex = /^(([a-f0-9]{1,4}:){7}|::([a-f0-9]{1,4}:){0,6}|([a-f0-9]{1,4}:){1}:([a-f0-9]{1,4}:){0,5}|([a-f0-9]{1,4}:){2}:([a-f0-9]{1,4}:){0,4}|([a-f0-9]{1,4}:){3}:([a-f0-9]{1,4}:){0,3}|([a-f0-9]{1,4}:){4}:([a-f0-9]{1,4}:){0,2}|([a-f0-9]{1,4}:){5}:([a-f0-9]{1,4}:){0,1})([a-f0-9]{1,4}|(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2})))$/; -// Adapted from https://stackoverflow.com/a/3143231 -const datetimeRegex = args => { - if (args.precision) { - if (args.offset) { - return new RegExp(`^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{${args.precision}}(([+-]\\d{2}(:?\\d{2})?)|Z)$`); - } else { - return new RegExp(`^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{${args.precision}}Z$`); + function safeRegexTest(regex, string) { + if (!string || !regex || string.length > constants.TEXT_LENGTH_CUTOFF) + return false; + return regex.test(string); + } + function pierceShadowTree(event, wantedTargetType) { + const { target } = event; + if (!(target instanceof Element) || !target?.shadowRoot || !event.composedPath) + return target; + const clickStack = event.composedPath(); + if (!wantedTargetType) { + return clickStack[0]; + } + return clickStack.find((el) => el instanceof wantedTargetType) || target; + } + function getActiveElement(root = document) { + const activeElement = root.activeElement; + if (!(activeElement instanceof Element) || !activeElement.shadowRoot) + return activeElement; + const innerActiveElement = activeElement.shadowRoot.activeElement; + if (innerActiveElement?.shadowRoot) { + return getActiveElement(innerActiveElement.shadowRoot); + } + return innerActiveElement; + } + + // src/Form/countryNames.js + var COUNTRY_CODES_TO_NAMES = { + AC: "Ascension Island", + AD: "Andorra", + AE: "United Arab Emirates", + AF: "Afghanistan", + AG: "Antigua & Barbuda", + AI: "Anguilla", + AL: "Albania", + AM: "Armenia", + AN: "Cura\xE7ao", + AO: "Angola", + AQ: "Antarctica", + AR: "Argentina", + AS: "American Samoa", + AT: "Austria", + AU: "Australia", + AW: "Aruba", + AX: "\xC5land Islands", + AZ: "Azerbaijan", + BA: "Bosnia & Herzegovina", + BB: "Barbados", + BD: "Bangladesh", + BE: "Belgium", + BF: "Burkina Faso", + BG: "Bulgaria", + BH: "Bahrain", + BI: "Burundi", + BJ: "Benin", + BL: "St. Barth\xE9lemy", + BM: "Bermuda", + BN: "Brunei", + BO: "Bolivia", + BQ: "Caribbean Netherlands", + BR: "Brazil", + BS: "Bahamas", + BT: "Bhutan", + BU: "Myanmar (Burma)", + BV: "Bouvet Island", + BW: "Botswana", + BY: "Belarus", + BZ: "Belize", + CA: "Canada", + CC: "Cocos (Keeling) Islands", + CD: "Congo - Kinshasa", + CF: "Central African Republic", + CG: "Congo - Brazzaville", + CH: "Switzerland", + CI: "C\xF4te d\u2019Ivoire", + CK: "Cook Islands", + CL: "Chile", + CM: "Cameroon", + CN: "China mainland", + CO: "Colombia", + CP: "Clipperton Island", + CR: "Costa Rica", + CS: "Serbia", + CU: "Cuba", + CV: "Cape Verde", + CW: "Cura\xE7ao", + CX: "Christmas Island", + CY: "Cyprus", + CZ: "Czechia", + DD: "Germany", + DE: "Germany", + DG: "Diego Garcia", + DJ: "Djibouti", + DK: "Denmark", + DM: "Dominica", + DO: "Dominican Republic", + DY: "Benin", + DZ: "Algeria", + EA: "Ceuta & Melilla", + EC: "Ecuador", + EE: "Estonia", + EG: "Egypt", + EH: "Western Sahara", + ER: "Eritrea", + ES: "Spain", + ET: "Ethiopia", + EU: "European Union", + EZ: "Eurozone", + FI: "Finland", + FJ: "Fiji", + FK: "Falkland Islands", + FM: "Micronesia", + FO: "Faroe Islands", + FR: "France", + FX: "France", + GA: "Gabon", + GB: "United Kingdom", + GD: "Grenada", + GE: "Georgia", + GF: "French Guiana", + GG: "Guernsey", + GH: "Ghana", + GI: "Gibraltar", + GL: "Greenland", + GM: "Gambia", + GN: "Guinea", + GP: "Guadeloupe", + GQ: "Equatorial Guinea", + GR: "Greece", + GS: "So. Georgia & So. Sandwich Isl.", + GT: "Guatemala", + GU: "Guam", + GW: "Guinea-Bissau", + GY: "Guyana", + HK: "Hong Kong", + HM: "Heard & McDonald Islands", + HN: "Honduras", + HR: "Croatia", + HT: "Haiti", + HU: "Hungary", + HV: "Burkina Faso", + IC: "Canary Islands", + ID: "Indonesia", + IE: "Ireland", + IL: "Israel", + IM: "Isle of Man", + IN: "India", + IO: "Chagos Archipelago", + IQ: "Iraq", + IR: "Iran", + IS: "Iceland", + IT: "Italy", + JE: "Jersey", + JM: "Jamaica", + JO: "Jordan", + JP: "Japan", + KE: "Kenya", + KG: "Kyrgyzstan", + KH: "Cambodia", + KI: "Kiribati", + KM: "Comoros", + KN: "St. Kitts & Nevis", + KP: "North Korea", + KR: "South Korea", + KW: "Kuwait", + KY: "Cayman Islands", + KZ: "Kazakhstan", + LA: "Laos", + LB: "Lebanon", + LC: "St. Lucia", + LI: "Liechtenstein", + LK: "Sri Lanka", + LR: "Liberia", + LS: "Lesotho", + LT: "Lithuania", + LU: "Luxembourg", + LV: "Latvia", + LY: "Libya", + MA: "Morocco", + MC: "Monaco", + MD: "Moldova", + ME: "Montenegro", + MF: "St. Martin", + MG: "Madagascar", + MH: "Marshall Islands", + MK: "North Macedonia", + ML: "Mali", + MM: "Myanmar (Burma)", + MN: "Mongolia", + MO: "Macao", + MP: "Northern Mariana Islands", + MQ: "Martinique", + MR: "Mauritania", + MS: "Montserrat", + MT: "Malta", + MU: "Mauritius", + MV: "Maldives", + MW: "Malawi", + MX: "Mexico", + MY: "Malaysia", + MZ: "Mozambique", + NA: "Namibia", + NC: "New Caledonia", + NE: "Niger", + NF: "Norfolk Island", + NG: "Nigeria", + NH: "Vanuatu", + NI: "Nicaragua", + NL: "Netherlands", + NO: "Norway", + NP: "Nepal", + NR: "Nauru", + NU: "Niue", + NZ: "New Zealand", + OM: "Oman", + PA: "Panama", + PE: "Peru", + PF: "French Polynesia", + PG: "Papua New Guinea", + PH: "Philippines", + PK: "Pakistan", + PL: "Poland", + PM: "St. Pierre & Miquelon", + PN: "Pitcairn Islands", + PR: "Puerto Rico", + PS: "Palestinian Territories", + PT: "Portugal", + PW: "Palau", + PY: "Paraguay", + QA: "Qatar", + QO: "Outlying Oceania", + RE: "R\xE9union", + RH: "Zimbabwe", + RO: "Romania", + RS: "Serbia", + RU: "Russia", + RW: "Rwanda", + SA: "Saudi Arabia", + SB: "Solomon Islands", + SC: "Seychelles", + SD: "Sudan", + SE: "Sweden", + SG: "Singapore", + SH: "St. Helena", + SI: "Slovenia", + SJ: "Svalbard & Jan Mayen", + SK: "Slovakia", + SL: "Sierra Leone", + SM: "San Marino", + SN: "Senegal", + SO: "Somalia", + SR: "Suriname", + SS: "South Sudan", + ST: "S\xE3o Tom\xE9 & Pr\xEDncipe", + SU: "Russia", + SV: "El Salvador", + SX: "Sint Maarten", + SY: "Syria", + SZ: "Eswatini", + TA: "Tristan da Cunha", + TC: "Turks & Caicos Islands", + TD: "Chad", + TF: "French Southern Territories", + TG: "Togo", + TH: "Thailand", + TJ: "Tajikistan", + TK: "Tokelau", + TL: "Timor-Leste", + TM: "Turkmenistan", + TN: "Tunisia", + TO: "Tonga", + TP: "Timor-Leste", + TR: "Turkey", + TT: "Trinidad & Tobago", + TV: "Tuvalu", + TW: "Taiwan", + TZ: "Tanzania", + UA: "Ukraine", + UG: "Uganda", + UK: "United Kingdom", + UM: "U.S. Outlying Islands", + UN: "United Nations", + US: "United States", + UY: "Uruguay", + UZ: "Uzbekistan", + VA: "Vatican City", + VC: "St. Vincent & Grenadines", + VD: "Vietnam", + VE: "Venezuela", + VG: "British Virgin Islands", + VI: "U.S. Virgin Islands", + VN: "Vietnam", + VU: "Vanuatu", + WF: "Wallis & Futuna", + WS: "Samoa", + XA: "Pseudo-Accents", + XB: "Pseudo-Bidi", + XK: "Kosovo", + YD: "Yemen", + YE: "Yemen", + YT: "Mayotte", + YU: "Serbia", + ZA: "South Africa", + ZM: "Zambia", + ZR: "Congo - Kinshasa", + ZW: "Zimbabwe", + ZZ: "Unknown Region" + }; + var COUNTRY_NAMES_TO_CODES = { + "Ascension Island": "AC", + Andorra: "AD", + "United Arab Emirates": "AE", + Afghanistan: "AF", + "Antigua & Barbuda": "AG", + Anguilla: "AI", + Albania: "AL", + Armenia: "AM", + "Cura\xE7ao": "CW", + Angola: "AO", + Antarctica: "AQ", + Argentina: "AR", + "American Samoa": "AS", + Austria: "AT", + Australia: "AU", + Aruba: "AW", + "\xC5land Islands": "AX", + Azerbaijan: "AZ", + "Bosnia & Herzegovina": "BA", + Barbados: "BB", + Bangladesh: "BD", + Belgium: "BE", + "Burkina Faso": "HV", + Bulgaria: "BG", + Bahrain: "BH", + Burundi: "BI", + Benin: "DY", + "St. Barth\xE9lemy": "BL", + Bermuda: "BM", + Brunei: "BN", + Bolivia: "BO", + "Caribbean Netherlands": "BQ", + Brazil: "BR", + Bahamas: "BS", + Bhutan: "BT", + "Myanmar (Burma)": "MM", + "Bouvet Island": "BV", + Botswana: "BW", + Belarus: "BY", + Belize: "BZ", + Canada: "CA", + "Cocos (Keeling) Islands": "CC", + "Congo - Kinshasa": "ZR", + "Central African Republic": "CF", + "Congo - Brazzaville": "CG", + Switzerland: "CH", + "C\xF4te d\u2019Ivoire": "CI", + "Cook Islands": "CK", + Chile: "CL", + Cameroon: "CM", + "China mainland": "CN", + Colombia: "CO", + "Clipperton Island": "CP", + "Costa Rica": "CR", + Serbia: "YU", + Cuba: "CU", + "Cape Verde": "CV", + "Christmas Island": "CX", + Cyprus: "CY", + Czechia: "CZ", + Germany: "DE", + "Diego Garcia": "DG", + Djibouti: "DJ", + Denmark: "DK", + Dominica: "DM", + "Dominican Republic": "DO", + Algeria: "DZ", + "Ceuta & Melilla": "EA", + Ecuador: "EC", + Estonia: "EE", + Egypt: "EG", + "Western Sahara": "EH", + Eritrea: "ER", + Spain: "ES", + Ethiopia: "ET", + "European Union": "EU", + Eurozone: "EZ", + Finland: "FI", + Fiji: "FJ", + "Falkland Islands": "FK", + Micronesia: "FM", + "Faroe Islands": "FO", + France: "FX", + Gabon: "GA", + "United Kingdom": "UK", + Grenada: "GD", + Georgia: "GE", + "French Guiana": "GF", + Guernsey: "GG", + Ghana: "GH", + Gibraltar: "GI", + Greenland: "GL", + Gambia: "GM", + Guinea: "GN", + Guadeloupe: "GP", + "Equatorial Guinea": "GQ", + Greece: "GR", + "So. Georgia & So. Sandwich Isl.": "GS", + Guatemala: "GT", + Guam: "GU", + "Guinea-Bissau": "GW", + Guyana: "GY", + "Hong Kong": "HK", + "Heard & McDonald Islands": "HM", + Honduras: "HN", + Croatia: "HR", + Haiti: "HT", + Hungary: "HU", + "Canary Islands": "IC", + Indonesia: "ID", + Ireland: "IE", + Israel: "IL", + "Isle of Man": "IM", + India: "IN", + "Chagos Archipelago": "IO", + Iraq: "IQ", + Iran: "IR", + Iceland: "IS", + Italy: "IT", + Jersey: "JE", + Jamaica: "JM", + Jordan: "JO", + Japan: "JP", + Kenya: "KE", + Kyrgyzstan: "KG", + Cambodia: "KH", + Kiribati: "KI", + Comoros: "KM", + "St. Kitts & Nevis": "KN", + "North Korea": "KP", + "South Korea": "KR", + Kuwait: "KW", + "Cayman Islands": "KY", + Kazakhstan: "KZ", + Laos: "LA", + Lebanon: "LB", + "St. Lucia": "LC", + Liechtenstein: "LI", + "Sri Lanka": "LK", + Liberia: "LR", + Lesotho: "LS", + Lithuania: "LT", + Luxembourg: "LU", + Latvia: "LV", + Libya: "LY", + Morocco: "MA", + Monaco: "MC", + Moldova: "MD", + Montenegro: "ME", + "St. Martin": "MF", + Madagascar: "MG", + "Marshall Islands": "MH", + "North Macedonia": "MK", + Mali: "ML", + Mongolia: "MN", + Macao: "MO", + "Northern Mariana Islands": "MP", + Martinique: "MQ", + Mauritania: "MR", + Montserrat: "MS", + Malta: "MT", + Mauritius: "MU", + Maldives: "MV", + Malawi: "MW", + Mexico: "MX", + Malaysia: "MY", + Mozambique: "MZ", + Namibia: "NA", + "New Caledonia": "NC", + Niger: "NE", + "Norfolk Island": "NF", + Nigeria: "NG", + Vanuatu: "VU", + Nicaragua: "NI", + Netherlands: "NL", + Norway: "NO", + Nepal: "NP", + Nauru: "NR", + Niue: "NU", + "New Zealand": "NZ", + Oman: "OM", + Panama: "PA", + Peru: "PE", + "French Polynesia": "PF", + "Papua New Guinea": "PG", + Philippines: "PH", + Pakistan: "PK", + Poland: "PL", + "St. Pierre & Miquelon": "PM", + "Pitcairn Islands": "PN", + "Puerto Rico": "PR", + "Palestinian Territories": "PS", + Portugal: "PT", + Palau: "PW", + Paraguay: "PY", + Qatar: "QA", + "Outlying Oceania": "QO", + "R\xE9union": "RE", + Zimbabwe: "ZW", + Romania: "RO", + Russia: "SU", + Rwanda: "RW", + "Saudi Arabia": "SA", + "Solomon Islands": "SB", + Seychelles: "SC", + Sudan: "SD", + Sweden: "SE", + Singapore: "SG", + "St. Helena": "SH", + Slovenia: "SI", + "Svalbard & Jan Mayen": "SJ", + Slovakia: "SK", + "Sierra Leone": "SL", + "San Marino": "SM", + Senegal: "SN", + Somalia: "SO", + Suriname: "SR", + "South Sudan": "SS", + "S\xE3o Tom\xE9 & Pr\xEDncipe": "ST", + "El Salvador": "SV", + "Sint Maarten": "SX", + Syria: "SY", + Eswatini: "SZ", + "Tristan da Cunha": "TA", + "Turks & Caicos Islands": "TC", + Chad: "TD", + "French Southern Territories": "TF", + Togo: "TG", + Thailand: "TH", + Tajikistan: "TJ", + Tokelau: "TK", + "Timor-Leste": "TP", + Turkmenistan: "TM", + Tunisia: "TN", + Tonga: "TO", + Turkey: "TR", + "Trinidad & Tobago": "TT", + Tuvalu: "TV", + Taiwan: "TW", + Tanzania: "TZ", + Ukraine: "UA", + Uganda: "UG", + "U.S. Outlying Islands": "UM", + "United Nations": "UN", + "United States": "US", + Uruguay: "UY", + Uzbekistan: "UZ", + "Vatican City": "VA", + "St. Vincent & Grenadines": "VC", + Vietnam: "VN", + Venezuela: "VE", + "British Virgin Islands": "VG", + "U.S. Virgin Islands": "VI", + "Wallis & Futuna": "WF", + Samoa: "WS", + "Pseudo-Accents": "XA", + "Pseudo-Bidi": "XB", + Kosovo: "XK", + Yemen: "YE", + Mayotte: "YT", + "South Africa": "ZA", + Zambia: "ZM", + "Unknown Region": "ZZ" + }; + + // src/Form/formatters.js + var DATE_SEPARATOR_REGEX = /\b((.)\2{1,3}|\d+)(?\s?[/\s.\-_—–]\s?)((.)\5{1,3}|\d+)\b/i; + var FOUR_DIGIT_YEAR_REGEX = /(\D)\1{3}|\d{4}/i; + var formatCCYear = (input, year, form) => { + const selector = form.matching.cssSelector("formInputsSelector"); + if (input.maxLength === 4 || checkPlaceholderAndLabels(input, FOUR_DIGIT_YEAR_REGEX, form.form, selector)) + return year; + return `${Number(year) - 2e3}`; + }; + var getUnifiedExpiryDate = (input, month, year, form) => { + const formattedYear = formatCCYear(input, year, form); + const paddedMonth = `${month}`.padStart(2, "0"); + const cssSelector = form.matching.cssSelector("formInputsSelector"); + const separator = matchInPlaceholderAndLabels(input, DATE_SEPARATOR_REGEX, form.form, cssSelector)?.groups?.separator || "/"; + return `${paddedMonth}${separator}${formattedYear}`; + }; + var formatFullName = ({ firstName = "", middleName = "", lastName = "" }) => `${firstName} ${middleName ? middleName + " " : ""}${lastName}`.trim(); + var getCountryDisplayName = (locale, addressCountryCode) => { + try { + const regionNames = new Intl.DisplayNames([locale], { type: "region" }); + return regionNames.of(addressCountryCode); + } catch (e) { + return COUNTRY_CODES_TO_NAMES[addressCountryCode] || addressCountryCode; } - } else if (args.precision === 0) { - if (args.offset) { - return new RegExp(`^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(([+-]\\d{2}(:?\\d{2})?)|Z)$`); - } else { - return new RegExp(`^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$`); + }; + var inferElementLocale = (el) => el.lang || el.form?.lang || document.body.lang || document.documentElement.lang || "en"; + var getCountryName = (el, options = {}) => { + const { addressCountryCode } = options; + if (!addressCountryCode) + return ""; + const elLocale = inferElementLocale(el); + const localisedCountryName = getCountryDisplayName(elLocale, addressCountryCode); + if (el.nodeName === "SELECT") { + const englishCountryName = getCountryDisplayName("en", addressCountryCode); + const countryNameRegex = new RegExp(String.raw`${localisedCountryName.replace(/ /g, ".?")}|${englishCountryName.replace(/ /g, ".?")}`, "i"); + const countryCodeRegex = new RegExp(String.raw`\b${addressCountryCode}\b`, "i"); + if (el instanceof HTMLSelectElement) { + for (const option of el.options) { + if (countryCodeRegex.test(option.value)) { + return option.value; + } + } + for (const option of el.options) { + if (countryNameRegex.test(option.value) || countryNameRegex.test(option.innerText)) + return option.value; + } + } } - } else { - if (args.offset) { - return new RegExp(`^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(([+-]\\d{2}(:?\\d{2})?)|Z)$`); - } else { - return new RegExp(`^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?Z$`); + return localisedCountryName; + }; + var getLocalisedCountryNamesToCodes = (el) => { + if (typeof Intl.DisplayNames !== "function") + return COUNTRY_NAMES_TO_CODES; + const elLocale = inferElementLocale(el); + return Object.fromEntries( + Object.entries(COUNTRY_CODES_TO_NAMES).map(([code]) => [getCountryDisplayName(elLocale, code), code]) + ); + }; + var inferCountryCodeFromElement = (el) => { + if (COUNTRY_CODES_TO_NAMES[el.value]) + return el.value; + if (COUNTRY_NAMES_TO_CODES[el.value]) + return COUNTRY_NAMES_TO_CODES[el.value]; + const localisedCountryNamesToCodes = getLocalisedCountryNamesToCodes(el); + if (localisedCountryNamesToCodes[el.value]) + return localisedCountryNamesToCodes[el.value]; + if (el instanceof HTMLSelectElement) { + const selectedText = el.selectedOptions[0]?.text; + if (COUNTRY_CODES_TO_NAMES[selectedText]) + return selectedText; + if (COUNTRY_NAMES_TO_CODES[selectedText]) + return localisedCountryNamesToCodes[selectedText]; + if (localisedCountryNamesToCodes[selectedText]) + return localisedCountryNamesToCodes[selectedText]; + } + return ""; + }; + var getMMAndYYYYFromString = (expiration) => { + const values = expiration.match(/(\d+)/g) || []; + return values?.reduce((output, current) => { + if (Number(current) > 12) { + output.expirationYear = current.padStart(4, "20"); + } else { + output.expirationMonth = current.padStart(2, "0"); + } + return output; + }, { expirationYear: "", expirationMonth: "" }); + }; + var shouldStoreCredentials = ({ credentials }) => Boolean(credentials.password); + var shouldStoreIdentities = ({ identities }) => Boolean( + (identities.firstName || identities.fullName) && identities.addressStreet && identities.addressCity + ); + var shouldStoreCreditCards = ({ creditCards }) => { + if (!creditCards.cardNumber) + return false; + if (creditCards.cardSecurityCode) + return true; + if (creditCards.expiration) + return true; + return Boolean(creditCards.expirationYear && creditCards.expirationMonth); + }; + var formatPhoneNumber = (phone) => phone.replaceAll(/[^0-9|+]/g, ""); + var prepareFormValuesForStorage = (formValues) => { + let { credentials, identities, creditCards } = formValues; + if (!creditCards.cardName && (identities?.fullName || identities?.firstName)) { + creditCards.cardName = identities?.fullName || formatFullName(identities); } - } -}; -function isValidIP(ip, version) { - if ((version === "v4" || !version) && ipv4Regex.test(ip)) { - return true; - } - if ((version === "v6" || !version) && ipv6Regex.test(ip)) { - return true; - } - return false; -} -class ZodString extends ZodType { - _parse(input) { - if (this._def.coerce) { - input.data = String(input.data); - } - const parsedType = this._getType(input); - if (parsedType !== util_1.ZodParsedType.string) { - const ctx = this._getOrReturnCtx(input); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_type, - expected: util_1.ZodParsedType.string, - received: ctx.parsedType + if (shouldStoreCredentials(formValues)) { + if (credentials.password && !credentials.username && identities.emailAddress) { + credentials.username = identities.emailAddress; } - // - ); - - return parseUtil_1.INVALID; - } - const status = new parseUtil_1.ParseStatus(); - let ctx = undefined; - for (const check of this._def.checks) { - if (check.kind === "min") { - if (input.data.length < check.value) { - ctx = this._getOrReturnCtx(input, ctx); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.too_small, - minimum: check.value, - type: "string", - inclusive: true, - exact: false, - message: check.message - }); - status.dirty(); - } - } else if (check.kind === "max") { - if (input.data.length > check.value) { - ctx = this._getOrReturnCtx(input, ctx); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.too_big, - maximum: check.value, - type: "string", - inclusive: true, - exact: false, - message: check.message - }); - status.dirty(); - } - } else if (check.kind === "length") { - const tooBig = input.data.length > check.value; - const tooSmall = input.data.length < check.value; - if (tooBig || tooSmall) { - ctx = this._getOrReturnCtx(input, ctx); - if (tooBig) { - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.too_big, - maximum: check.value, - type: "string", - inclusive: true, - exact: true, - message: check.message - }); - } else if (tooSmall) { - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.too_small, - minimum: check.value, - type: "string", - inclusive: true, - exact: true, - message: check.message - }); + } else { + credentials = void 0; + } + if (shouldStoreIdentities(formValues)) { + if (identities.fullName) { + if (!(identities.firstName && identities.lastName)) { + const nameParts = identities.fullName.trim().split(/\s+/); + if (nameParts.length === 2) { + identities.firstName = nameParts[0]; + identities.lastName = nameParts[1]; + } else { + identities.firstName = identities.fullName; } - status.dirty(); - } - } else if (check.kind === "email") { - if (!emailRegex.test(input.data)) { - ctx = this._getOrReturnCtx(input, ctx); - (0, parseUtil_1.addIssueToContext)(ctx, { - validation: "email", - code: ZodError_1.ZodIssueCode.invalid_string, - message: check.message - }); - status.dirty(); - } - } else if (check.kind === "emoji") { - if (!emojiRegex) { - emojiRegex = new RegExp(_emojiRegex, "u"); - } - if (!emojiRegex.test(input.data)) { - ctx = this._getOrReturnCtx(input, ctx); - (0, parseUtil_1.addIssueToContext)(ctx, { - validation: "emoji", - code: ZodError_1.ZodIssueCode.invalid_string, - message: check.message - }); - status.dirty(); - } - } else if (check.kind === "uuid") { - if (!uuidRegex.test(input.data)) { - ctx = this._getOrReturnCtx(input, ctx); - (0, parseUtil_1.addIssueToContext)(ctx, { - validation: "uuid", - code: ZodError_1.ZodIssueCode.invalid_string, - message: check.message - }); - status.dirty(); - } - } else if (check.kind === "cuid") { - if (!cuidRegex.test(input.data)) { - ctx = this._getOrReturnCtx(input, ctx); - (0, parseUtil_1.addIssueToContext)(ctx, { - validation: "cuid", - code: ZodError_1.ZodIssueCode.invalid_string, - message: check.message - }); - status.dirty(); - } - } else if (check.kind === "cuid2") { - if (!cuid2Regex.test(input.data)) { - ctx = this._getOrReturnCtx(input, ctx); - (0, parseUtil_1.addIssueToContext)(ctx, { - validation: "cuid2", - code: ZodError_1.ZodIssueCode.invalid_string, - message: check.message - }); - status.dirty(); - } - } else if (check.kind === "ulid") { - if (!ulidRegex.test(input.data)) { - ctx = this._getOrReturnCtx(input, ctx); - (0, parseUtil_1.addIssueToContext)(ctx, { - validation: "ulid", - code: ZodError_1.ZodIssueCode.invalid_string, - message: check.message - }); - status.dirty(); - } - } else if (check.kind === "url") { - try { - new URL(input.data); - } catch (_a) { - ctx = this._getOrReturnCtx(input, ctx); - (0, parseUtil_1.addIssueToContext)(ctx, { - validation: "url", - code: ZodError_1.ZodIssueCode.invalid_string, - message: check.message - }); - status.dirty(); - } - } else if (check.kind === "regex") { - check.regex.lastIndex = 0; - const testResult = check.regex.test(input.data); - if (!testResult) { - ctx = this._getOrReturnCtx(input, ctx); - (0, parseUtil_1.addIssueToContext)(ctx, { - validation: "regex", - code: ZodError_1.ZodIssueCode.invalid_string, - message: check.message - }); - status.dirty(); - } - } else if (check.kind === "trim") { - input.data = input.data.trim(); - } else if (check.kind === "includes") { - if (!input.data.includes(check.value, check.position)) { - ctx = this._getOrReturnCtx(input, ctx); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_string, - validation: { - includes: check.value, - position: check.position - }, - message: check.message - }); - status.dirty(); - } - } else if (check.kind === "toLowerCase") { - input.data = input.data.toLowerCase(); - } else if (check.kind === "toUpperCase") { - input.data = input.data.toUpperCase(); - } else if (check.kind === "startsWith") { - if (!input.data.startsWith(check.value)) { - ctx = this._getOrReturnCtx(input, ctx); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_string, - validation: { - startsWith: check.value - }, - message: check.message - }); - status.dirty(); - } - } else if (check.kind === "endsWith") { - if (!input.data.endsWith(check.value)) { - ctx = this._getOrReturnCtx(input, ctx); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_string, - validation: { - endsWith: check.value - }, - message: check.message - }); - status.dirty(); - } - } else if (check.kind === "datetime") { - const regex = datetimeRegex(check); - if (!regex.test(input.data)) { - ctx = this._getOrReturnCtx(input, ctx); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_string, - validation: "datetime", - message: check.message - }); - status.dirty(); } - } else if (check.kind === "ip") { - if (!isValidIP(input.data, check.version)) { - ctx = this._getOrReturnCtx(input, ctx); - (0, parseUtil_1.addIssueToContext)(ctx, { - validation: "ip", - code: ZodError_1.ZodIssueCode.invalid_string, - message: check.message - }); - status.dirty(); - } - } else { - util_1.util.assertNever(check); + delete identities.fullName; + } + if (identities.phone) { + identities.phone = formatPhoneNumber(identities.phone); + } + } else { + identities = void 0; + } + if (shouldStoreCreditCards(formValues)) { + if (creditCards.expiration) { + const { expirationMonth, expirationYear } = getMMAndYYYYFromString(creditCards.expiration); + creditCards.expirationMonth = expirationMonth; + creditCards.expirationYear = expirationYear; + delete creditCards.expiration; } + creditCards.expirationYear = creditCards.expirationYear?.padStart(4, "20"); + if (creditCards.cardNumber) { + creditCards.cardNumber = creditCards.cardNumber.replace(/\D/g, ""); + } + } else { + creditCards = void 0; + } + return { credentials, identities, creditCards }; + }; + + // src/InputTypes/Credentials.js + var AUTOGENERATED_KEY = "autogenerated"; + var PROVIDER_LOCKED = "provider_locked"; + var _data; + var CredentialsTooltipItem = class { + /** @param {CredentialsObject} data */ + constructor(data) { + /** @type {CredentialsObject} */ + __privateAdd(this, _data, void 0); + __publicField(this, "id", () => String(__privateGet(this, _data).id)); + __publicField(this, "labelMedium", (_subtype) => { + if (__privateGet(this, _data).username) { + return __privateGet(this, _data).username; + } + if (__privateGet(this, _data).origin?.url) { + return `Password for ${truncateFromMiddle(__privateGet(this, _data).origin.url)}`; + } + return ""; + }); + __publicField(this, "labelSmall", (_subtype) => { + if (__privateGet(this, _data).origin?.url) { + return truncateFromMiddle(__privateGet(this, _data).origin.url); + } + return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"; + }); + __publicField(this, "credentialsProvider", () => __privateGet(this, _data).credentialsProvider); + __privateSet(this, _data, data); } + }; + _data = new WeakMap(); + var _data2; + var AutoGeneratedCredential = class { + /** @param {CredentialsObject} data */ + constructor(data) { + /** @type {CredentialsObject} */ + __privateAdd(this, _data2, void 0); + __publicField(this, "id", () => String(__privateGet(this, _data2).id)); + __publicField(this, "label", (_subtype) => __privateGet(this, _data2).password); + __publicField(this, "labelMedium", (_subtype) => "Generated password"); + __publicField(this, "labelSmall", (_subtype) => "Password will be saved for this website"); + __privateSet(this, _data2, data); + } + }; + _data2 = new WeakMap(); + function fromPassword(password, username) { return { - status: status.value, - value: input.data + [AUTOGENERATED_KEY]: true, + password, + username }; } - _regex(regex, validation, message) { - return this.refinement(data => regex.test(data), { - validation, - code: ZodError_1.ZodIssueCode.invalid_string, - ...errorUtil_1.errorUtil.errToObj(message) - }); - } - _addCheck(check) { - return new ZodString({ - ...this._def, - checks: [...this._def.checks, check] - }); - } - email(message) { - return this._addCheck({ - kind: "email", - ...errorUtil_1.errorUtil.errToObj(message) - }); - } - url(message) { - return this._addCheck({ - kind: "url", - ...errorUtil_1.errorUtil.errToObj(message) - }); - } - emoji(message) { - return this._addCheck({ - kind: "emoji", - ...errorUtil_1.errorUtil.errToObj(message) - }); - } - uuid(message) { - return this._addCheck({ - kind: "uuid", - ...errorUtil_1.errorUtil.errToObj(message) - }); - } - cuid(message) { - return this._addCheck({ - kind: "cuid", - ...errorUtil_1.errorUtil.errToObj(message) - }); - } - cuid2(message) { - return this._addCheck({ - kind: "cuid2", - ...errorUtil_1.errorUtil.errToObj(message) - }); - } - ulid(message) { - return this._addCheck({ - kind: "ulid", - ...errorUtil_1.errorUtil.errToObj(message) - }); - } - ip(options) { - return this._addCheck({ - kind: "ip", - ...errorUtil_1.errorUtil.errToObj(options) - }); - } - datetime(options) { - var _a; - if (typeof options === "string") { - return this._addCheck({ - kind: "datetime", - precision: null, - offset: false, - message: options - }); + var _data3; + var ProviderLockedItem = class { + /** @param {CredentialsObject} data */ + constructor(data) { + /** @type {CredentialsObject} */ + __privateAdd(this, _data3, void 0); + __publicField(this, "id", () => String(__privateGet(this, _data3).id)); + __publicField(this, "labelMedium", (_subtype) => "Bitwarden is locked"); + __publicField(this, "labelSmall", (_subtype) => "Unlock your vault to access credentials or generate passwords"); + __publicField(this, "credentialsProvider", () => __privateGet(this, _data3).credentialsProvider); + __privateSet(this, _data3, data); } - return this._addCheck({ - kind: "datetime", - precision: typeof (options === null || options === void 0 ? void 0 : options.precision) === "undefined" ? null : options === null || options === void 0 ? void 0 : options.precision, - offset: (_a = options === null || options === void 0 ? void 0 : options.offset) !== null && _a !== void 0 ? _a : false, - ...errorUtil_1.errorUtil.errToObj(options === null || options === void 0 ? void 0 : options.message) - }); - } - regex(regex, message) { - return this._addCheck({ - kind: "regex", - regex: regex, - ...errorUtil_1.errorUtil.errToObj(message) - }); - } - includes(value, options) { - return this._addCheck({ - kind: "includes", - value: value, - position: options === null || options === void 0 ? void 0 : options.position, - ...errorUtil_1.errorUtil.errToObj(options === null || options === void 0 ? void 0 : options.message) - }); - } - startsWith(value, message) { - return this._addCheck({ - kind: "startsWith", - value: value, - ...errorUtil_1.errorUtil.errToObj(message) - }); - } - endsWith(value, message) { - return this._addCheck({ - kind: "endsWith", - value: value, - ...errorUtil_1.errorUtil.errToObj(message) - }); - } - min(minLength, message) { - return this._addCheck({ - kind: "min", - value: minLength, - ...errorUtil_1.errorUtil.errToObj(message) - }); - } - max(maxLength, message) { - return this._addCheck({ - kind: "max", - value: maxLength, - ...errorUtil_1.errorUtil.errToObj(message) - }); - } - length(len, message) { - return this._addCheck({ - kind: "length", - value: len, - ...errorUtil_1.errorUtil.errToObj(message) - }); - } - /** - * @deprecated Use z.string().min(1) instead. - * @see {@link ZodString.min} - */ - nonempty(message) { - return this.min(1, errorUtil_1.errorUtil.errToObj(message)); - } - trim() { - return new ZodString({ - ...this._def, - checks: [...this._def.checks, { - kind: "trim" - }] - }); - } - toLowerCase() { - return new ZodString({ - ...this._def, - checks: [...this._def.checks, { - kind: "toLowerCase" - }] - }); - } - toUpperCase() { - return new ZodString({ - ...this._def, - checks: [...this._def.checks, { - kind: "toUpperCase" - }] - }); - } - get isDatetime() { - return !!this._def.checks.find(ch => ch.kind === "datetime"); - } - get isEmail() { - return !!this._def.checks.find(ch => ch.kind === "email"); - } - get isURL() { - return !!this._def.checks.find(ch => ch.kind === "url"); - } - get isEmoji() { - return !!this._def.checks.find(ch => ch.kind === "emoji"); - } - get isUUID() { - return !!this._def.checks.find(ch => ch.kind === "uuid"); - } - get isCUID() { - return !!this._def.checks.find(ch => ch.kind === "cuid"); - } - get isCUID2() { - return !!this._def.checks.find(ch => ch.kind === "cuid2"); - } - get isULID() { - return !!this._def.checks.find(ch => ch.kind === "ulid"); - } - get isIP() { - return !!this._def.checks.find(ch => ch.kind === "ip"); - } - get minLength() { - let min = null; - for (const ch of this._def.checks) { - if (ch.kind === "min") { - if (min === null || ch.value > min) min = ch.value; - } + }; + _data3 = new WeakMap(); + function appendGeneratedKey(data, autofilledFields = {}) { + let autogenerated = false; + if (autofilledFields.password && data.credentials?.password === autofilledFields.password) { + autogenerated = true; } - return min; - } - get maxLength() { - let max = null; - for (const ch of this._def.checks) { - if (ch.kind === "max") { - if (max === null || ch.value < max) max = ch.value; + if (autofilledFields.username && data.credentials?.username === autofilledFields.username) { + autogenerated = true; + } + if (!autogenerated) + return data; + return { + ...data, + credentials: { + ...data.credentials, + [AUTOGENERATED_KEY]: true } + }; + } + function createCredentialsTooltipItem(data) { + if (data.id === PROVIDER_LOCKED) { + return new ProviderLockedItem(data); } - return max; + if (AUTOGENERATED_KEY in data && data.password) { + return new AutoGeneratedCredential(data); + } + return new CredentialsTooltipItem(data); } -} -exports.ZodString = ZodString; -ZodString.create = params => { - var _a; - return new ZodString({ - checks: [], - typeName: ZodFirstPartyTypeKind.ZodString, - coerce: (_a = params === null || params === void 0 ? void 0 : params.coerce) !== null && _a !== void 0 ? _a : false, - ...processCreateParams(params) - }); -}; -// https://stackoverflow.com/questions/3966484/why-does-modulus-operator-return-fractional-number-in-javascript/31711034#31711034 -function floatSafeRemainder(val, step) { - const valDecCount = (val.toString().split(".")[1] || "").length; - const stepDecCount = (step.toString().split(".")[1] || "").length; - const decCount = valDecCount > stepDecCount ? valDecCount : stepDecCount; - const valInt = parseInt(val.toFixed(decCount).replace(".", "")); - const stepInt = parseInt(step.toFixed(decCount).replace(".", "")); - return valInt % stepInt / Math.pow(10, decCount); -} -class ZodNumber extends ZodType { - constructor() { - super(...arguments); - this.min = this.gte; - this.max = this.lte; - this.step = this.multipleOf; + + // packages/password/lib/rules-parser.js + var Identifier = { + ASCII_PRINTABLE: "ascii-printable", + DIGIT: "digit", + LOWER: "lower", + SPECIAL: "special", + UNICODE: "unicode", + UPPER: "upper" + }; + var RuleName = { + ALLOWED: "allowed", + MAX_CONSECUTIVE: "max-consecutive", + REQUIRED: "required", + MIN_LENGTH: "minlength", + MAX_LENGTH: "maxlength" + }; + var CHARACTER_CLASS_START_SENTINEL = "["; + var CHARACTER_CLASS_END_SENTINEL = "]"; + var PROPERTY_VALUE_SEPARATOR = ","; + var PROPERTY_SEPARATOR = ";"; + var PROPERTY_VALUE_START_SENTINEL = ":"; + var SPACE_CODE_POINT = " ".codePointAt(0); + var SHOULD_NOT_BE_REACHED = "Should not be reached"; + var Rule = class { + constructor(name, value) { + this._name = name; + this.value = value; + } + get name() { + return this._name; + } + toString() { + return JSON.stringify(this); + } + }; + var NamedCharacterClass = class { + constructor(name) { + console.assert(_isValidRequiredOrAllowedPropertyValueIdentifier(name)); + this._name = name; + } + get name() { + return this._name.toLowerCase(); + } + toString() { + return this._name; + } + toHTMLString() { + return this._name; + } + }; + var ParserError = class extends Error { + }; + var CustomCharacterClass = class { + constructor(characters) { + console.assert(characters instanceof Array); + this._characters = characters; + } + get characters() { + return this._characters; + } + toString() { + return `[${this._characters.join("")}]`; + } + toHTMLString() { + return `[${this._characters.join("").replace('"', """)}]`; + } + }; + function _isIdentifierCharacter(c) { + console.assert(c.length === 1); + return c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c === "-"; + } + function _isASCIIDigit(c) { + console.assert(c.length === 1); + return c >= "0" && c <= "9"; + } + function _isASCIIPrintableCharacter(c) { + console.assert(c.length === 1); + return c >= " " && c <= "~"; + } + function _isASCIIWhitespace(c) { + console.assert(c.length === 1); + return c === " " || c === "\f" || c === "\n" || c === "\r" || c === " "; + } + function _bitSetIndexForCharacter(c) { + console.assert(c.length === 1); + return c.codePointAt(0) - SPACE_CODE_POINT; + } + function _characterAtBitSetIndex(index) { + return String.fromCodePoint(index + SPACE_CODE_POINT); + } + function _markBitsForNamedCharacterClass(bitSet, namedCharacterClass) { + console.assert(bitSet instanceof Array); + console.assert(namedCharacterClass.name !== Identifier.UNICODE); + console.assert(namedCharacterClass.name !== Identifier.ASCII_PRINTABLE); + if (namedCharacterClass.name === Identifier.UPPER) { + bitSet.fill(true, _bitSetIndexForCharacter("A"), _bitSetIndexForCharacter("Z") + 1); + } else if (namedCharacterClass.name === Identifier.LOWER) { + bitSet.fill(true, _bitSetIndexForCharacter("a"), _bitSetIndexForCharacter("z") + 1); + } else if (namedCharacterClass.name === Identifier.DIGIT) { + bitSet.fill(true, _bitSetIndexForCharacter("0"), _bitSetIndexForCharacter("9") + 1); + } else if (namedCharacterClass.name === Identifier.SPECIAL) { + bitSet.fill(true, _bitSetIndexForCharacter(" "), _bitSetIndexForCharacter("/") + 1); + bitSet.fill(true, _bitSetIndexForCharacter(":"), _bitSetIndexForCharacter("@") + 1); + bitSet.fill(true, _bitSetIndexForCharacter("["), _bitSetIndexForCharacter("`") + 1); + bitSet.fill(true, _bitSetIndexForCharacter("{"), _bitSetIndexForCharacter("~") + 1); + } else { + console.assert(false, SHOULD_NOT_BE_REACHED, namedCharacterClass); + } } - _parse(input) { - if (this._def.coerce) { - input.data = Number(input.data); + function _markBitsForCustomCharacterClass(bitSet, customCharacterClass) { + for (let character of customCharacterClass.characters) { + bitSet[_bitSetIndexForCharacter(character)] = true; } - const parsedType = this._getType(input); - if (parsedType !== util_1.ZodParsedType.number) { - const ctx = this._getOrReturnCtx(input); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_type, - expected: util_1.ZodParsedType.number, - received: ctx.parsedType - }); - return parseUtil_1.INVALID; - } - let ctx = undefined; - const status = new parseUtil_1.ParseStatus(); - for (const check of this._def.checks) { - if (check.kind === "int") { - if (!util_1.util.isInteger(input.data)) { - ctx = this._getOrReturnCtx(input, ctx); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_type, - expected: "integer", - received: "float", - message: check.message - }); - status.dirty(); - } - } else if (check.kind === "min") { - const tooSmall = check.inclusive ? input.data < check.value : input.data <= check.value; - if (tooSmall) { - ctx = this._getOrReturnCtx(input, ctx); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.too_small, - minimum: check.value, - type: "number", - inclusive: check.inclusive, - exact: false, - message: check.message - }); - status.dirty(); - } - } else if (check.kind === "max") { - const tooBig = check.inclusive ? input.data > check.value : input.data >= check.value; - if (tooBig) { - ctx = this._getOrReturnCtx(input, ctx); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.too_big, - maximum: check.value, - type: "number", - inclusive: check.inclusive, - exact: false, - message: check.message - }); - status.dirty(); + } + function _canonicalizedPropertyValues(propertyValues, keepCustomCharacterClassFormatCompliant) { + let asciiPrintableBitSet = new Array("~".codePointAt(0) - " ".codePointAt(0) + 1); + for (let propertyValue of propertyValues) { + if (propertyValue instanceof NamedCharacterClass) { + if (propertyValue.name === Identifier.UNICODE) { + return [new NamedCharacterClass(Identifier.UNICODE)]; } - } else if (check.kind === "multipleOf") { - if (floatSafeRemainder(input.data, check.value) !== 0) { - ctx = this._getOrReturnCtx(input, ctx); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.not_multiple_of, - multipleOf: check.value, - message: check.message - }); - status.dirty(); + if (propertyValue.name === Identifier.ASCII_PRINTABLE) { + return [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)]; } - } else if (check.kind === "finite") { - if (!Number.isFinite(input.data)) { - ctx = this._getOrReturnCtx(input, ctx); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.not_finite, - message: check.message - }); - status.dirty(); + _markBitsForNamedCharacterClass(asciiPrintableBitSet, propertyValue); + } else if (propertyValue instanceof CustomCharacterClass) { + _markBitsForCustomCharacterClass(asciiPrintableBitSet, propertyValue); + } + } + let charactersSeen = []; + function checkRange(start, end) { + let temp2 = []; + for (let i = _bitSetIndexForCharacter(start); i <= _bitSetIndexForCharacter(end); ++i) { + if (asciiPrintableBitSet[i]) { + temp2.push(_characterAtBitSetIndex(i)); } + } + let result2 = temp2.length === _bitSetIndexForCharacter(end) - _bitSetIndexForCharacter(start) + 1; + if (!result2) { + charactersSeen = charactersSeen.concat(temp2); + } + return result2; + } + let hasAllUpper = checkRange("A", "Z"); + let hasAllLower = checkRange("a", "z"); + let hasAllDigits = checkRange("0", "9"); + let hasAllSpecial = false; + let hasDash = false; + let hasRightSquareBracket = false; + let temp = []; + for (let i = _bitSetIndexForCharacter(" "); i <= _bitSetIndexForCharacter("/"); ++i) { + if (!asciiPrintableBitSet[i]) { + continue; + } + let character = _characterAtBitSetIndex(i); + if (keepCustomCharacterClassFormatCompliant && character === "-") { + hasDash = true; } else { - util_1.util.assertNever(check); + temp.push(character); } } - return { - status: status.value, - value: input.data - }; - } - gte(value, message) { - return this.setLimit("min", value, true, errorUtil_1.errorUtil.toString(message)); - } - gt(value, message) { - return this.setLimit("min", value, false, errorUtil_1.errorUtil.toString(message)); - } - lte(value, message) { - return this.setLimit("max", value, true, errorUtil_1.errorUtil.toString(message)); - } - lt(value, message) { - return this.setLimit("max", value, false, errorUtil_1.errorUtil.toString(message)); - } - setLimit(kind, value, inclusive, message) { - return new ZodNumber({ - ...this._def, - checks: [...this._def.checks, { - kind, - value, - inclusive, - message: errorUtil_1.errorUtil.toString(message) - }] - }); - } - _addCheck(check) { - return new ZodNumber({ - ...this._def, - checks: [...this._def.checks, check] - }); - } - int(message) { - return this._addCheck({ - kind: "int", - message: errorUtil_1.errorUtil.toString(message) - }); - } - positive(message) { - return this._addCheck({ - kind: "min", - value: 0, - inclusive: false, - message: errorUtil_1.errorUtil.toString(message) - }); - } - negative(message) { - return this._addCheck({ - kind: "max", - value: 0, - inclusive: false, - message: errorUtil_1.errorUtil.toString(message) - }); - } - nonpositive(message) { - return this._addCheck({ - kind: "max", - value: 0, - inclusive: true, - message: errorUtil_1.errorUtil.toString(message) - }); - } - nonnegative(message) { - return this._addCheck({ - kind: "min", - value: 0, - inclusive: true, - message: errorUtil_1.errorUtil.toString(message) - }); - } - multipleOf(value, message) { - return this._addCheck({ - kind: "multipleOf", - value: value, - message: errorUtil_1.errorUtil.toString(message) - }); - } - finite(message) { - return this._addCheck({ - kind: "finite", - message: errorUtil_1.errorUtil.toString(message) - }); - } - safe(message) { - return this._addCheck({ - kind: "min", - inclusive: true, - value: Number.MIN_SAFE_INTEGER, - message: errorUtil_1.errorUtil.toString(message) - })._addCheck({ - kind: "max", - inclusive: true, - value: Number.MAX_SAFE_INTEGER, - message: errorUtil_1.errorUtil.toString(message) - }); - } - get minValue() { - let min = null; - for (const ch of this._def.checks) { - if (ch.kind === "min") { - if (min === null || ch.value > min) min = ch.value; + for (let i = _bitSetIndexForCharacter(":"); i <= _bitSetIndexForCharacter("@"); ++i) { + if (asciiPrintableBitSet[i]) { + temp.push(_characterAtBitSetIndex(i)); } } - return min; - } - get maxValue() { - let max = null; - for (const ch of this._def.checks) { - if (ch.kind === "max") { - if (max === null || ch.value < max) max = ch.value; + for (let i = _bitSetIndexForCharacter("["); i <= _bitSetIndexForCharacter("`"); ++i) { + if (!asciiPrintableBitSet[i]) { + continue; + } + let character = _characterAtBitSetIndex(i); + if (keepCustomCharacterClassFormatCompliant && character === "]") { + hasRightSquareBracket = true; + } else { + temp.push(character); } } - return max; - } - get isInt() { - return !!this._def.checks.find(ch => ch.kind === "int" || ch.kind === "multipleOf" && util_1.util.isInteger(ch.value)); - } - get isFinite() { - let max = null, - min = null; - for (const ch of this._def.checks) { - if (ch.kind === "finite" || ch.kind === "int" || ch.kind === "multipleOf") { - return true; - } else if (ch.kind === "min") { - if (min === null || ch.value > min) min = ch.value; - } else if (ch.kind === "max") { - if (max === null || ch.value < max) max = ch.value; + for (let i = _bitSetIndexForCharacter("{"); i <= _bitSetIndexForCharacter("~"); ++i) { + if (asciiPrintableBitSet[i]) { + temp.push(_characterAtBitSetIndex(i)); } } - return Number.isFinite(min) && Number.isFinite(max); - } -} -exports.ZodNumber = ZodNumber; -ZodNumber.create = params => { - return new ZodNumber({ - checks: [], - typeName: ZodFirstPartyTypeKind.ZodNumber, - coerce: (params === null || params === void 0 ? void 0 : params.coerce) || false, - ...processCreateParams(params) - }); -}; -class ZodBigInt extends ZodType { - constructor() { - super(...arguments); - this.min = this.gte; - this.max = this.lte; - } - _parse(input) { - if (this._def.coerce) { - input.data = BigInt(input.data); + if (hasDash) { + temp.unshift("-"); } - const parsedType = this._getType(input); - if (parsedType !== util_1.ZodParsedType.bigint) { - const ctx = this._getOrReturnCtx(input); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_type, - expected: util_1.ZodParsedType.bigint, - received: ctx.parsedType - }); - return parseUtil_1.INVALID; - } - let ctx = undefined; - const status = new parseUtil_1.ParseStatus(); - for (const check of this._def.checks) { - if (check.kind === "min") { - const tooSmall = check.inclusive ? input.data < check.value : input.data <= check.value; - if (tooSmall) { - ctx = this._getOrReturnCtx(input, ctx); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.too_small, - type: "bigint", - minimum: check.value, - inclusive: check.inclusive, - message: check.message - }); - status.dirty(); - } - } else if (check.kind === "max") { - const tooBig = check.inclusive ? input.data > check.value : input.data >= check.value; - if (tooBig) { - ctx = this._getOrReturnCtx(input, ctx); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.too_big, - type: "bigint", - maximum: check.value, - inclusive: check.inclusive, - message: check.message - }); - status.dirty(); - } - } else if (check.kind === "multipleOf") { - if (input.data % check.value !== BigInt(0)) { - ctx = this._getOrReturnCtx(input, ctx); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.not_multiple_of, - multipleOf: check.value, - message: check.message - }); - status.dirty(); - } - } else { - util_1.util.assertNever(check); - } + if (hasRightSquareBracket) { + temp.push("]"); } - return { - status: status.value, - value: input.data - }; - } - gte(value, message) { - return this.setLimit("min", value, true, errorUtil_1.errorUtil.toString(message)); - } - gt(value, message) { - return this.setLimit("min", value, false, errorUtil_1.errorUtil.toString(message)); - } - lte(value, message) { - return this.setLimit("max", value, true, errorUtil_1.errorUtil.toString(message)); - } - lt(value, message) { - return this.setLimit("max", value, false, errorUtil_1.errorUtil.toString(message)); - } - setLimit(kind, value, inclusive, message) { - return new ZodBigInt({ - ...this._def, - checks: [...this._def.checks, { - kind, - value, - inclusive, - message: errorUtil_1.errorUtil.toString(message) - }] - }); - } - _addCheck(check) { - return new ZodBigInt({ - ...this._def, - checks: [...this._def.checks, check] - }); - } - positive(message) { - return this._addCheck({ - kind: "min", - value: BigInt(0), - inclusive: false, - message: errorUtil_1.errorUtil.toString(message) - }); - } - negative(message) { - return this._addCheck({ - kind: "max", - value: BigInt(0), - inclusive: false, - message: errorUtil_1.errorUtil.toString(message) - }); + let numberOfSpecialCharacters = _bitSetIndexForCharacter("/") - _bitSetIndexForCharacter(" ") + 1 + (_bitSetIndexForCharacter("@") - _bitSetIndexForCharacter(":") + 1) + (_bitSetIndexForCharacter("`") - _bitSetIndexForCharacter("[") + 1) + (_bitSetIndexForCharacter("~") - _bitSetIndexForCharacter("{") + 1); + hasAllSpecial = temp.length === numberOfSpecialCharacters; + if (!hasAllSpecial) { + charactersSeen = charactersSeen.concat(temp); + } + let result = []; + if (hasAllUpper && hasAllLower && hasAllDigits && hasAllSpecial) { + return [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)]; + } + if (hasAllUpper) { + result.push(new NamedCharacterClass(Identifier.UPPER)); + } + if (hasAllLower) { + result.push(new NamedCharacterClass(Identifier.LOWER)); + } + if (hasAllDigits) { + result.push(new NamedCharacterClass(Identifier.DIGIT)); + } + if (hasAllSpecial) { + result.push(new NamedCharacterClass(Identifier.SPECIAL)); + } + if (charactersSeen.length) { + result.push(new CustomCharacterClass(charactersSeen)); + } + return result; } - nonpositive(message) { - return this._addCheck({ - kind: "max", - value: BigInt(0), - inclusive: true, - message: errorUtil_1.errorUtil.toString(message) - }); + function _indexOfNonWhitespaceCharacter(input, position = 0) { + console.assert(position >= 0); + console.assert(position <= input.length); + let length = input.length; + while (position < length && _isASCIIWhitespace(input[position])) { + ++position; + } + return position; } - nonnegative(message) { - return this._addCheck({ - kind: "min", - value: BigInt(0), - inclusive: true, - message: errorUtil_1.errorUtil.toString(message) - }); + function _parseIdentifier(input, position) { + console.assert(position >= 0); + console.assert(position < input.length); + console.assert(_isIdentifierCharacter(input[position])); + let length = input.length; + let seenIdentifiers = []; + do { + let c = input[position]; + if (!_isIdentifierCharacter(c)) { + break; + } + seenIdentifiers.push(c); + ++position; + } while (position < length); + return [seenIdentifiers.join(""), position]; } - multipleOf(value, message) { - return this._addCheck({ - kind: "multipleOf", - value, - message: errorUtil_1.errorUtil.toString(message) - }); + function _isValidRequiredOrAllowedPropertyValueIdentifier(identifier) { + return identifier && Object.values(Identifier).includes(identifier.toLowerCase()); } - get minValue() { - let min = null; - for (const ch of this._def.checks) { - if (ch.kind === "min") { - if (min === null || ch.value > min) min = ch.value; + function _parseCustomCharacterClass(input, position) { + console.assert(position >= 0); + console.assert(position < input.length); + console.assert(input[position] === CHARACTER_CLASS_START_SENTINEL); + let length = input.length; + ++position; + if (position >= length) { + return [null, position]; + } + let initialPosition = position; + let result = []; + do { + let c = input[position]; + if (!_isASCIIPrintableCharacter(c)) { + ++position; + continue; + } + if (c === "-" && position - initialPosition > 0) { + console.warn("Ignoring '-'; a '-' may only appear as the first character in a character class"); + ++position; + continue; + } + result.push(c); + ++position; + if (c === CHARACTER_CLASS_END_SENTINEL) { + break; } + } while (position < length); + if (position < length && input[position] !== CHARACTER_CLASS_END_SENTINEL) { + result.pop(); + return [result, position]; + } else if (position === length && input[position - 1] === CHARACTER_CLASS_END_SENTINEL) { + result.pop(); + return [result, position]; + } + if (position < length && input[position] === CHARACTER_CLASS_END_SENTINEL) { + return [result, position + 1]; } - return min; + return [null, position]; } - get maxValue() { - let max = null; - for (const ch of this._def.checks) { - if (ch.kind === "max") { - if (max === null || ch.value < max) max = ch.value; + function _parsePasswordRequiredOrAllowedPropertyValue(input, position) { + console.assert(position >= 0); + console.assert(position < input.length); + let length = input.length; + let propertyValues = []; + while (true) { + if (_isIdentifierCharacter(input[position])) { + let identifierStartPosition = position; + var [propertyValue, position] = _parseIdentifier(input, position); + if (!_isValidRequiredOrAllowedPropertyValueIdentifier(propertyValue)) { + return [null, identifierStartPosition]; + } + propertyValues.push(new NamedCharacterClass(propertyValue)); + } else if (input[position] === CHARACTER_CLASS_START_SENTINEL) { + var [propertyValue, position] = _parseCustomCharacterClass(input, position); + if (propertyValue && propertyValue.length) { + propertyValues.push(new CustomCharacterClass(propertyValue)); + } + } else { + return [null, position]; + } + position = _indexOfNonWhitespaceCharacter(input, position); + if (position >= length || input[position] === PROPERTY_SEPARATOR) { + break; + } + if (input[position] === PROPERTY_VALUE_SEPARATOR) { + position = _indexOfNonWhitespaceCharacter(input, position + 1); + if (position >= length) { + return [null, position]; + } + continue; } + return [null, position]; } - return max; + return [propertyValues, position]; } -} -exports.ZodBigInt = ZodBigInt; -ZodBigInt.create = params => { - var _a; - return new ZodBigInt({ - checks: [], - typeName: ZodFirstPartyTypeKind.ZodBigInt, - coerce: (_a = params === null || params === void 0 ? void 0 : params.coerce) !== null && _a !== void 0 ? _a : false, - ...processCreateParams(params) - }); -}; -class ZodBoolean extends ZodType { - _parse(input) { - if (this._def.coerce) { - input.data = Boolean(input.data); - } - const parsedType = this._getType(input); - if (parsedType !== util_1.ZodParsedType.boolean) { - const ctx = this._getOrReturnCtx(input); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_type, - expected: util_1.ZodParsedType.boolean, - received: ctx.parsedType - }); - return parseUtil_1.INVALID; + function _parsePasswordRule(input, position) { + console.assert(position >= 0); + console.assert(position < input.length); + console.assert(_isIdentifierCharacter(input[position])); + let length = input.length; + var mayBeIdentifierStartPosition = position; + var [identifier, position] = _parseIdentifier(input, position); + if (!Object.values(RuleName).includes(identifier)) { + return [null, mayBeIdentifierStartPosition, void 0]; } - return (0, parseUtil_1.OK)(input.data); - } -} -exports.ZodBoolean = ZodBoolean; -ZodBoolean.create = params => { - return new ZodBoolean({ - typeName: ZodFirstPartyTypeKind.ZodBoolean, - coerce: (params === null || params === void 0 ? void 0 : params.coerce) || false, - ...processCreateParams(params) - }); -}; -class ZodDate extends ZodType { - _parse(input) { - if (this._def.coerce) { - input.data = new Date(input.data); - } - const parsedType = this._getType(input); - if (parsedType !== util_1.ZodParsedType.date) { - const ctx = this._getOrReturnCtx(input); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_type, - expected: util_1.ZodParsedType.date, - received: ctx.parsedType - }); - return parseUtil_1.INVALID; + if (position >= length) { + return [null, position, void 0]; } - if (isNaN(input.data.getTime())) { - const ctx = this._getOrReturnCtx(input); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_date - }); - return parseUtil_1.INVALID; - } - const status = new parseUtil_1.ParseStatus(); - let ctx = undefined; - for (const check of this._def.checks) { - if (check.kind === "min") { - if (input.data.getTime() < check.value) { - ctx = this._getOrReturnCtx(input, ctx); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.too_small, - message: check.message, - inclusive: true, - exact: false, - minimum: check.value, - type: "date" - }); - status.dirty(); + if (input[position] !== PROPERTY_VALUE_START_SENTINEL) { + return [null, position, void 0]; + } + let property = { name: identifier, value: null }; + position = _indexOfNonWhitespaceCharacter(input, position + 1); + if (position >= length || input[position] === PROPERTY_SEPARATOR) { + return [new Rule(property.name, property.value), position, void 0]; + } + switch (identifier) { + case RuleName.ALLOWED: + case RuleName.REQUIRED: { + var [propertyValue, position] = _parsePasswordRequiredOrAllowedPropertyValue(input, position); + if (propertyValue) { + property.value = propertyValue; } - } else if (check.kind === "max") { - if (input.data.getTime() > check.value) { - ctx = this._getOrReturnCtx(input, ctx); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.too_big, - message: check.message, - inclusive: true, - exact: false, - maximum: check.value, - type: "date" - }); - status.dirty(); + return [new Rule(property.name, property.value), position, void 0]; + } + case RuleName.MAX_CONSECUTIVE: { + var [propertyValue, position] = _parseMaxConsecutivePropertyValue(input, position); + if (propertyValue) { + property.value = propertyValue; } - } else { - util_1.util.assertNever(check); + return [new Rule(property.name, property.value), position, void 0]; + } + case RuleName.MIN_LENGTH: + case RuleName.MAX_LENGTH: { + var [propertyValue, position] = _parseMinLengthMaxLengthPropertyValue(input, position); + if (propertyValue) { + property.value = propertyValue; + } + return [new Rule(property.name, property.value), position, void 0]; } } - return { - status: status.value, - value: new Date(input.data.getTime()) - }; - } - _addCheck(check) { - return new ZodDate({ - ...this._def, - checks: [...this._def.checks, check] - }); + console.assert(false, SHOULD_NOT_BE_REACHED); + return [null, -1, void 0]; } - min(minDate, message) { - return this._addCheck({ - kind: "min", - value: minDate.getTime(), - message: errorUtil_1.errorUtil.toString(message) - }); + function _parseMinLengthMaxLengthPropertyValue(input, position) { + return _parseInteger(input, position); } - max(maxDate, message) { - return this._addCheck({ - kind: "max", - value: maxDate.getTime(), - message: errorUtil_1.errorUtil.toString(message) - }); + function _parseMaxConsecutivePropertyValue(input, position) { + return _parseInteger(input, position); } - get minDate() { - let min = null; - for (const ch of this._def.checks) { - if (ch.kind === "min") { - if (min === null || ch.value > min) min = ch.value; - } + function _parseInteger(input, position) { + console.assert(position >= 0); + console.assert(position < input.length); + if (!_isASCIIDigit(input[position])) { + return [null, position]; + } + let length = input.length; + let result = 0; + do { + result = 10 * result + parseInt(input[position], 10); + ++position; + } while (position < length && input[position] !== PROPERTY_SEPARATOR && _isASCIIDigit(input[position])); + if (position >= length || input[position] === PROPERTY_SEPARATOR) { + return [result, position]; } - return min != null ? new Date(min) : null; + return [null, position]; } - get maxDate() { - let max = null; - for (const ch of this._def.checks) { - if (ch.kind === "max") { - if (max === null || ch.value < max) max = ch.value; + function _parsePasswordRulesInternal(input) { + let parsedProperties = []; + let length = input.length; + var position = _indexOfNonWhitespaceCharacter(input); + while (position < length) { + if (!_isIdentifierCharacter(input[position])) { + return [parsedProperties, void 0]; + } + var [parsedProperty, position, message] = _parsePasswordRule(input, position); + if (parsedProperty && parsedProperty.value) { + parsedProperties.push(parsedProperty); + } + position = _indexOfNonWhitespaceCharacter(input, position); + if (position >= length) { + break; + } + if (input[position] === PROPERTY_SEPARATOR) { + position = _indexOfNonWhitespaceCharacter(input, position + 1); + if (position >= length) { + return [parsedProperties, void 0]; + } + continue; } + return [null, message || "Failed to find start of next property: " + input.substr(position)]; } - return max != null ? new Date(max) : null; + return [parsedProperties, void 0]; } -} -exports.ZodDate = ZodDate; -ZodDate.create = params => { - return new ZodDate({ - checks: [], - coerce: (params === null || params === void 0 ? void 0 : params.coerce) || false, - typeName: ZodFirstPartyTypeKind.ZodDate, - ...processCreateParams(params) - }); -}; -class ZodSymbol extends ZodType { - _parse(input) { - const parsedType = this._getType(input); - if (parsedType !== util_1.ZodParsedType.symbol) { - const ctx = this._getOrReturnCtx(input); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_type, - expected: util_1.ZodParsedType.symbol, - received: ctx.parsedType - }); - return parseUtil_1.INVALID; + function parsePasswordRules(input, formatRulesForMinifiedVersion) { + let [passwordRules, maybeMessage] = _parsePasswordRulesInternal(input); + if (!passwordRules) { + throw new ParserError(maybeMessage); } - return (0, parseUtil_1.OK)(input.data); - } -} -exports.ZodSymbol = ZodSymbol; -ZodSymbol.create = params => { - return new ZodSymbol({ - typeName: ZodFirstPartyTypeKind.ZodSymbol, - ...processCreateParams(params) - }); -}; -class ZodUndefined extends ZodType { - _parse(input) { - const parsedType = this._getType(input); - if (parsedType !== util_1.ZodParsedType.undefined) { - const ctx = this._getOrReturnCtx(input); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_type, - expected: util_1.ZodParsedType.undefined, - received: ctx.parsedType - }); - return parseUtil_1.INVALID; + if (passwordRules.length === 0) { + throw new ParserError("No valid rules were provided"); } - return (0, parseUtil_1.OK)(input.data); - } -} -exports.ZodUndefined = ZodUndefined; -ZodUndefined.create = params => { - return new ZodUndefined({ - typeName: ZodFirstPartyTypeKind.ZodUndefined, - ...processCreateParams(params) - }); -}; -class ZodNull extends ZodType { - _parse(input) { - const parsedType = this._getType(input); - if (parsedType !== util_1.ZodParsedType.null) { - const ctx = this._getOrReturnCtx(input); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_type, - expected: util_1.ZodParsedType.null, - received: ctx.parsedType - }); - return parseUtil_1.INVALID; + let suppressCopyingRequiredToAllowed = formatRulesForMinifiedVersion; + let requiredRules = []; + let newAllowedValues = []; + let minimumMaximumConsecutiveCharacters = null; + let maximumMinLength = 0; + let minimumMaxLength = null; + for (let rule of passwordRules) { + switch (rule.name) { + case RuleName.MAX_CONSECUTIVE: + minimumMaximumConsecutiveCharacters = minimumMaximumConsecutiveCharacters ? Math.min(rule.value, minimumMaximumConsecutiveCharacters) : rule.value; + break; + case RuleName.MIN_LENGTH: + maximumMinLength = Math.max(rule.value, maximumMinLength); + break; + case RuleName.MAX_LENGTH: + minimumMaxLength = minimumMaxLength ? Math.min(rule.value, minimumMaxLength) : rule.value; + break; + case RuleName.REQUIRED: + rule.value = _canonicalizedPropertyValues(rule.value, formatRulesForMinifiedVersion); + requiredRules.push(rule); + if (!suppressCopyingRequiredToAllowed) { + newAllowedValues = newAllowedValues.concat(rule.value); + } + break; + case RuleName.ALLOWED: + newAllowedValues = newAllowedValues.concat(rule.value); + break; + } } - return (0, parseUtil_1.OK)(input.data); - } -} -exports.ZodNull = ZodNull; -ZodNull.create = params => { - return new ZodNull({ - typeName: ZodFirstPartyTypeKind.ZodNull, - ...processCreateParams(params) - }); -}; -class ZodAny extends ZodType { - constructor() { - super(...arguments); - // to prevent instances of other classes from extending ZodAny. this causes issues with catchall in ZodObject. - this._any = true; - } - _parse(input) { - return (0, parseUtil_1.OK)(input.data); - } -} -exports.ZodAny = ZodAny; -ZodAny.create = params => { - return new ZodAny({ - typeName: ZodFirstPartyTypeKind.ZodAny, - ...processCreateParams(params) - }); -}; -class ZodUnknown extends ZodType { - constructor() { - super(...arguments); - // required - this._unknown = true; - } - _parse(input) { - return (0, parseUtil_1.OK)(input.data); - } -} -exports.ZodUnknown = ZodUnknown; -ZodUnknown.create = params => { - return new ZodUnknown({ - typeName: ZodFirstPartyTypeKind.ZodUnknown, - ...processCreateParams(params) + let newPasswordRules = []; + if (maximumMinLength > 0) { + newPasswordRules.push(new Rule(RuleName.MIN_LENGTH, maximumMinLength)); + } + if (minimumMaxLength !== null) { + newPasswordRules.push(new Rule(RuleName.MAX_LENGTH, minimumMaxLength)); + } + if (minimumMaximumConsecutiveCharacters !== null) { + newPasswordRules.push(new Rule(RuleName.MAX_CONSECUTIVE, minimumMaximumConsecutiveCharacters)); + } + let sortedRequiredRules = requiredRules.sort(function(a, b) { + const namedCharacterClassOrder = [Identifier.LOWER, Identifier.UPPER, Identifier.DIGIT, Identifier.SPECIAL, Identifier.ASCII_PRINTABLE, Identifier.UNICODE]; + let aIsJustOneNamedCharacterClass = a.value.length === 1 && a.value[0] instanceof NamedCharacterClass; + let bIsJustOneNamedCharacterClass = b.value.length === 1 && b.value[0] instanceof NamedCharacterClass; + if (aIsJustOneNamedCharacterClass && !bIsJustOneNamedCharacterClass) { + return -1; + } + if (!aIsJustOneNamedCharacterClass && bIsJustOneNamedCharacterClass) { + return 1; + } + if (aIsJustOneNamedCharacterClass && bIsJustOneNamedCharacterClass) { + let aIndex = namedCharacterClassOrder.indexOf(a.value[0].name); + let bIndex = namedCharacterClassOrder.indexOf(b.value[0].name); + return aIndex - bIndex; + } + return 0; + }); + newPasswordRules = newPasswordRules.concat(sortedRequiredRules); + newAllowedValues = _canonicalizedPropertyValues(newAllowedValues, suppressCopyingRequiredToAllowed); + if (!suppressCopyingRequiredToAllowed && !newAllowedValues.length) { + newAllowedValues = [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)]; + } + if (newAllowedValues.length) { + newPasswordRules.push(new Rule(RuleName.ALLOWED, newAllowedValues)); + } + return newPasswordRules; + } + + // packages/password/lib/constants.js + var DEFAULT_MIN_LENGTH = 20; + var DEFAULT_MAX_LENGTH = 30; + var DEFAULT_REQUIRED_CHARS = "-!?$&#%"; + var DEFAULT_UNAMBIGUOUS_CHARS = "abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ0123456789"; + var DEFAULT_PASSWORD_RULES = [ + `minlength: ${DEFAULT_MIN_LENGTH}`, + `maxlength: ${DEFAULT_MAX_LENGTH}`, + `required: [${DEFAULT_REQUIRED_CHARS}]`, + `allowed: [${DEFAULT_UNAMBIGUOUS_CHARS}]` + ].join("; "); + var constants2 = { + DEFAULT_MIN_LENGTH, + DEFAULT_MAX_LENGTH, + DEFAULT_PASSWORD_RULES, + DEFAULT_REQUIRED_CHARS, + DEFAULT_UNAMBIGUOUS_CHARS + }; + + // packages/password/lib/apple.password.js + var defaults = Object.freeze({ + SCAN_SET_ORDER: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-~!@#$%^&*_+=`|(){}[:;\\\"'<>,.?/ ]", + defaultUnambiguousCharacters: "abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ0123456789", + defaultPasswordLength: constants2.DEFAULT_MIN_LENGTH, + defaultPasswordRules: constants2.DEFAULT_PASSWORD_RULES, + defaultRequiredCharacterSets: ["abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "0123456789"], + /** + * @type {typeof window.crypto.getRandomValues | null} + */ + getRandomValues: null }); -}; -class ZodNever extends ZodType { - _parse(input) { - const ctx = this._getOrReturnCtx(input); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_type, - expected: util_1.ZodParsedType.never, - received: ctx.parsedType - }); - return parseUtil_1.INVALID; + var safeGlobals = {}; + if (typeof window !== "undefined") { + safeGlobals.getRandomValues = window.crypto.getRandomValues.bind(window.crypto); } -} -exports.ZodNever = ZodNever; -ZodNever.create = params => { - return new ZodNever({ - typeName: ZodFirstPartyTypeKind.ZodNever, - ...processCreateParams(params) - }); -}; -class ZodVoid extends ZodType { - _parse(input) { - const parsedType = this._getType(input); - if (parsedType !== util_1.ZodParsedType.undefined) { - const ctx = this._getOrReturnCtx(input); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_type, - expected: util_1.ZodParsedType.void, - received: ctx.parsedType - }); - return parseUtil_1.INVALID; + var Password = class { + /** + * @param {Partial} [options] + */ + constructor(options = {}) { + this.options = { + ...defaults, + ...options + }; + return this; } - return (0, parseUtil_1.OK)(input.data); - } -} -exports.ZodVoid = ZodVoid; -ZodVoid.create = params => { - return new ZodVoid({ - typeName: ZodFirstPartyTypeKind.ZodVoid, - ...processCreateParams(params) - }); -}; -class ZodArray extends ZodType { - _parse(input) { - const { - ctx, - status - } = this._processInputParams(input); - const def = this._def; - if (ctx.parsedType !== util_1.ZodParsedType.array) { - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_type, - expected: util_1.ZodParsedType.array, - received: ctx.parsedType - }); - return parseUtil_1.INVALID; - } - if (def.exactLength !== null) { - const tooBig = ctx.data.length > def.exactLength.value; - const tooSmall = ctx.data.length < def.exactLength.value; - if (tooBig || tooSmall) { - (0, parseUtil_1.addIssueToContext)(ctx, { - code: tooBig ? ZodError_1.ZodIssueCode.too_big : ZodError_1.ZodIssueCode.too_small, - minimum: tooSmall ? def.exactLength.value : undefined, - maximum: tooBig ? def.exactLength.value : undefined, - type: "array", - inclusive: true, - exact: true, - message: def.exactLength.message - }); - status.dirty(); - } + static get defaults() { + return defaults; } - if (def.minLength !== null) { - if (ctx.data.length < def.minLength.value) { - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.too_small, - minimum: def.minLength.value, - type: "array", - inclusive: true, - exact: false, - message: def.minLength.message - }); - status.dirty(); - } + /** + * Generates a password from the given input. + * + * Note: This method will throw an error if parsing fails - use with caution + * + * @example + * + * ```javascript + * const password = Password.generateOrThrow("minlength: 20") + * ``` + * @public + * @param {string} inputString + * @param {Partial} [options] + * @throws {ParserError|Error} + * @returns {string} + */ + static generateOrThrow(inputString, options = {}) { + return new Password(options).parse(inputString).generate(); } - if (def.maxLength !== null) { - if (ctx.data.length > def.maxLength.value) { - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.too_big, - maximum: def.maxLength.value, - type: "array", - inclusive: true, - exact: false, - message: def.maxLength.message - }); - status.dirty(); - } + /** + * Generates a password using the default ruleset. + * + * @example + * + * ```javascript + * const password = Password.generateDefault() + * ``` + * + * @public + * @param {Partial} [options] + * @returns {string} + */ + static generateDefault(options = {}) { + return new Password(options).parse(Password.defaults.defaultPasswordRules).generate(); } - if (ctx.common.async) { - return Promise.all([...ctx.data].map((item, i) => { - return def.type._parseAsync(new ParseInputLazyPath(ctx, item, ctx.path, i)); - })).then(result => { - return parseUtil_1.ParseStatus.mergeArray(status, result); - }); + /** + * Convert a ruleset into it's internally-used component pieces. + * + * @param {string} inputString + * @throws {parser.ParserError|Error} + * @returns {{ + * requirements: Requirements; + * parameters: PasswordParameters; + * rules: parser.Rule[], + * get entropy(): number; + * generate: () => string; + * }} + */ + parse(inputString) { + const rules = parsePasswordRules(inputString); + const requirements = this._requirementsFromRules(rules); + if (!requirements) + throw new Error("could not generate requirements for " + JSON.stringify(inputString)); + const parameters = this._passwordGenerationParametersDictionary(requirements); + return { + requirements, + parameters, + rules, + get entropy() { + return Math.log2(parameters.PasswordAllowedCharacters.length ** parameters.NumberOfRequiredRandomCharacters); + }, + generate: () => { + const password = this._generatedPasswordMatchingRequirements(requirements, parameters); + if (password === "") + throw new Error("unreachable"); + return password; + } + }; } - const result = [...ctx.data].map((item, i) => { - return def.type._parseSync(new ParseInputLazyPath(ctx, item, ctx.path, i)); - }); - return parseUtil_1.ParseStatus.mergeArray(status, result); - } - get element() { - return this._def.type; - } - min(minLength, message) { - return new ZodArray({ - ...this._def, - minLength: { - value: minLength, - message: errorUtil_1.errorUtil.toString(message) + /** + * Given an array of `Rule's`, convert into `Requirements` + * + * @param {parser.Rule[]} passwordRules + * @returns {Requirements | null} + */ + _requirementsFromRules(passwordRules) { + const requirements = {}; + for (let rule of passwordRules) { + if (rule.name === RuleName.ALLOWED) { + console.assert(!("PasswordAllowedCharacters" in requirements)); + const chars = this._charactersFromCharactersClasses(rule.value); + const scanSet = this._canonicalizedScanSetFromCharacters(chars); + if (scanSet) { + requirements.PasswordAllowedCharacters = scanSet; + } + } else if (rule.name === RuleName.MAX_CONSECUTIVE) { + console.assert(!("PasswordRepeatedCharacterLimit" in requirements)); + requirements.PasswordRepeatedCharacterLimit = rule.value; + } else if (rule.name === RuleName.REQUIRED) { + let requiredCharacters = requirements.PasswordRequiredCharacters; + if (!requiredCharacters) { + requiredCharacters = requirements.PasswordRequiredCharacters = []; + } + requiredCharacters.push(this._canonicalizedScanSetFromCharacters(this._charactersFromCharactersClasses(rule.value))); + } else if (rule.name === RuleName.MIN_LENGTH) { + requirements.PasswordMinLength = rule.value; + } else if (rule.name === RuleName.MAX_LENGTH) { + requirements.PasswordMaxLength = rule.value; + } } - }); - } - max(maxLength, message) { - return new ZodArray({ - ...this._def, - maxLength: { - value: maxLength, - message: errorUtil_1.errorUtil.toString(message) + if (requirements.PasswordAllowedCharacters === this.options.SCAN_SET_ORDER && !requirements.PasswordRequiredCharacters) { + delete requirements.PasswordAllowedCharacters; } - }); - } - length(len, message) { - return new ZodArray({ - ...this._def, - exactLength: { - value: len, - message: errorUtil_1.errorUtil.toString(message) + if (requirements.PasswordRequiredCharacters && requirements.PasswordRequiredCharacters.length === 1 && requirements.PasswordRequiredCharacters[0] === this.options.SCAN_SET_ORDER) { + delete requirements.PasswordRequiredCharacters; } - }); - } - nonempty(message) { - return this.min(1, message); - } -} -exports.ZodArray = ZodArray; -ZodArray.create = (schema, params) => { - return new ZodArray({ - type: schema, - minLength: null, - maxLength: null, - exactLength: null, - typeName: ZodFirstPartyTypeKind.ZodArray, - ...processCreateParams(params) - }); -}; -function deepPartialify(schema) { - if (schema instanceof ZodObject) { - const newShape = {}; - for (const key in schema.shape) { - const fieldSchema = schema.shape[key]; - newShape[key] = ZodOptional.create(deepPartialify(fieldSchema)); + return Object.keys(requirements).length ? requirements : null; } - return new ZodObject({ - ...schema._def, - shape: () => newShape - }); - } else if (schema instanceof ZodArray) { - return new ZodArray({ - ...schema._def, - type: deepPartialify(schema.element) - }); - } else if (schema instanceof ZodOptional) { - return ZodOptional.create(deepPartialify(schema.unwrap())); - } else if (schema instanceof ZodNullable) { - return ZodNullable.create(deepPartialify(schema.unwrap())); - } else if (schema instanceof ZodTuple) { - return ZodTuple.create(schema.items.map(item => deepPartialify(item))); - } else { - return schema; - } -} -class ZodObject extends ZodType { - constructor() { - super(...arguments); - this._cached = null; /** - * @deprecated In most cases, this is no longer needed - unknown properties are now silently stripped. - * If you want to pass through unknown properties, use `.passthrough()` instead. + * @param {number} range + * @returns {number} */ - this.nonstrict = this.passthrough; - // extend< - // Augmentation extends ZodRawShape, - // NewOutput extends util.flatten<{ - // [k in keyof Augmentation | keyof Output]: k extends keyof Augmentation - // ? Augmentation[k]["_output"] - // : k extends keyof Output - // ? Output[k] - // : never; - // }>, - // NewInput extends util.flatten<{ - // [k in keyof Augmentation | keyof Input]: k extends keyof Augmentation - // ? Augmentation[k]["_input"] - // : k extends keyof Input - // ? Input[k] - // : never; - // }> - // >( - // augmentation: Augmentation - // ): ZodObject< - // extendShape, - // UnknownKeys, - // Catchall, - // NewOutput, - // NewInput - // > { - // return new ZodObject({ - // ...this._def, - // shape: () => ({ - // ...this._def.shape(), - // ...augmentation, - // }), - // }) as any; - // } + _randomNumberWithUniformDistribution(range) { + const getRandomValues = this.options.getRandomValues || safeGlobals.getRandomValues; + const max = Math.floor(2 ** 32 / range) * range; + let x; + do { + x = getRandomValues(new Uint32Array(1))[0]; + } while (x >= max); + return x % range; + } /** - * @deprecated Use `.extend` instead - * */ - this.augment = this.extend; - } - _getCached() { - if (this._cached !== null) return this._cached; - const shape = this._def.shape(); - const keys = util_1.util.objectKeys(shape); - return this._cached = { - shape, - keys - }; - } - _parse(input) { - const parsedType = this._getType(input); - if (parsedType !== util_1.ZodParsedType.object) { - const ctx = this._getOrReturnCtx(input); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_type, - expected: util_1.ZodParsedType.object, - received: ctx.parsedType - }); - return parseUtil_1.INVALID; + * @param {number} numberOfRequiredRandomCharacters + * @param {string} allowedCharacters + */ + _classicPassword(numberOfRequiredRandomCharacters, allowedCharacters) { + const length = allowedCharacters.length; + const randomCharArray = Array(numberOfRequiredRandomCharacters); + for (let i = 0; i < numberOfRequiredRandomCharacters; i++) { + const index = this._randomNumberWithUniformDistribution(length); + randomCharArray[i] = allowedCharacters[index]; + } + return randomCharArray.join(""); } - const { - status, - ctx - } = this._processInputParams(input); - const { - shape, - keys: shapeKeys - } = this._getCached(); - const extraKeys = []; - if (!(this._def.catchall instanceof ZodNever && this._def.unknownKeys === "strip")) { - for (const key in ctx.data) { - if (!shapeKeys.includes(key)) { - extraKeys.push(key); + /** + * @param {string} password + * @param {number} consecutiveCharLimit + * @returns {boolean} + */ + _passwordHasNotExceededConsecutiveCharLimit(password, consecutiveCharLimit) { + let longestConsecutiveCharLength = 1; + let firstConsecutiveCharIndex = 0; + let isSequenceAscending; + for (let i = 1; i < password.length; i++) { + const currCharCode = password.charCodeAt(i); + const prevCharCode = password.charCodeAt(i - 1); + if (isSequenceAscending) { + if (isSequenceAscending.valueOf() && currCharCode === prevCharCode + 1 || !isSequenceAscending.valueOf() && currCharCode === prevCharCode - 1) { + continue; + } + if (currCharCode === prevCharCode + 1) { + firstConsecutiveCharIndex = i - 1; + isSequenceAscending = Boolean(true); + continue; + } + if (currCharCode === prevCharCode - 1) { + firstConsecutiveCharIndex = i - 1; + isSequenceAscending = Boolean(false); + continue; + } + isSequenceAscending = null; + } else if (currCharCode === prevCharCode + 1) { + isSequenceAscending = Boolean(true); + continue; + } else if (currCharCode === prevCharCode - 1) { + isSequenceAscending = Boolean(false); + continue; } + const currConsecutiveCharLength = i - firstConsecutiveCharIndex; + if (currConsecutiveCharLength > longestConsecutiveCharLength) { + longestConsecutiveCharLength = currConsecutiveCharLength; + } + firstConsecutiveCharIndex = i; } + if (isSequenceAscending) { + const currConsecutiveCharLength = password.length - firstConsecutiveCharIndex; + if (currConsecutiveCharLength > longestConsecutiveCharLength) { + longestConsecutiveCharLength = currConsecutiveCharLength; + } + } + return longestConsecutiveCharLength <= consecutiveCharLimit; } - const pairs = []; - for (const key of shapeKeys) { - const keyValidator = shape[key]; - const value = ctx.data[key]; - pairs.push({ - key: { - status: "valid", - value: key - }, - value: keyValidator._parse(new ParseInputLazyPath(ctx, value, ctx.path, key)), - alwaysSet: key in ctx.data - }); - } - if (this._def.catchall instanceof ZodNever) { - const unknownKeys = this._def.unknownKeys; - if (unknownKeys === "passthrough") { - for (const key of extraKeys) { - pairs.push({ - key: { - status: "valid", - value: key - }, - value: { - status: "valid", - value: ctx.data[key] - } - }); + /** + * @param {string} password + * @param {number} repeatedCharLimit + * @returns {boolean} + */ + _passwordHasNotExceededRepeatedCharLimit(password, repeatedCharLimit) { + let longestRepeatedCharLength = 1; + let lastRepeatedChar = password.charAt(0); + let lastRepeatedCharIndex = 0; + for (let i = 1; i < password.length; i++) { + const currChar = password.charAt(i); + if (currChar === lastRepeatedChar) { + continue; } - } else if (unknownKeys === "strict") { - if (extraKeys.length > 0) { - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.unrecognized_keys, - keys: extraKeys - }); - status.dirty(); + const currRepeatedCharLength = i - lastRepeatedCharIndex; + if (currRepeatedCharLength > longestRepeatedCharLength) { + longestRepeatedCharLength = currRepeatedCharLength; } - } else if (unknownKeys === "strip") {} else { - throw new Error(`Internal ZodObject error: invalid unknownKeys value.`); + lastRepeatedChar = currChar; + lastRepeatedCharIndex = i; } - } else { - // run catchall validation - const catchall = this._def.catchall; - for (const key of extraKeys) { - const value = ctx.data[key]; - pairs.push({ - key: { - status: "valid", - value: key - }, - value: catchall._parse(new ParseInputLazyPath(ctx, value, ctx.path, key) //, ctx.child(key), value, getParsedType(value) - ), - - alwaysSet: key in ctx.data - }); + return longestRepeatedCharLength <= repeatedCharLimit; + } + /** + * @param {string} password + * @param {string[]} requiredCharacterSets + * @returns {boolean} + */ + _passwordContainsRequiredCharacters(password, requiredCharacterSets) { + const requiredCharacterSetsLength = requiredCharacterSets.length; + const passwordLength = password.length; + for (let i = 0; i < requiredCharacterSetsLength; i++) { + const requiredCharacterSet = requiredCharacterSets[i]; + let hasRequiredChar = false; + for (let j = 0; j < passwordLength; j++) { + const char = password.charAt(j); + if (requiredCharacterSet.indexOf(char) !== -1) { + hasRequiredChar = true; + break; + } + } + if (!hasRequiredChar) { + return false; + } } + return true; } - if (ctx.common.async) { - return Promise.resolve().then(async () => { - const syncPairs = []; - for (const pair of pairs) { - const key = await pair.key; - syncPairs.push({ - key, - value: await pair.value, - alwaysSet: pair.alwaysSet - }); + /** + * @param {string} string1 + * @param {string} string2 + * @returns {boolean} + */ + _stringsHaveAtLeastOneCommonCharacter(string1, string2) { + const string2Length = string2.length; + for (let i = 0; i < string2Length; i++) { + const char = string2.charAt(i); + if (string1.indexOf(char) !== -1) { + return true; } - return syncPairs; - }).then(syncPairs => { - return parseUtil_1.ParseStatus.mergeObjectSync(status, syncPairs); - }); - } else { - return parseUtil_1.ParseStatus.mergeObjectSync(status, pairs); + } + return false; } - } - get shape() { - return this._def.shape(); - } - strict(message) { - errorUtil_1.errorUtil.errToObj; - return new ZodObject({ - ...this._def, - unknownKeys: "strict", - ...(message !== undefined ? { - errorMap: (issue, ctx) => { - var _a, _b, _c, _d; - const defaultError = (_c = (_b = (_a = this._def).errorMap) === null || _b === void 0 ? void 0 : _b.call(_a, issue, ctx).message) !== null && _c !== void 0 ? _c : ctx.defaultError; - if (issue.code === "unrecognized_keys") return { - message: (_d = errorUtil_1.errorUtil.errToObj(message).message) !== null && _d !== void 0 ? _d : defaultError - }; - return { - message: defaultError - }; + /** + * @param {Requirements} requirements + * @returns {PasswordParameters} + */ + _passwordGenerationParametersDictionary(requirements) { + let minPasswordLength = requirements.PasswordMinLength; + const maxPasswordLength = requirements.PasswordMaxLength; + if (minPasswordLength > maxPasswordLength) { + minPasswordLength = 0; + } + const requiredCharacterArray = requirements.PasswordRequiredCharacters; + let allowedCharacters = requirements.PasswordAllowedCharacters; + let requiredCharacterSets = this.options.defaultRequiredCharacterSets; + if (requiredCharacterArray) { + const mutatedRequiredCharacterSets2 = []; + const requiredCharacterArrayLength = requiredCharacterArray.length; + for (let i = 0; i < requiredCharacterArrayLength; i++) { + const requiredCharacters = requiredCharacterArray[i]; + if (allowedCharacters && this._stringsHaveAtLeastOneCommonCharacter(requiredCharacters, allowedCharacters)) { + mutatedRequiredCharacterSets2.push(requiredCharacters); + } } - } : {}) - }); - } - strip() { - return new ZodObject({ - ...this._def, - unknownKeys: "strip" - }); - } - passthrough() { - return new ZodObject({ - ...this._def, - unknownKeys: "passthrough" - }); - } - // const AugmentFactory = - // (def: Def) => - // ( - // augmentation: Augmentation - // ): ZodObject< - // extendShape, Augmentation>, - // Def["unknownKeys"], - // Def["catchall"] - // > => { - // return new ZodObject({ - // ...def, - // shape: () => ({ - // ...def.shape(), - // ...augmentation, - // }), - // }) as any; - // }; - extend(augmentation) { - return new ZodObject({ - ...this._def, - shape: () => ({ - ...this._def.shape(), - ...augmentation - }) - }); - } - /** - * Prior to zod@1.0.12 there was a bug in the - * inferred type of merged objects. Please - * upgrade if you are experiencing issues. - */ - merge(merging) { - const merged = new ZodObject({ - unknownKeys: merging._def.unknownKeys, - catchall: merging._def.catchall, - shape: () => ({ - ...this._def.shape(), - ...merging._def.shape() - }), - typeName: ZodFirstPartyTypeKind.ZodObject - }); - return merged; - } - // merge< - // Incoming extends AnyZodObject, - // Augmentation extends Incoming["shape"], - // NewOutput extends { - // [k in keyof Augmentation | keyof Output]: k extends keyof Augmentation - // ? Augmentation[k]["_output"] - // : k extends keyof Output - // ? Output[k] - // : never; - // }, - // NewInput extends { - // [k in keyof Augmentation | keyof Input]: k extends keyof Augmentation - // ? Augmentation[k]["_input"] - // : k extends keyof Input - // ? Input[k] - // : never; - // } - // >( - // merging: Incoming - // ): ZodObject< - // extendShape>, - // Incoming["_def"]["unknownKeys"], - // Incoming["_def"]["catchall"], - // NewOutput, - // NewInput - // > { - // const merged: any = new ZodObject({ - // unknownKeys: merging._def.unknownKeys, - // catchall: merging._def.catchall, - // shape: () => - // objectUtil.mergeShapes(this._def.shape(), merging._def.shape()), - // typeName: ZodFirstPartyTypeKind.ZodObject, - // }) as any; - // return merged; - // } - setKey(key, schema) { - return this.augment({ - [key]: schema - }); - } - // merge( - // merging: Incoming - // ): //ZodObject = (merging) => { - // ZodObject< - // extendShape>, - // Incoming["_def"]["unknownKeys"], - // Incoming["_def"]["catchall"] - // > { - // // const mergedShape = objectUtil.mergeShapes( - // // this._def.shape(), - // // merging._def.shape() - // // ); - // const merged: any = new ZodObject({ - // unknownKeys: merging._def.unknownKeys, - // catchall: merging._def.catchall, - // shape: () => - // objectUtil.mergeShapes(this._def.shape(), merging._def.shape()), - // typeName: ZodFirstPartyTypeKind.ZodObject, - // }) as any; - // return merged; - // } - catchall(index) { - return new ZodObject({ - ...this._def, - catchall: index - }); - } - pick(mask) { - const shape = {}; - util_1.util.objectKeys(mask).forEach(key => { - if (mask[key] && this.shape[key]) { - shape[key] = this.shape[key]; + requiredCharacterSets = mutatedRequiredCharacterSets2; } - }); - return new ZodObject({ - ...this._def, - shape: () => shape - }); - } - omit(mask) { - const shape = {}; - util_1.util.objectKeys(this.shape).forEach(key => { - if (!mask[key]) { - shape[key] = this.shape[key]; + let numberOfRequiredRandomCharacters = this.options.defaultPasswordLength; + if (minPasswordLength && minPasswordLength > numberOfRequiredRandomCharacters) { + numberOfRequiredRandomCharacters = minPasswordLength; } - }); - return new ZodObject({ - ...this._def, - shape: () => shape - }); - } - /** - * @deprecated - */ - deepPartial() { - return deepPartialify(this); - } - partial(mask) { - const newShape = {}; - util_1.util.objectKeys(this.shape).forEach(key => { - const fieldSchema = this.shape[key]; - if (mask && !mask[key]) { - newShape[key] = fieldSchema; - } else { - newShape[key] = fieldSchema.optional(); + if (maxPasswordLength && maxPasswordLength < numberOfRequiredRandomCharacters) { + numberOfRequiredRandomCharacters = maxPasswordLength; } - }); - return new ZodObject({ - ...this._def, - shape: () => newShape - }); - } - required(mask) { - const newShape = {}; - util_1.util.objectKeys(this.shape).forEach(key => { - if (mask && !mask[key]) { - newShape[key] = this.shape[key]; - } else { - const fieldSchema = this.shape[key]; - let newField = fieldSchema; - while (newField instanceof ZodOptional) { - newField = newField._def.innerType; + if (!allowedCharacters) { + allowedCharacters = this.options.defaultUnambiguousCharacters; + } + if (!requiredCharacterSets) { + requiredCharacterSets = this.options.defaultRequiredCharacterSets; + } + if (requiredCharacterSets.length > numberOfRequiredRandomCharacters) { + requiredCharacterSets = []; + } + const requiredCharacterSetsLength = requiredCharacterSets.length; + const mutatedRequiredCharacterSets = []; + const allowedCharactersLength = allowedCharacters.length; + for (let i = 0; i < requiredCharacterSetsLength; i++) { + const requiredCharacterSet = requiredCharacterSets[i]; + let requiredCharacterSetContainsAllowedCharacters = false; + for (let j = 0; j < allowedCharactersLength; j++) { + const character = allowedCharacters.charAt(j); + if (requiredCharacterSet.indexOf(character) !== -1) { + requiredCharacterSetContainsAllowedCharacters = true; + break; + } + } + if (requiredCharacterSetContainsAllowedCharacters) { + mutatedRequiredCharacterSets.push(requiredCharacterSet); } - newShape[key] = newField; } - }); - return new ZodObject({ - ...this._def, - shape: () => newShape - }); - } - keyof() { - return createZodEnum(util_1.util.objectKeys(this.shape)); - } -} -exports.ZodObject = ZodObject; -ZodObject.create = (shape, params) => { - return new ZodObject({ - shape: () => shape, - unknownKeys: "strip", - catchall: ZodNever.create(), - typeName: ZodFirstPartyTypeKind.ZodObject, - ...processCreateParams(params) - }); -}; -ZodObject.strictCreate = (shape, params) => { - return new ZodObject({ - shape: () => shape, - unknownKeys: "strict", - catchall: ZodNever.create(), - typeName: ZodFirstPartyTypeKind.ZodObject, - ...processCreateParams(params) - }); -}; -ZodObject.lazycreate = (shape, params) => { - return new ZodObject({ - shape, - unknownKeys: "strip", - catchall: ZodNever.create(), - typeName: ZodFirstPartyTypeKind.ZodObject, - ...processCreateParams(params) - }); -}; -class ZodUnion extends ZodType { - _parse(input) { - const { - ctx - } = this._processInputParams(input); - const options = this._def.options; - function handleResults(results) { - // return first issue-free validation if it exists - for (const result of results) { - if (result.result.status === "valid") { - return result.result; - } - } - for (const result of results) { - if (result.result.status === "dirty") { - // add issues from dirty option - ctx.common.issues.push(...result.ctx.common.issues); - return result.result; - } - } - // return invalid - const unionErrors = results.map(result => new ZodError_1.ZodError(result.ctx.common.issues)); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_union, - unionErrors - }); - return parseUtil_1.INVALID; - } - if (ctx.common.async) { - return Promise.all(options.map(async option => { - const childCtx = { - ...ctx, - common: { - ...ctx.common, - issues: [] - }, - parent: null - }; - return { - result: await option._parseAsync({ - data: ctx.data, - path: ctx.path, - parent: childCtx - }), - ctx: childCtx - }; - })).then(handleResults); - } else { - let dirty = undefined; - const issues = []; - for (const option of options) { - const childCtx = { - ...ctx, - common: { - ...ctx.common, - issues: [] - }, - parent: null - }; - const result = option._parseSync({ - data: ctx.data, - path: ctx.path, - parent: childCtx - }); - if (result.status === "valid") { - return result; - } else if (result.status === "dirty" && !dirty) { - dirty = { - result, - ctx: childCtx - }; + requiredCharacterSets = mutatedRequiredCharacterSets; + return { + NumberOfRequiredRandomCharacters: numberOfRequiredRandomCharacters, + PasswordAllowedCharacters: allowedCharacters, + RequiredCharacterSets: requiredCharacterSets + }; + } + /** + * @param {Requirements | null} requirements + * @param {PasswordParameters} [parameters] + * @returns {string} + */ + _generatedPasswordMatchingRequirements(requirements, parameters) { + requirements = requirements || {}; + parameters = parameters || this._passwordGenerationParametersDictionary(requirements); + const numberOfRequiredRandomCharacters = parameters.NumberOfRequiredRandomCharacters; + const repeatedCharLimit = requirements.PasswordRepeatedCharacterLimit; + const allowedCharacters = parameters.PasswordAllowedCharacters; + const shouldCheckRepeatedCharRequirement = !!repeatedCharLimit; + while (true) { + const password = this._classicPassword(numberOfRequiredRandomCharacters, allowedCharacters); + if (!this._passwordContainsRequiredCharacters(password, parameters.RequiredCharacterSets)) { + continue; + } + if (shouldCheckRepeatedCharRequirement) { + if (repeatedCharLimit !== void 0 && repeatedCharLimit >= 1 && !this._passwordHasNotExceededRepeatedCharLimit(password, repeatedCharLimit)) { + continue; + } } - if (childCtx.common.issues.length) { - issues.push(childCtx.common.issues); + const consecutiveCharLimit = requirements.PasswordConsecutiveCharacterLimit; + if (consecutiveCharLimit && consecutiveCharLimit >= 1) { + if (!this._passwordHasNotExceededConsecutiveCharLimit(password, consecutiveCharLimit)) { + continue; + } } + return password || ""; } - if (dirty) { - ctx.common.issues.push(...dirty.ctx.common.issues); - return dirty.result; + } + /** + * @param {parser.CustomCharacterClass | parser.NamedCharacterClass} characterClass + * @returns {string[]} + */ + _scanSetFromCharacterClass(characterClass) { + if (characterClass instanceof CustomCharacterClass) { + return characterClass.characters; + } + console.assert(characterClass instanceof NamedCharacterClass); + switch (characterClass.name) { + case Identifier.ASCII_PRINTABLE: + case Identifier.UNICODE: + return this.options.SCAN_SET_ORDER.split(""); + case Identifier.DIGIT: + return this.options.SCAN_SET_ORDER.substring(this.options.SCAN_SET_ORDER.indexOf("0"), this.options.SCAN_SET_ORDER.indexOf("9") + 1).split(""); + case Identifier.LOWER: + return this.options.SCAN_SET_ORDER.substring(this.options.SCAN_SET_ORDER.indexOf("a"), this.options.SCAN_SET_ORDER.indexOf("z") + 1).split(""); + case Identifier.SPECIAL: + return this.options.SCAN_SET_ORDER.substring(this.options.SCAN_SET_ORDER.indexOf("-"), this.options.SCAN_SET_ORDER.indexOf("]") + 1).split(""); + case Identifier.UPPER: + return this.options.SCAN_SET_ORDER.substring(this.options.SCAN_SET_ORDER.indexOf("A"), this.options.SCAN_SET_ORDER.indexOf("Z") + 1).split(""); + } + console.assert(false, SHOULD_NOT_BE_REACHED); + return []; + } + /** + * @param {(parser.CustomCharacterClass | parser.NamedCharacterClass)[]} characterClasses + */ + _charactersFromCharactersClasses(characterClasses) { + const output = []; + for (let characterClass of characterClasses) { + output.push(...this._scanSetFromCharacterClass(characterClass)); } - const unionErrors = issues.map(issues => new ZodError_1.ZodError(issues)); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_union, - unionErrors - }); - return parseUtil_1.INVALID; + return output; } - } - get options() { - return this._def.options; - } -} -exports.ZodUnion = ZodUnion; -ZodUnion.create = (types, params) => { - return new ZodUnion({ - options: types, - typeName: ZodFirstPartyTypeKind.ZodUnion, - ...processCreateParams(params) - }); -}; -///////////////////////////////////////////////////// -///////////////////////////////////////////////////// -////////// ////////// -////////// ZodDiscriminatedUnion ////////// -////////// ////////// -///////////////////////////////////////////////////// -///////////////////////////////////////////////////// -const getDiscriminator = type => { - if (type instanceof ZodLazy) { - return getDiscriminator(type.schema); - } else if (type instanceof ZodEffects) { - return getDiscriminator(type.innerType()); - } else if (type instanceof ZodLiteral) { - return [type.value]; - } else if (type instanceof ZodEnum) { - return type.options; - } else if (type instanceof ZodNativeEnum) { - // eslint-disable-next-line ban/ban - return Object.keys(type.enum); - } else if (type instanceof ZodDefault) { - return getDiscriminator(type._def.innerType); - } else if (type instanceof ZodUndefined) { - return [undefined]; - } else if (type instanceof ZodNull) { - return [null]; - } else { - return null; - } -}; -class ZodDiscriminatedUnion extends ZodType { - _parse(input) { - const { - ctx - } = this._processInputParams(input); - if (ctx.parsedType !== util_1.ZodParsedType.object) { - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_type, - expected: util_1.ZodParsedType.object, - received: ctx.parsedType - }); - return parseUtil_1.INVALID; - } - const discriminator = this.discriminator; - const discriminatorValue = ctx.data[discriminator]; - const option = this.optionsMap.get(discriminatorValue); - if (!option) { - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_union_discriminator, - options: Array.from(this.optionsMap.keys()), - path: [discriminator] - }); - return parseUtil_1.INVALID; + /** + * @param {string[]} characters + * @returns {string} + */ + _canonicalizedScanSetFromCharacters(characters) { + if (!characters.length) { + return ""; + } + let shadowCharacters = Array.prototype.slice.call(characters); + shadowCharacters.sort((a, b) => this.options.SCAN_SET_ORDER.indexOf(a) - this.options.SCAN_SET_ORDER.indexOf(b)); + let uniqueCharacters = [shadowCharacters[0]]; + for (let i = 1, length = shadowCharacters.length; i < length; ++i) { + if (shadowCharacters[i] === shadowCharacters[i - 1]) { + continue; + } + uniqueCharacters.push(shadowCharacters[i]); + } + return uniqueCharacters.join(""); } - if (ctx.common.async) { - return option._parseAsync({ - data: ctx.data, - path: ctx.path, - parent: ctx - }); - } else { - return option._parseSync({ - data: ctx.data, - path: ctx.path, - parent: ctx - }); + }; + + // packages/password/index.js + function generate(options = {}) { + try { + if (typeof options?.input === "string") { + return Password.generateOrThrow(options.input); + } + if (typeof options?.domain === "string") { + if (options?.rules) { + const rules = _selectPasswordRules(options.domain, options.rules); + if (rules) { + return Password.generateOrThrow(rules); + } + } + } + } catch (e) { + if (options?.onError && typeof options?.onError === "function") { + options.onError(e); + } else { + const isKnownError = e instanceof ParserError || e instanceof HostnameInputError; + if (!isKnownError) { + console.error(e); + } + } } + return Password.generateDefault(); } - get discriminator() { - return this._def.discriminator; - } - get options() { - return this._def.options; - } - get optionsMap() { - return this._def.optionsMap; - } - /** - * The constructor of the discriminated union schema. Its behaviour is very similar to that of the normal z.union() constructor. - * However, it only allows a union of objects, all of which need to share a discriminator property. This property must - * have a different value for each object in the union. - * @param discriminator the name of the discriminator property - * @param types an array of object schemas - * @param params - */ - static create(discriminator, options, params) { - // Get all the valid discriminator values - const optionsMap = new Map(); - // try { - for (const type of options) { - const discriminatorValues = getDiscriminator(type.shape[discriminator]); - if (!discriminatorValues) { - throw new Error(`A discriminator value for key \`${discriminator}\` could not be extracted from all schema options`); - } - for (const value of discriminatorValues) { - if (optionsMap.has(value)) { - throw new Error(`Discriminator property ${String(discriminator)} has duplicate value ${String(value)}`); - } - optionsMap.set(value, type); - } - } - return new ZodDiscriminatedUnion({ - typeName: ZodFirstPartyTypeKind.ZodDiscriminatedUnion, - discriminator, - options, - optionsMap, - ...processCreateParams(params) - }); - } -} -exports.ZodDiscriminatedUnion = ZodDiscriminatedUnion; -function mergeValues(a, b) { - const aType = (0, util_1.getParsedType)(a); - const bType = (0, util_1.getParsedType)(b); - if (a === b) { - return { - valid: true, - data: a - }; - } else if (aType === util_1.ZodParsedType.object && bType === util_1.ZodParsedType.object) { - const bKeys = util_1.util.objectKeys(b); - const sharedKeys = util_1.util.objectKeys(a).filter(key => bKeys.indexOf(key) !== -1); - const newObj = { - ...a, - ...b - }; - for (const key of sharedKeys) { - const sharedValue = mergeValues(a[key], b[key]); - if (!sharedValue.valid) { - return { - valid: false - }; + var HostnameInputError = class extends Error { + }; + function _selectPasswordRules(inputHostname, rules) { + const hostname = _safeHostname(inputHostname); + if (rules[hostname]) { + return rules[hostname]["password-rules"]; + } + const pieces = hostname.split("."); + while (pieces.length > 1) { + pieces.shift(); + const joined = pieces.join("."); + if (rules[joined]) { + return rules[joined]["password-rules"]; } - newObj[key] = sharedValue.data; } - return { - valid: true, - data: newObj - }; - } else if (aType === util_1.ZodParsedType.array && bType === util_1.ZodParsedType.array) { - if (a.length !== b.length) { - return { - valid: false - }; + return void 0; + } + function _safeHostname(inputHostname) { + if (inputHostname.startsWith("http:") || inputHostname.startsWith("https:")) { + throw new HostnameInputError("invalid input, you can only provide a hostname but you gave a scheme"); } - const newArray = []; - for (let index = 0; index < a.length; index++) { - const itemA = a[index]; - const itemB = b[index]; - const sharedValue = mergeValues(itemA, itemB); - if (!sharedValue.valid) { + if (inputHostname.includes(":")) { + throw new HostnameInputError("invalid input, you can only provide a hostname but you gave a :port"); + } + try { + const asUrl = new URL("https://" + inputHostname); + return asUrl.hostname; + } catch (e) { + throw new HostnameInputError(`could not instantiate a URL from that hostname ${inputHostname}`); + } + } + + // packages/password/rules.json + var rules_default = { + "163.com": { + "password-rules": "minlength: 6; maxlength: 16;" + }, + "1800flowers.com": { + "password-rules": "minlength: 6; required: lower, upper; required: digit;" + }, + "access.service.gov.uk": { + "password-rules": "minlength: 10; required: lower; required: upper; required: digit; required: special;" + }, + "admiral.com": { + "password-rules": "minlength: 8; required: digit; required: [- !\"#$&'()*+,.:;<=>?@[^_`{|}~]]; allowed: lower, upper;" + }, + "ae.com": { + "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit;" + }, + "aetna.com": { + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 2; required: upper; required: digit; allowed: lower, [-_&#@];" + }, + "airasia.com": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;" + }, + "airfrance.com": { + "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit; allowed: [-!#$&+/?@_];" + }, + "airfrance.us": { + "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit; allowed: [-!#$&+/?@_];" + }, + "ajisushionline.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; allowed: [ !#$%&*?@];" + }, + "aliexpress.com": { + "password-rules": "minlength: 6; maxlength: 20; allowed: lower, upper, digit;" + }, + "alliantcreditunion.com": { + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower, upper; required: digit; allowed: [!#$*];" + }, + "allianz.com.br": { + "password-rules": "minlength: 4; maxlength: 4;" + }, + "americanexpress.com": { + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 4; required: lower, upper; required: digit; allowed: [%&_?#=];" + }, + "anatel.gov.br": { + "password-rules": "minlength: 6; maxlength: 15; allowed: lower, upper, digit;" + }, + "ancestry.com": { + "password-rules": "minlength: 8; required: lower, upper; required: digit;" + }, + "angieslist.com": { + "password-rules": "minlength: 6; maxlength: 15;" + }, + "anthem.com": { + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower, upper; required: digit; allowed: [!$*?@|];" + }, + "app.digio.in": { + "password-rules": "minlength: 8; maxlength: 15;" + }, + "app.parkmobile.io": { + "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit; required: [!@#$%^&];" + }, + "apple.com": { + "password-rules": "minlength: 8; maxlength: 63; required: lower; required: upper; required: digit; allowed: ascii-printable;" + }, + "areariservata.bancaetica.it": { + "password-rules": "minlength: 8; maxlength: 10; required: lower; required: upper; required: digit; required: [!#&*+/=@_];" + }, + "artscyclery.com": { + "password-rules": "minlength: 6; maxlength: 19;" + }, + "astonmartinf1.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: special;" + }, + "auth.readymag.com": { + "password-rules": "minlength: 8; maxlength: 128; required: lower; required: upper; allowed: special;" + }, + "autify.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!\"#$%&'()*+,./:;<=>?@[^_`{|}~]];" + }, + "axa.de": { + "password-rules": `minlength: 8; maxlength: 65; required: lower; required: upper; required: digit; allowed: [-!"\xA7$%&/()=?;:_+*'#];` + }, + "baidu.com": { + "password-rules": "minlength: 6; maxlength: 14;" + }, + "bancochile.cl": { + "password-rules": "minlength: 8; maxlength: 8; required: lower; required: upper; required: digit;" + }, + "bankofamerica.com": { + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower; required: upper; required: digit; allowed: [-@#*()+={}/?~;,._];" + }, + "battle.net": { + "password-rules": "minlength: 8; maxlength: 16; required: lower, upper; allowed: digit, special;" + }, + "bcassessment.ca": { + "password-rules": "minlength: 8; maxlength: 14;" + }, + "belkin.com": { + "password-rules": "minlength: 8; required: lower, upper; required: digit; required: [$!@~_,%&];" + }, + "benefitslogin.discoverybenefits.com": { + "password-rules": "minlength: 10; required: upper; required: digit; required: [!#$%&*?@]; allowed: lower;" + }, + "benjerry.com": { + "password-rules": "required: upper; required: upper; required: digit; required: digit; required: special; required: special; allowed: lower;" + }, + "bestbuy.com": { + "password-rules": "minlength: 20; required: lower; required: upper; required: digit; required: special;" + }, + "bhphotovideo.com": { + "password-rules": "maxlength: 15;" + }, + "bilibili.com": { + "password-rules": "maxlength: 16;" + }, + "billerweb.com": { + "password-rules": "minlength: 8; max-consecutive: 2; required: digit; required: upper,lower;" + }, + "biovea.com": { + "password-rules": "maxlength: 19;" + }, + "bitly.com": { + "password-rules": "minlength: 6; required: lower; required: upper; required: digit; required: [`!@#$%^&*()+~{}'\";:<>?]];" + }, + "bloomingdales.com": { + "password-rules": "minlength: 7; maxlength: 16; required: lower, upper; required: digit; required: [`!@#$%^&*()+~{}'\";:<>?]];" + }, + "bluesguitarunleashed.com": { + "password-rules": "allowed: lower, upper, digit, [!$#@];" + }, + "bochk.com": { + "password-rules": "minlength: 8; maxlength: 12; max-consecutive: 3; required: lower; required: upper; required: digit; allowed: [#$%&()*+,.:;<=>?@_];" + }, + "box.com": { + "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: digit;" + }, + "brighthorizons.com": { + "password-rules": "minlength: 8; maxlength: 16;" + }, + "callofduty.com": { + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 2; required: lower, upper; required: digit;" + }, + "capitalone.com": { + "password-rules": "minlength: 8; maxlength: 32; required: lower, upper; required: digit; allowed: [-_./\\@$*&!#];" + }, + "cardbenefitservices.com": { + "password-rules": "minlength: 7; maxlength: 100; required: lower, upper; required: digit;" + }, + "carrefour.it": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&*?@_];" + }, + "cb2.com": { + "password-rules": "minlength: 7; maxlength: 18; required: lower, upper; required: digit;" + }, + "ccs-grp.com": { + "password-rules": "minlength: 8; maxlength: 16; required: digit; required: upper,lower; allowed: [-!#$%&'+./=?\\^_`{|}~];" + }, + "cecredentialtrust.com": { + "password-rules": "minlength: 12; required: lower; required: upper; required: digit; required: [!#$%&*@^];" + }, + "chase.com": { + "password-rules": "minlength: 8; maxlength: 32; max-consecutive: 2; required: lower, upper; required: digit; required: [!#$%+/=@~];" + }, + "cigna.co.uk": { + "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit;" + }, + "citi.com": { + "password-rules": "minlength: 8; maxlength: 64; max-consecutive: 2; required: digit; required: upper; required: lower; required: [-~`!@#$%^&*()_\\/|];" + }, + "claimlookup.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [@#$%^&+=!];" + }, + "claro.com.br": { + "password-rules": "minlength: 8; required: lower; allowed: upper, digit, [-!@#$%&*_+=<>];" + }, + "classmates.com": { + "password-rules": "minlength: 6; maxlength: 20; allowed: lower, upper, digit, [!@#$%^&*];" + }, + "clegc-gckey.gc.ca": { + "password-rules": "minlength: 8; maxlength: 16; required: lower, upper, digit;" + }, + "clien.net": { + "password-rules": "minlength: 5; required: lower, upper; required: digit;" + }, + "cogmembers.org": { + "password-rules": "minlength: 8; maxlength: 14; required: upper; required: digit, allowed: lower;" + }, + "collectivehealth.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" + }, + "comcastpaymentcenter.com": { + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 2;required: lower, upper; required: digit;" + }, + "comed.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [-~!@#$%^&*_+=`|(){}[:;\"'<>,.?/\\]];" + }, + "commerzbank.de": { + "password-rules": "minlength: 5; maxlength: 8; required: lower, upper; required: digit;" + }, + "consorsbank.de": { + "password-rules": "minlength: 5; maxlength: 5; required: lower, upper, digit;" + }, + "consorsfinanz.de": { + "password-rules": "minlength: 6; maxlength: 15; allowed: lower, upper, digit, [-.];" + }, + "costco.com": { + "password-rules": "minlength: 8; maxlength: 20; required: lower, upper; allowed: digit, [-!#$%&'()*+/:;=?@[^_`{|}~]];" + }, + "coursera.com": { + "password-rules": "minlength: 8; maxlength: 72;" + }, + "cox.com": { + "password-rules": "minlength: 8; maxlength: 24; required: digit; required: upper,lower; allowed: [!#$%()*@^];" + }, + "crateandbarrel.com": { + "password-rules": 'minlength: 9; maxlength: 64; required: lower; required: upper; required: digit; required: [!"#$%&()*,.:<>?@^_{|}];' + }, + "cvs.com": { + "password-rules": "minlength: 8; maxlength: 25; required: lower, upper; required: digit; allowed: [!@#$%^&*()];" + }, + "dailymail.co.uk": { + "password-rules": "minlength: 5; maxlength: 15;" + }, + "dan.org": { + "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit; required: [!@$%^&*];" + }, + "danawa.com": { + "password-rules": "minlength: 8; maxlength: 21; required: lower, upper; required: digit; required: [!@$%^&*];" + }, + "darty.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" + }, + "dbs.com.hk": { + "password-rules": "minlength: 8; maxlength: 30; required: lower; required: upper; required: digit;" + }, + "decluttr.com": { + "password-rules": "minlength: 8; maxlength: 45; required: lower; required: upper; required: digit;" + }, + "delta.com": { + "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit;" + }, + "deutsche-bank.de": { + "password-rules": "minlength: 5; maxlength: 5; required: lower, upper, digit;" + }, + "devstore.cn": { + "password-rules": "minlength: 6; maxlength: 12;" + }, + "dickssportinggoods.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&*?@^];" + }, + "dkb.de": { + "password-rules": "minlength: 8; maxlength: 38; required: lower, upper; required: digit; allowed: [-\xE4\xFC\xF6\xC4\xDC\xD6\xDF!$%&/()=?+#,.:];" + }, + "dmm.com": { + "password-rules": "minlength: 4; maxlength: 16; required: lower; required: upper; required: digit;" + }, + "dowjones.com": { + "password-rules": "maxlength: 15;" + }, + "ea.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: special;" + }, + "easycoop.com": { + "password-rules": "minlength: 8; required: upper; required: special; allowed: lower, digit;" + }, + "easyjet.com": { + "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: [-];" + }, + "ebrap.org": { + "password-rules": "minlength: 15; required: lower; required: lower; required: upper; required: upper; required: digit; required: digit; required: [-!@#$%^&*()_+|~=`{}[:\";'?,./.]]; required: [-!@#$%^&*()_+|~=`{}[:\";'?,./.]];" + }, + "ecompanystore.com": { + "password-rules": "minlength: 8; maxlength: 16; max-consecutive: 2; required: lower; required: upper; required: digit; required: [#$%*+.=@^_];" + }, + "eddservices.edd.ca.gov": { + "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit; required: [!@#$%^&*()];" + }, + "edistrict.kerala.gov.in": { + "password-rules": "minlength: 5; maxlength: 15; required: lower; required: upper; required: digit; required: [!@#$];" + }, + "empower-retirement.com": { + "password-rules": "minlength: 8; maxlength: 16;" + }, + "epicgames.com": { + "password-rules": "minlength: 7; required: lower; required: upper; required: digit; required: [-!\"#$%&'()*+,./:;<=>?@[^_`{|}~]];" + }, + "epicmix.com": { + "password-rules": "minlength: 8; maxlength: 16;" + }, + "equifax.com": { + "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; required: [!$*+@];" + }, + "essportal.excelityglobal.com": { + "password-rules": "minlength: 6; maxlength: 8; allowed: lower, upper, digit;" + }, + "ettoday.net": { + "password-rules": "minlength: 6; maxlength: 12;" + }, + "examservice.com.tw": { + "password-rules": "minlength: 6; maxlength: 8;" + }, + "expertflyer.com": { + "password-rules": "minlength: 5; maxlength: 16; required: lower, upper; required: digit;" + }, + "extraspace.com": { + "password-rules": "minlength: 8; maxlength: 20; allowed: lower; required: upper, digit, [!#$%&*?@];" + }, + "ezpassva.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;" + }, + "fc2.com": { + "password-rules": "minlength: 8; maxlength: 16;" + }, + "fccaccessonline.com": { + "password-rules": "minlength: 8; maxlength: 14; max-consecutive: 3; required: lower; required: upper; required: digit; required: [!#$%*^_];" + }, + "fedex.com": { + "password-rules": "minlength: 8; max-consecutive: 3; required: lower; required: upper; required: digit; allowed: [-!@#$%^&*_+=`|(){}[:;,.?]];" + }, + "fidelity.com": { + "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: [-!$%+,./:;=?@^_|]; max-consecutive: 2;" + }, + "flysas.com": { + "password-rules": "minlength: 8; maxlength: 14; required: lower; required: upper; required: digit; required: [-~!@#$%^&_+=`|(){}[:\"'<>,.?]];" + }, + "fnac.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" + }, + "fuelrewards.com": { + "password-rules": "minlength: 8; maxlength: 16; allowed: upper,lower,digit,[!#$%@];" + }, + "gamestop.com": { + "password-rules": "minlength: 8; maxlength: 225; required: lower; required: upper; required: digit; required: [!@#$%];" + }, + "garmin.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" + }, + "getflywheel.com": { + "password-rules": "minlength: 7; maxlength: 72;" + }, + "girlscouts.org": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [$#!];" + }, + "gmx.net": { + "password-rules": "minlength: 8; maxlength: 40; allowed: lower, upper, digit, [-<=>~!|()@#{}$%,.?^'&*_+`:;\"[]];" + }, + "gocurb.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [$%&#*?!@^];" + }, + "google.com": { + "password-rules": `minlength: 8; allowed: lower, upper, digit, [-!"#$%&'()*+,./:;<=>?@[^_{|}~]];` + }, + "guardiananytime.com": { + "password-rules": "minlength: 8; maxlength: 50; max-consecutive: 2; required: lower; required: upper; required: digit, [-~!@#$%^&*_+=`|(){}[:;,.?]];" + }, + "gwl.greatwestlife.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [-!#$%_=+<>];" + }, + "hangseng.com": { + "password-rules": "minlength: 8; maxlength: 30; required: lower; required: upper; required: digit;" + }, + "hawaiianairlines.com": { + "password-rules": "maxlength: 16;" + }, + "hertz-japan.com": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz-kuwait.com": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz-saudi.com": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.at": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.be": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.bh": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.ca": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.ch": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.cn": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.co.ao": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.co.id": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.co.kr": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.co.nz": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.co.th": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.co.uk": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.au": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.bh": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.hk": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.kw": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.mt": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.pl": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.pt": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.sg": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.tw": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.cv": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.cz": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.de": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.ee": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.es": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.fi": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.fr": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.hu": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.ie": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.it": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.jo": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.lt": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.nl": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.no": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.nu": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.pl": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.pt": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.qa": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.ru": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.se": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.si": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertzcaribbean.com": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hetzner.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit, special;" + }, + "hilton.com": { + "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit;" + }, + "hkbea.com": { + "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit;" + }, + "hkexpress.com": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: special;" + }, + "hotels.com": { + "password-rules": "minlength: 6; maxlength: 20; required: digit; required: [-~#@$%&!*_?^]; allowed: lower, upper;" + }, + "hotwire.com": { + "password-rules": "minlength: 6; maxlength: 30; allowed: lower, upper, digit, [-~!@#$%^&*_+=`|(){}[:;\"'<>,.?]];" + }, + "hrblock.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [$#%!];" + }, + "hsbc.com.hk": { + "password-rules": "minlength: 6; maxlength: 30; required: lower; required: upper; required: digit; allowed: ['.@_];" + }, + "hsbc.com.my": { + "password-rules": "minlength: 8; maxlength: 30; required: lower, upper; required: digit; allowed: [-!$*.=?@_'];" + }, + "hypovereinsbank.de": { + "password-rules": 'minlength: 6; maxlength: 10; required: lower, upper, digit; allowed: [!"#$%&()*+:;<=>?@[{}~]];' + }, + "hyresbostader.se": { + "password-rules": "minlength: 6; maxlength: 20; required: lower, upper; required: digit;" + }, + "ichunqiu.com": { + "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit;" + }, + "id.sonyentertainmentnetwork.com": { + "password-rules": "minlength: 8; maxlength: 30; required: lower, upper; required: digit; allowed: [-!@#^&*=+;:];" + }, + "identitytheft.gov": { + "password-rules": "allowed: lower, upper, digit, [!#%&*@^];" + }, + "idestination.info": { + "password-rules": "maxlength: 15;" + }, + "impots.gouv.fr": { + "password-rules": "minlength: 12; maxlength: 128; required: lower; required: digit; allowed: [-!#$%&*+/=?^_'.{|}];" + }, + "indochino.com": { + "password-rules": "minlength: 6; maxlength: 15; required: upper; required: digit; allowed: lower, special;" + }, + "inntopia.travel": { + "password-rules": "minlength: 7; maxlength: 19; required: digit; allowed: upper,lower,[-];" + }, + "internationalsos.com": { + "password-rules": "required: lower; required: upper; required: digit; required: [@#$%^&+=_];" + }, + "irctc.co.in": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [!@#$%^&*()+];" + }, + "irs.gov": { + "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit; required: [!#$%&*@];" + }, + "jal.co.jp": { + "password-rules": "minlength: 8; maxlength: 16;" + }, + "japanpost.jp": { + "password-rules": "minlength: 8; maxlength: 16; required: digit; required: upper,lower;" + }, + "jordancu-onlinebanking.org": { + "password-rules": "minlength: 6; maxlength: 32; allowed: upper, lower, digit,[-!\"#$%&'()*+,.:;<=>?@[^_`{|}~]];" + }, + "keldoc.com": { + "password-rules": "minlength: 12; required: lower; required: upper; required: digit; required: [!@#$%^&*];" + }, + "key.harvard.edu": { + "password-rules": "minlength: 10; maxlength: 100; required: lower; required: upper; required: digit; allowed: [-@_#!&$`%*+()./,;~:{}|?>=<^[']];" + }, + "kfc.ca": { + "password-rules": "minlength: 6; maxlength: 15; required: lower; required: upper; required: digit; required: [!@#$%&?*];" + }, + "klm.com": { + "password-rules": "minlength: 8; maxlength: 12;" + }, + "kundenportal.edeka-smart.de": { + "password-rules": 'minlength: 8; maxlength: 16; required: digit; required: upper, lower; required: [!"\xA7$%&#];' + }, + "la-z-boy.com": { + "password-rules": "minlength: 6; maxlength: 15; required: lower, upper; required: digit;" + }, + "labcorp.com": { + "password-rules": "minlength: 8; maxlength: 20; required: upper; required: lower; required: digit; required: [!@#$%^&*];" + }, + "ladwp.com": { + "password-rules": "minlength: 8; maxlength: 20; required: digit; allowed: lower, upper;" + }, + "launtel.net.au": { + "password-rules": "minlength: 8; required: digit; required: digit; allowed: lower, upper;" + }, + "leetchi.com": { + "password-rules": 'minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&()*+,./:;<>?@"_];' + }, + "lepida.it": { + "password-rules": "minlength: 8; maxlength: 16; max-consecutive: 2; required: lower; required: upper; required: digit; required: [-!\"#$%&'()*+,.:;<=>?@[^_`{|}~]];" + }, + "lg.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [-!#$%&'()*+,.:;=?@[^_{|}~]];" + }, + "linearity.io": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: special;" + }, + "live.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; allowed: [-@_#!&$`%*+()./,;~:{}|?>=<^'[]];" + }, + "lloydsbank.co.uk": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: digit; allowed: upper;" + }, + "lowes.com": { + "password-rules": "minlength: 8; maxlength: 128; max-consecutive: 3; required: lower, upper; required: digit;" + }, + "loyalty.accor.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&=@];" + }, + "lsacsso.b2clogin.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit, [-!#$%&*?@^_];" + }, + "lufthansa.com": { + "password-rules": 'minlength: 8; maxlength: 32; required: lower; required: upper; required: digit; required: [!#$%&()*+,./:;<>?@"_];' + }, + "macys.com": { + "password-rules": "minlength: 7; maxlength: 16; allowed: lower, upper, digit, [~!@#$%^&*+`(){}[:;\"'<>?]];" + }, + "mailbox.org": { + "password-rules": 'minlength: 8; required: lower; required: upper; required: digit; allowed: [-!$"%&/()=*+#.,;:@?{}[]];' + }, + "makemytrip.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [@$!%*#?&];" + }, + "marriott.com": { + "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; allowed: [$!#&@?%=];" + }, + "maybank2u.com.my": { + "password-rules": "minlength: 8; maxlength: 12; max-consecutive: 2; required: lower; required: upper; required: digit; required: [-~!@#$%^&*_+=`|(){}[:;\"'<>,.?];" + }, + "medicare.gov": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [@!$%^*()];" + }, + "member.everbridge.net": { + "password-rules": "minlength: 8; required: lower, upper; required: digit; allowed: [!@#$%^&*()];" + }, + "metlife.com": { + "password-rules": "minlength: 6; maxlength: 20;" + }, + "microsoft.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: special;" + }, + "milogin.michigan.gov": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [@#$!~&];" + }, + "mintmobile.com": { + "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; required: special; allowed: [!#$%&()*+:;=@[^_`{}~]];" + }, + "mlb.com": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;" + }, + "mpv.tickets.com": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;" + }, + "museumofflight.org": { + "password-rules": "minlength: 8; maxlength: 15;" + }, + "my.konami.net": { + "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit;" + }, + "myaccess.dmdc.osd.mil": { + "password-rules": "minlength: 9; maxlength: 20; required: lower; required: upper; required: digit; allowed: [-@_#!&$`%*+()./,;~:{}|?>=<^'[]];" + }, + "mygoodtogo.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower, upper, digit;" + }, + "myhealthrecord.com": { + "password-rules": "minlength: 8; maxlength: 20; allowed: lower, upper, digit, [_.!$*=];" + }, + "mysavings.breadfinancial.com": { + "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit; required: [+_%@!$*~];" + }, + "mysedgwick.com": { + "password-rules": "minlength: 8; maxlength: 16; allowed: lower; required: upper; required: digit; required: [@#%^&+=!]; allowed: [-~_$.,;]" + }, + "mysubaru.com": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; allowed: [!#$%()*+,./:;=?@\\^`~];" + }, + "naver.com": { + "password-rules": "minlength: 6; maxlength: 16;" + }, + "nelnet.net": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit, [!@#$&*];" + }, + "netflix.com": { + "password-rules": "minlength: 4; maxlength: 60; required: lower, upper, digit; allowed: special;" + }, + "netgear.com": { + "password-rules": "minlength: 6; maxlength: 128; allowed: lower, upper, digit, [!@#$%^&*()];" + }, + "nowinstock.net": { + "password-rules": "minlength: 6; maxlength: 20; allowed: lower, upper, digit;" + }, + "order.wendys.com": { + "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; allowed: [!#$%&()*+/=?^_{}];" + }, + "ototoy.jp": { + "password-rules": "minlength: 8; allowed: upper,lower,digit,[- .=_];" + }, + "packageconciergeadmin.com": { + "password-rules": "minlength: 4; maxlength: 4; allowed: digit;" + }, + "paypal.com": { + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower, upper; required: digit, [!@#$%^&*()];" + }, + "payvgm.youraccountadvantage.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: special;" + }, + "pilotflyingj.com": { + "password-rules": "minlength: 7; required: digit; allowed: lower, upper;" + }, + "pixnet.cc": { + "password-rules": "minlength: 4; maxlength: 16; allowed: lower, upper;" + }, + "planetary.org": { + "password-rules": "minlength: 5; maxlength: 20; required: lower; required: upper; required: digit; allowed: ascii-printable;" + }, + "plazapremiumlounge.com": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; allowed: [!#$%&*,@^];" + }, + "portal.edd.ca.gov": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&()*@^];" + }, + "portals.emblemhealth.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&'()*+,./:;<>?@\\^_`{|}~[]];" + }, + "portlandgeneral.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [!#$%&*?@];" + }, + "poste.it": { + "password-rules": "minlength: 8; maxlength: 16; max-consecutive: 2; required: lower; required: upper; required: digit; required: special;" + }, + "posteo.de": { + "password-rules": 'minlength: 8; required: lower; required: upper; required: digit, [-~!#$%&_+=|(){}[:;"\u2019<>,.? ]];' + }, + "powells.com": { + "password-rules": 'minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: ["!@#$%^&*(){}[]];' + }, + "preferredhotels.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&()*+@^_];" + }, + "premier.ticketek.com.au": { + "password-rules": "minlength: 6; maxlength: 16;" + }, + "premierinn.com": { + "password-rules": "minlength: 8; required: upper; required: digit; allowed: lower;" + }, + "prepaid.bankofamerica.com": { + "password-rules": `minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [!@#$%^&*()+~{}'";:<>?];` + }, + "prestocard.ca": { + "password-rules": `minlength: 8; required: lower; required: upper; required: digit,[!"#$%&'()*+,<>?@];` + }, + "propelfuels.com": { + "password-rules": "minlength: 6; maxlength: 16;" + }, + "qdosstatusreview.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&@^];" + }, + "questdiagnostics.com": { + "password-rules": "minlength: 8; maxlength: 30; required: upper, lower; required: digit, [!#$%&()*+<>?@^_~];" + }, + "rejsekort.dk": { + "password-rules": "minlength: 7; maxlength: 15; required: lower; required: upper; required: digit;" + }, + "renaud-bray.com": { + "password-rules": "minlength: 8; maxlength: 38; allowed: upper,lower,digit;" + }, + "ring.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!@#$%^&*<>?];" + }, + "riteaid.com": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;" + }, + "robinhood.com": { + "password-rules": "minlength: 10;" + }, + "rogers.com": { + "password-rules": "minlength: 8; required: lower, upper; required: digit; required: [!@#$];" + }, + "ruc.dk": { + "password-rules": "minlength: 6; maxlength: 8; required: lower, upper; required: [-!#%&(){}*+;%/<=>?_];" + }, + "runescape.com": { + "password-rules": "minlength: 5; maxlength: 20; required: lower; required: upper; required: digit;" + }, + "ruten.com.tw": { + "password-rules": "minlength: 6; maxlength: 15; required: lower, upper;" + }, + "salslimo.com": { + "password-rules": "minlength: 8; maxlength: 50; required: upper; required: lower; required: digit; required: [!@#$&*];" + }, + "santahelenasaude.com.br": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [-!@#$%&*_+=<>];" + }, + "santander.de": { + "password-rules": "minlength: 8; maxlength: 12; required: lower, upper; required: digit; allowed: [-!#$%&'()*,.:;=?^{}];" + }, + "sbisec.co.jp": { + "password-rules": "minlength: 10; maxlength: 20; allowed: upper,lower,digit;" + }, + "secure-arborfcu.org": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [!#$%&'()+,.:?@[_`~]];" + }, + "secure.orclinic.com": { + "password-rules": "minlength: 6; maxlength: 15; required: lower; required: digit; allowed: ascii-printable;" + }, + "secure.snnow.ca": { + "password-rules": "minlength: 7; maxlength: 16; required: digit; allowed: lower, upper;" + }, + "sephora.com": { + "password-rules": "minlength: 6; maxlength: 12;" + }, + "serviziconsolari.esteri.it": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;" + }, + "servizioelettriconazionale.it": { + "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; required: [!#$%&*?@^_~];" + }, + "sfwater.org": { + "password-rules": "minlength: 10; maxlength: 30; required: digit; allowed: lower, upper, [!@#$%*()_+^}{:;?.];" + }, + "signin.ea.com": { + "password-rules": "minlength: 8; maxlength: 64; required: lower, upper; required: digit; allowed: [-!@#^&*=+;:];" + }, + "southwest.com": { + "password-rules": "minlength: 8; maxlength: 16; required: upper; required: digit; allowed: lower, [!@#$%^*(),.;:/\\];" + }, + "speedway.com": { + "password-rules": "minlength: 4; maxlength: 8; required: digit;" + }, + "spirit.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [!@#$%^&*()];" + }, + "splunk.com": { + "password-rules": "minlength: 8; maxlength: 64; required: lower; required: upper; required: digit; required: [-!@#$%&*_+=<>];" + }, + "ssa.gov": { + "password-rules": "required: lower; required: upper; required: digit; required: [~!@#$%^&*];" + }, + "store.nintendo.co.uk": { + "password-rules": "minlength: 8; maxlength: 20;" + }, + "store.nvidia.com": { + "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit; required: [-!@#$%^*~:;&><[{}|_+=?]];" + }, + "store.steampowered.com": { + "password-rules": "minlength: 6; required: lower; required: upper; required: digit; allowed: [~!@#$%^&*];" + }, + "successfactors.eu": { + "password-rules": "minlength: 8; maxlength: 18; required: lower; required: upper; required: digit,[-!\"#$%&'()*+,.:;<=>?@[^_`{|}~]];" + }, + "sulamericaseguros.com.br": { + "password-rules": "minlength: 6; maxlength: 6;" + }, + "sunlife.com": { + "password-rules": "minlength: 8; maxlength: 10; required: digit; required: lower, upper;" + }, + "t-mobile.net": { + "password-rules": "minlength: 8; maxlength: 16;" + }, + "target.com": { + "password-rules": "minlength: 8; maxlength: 20; required: lower, upper; required: digit, [-!\"#$%&'()*+,./:;=?@[\\^_`{|}~];" + }, + "tdscpc.gov.in": { + "password-rules": `minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [ &',;"];` + }, + "telekom-dienste.de": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [#$%&()*+,./<=>?@_{|}~];" + }, + "thameswater.co.uk": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;" + }, + "tix.soundrink.com": { + "password-rules": "minlength: 6; maxlength: 16;" + }, + "training.confluent.io": { + "password-rules": "minlength: 6; maxlength: 16; required: lower; required: upper; required: digit; allowed: [!#$%*@^_~];" + }, + "treasurer.mo.gov": { + "password-rules": "minlength: 8; maxlength: 26; required: lower; required: upper; required: digit; required: [!#$&];" + }, + "twitch.tv": { + "password-rules": "minlength: 8; maxlength: 71;" + }, + "twitter.com": { + "password-rules": "minlength: 8;" + }, + "ubisoft.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [-]; required: [!@#$%^&*()+];" + }, + "udel.edu": { + "password-rules": "minlength: 12; maxlength: 30; required: lower; required: upper; required: digit; required: [!@#$%^&*()+];" + }, + "user.ornl.gov": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower, upper; required: digit; allowed: [!#$%./_];" + }, + "usps.com": { + "password-rules": `minlength: 8; maxlength: 50; max-consecutive: 2; required: lower; required: upper; required: digit; allowed: [-!"#&'()+,./?@];` + }, + "vanguard.com": { + "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: digit;" + }, + "vanguardinvestor.co.uk": { + "password-rules": "minlength: 8; maxlength: 50; required: lower; required: upper; required: digit; required: digit;" + }, + "ventrachicago.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit, [!@#$%^];" + }, + "verizonwireless.com": { + "password-rules": "minlength: 8; maxlength: 20; required: lower, upper; required: digit; allowed: unicode;" + }, + "vetsfirstchoice.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; allowed: [?!@$%^+=&];" + }, + "vince.com": { + "password-rules": "minlength: 8; required: digit; required: lower; required: upper; required: [$%/(){}=?!.,_*|+~#[]];" + }, + "virginmobile.ca": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$@];" + }, + "visa.com": { + "password-rules": "minlength: 6; maxlength: 32;" + }, + "visabenefits-auth.axa-assistance.us": { + "password-rules": 'minlength: 8; required: lower; required: upper; required: digit; required: [!"#$%&()*,.:<>?@^{|}];' + }, + "vivo.com.br": { + "password-rules": "maxlength: 6; max-consecutive: 3; allowed: digit;" + }, + "volaris.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;" + }, + "wa.aaa.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: ascii-printable;" + }, + "walkhighlands.co.uk": { + "password-rules": "minlength: 9; maxlength: 15; required: lower; required: upper; required: digit; allowed: special;" + }, + "walmart.com": { + "password-rules": "allowed: lower, upper, digit, [-(~!@#$%^&*_+=`|(){}[:;\"'<>,.?]];" + }, + "waze.com": { + "password-rules": "minlength: 8; maxlength: 64; required: lower, upper, digit;" + }, + "wccls.org": { + "password-rules": "minlength: 4; maxlength: 16; allowed: lower, upper, digit;" + }, + "web.de": { + "password-rules": "minlength: 8; maxlength: 40; allowed: lower, upper, digit, [-<=>~!|()@#{}$%,.?^'&*_+`:;\"[]];" + }, + "wegmans.com": { + "password-rules": "minlength: 8; required: digit; required: upper,lower; required: [!#$%&*+=?@^];" + }, + "weibo.com": { + "password-rules": "minlength: 6; maxlength: 16;" + }, + "wellsfargo.com": { + "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit;" + }, + "wmata.com": { + "password-rules": 'minlength: 8; required: lower, upper; required: digit; required: digit; required: [-!@#$%^&*~/"()_=+\\|,.?[]];' + }, + "worldstrides.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [-!#$%&*+=?@^_~];" + }, + "wsj.com": { + "password-rules": "minlength: 5; maxlength: 15; required: digit; allowed: lower, upper, [-~!@#$^*_=`|(){}[:;\"'<>,.?]];" + }, + "xfinity.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower, upper; required: digit;" + }, + "xvoucher.com": { + "password-rules": "minlength: 11; required: upper; required: digit; required: [!@#$%&_];" + }, + "yatra.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&'()+,.:?@[_`~]];" + }, + "zara.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" + }, + "zdf.de": { + "password-rules": "minlength: 8; required: upper; required: digit; allowed: lower, special;" + }, + "zoom.us": { + "password-rules": "minlength: 8; maxlength: 32; max-consecutive: 6; required: lower; required: upper; required: digit;" + } + }; + + // src/PasswordGenerator.js + var _previous; + var PasswordGenerator = class { + constructor() { + /** @type {string|null} */ + __privateAdd(this, _previous, null); + } + /** @returns {boolean} */ + get generated() { + return __privateGet(this, _previous) !== null; + } + /** @returns {string|null} */ + get password() { + return __privateGet(this, _previous); + } + /** @param {import('../packages/password').GenerateOptions} [params] */ + generate(params = {}) { + if (__privateGet(this, _previous)) { + return __privateGet(this, _previous); + } + __privateSet(this, _previous, generate({ ...params, rules: rules_default })); + return __privateGet(this, _previous); + } + }; + _previous = new WeakMap(); + + // src/Form/FormAnalyzer.js + var FormAnalyzer = class { + /** + * @param {HTMLElement} form + * @param {HTMLInputElement|HTMLSelectElement} input + * @param {Matching} [matching] + */ + constructor(form, input, matching) { + /** @type HTMLElement */ + __publicField(this, "form"); + /** @type Matching */ + __publicField(this, "matching"); + /** @type {undefined|boolean} */ + __publicField(this, "_isCCForm"); + this.form = form; + this.matching = matching || new Matching(matchingConfiguration); + this.autofillSignal = 0; + this.hybridSignal = 0; + this.signals = []; + this.evaluateElAttributes(input, 1, true); + form ? this.evaluateForm() : this.evaluatePage(); + return this; + } + /** + * Hybrid forms can be used for both login and signup + * @returns {boolean} + */ + get isHybrid() { + const areOtherSignalsWeak = Math.abs(this.autofillSignal) < 10; + return this.hybridSignal > 0 && areOtherSignalsWeak; + } + get isLogin() { + if (this.isHybrid) + return false; + return this.autofillSignal < 0; + } + get isSignup() { + if (this.isHybrid) + return false; + return this.autofillSignal >= 0; + } + /** + * Tilts the scoring towards Signup + * @param {number} strength + * @param {string} signal + * @returns {FormAnalyzer} + */ + increaseSignalBy(strength, signal) { + this.autofillSignal += strength; + this.signals.push(`${signal}: +${strength}`); + return this; + } + /** + * Tilts the scoring towards Login + * @param {number} strength + * @param {string} signal + * @returns {FormAnalyzer} + */ + decreaseSignalBy(strength, signal) { + this.autofillSignal -= strength; + this.signals.push(`${signal}: -${strength}`); + return this; + } + /** + * Increases the probability that this is a hybrid form (can be either login or signup) + * @param {number} strength + * @param {string} signal + * @returns {FormAnalyzer} + */ + increaseHybridSignal(strength, signal) { + this.hybridSignal += strength; + this.signals.push(`${signal} (hybrid): +${strength}`); + return this; + } + /** + * Updates the Login<->Signup signal according to the provided parameters + * @param {object} p + * @param {string} p.string - The string to check + * @param {number} p.strength - Strength of the signal + * @param {string} [p.signalType] - For debugging purposes, we give a name to the signal + * @param {boolean} [p.shouldFlip] - Flips the signals, i.e. when a link points outside. See below + * @param {boolean} [p.shouldCheckUnifiedForm] - Should check for login/signup forms + * @param {boolean} [p.shouldBeConservative] - Should use the conservative signup regex + * @returns {FormAnalyzer} + */ + updateSignal({ + string, + strength, + signalType = "generic", + shouldFlip = false, + shouldCheckUnifiedForm = false, + shouldBeConservative = false + }) { + if (!string || string.length > constants.TEXT_LENGTH_CUTOFF) + return this; + const matchesLogin = safeRegexTest(/current.?password/i, string) || safeRegexTest(this.matching.getDDGMatcherRegex("loginRegex"), string) || safeRegexTest(this.matching.getDDGMatcherRegex("resetPasswordLink"), string); + if (shouldCheckUnifiedForm && matchesLogin && safeRegexTest(this.matching.getDDGMatcherRegex("conservativeSignupRegex"), string)) { + this.increaseHybridSignal(strength, signalType); + return this; + } + const signupRegexToUse = this.matching.getDDGMatcherRegex(shouldBeConservative ? "conservativeSignupRegex" : "signupRegex"); + const matchesSignup = safeRegexTest(/new.?password/i, string) || safeRegexTest(signupRegexToUse, string); + if (shouldFlip) { + if (matchesLogin) + this.increaseSignalBy(strength, signalType); + if (matchesSignup) + this.decreaseSignalBy(strength, signalType); + } else { + if (matchesLogin) + this.decreaseSignalBy(strength, signalType); + if (matchesSignup) + this.increaseSignalBy(strength, signalType); + } + return this; + } + evaluateElAttributes(el, signalStrength = 3, isInput = false) { + Array.from(el.attributes).forEach((attr) => { + if (attr.name === "style") + return; + const attributeString = `${attr.name}=${attr.value}`; + this.updateSignal({ + string: attributeString, + strength: signalStrength, + signalType: `${el.name} attr: ${attributeString}`, + shouldCheckUnifiedForm: isInput + }); + }); + } + evaluateUrl() { + const path = window.location.pathname; + const matchesLogin = safeRegexTest(this.matching.getDDGMatcherRegex("loginRegex"), path); + const matchesSignup = safeRegexTest(this.matching.getDDGMatcherRegex("conservativeSignupRegex"), path); + if (matchesLogin && matchesSignup) + return; + if (matchesLogin) { + this.decreaseSignalBy(1, "url matches login"); + } + if (matchesSignup) { + this.increaseSignalBy(1, "url matches signup"); + } + } + evaluatePageTitle() { + const pageTitle = document.title; + this.updateSignal({ string: pageTitle, strength: 2, signalType: `page title: ${pageTitle}`, shouldCheckUnifiedForm: true }); + } + evaluatePageHeadings() { + const headings = document.querySelectorAll("h1, h2, h3"); + headings.forEach((heading) => { + const textContent = removeExcessWhitespace(heading.textContent || ""); + this.updateSignal({ + string: textContent, + strength: 0.5, + signalType: `heading: ${textContent}`, + shouldCheckUnifiedForm: true, + shouldBeConservative: true + }); + }); + } + evaluatePage() { + this.evaluatePageTitle(); + this.evaluatePageHeadings(); + const buttons = document.querySelectorAll(this.matching.cssSelector("submitButtonSelector")); + buttons.forEach((button) => { + if (button instanceof HTMLButtonElement) { + if (!button.form && !button.closest("form")) { + this.evaluateElement(button); + this.evaluateElAttributes(button, 0.5); + } + } + }); + } + evaluateElement(el) { + const string = getTextShallow(el); + if (el.matches(this.matching.cssSelector("password"))) { + this.updateSignal({ + string: el.getAttribute("autocomplete") || el.getAttribute("name") || "", + strength: 5, + signalType: `explicit: ${el.getAttribute("autocomplete")}` + }); + return; + } + if (el.matches(this.matching.cssSelector("submitButtonSelector") + ", *[class*=button]")) { + let likelyASubmit = isLikelyASubmitButton(el, this.matching); + let shouldFlip = false; + if (likelyASubmit) { + this.form.querySelectorAll("input[type=submit], button[type=submit]").forEach( + (submit) => { + if (el.type !== "submit" && el !== submit) { + likelyASubmit = false; + } + } + ); + } else { + const thereIsASubmitButton = Boolean(this.form.querySelector("input[type=submit], button[type=submit]")); + const isSocialButton = /facebook|twitter|google|apple/i.test(string); + shouldFlip = thereIsASubmitButton && !isSocialButton; + } + const strength = likelyASubmit ? 20 : 4; + this.updateSignal({ string, strength, signalType: `button: ${string}`, shouldFlip }); + return; + } + if (el instanceof HTMLAnchorElement && el.href && el.getAttribute("href") !== "#" || (el.getAttribute("role") || "").toUpperCase() === "LINK" || el.matches("button[class*=secondary]")) { + let shouldFlip = true; + let strength = 1; + if (safeRegexTest(this.matching.getDDGMatcherRegex("resetPasswordLink"), string)) { + shouldFlip = false; + strength = 3; + } else if (safeRegexTest(this.matching.getDDGMatcherRegex("loginProvidersRegex"), string)) { + shouldFlip = false; + } + this.updateSignal({ string, strength, signalType: `external link: ${string}`, shouldFlip }); + } else { + this.updateSignal({ string, strength: 1, signalType: `generic: ${string}`, shouldCheckUnifiedForm: true }); + } + } + evaluateForm() { + this.evaluateUrl(); + this.evaluatePageTitle(); + this.evaluateElAttributes(this.form); + this.form.querySelectorAll(this.matching.cssSelector("safeUniversalSelector")).forEach((el) => { + const displayValue = window.getComputedStyle(el, null).getPropertyValue("display"); + if (displayValue !== "none") + this.evaluateElement(el); + }); + const relevantFields = this.form.querySelectorAll(this.matching.cssSelector("genericTextField")); + if (relevantFields.length >= 4) { + this.increaseSignalBy(relevantFields.length * 1.5, "many fields: it is probably not a login"); + } + if (this.autofillSignal === 0) { + this.evaluatePageHeadings(); + } + return this; + } + /** + * Tries to infer if it's a credit card form + * @returns {boolean} + */ + isCCForm() { + if (this._isCCForm !== void 0) + return this._isCCForm; + const formEl = this.form; + const ccFieldSelector = this.matching.joinCssSelectors("cc"); + if (!ccFieldSelector) { + this._isCCForm = false; + return this._isCCForm; + } + const hasCCSelectorChild = formEl.matches(ccFieldSelector) || formEl.querySelector(ccFieldSelector); + if (hasCCSelectorChild) { + this._isCCForm = true; + return this._isCCForm; + } + const hasCCAttribute = [...formEl.attributes].some( + ({ name, value }) => safeRegexTest(/(credit|payment).?card/i, `${name}=${value}`) + ); + if (hasCCAttribute) { + this._isCCForm = true; + return this._isCCForm; + } + const textMatches = formEl.textContent?.match(/(credit|payment).?card(.?number)?|ccv|security.?code|cvv|cvc|csc/ig); + const deDupedMatches = new Set(textMatches?.map((match) => match.toLowerCase())); + this._isCCForm = Boolean(textMatches && deDupedMatches.size > 1); + return this._isCCForm; + } + }; + var FormAnalyzer_default = FormAnalyzer; + + // src/Form/logo-svg.js + var daxSvg = ` + + + + + + + + + + + + + +`.trim(); + var daxBase64 = `data:image/svg+xml;base64,${window.btoa(daxSvg)}`; + var daxGrayscaleSvg = ` + + + + + + +`.trim(); + var daxGrayscaleBase64 = `data:image/svg+xml;base64,${window.btoa(daxGrayscaleSvg)}`; + + // src/UI/img/ddgPasswordIcon.js + var ddgPasswordIconBase = "data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNS4zMzQgNi42NjdhMiAyIDAgMSAwIDAgNCAyIDIgMCAwIDAgMC00Wm0tLjY2NyAyYS42NjcuNjY3IDAgMSAxIDEuMzMzIDAgLjY2Ny42NjcgMCAwIDEtMS4zMzMgMFoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTQuNjY3IDRhNS4zMzMgNS4zMzMgMCAwIDAtNS4xODggNi41NzhsLTUuMjg0IDUuMjg0YS42NjcuNjY3IDAgMCAwLS4xOTUuNDcxdjNjMCAuMzY5LjI5OC42NjcuNjY3LjY2N2gyLjY2NmMuNzM3IDAgMS4zMzQtLjU5NyAxLjMzNC0xLjMzM1YxOGguNjY2Yy43MzcgMCAxLjMzNC0uNTk3IDEuMzM0LTEuMzMzdi0xLjMzNEgxMmMuMTc3IDAgLjM0Ni0uMDcuNDcxLS4xOTVsLjY4OC0uNjg4QTUuMzMzIDUuMzMzIDAgMSAwIDE0LjY2NyA0Wm0tNCA1LjMzM2E0IDQgMCAxIDEgMi41NTUgMy43MzIuNjY3LjY2NyAwIDAgMC0uNzEzLjE1bC0uNzg1Ljc4NUgxMGEuNjY3LjY2NyAwIDAgMC0uNjY3LjY2N3YySDhhLjY2Ny42NjcgMCAwIDAtLjY2Ny42NjZ2MS4zMzRoLTJ2LTIuMDU4bDUuMzY1LTUuMzY0YS42NjcuNjY3IDAgMCAwIC4xNjMtLjY3NyAzLjk5NiAzLjk5NiAwIDAgMS0uMTk0LTEuMjM1WiIgY2xpcC1ydWxlPSJldmVub2RkIi8+Cjwvc3ZnPgo="; + var ddgPasswordIconFilled = "data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjNzY0MzEwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNS4yNSA2Ljc1YTIgMiAwIDEgMCAwIDQgMiAyIDAgMCAwIDAtNFptLS41IDJhLjUuNSAwIDEgMSAxIDAgLjUuNSAwIDAgMS0xIDBaIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz4KICAgIDxwYXRoIGZpbGw9IiM3NjQzMTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTE0LjYyNSA0YTUuMzc1IDUuMzc1IDAgMCAwLTUuMjQ0IDYuNTU5bC01LjE2MSA1LjE2YS43NS43NSAwIDAgMC0uMjIuNTMxdjNjMCAuNDE0LjMzNi43NS43NS43NWgyLjk5N2MuNTU0IDAgMS4wMDMtLjQ1IDEuMDAzLTEuMDAzVjE4aC45OTdjLjU1NCAwIDEuMDAzLS40NSAxLjAwMy0xLjAwM1YxNS41SDEyYS43NS43NSAwIDAgMCAuNTMtLjIybC43MS0uNzFBNS4zOCA1LjM4IDAgMCAwIDIwIDkuMzc1IDUuMzc1IDUuMzc1IDAgMCAwIDE0LjYyNSA0Wk0xMC43NSA5LjM3NWEzLjg3NSAzLjg3NSAwIDEgMSAyLjU0IDMuNjQuNzUuNzUgMCAwIDAtLjc4OS4xNzNMMTEuNjkgMTRIMTBhLjc1Ljc1IDAgMCAwLS43NS43NXYxLjc1SDhhLjc1Ljc1IDAgMCAwLS43NS43NXYxLjI1SDUuNXYtMS45NGw1LjI0OC01LjI0OGEuNzUuNzUgMCAwIDAgLjE4NC0uNzU4IDMuODcyIDMuODcyIDAgMCAxLS4xODItMS4xNzlaIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz4KPC9zdmc+Cg=="; + var ddgPasswordGenIconBase = "data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjMDAwIiBkPSJNOC4wNDcgNC42MjVDNy45MzcgNC4xMjUgNy44NjIgNCA3LjUgNGMtLjM2MiAwLS40MzguMTI1LS41NDcuNjI1LS4wNjguMzEtLjE3NyAxLjMzOC0uMjUxIDIuMDc3LS43MzguMDc0LTEuNzY3LjE4My0yLjA3Ny4yNTEtLjUuMTEtLjYyNS4xODQtLjYyNS41NDcgMCAuMzYyLjEyNS40MzcuNjI1LjU0Ny4zMS4wNjcgMS4zMzYuMTc3IDIuMDc0LjI1LjA3My43NjcuMTg1IDEuODQyLjI1NCAyLjA3OC4xMS4zNzUuMTg1LjYyNS41NDcuNjI1LjM2MiAwIC40MzgtLjEyNS41NDctLjYyNS4wNjgtLjMxLjE3Ny0xLjMzNi4yNS0yLjA3NC43NjctLjA3MyAxLjg0Mi0uMTg1IDIuMDc4LS4yNTQuMzc1LS4xMS42MjUtLjE4NS42MjUtLjU0NyAwLS4zNjMtLjEyNS0uNDM4LS42MjUtLjU0Ny0uMzEtLjA2OC0xLjMzOS0uMTc3LTIuMDc3LS4yNTEtLjA3NC0uNzM5LS4xODMtMS43NjctLjI1MS0yLjA3N1oiLz4KICAgIDxwYXRoIGZpbGw9IiMwMDAiIGQ9Ik0xNC42ODEgNS4xOTljLS43NjYgMC0xLjQ4Mi4yMS0yLjA5My41NzhhLjYzNi42MzYgMCAwIDEtLjY1NS0xLjA5IDUuMzQgNS4zNCAwIDEgMSAxLjMwMiA5LjcyMmwtLjc3NS43NzZhLjYzNi42MzYgMCAwIDEtLjQ1LjE4NmgtMS4zOTh2MS42NWMwIC40OTMtLjQuODkzLS44OTMuODkzSDguNTc4djEuMTQxYzAgLjQ5NC0uNC44OTMtLjg5NC44OTNINC42MzZBLjYzNi42MzYgMCAwIDEgNCAxOS4zMTNWMTYuMjZjMC0uMTY5LjA2Ny0uMzMuMTg2LS40NWw1LjU2Mi01LjU2MmEuNjM2LjYzNiAwIDEgMSAuOS45bC01LjM3NiA1LjM3NXYyLjE1M2gyLjAzNHYtMS4zOTljMC0uMzUuMjg1LS42MzYuNjM2LS42MzZIOS4zNHYtMS45MDdjMC0uMzUxLjI4NC0uNjM2LjYzNS0uNjM2aDEuNzcxbC44NjQtLjg2M2EuNjM2LjYzNiAwIDAgMSAuNjY4LS4xNDcgNC4wNjkgNC4wNjkgMCAxIDAgMS40MDItNy44OVoiLz4KICAgIDxwYXRoIGZpbGw9IiMwMDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTEzLjYyNSA4LjQ5OWExLjg3NSAxLjg3NSAwIDEgMSAzLjc1IDAgMS44NzUgMS44NzUgMCAwIDEtMy43NSAwWm0xLjg3NS0uNjI1YS42MjUuNjI1IDAgMSAwIDAgMS4yNS42MjUuNjI1IDAgMCAwIDAtMS4yNVoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZmlsbD0iIzAwMCIgZD0iTTQuNjI1IDEyLjEyNWEuNjI1LjYyNSAwIDEgMCAwLTEuMjUuNjI1LjYyNSAwIDAgMCAwIDEuMjVaIi8+Cjwvc3ZnPgo="; + var ddgPasswordGenIconFilled = "data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjNzY0MzEwIiBkPSJNOC4wNDcgNC42MjVDNy45MzcgNC4xMjUgNy44NjIgNCA3LjUgNGMtLjM2MiAwLS40MzguMTI1LS41NDcuNjI1LS4wNjguMzEtLjE3NyAxLjMzOS0uMjUxIDIuMDc3LS43MzguMDc0LTEuNzY3LjE4My0yLjA3Ny4yNTEtLjUuMTEtLjYyNS4xODUtLjYyNS41NDcgMCAuMzYyLjEyNS40MzcuNjI1LjU0Ny4zMS4wNjcgMS4zMzYuMTc3IDIuMDc0LjI1LjA3My43NjcuMTg1IDEuODQyLjI1NCAyLjA3OC4xMS4zNzUuMTg1LjYyNS41NDcuNjI1LjM2MiAwIC40MzgtLjEyNS41NDctLjYyNS4wNjgtLjMxLjE3Ny0xLjMzNi4yNS0yLjA3NC43NjctLjA3MyAxLjg0Mi0uMTg1IDIuMDc4LS4yNTQuMzc1LS4xMS42MjUtLjE4NS42MjUtLjU0NyAwLS4zNjItLjEyNS0uNDM4LS42MjUtLjU0Ny0uMzEtLjA2OC0xLjMzOS0uMTc3LTIuMDc3LS4yNTEtLjA3NC0uNzM4LS4xODMtMS43NjctLjI1MS0yLjA3N1oiLz4KICAgIDxwYXRoIGZpbGw9IiM3NjQzMTAiIGQ9Ik0xNC42ODEgNS4xOTljLS43NjYgMC0xLjQ4Mi4yMTEtMi4wOTMuNTc4YS42MzYuNjM2IDAgMCAxLS42NTUtMS4wOSA1LjM0IDUuMzQgMCAxIDEgMS4zMDIgOS43MjNsLS43NzUuNzc1YS42MzYuNjM2IDAgMCAxLS40NS4xODZoLTEuMzk4djEuNjVjMCAuNDkzLS40Ljg5My0uODkzLjg5M0g4LjU3OHYxLjE0MWMwIC40OTQtLjQuODk0LS44OTQuODk0SDQuNjM2QS42MzYuNjM2IDAgMCAxIDQgMTkuMzEzVjE2LjI2YS42NC42NCAwIDAgMSAuMTg2LS40NWw1LjU2Mi01LjU2MmEuNjM2LjYzNiAwIDEgMSAuOS45bC01LjM3NiA1LjM3NXYyLjE1M2gyLjAzNHYtMS4zOTljMC0uMzUuMjg1LS42MzYuNjM2LS42MzZIOS4zNHYtMS45MDdjMC0uMzUuMjg0LS42MzYuNjM1LS42MzZoMS43NzFsLjg2NC0uODYzYS42MzYuNjM2IDAgMCAxIC42NjgtLjE0NyA0LjA2OSA0LjA2OSAwIDEgMCAxLjQwMi03Ljg5WiIvPgogICAgPHBhdGggZmlsbD0iIzc2NDMxMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTMuNjI1IDguNWExLjg3NSAxLjg3NSAwIDEgMSAzLjc1IDAgMS44NzUgMS44NzUgMCAwIDEtMy43NSAwWm0xLjg3NS0uNjI2YS42MjUuNjI1IDAgMSAwIDAgMS4yNS42MjUuNjI1IDAgMCAwIDAtMS4yNVoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZmlsbD0iIzc2NDMxMCIgZD0iTTQuNjI1IDEyLjEyNWEuNjI1LjYyNSAwIDEgMCAwLTEuMjUuNjI1LjYyNSAwIDAgMCAwIDEuMjVaIi8+Cjwvc3ZnPgo="; + + // src/InputTypes/CreditCard.js + var _data4; + var CreditCardTooltipItem = class { + /** @param {CreditCardObject} data */ + constructor(data) { + /** @type {CreditCardObject} */ + __privateAdd(this, _data4, void 0); + __publicField(this, "id", () => String(__privateGet(this, _data4).id)); + __publicField(this, "labelMedium", (_) => __privateGet(this, _data4).title); + __publicField(this, "labelSmall", (_) => __privateGet(this, _data4).displayNumber); + __privateSet(this, _data4, data); + } + }; + _data4 = new WeakMap(); + + // src/InputTypes/Identity.js + var _data5; + var IdentityTooltipItem = class { + /** @param {IdentityObject} data */ + constructor(data) { + /** @type {IdentityObject} */ + __privateAdd(this, _data5, void 0); + __publicField(this, "id", () => String(__privateGet(this, _data5).id)); + __publicField(this, "labelMedium", (subtype) => { + if (subtype === "addressCountryCode") { + return getCountryDisplayName("en", __privateGet(this, _data5).addressCountryCode || ""); + } + if (__privateGet(this, _data5).id === "privateAddress") { + return "Generate Private Duck Address"; + } + return __privateGet(this, _data5)[subtype]; + }); + __publicField(this, "labelSmall", (_) => { + return __privateGet(this, _data5).title; + }); + __privateSet(this, _data5, data); + } + label(subtype) { + if (__privateGet(this, _data5).id === "privateAddress") { + return __privateGet(this, _data5)[subtype]; + } + return null; + } + }; + _data5 = new WeakMap(); + + // src/Form/inputTypeConfig.js + var getIdentitiesIcon = (input, { device }) => { + if (!canBeInteractedWith(input)) + return ""; + const { isDDGApp, isFirefox, isExtension } = device.globalConfig; + const subtype = getInputSubtype(input); + if (device.inContextSignup?.isAvailable(subtype)) { + if (isDDGApp || isFirefox) { + return daxGrayscaleBase64; + } else if (isExtension) { + return chrome.runtime.getURL("img/logo-small-grayscale.svg"); + } + } + if (subtype === "emailAddress" && device.isDeviceSignedIn()) { + if (isDDGApp || isFirefox) { + return daxBase64; + } else if (isExtension) { + return chrome.runtime.getURL("img/logo-small.svg"); + } + } + return ""; + }; + var getIdentitiesAlternateIcon = (input, { device }) => { + if (!canBeInteractedWith(input)) + return ""; + const { isDDGApp, isFirefox, isExtension } = device.globalConfig; + const subtype = getInputSubtype(input); + const isIncontext = device.inContextSignup?.isAvailable(subtype); + const isEmailProtection = subtype === "emailAddress" && device.isDeviceSignedIn(); + if (isIncontext || isEmailProtection) { + if (isDDGApp || isFirefox) { + return daxBase64; + } else if (isExtension) { + return chrome.runtime.getURL("img/logo-small.svg"); + } + } + return ""; + }; + var canBeInteractedWith = (input) => !input.readOnly && !input.disabled; + var canBeAutofilled = async (input, device) => { + if (!canBeInteractedWith(input)) + return false; + const mainType = getInputMainType(input); + const subtype = getInputSubtype(input); + await device.settings.populateDataIfNeeded({ mainType, subtype }); + const canAutofill = device.settings.canAutofillType({ mainType, subtype }, device.inContextSignup); + return Boolean(canAutofill); + }; + var inputTypeConfig = { + /** @type {CredentialsInputTypeConfig} */ + credentials: { + type: "credentials", + displayName: "passwords", + getIconBase: (input, { device }) => { + if (!canBeInteractedWith(input)) + return ""; + if (device.settings.featureToggles.inlineIcon_credentials) { + const subtype = getInputSubtype(input); + const variant = getInputVariant(input); + if (subtype === "password" && variant === "new") { + return ddgPasswordGenIconBase; + } + return ddgPasswordIconBase; + } + return ""; + }, + getIconFilled: (input, { device }) => { + if (device.settings.featureToggles.inlineIcon_credentials) { + const subtype = getInputSubtype(input); + const variant = getInputVariant(input); + if (subtype === "password" && variant === "new") { + return ddgPasswordGenIconFilled; + } + return ddgPasswordIconFilled; + } + return ""; + }, + getIconAlternate: () => "", + shouldDecorate: async (input, { isLogin, isHybrid, device }) => { + const subtype = getInputSubtype(input); + const variant = getInputVariant(input); + if (device.settings.featureToggles.password_generation) { + if (subtype === "password" && variant === "new") { + return canBeInteractedWith(input); + } + } + if (isLogin || isHybrid || variant === "current") { + return canBeAutofilled(input, device); + } + return false; + }, + dataType: "Credentials", + tooltipItem: (data) => createCredentialsTooltipItem(data) + }, + /** @type {CreditCardsInputTypeConfig} */ + creditCards: { + type: "creditCards", + displayName: "credit cards", + getIconBase: () => "", + getIconFilled: () => "", + getIconAlternate: () => "", + shouldDecorate: async (input, { device }) => { + return canBeAutofilled(input, device); + }, + dataType: "CreditCards", + tooltipItem: (data) => new CreditCardTooltipItem(data) + }, + /** @type {IdentitiesInputTypeConfig} */ + identities: { + type: "identities", + displayName: "identities", + getIconBase: getIdentitiesIcon, + getIconFilled: getIdentitiesIcon, + getIconAlternate: getIdentitiesAlternateIcon, + shouldDecorate: async (input, { device }) => { + return canBeAutofilled(input, device); + }, + dataType: "Identities", + tooltipItem: (data) => new IdentityTooltipItem(data) + }, + /** @type {UnknownInputTypeConfig} */ + unknown: { + type: "unknown", + displayName: "", + getIconBase: () => "", + getIconFilled: () => "", + getIconAlternate: () => "", + shouldDecorate: async () => false, + dataType: "", + tooltipItem: (_data7) => { + throw new Error("unreachable - setting tooltip to unknown field type"); + } + } + }; + var getInputConfig = (input) => { + const inputType = getInputType(input); + return getInputConfigFromType(inputType); + }; + var getInputConfigFromType = (inputType) => { + const inputMainType = getMainTypeFromType(inputType); + return inputTypeConfig[inputMainType]; + }; + var isFieldDecorated = (input) => { + return input.hasAttribute(constants.ATTR_INPUT_TYPE); + }; + + // src/Form/inputStyles.js + var getIcon = (input, form, type = "base") => { + const config = getInputConfig(input); + if (type === "base") { + return config.getIconBase(input, form); + } + if (type === "filled") { + return config.getIconFilled(input, form); + } + if (type === "alternate") { + return config.getIconAlternate(input, form); + } + return ""; + }; + var getBasicStyles = (input, icon) => ({ + // Height must be > 0 to account for fields initially hidden + "background-size": `auto ${input.offsetHeight <= 30 && input.offsetHeight > 0 ? "100%" : "24px"}`, + "background-position": "center right", + "background-repeat": "no-repeat", + "background-origin": "content-box", + "background-image": `url(${icon})`, + "transition": "background 0s" + }); + var getIconStylesBase = (input, form) => { + const icon = getIcon(input, form); + if (!icon) + return {}; + return getBasicStyles(input, icon); + }; + var getIconStylesAlternate = (input, form) => { + const icon = getIcon(input, form, "alternate"); + if (!icon) + return {}; + return { + ...getBasicStyles(input, icon) + }; + }; + var getIconStylesAutofilled = (input, form) => { + const icon = getIcon(input, form, "filled"); + const iconStyle = icon ? getBasicStyles(input, icon) : {}; + return { + ...iconStyle, + "background-color": "#F8F498", + "color": "#333333" + }; + }; + + // src/Form/Form.js + var { + ATTR_AUTOFILL, + ATTR_INPUT_TYPE: ATTR_INPUT_TYPE2, + MAX_INPUTS_PER_FORM, + MAX_FORM_RESCANS + } = constants; + var Form = class { + /** + * @param {HTMLElement} form + * @param {HTMLInputElement|HTMLSelectElement} input + * @param {import("../DeviceInterface/InterfacePrototype").default} deviceInterface + * @param {import("../Form/matching").Matching} [matching] + * @param {Boolean} [shouldAutoprompt] + */ + constructor(form, input, deviceInterface, matching, shouldAutoprompt = false) { + /** @type {import("../Form/matching").Matching} */ + __publicField(this, "matching"); + /** @type {HTMLElement} */ + __publicField(this, "form"); + /** @type {HTMLInputElement | null} */ + __publicField(this, "activeInput"); + this.form = form; + this.matching = matching || createMatching(); + this.formAnalyzer = new FormAnalyzer_default(form, input, matching); + this.device = deviceInterface; + this.inputs = { + all: /* @__PURE__ */ new Set(), + credentials: /* @__PURE__ */ new Set(), + creditCards: /* @__PURE__ */ new Set(), + identities: /* @__PURE__ */ new Set(), + unknown: /* @__PURE__ */ new Set() + }; + this.touched = /* @__PURE__ */ new Set(); + this.listeners = /* @__PURE__ */ new Set(); + this.activeInput = null; + this.isAutofilling = false; + this.submitHandlerExecuted = false; + this.shouldPromptToStoreData = deviceInterface.settings.featureToggles.credentials_saving; + this.shouldAutoSubmit = this.device.globalConfig.isMobileApp; + this.intObs = new IntersectionObserver((entries) => { + for (const entry of entries) { + if (!entry.isIntersecting) + this.removeTooltip(); + } + }); + this.rescanCount = 0; + this.mutObsConfig = { childList: true, subtree: true }; + this.mutObs = new MutationObserver( + (records) => { + const anythingRemoved = records.some((record) => record.removedNodes.length > 0); + if (anythingRemoved) { + if (!this.form.isConnected) { + this.destroy(); + return; + } + if ([...this.inputs.all].some((input2) => !input2.isConnected)) { + this.mutObs.disconnect(); + window.requestIdleCallback(() => { + this.formAnalyzer = new FormAnalyzer_default(this.form, input, this.matching); + this.recategorizeAllInputs(); + }); + } + } + } + ); + this.initFormListeners(); + this.categorizeInputs(); + this.logFormInfo(); + if (shouldAutoprompt) { + this.promptLoginIfNeeded(); + } + } + get isLogin() { + return this.formAnalyzer.isLogin; + } + get isSignup() { + return this.formAnalyzer.isSignup; + } + get isHybrid() { + return this.formAnalyzer.isHybrid; + } + get isCCForm() { + return this.formAnalyzer.isCCForm(); + } + logFormInfo() { + if (!shouldLog()) + return; + console.log(`Form type: %c${this.getFormType()}`, "font-weight: bold"); + console.log("Signals: ", this.formAnalyzer.signals); + console.log("Wrapping element: ", this.form); + console.log("Inputs: ", this.inputs); + console.log("Submit Buttons: ", this.submitButtons); + } + getFormType() { + if (this.isHybrid) + return `hybrid (hybrid score: ${this.formAnalyzer.hybridSignal}, score: ${this.formAnalyzer.autofillSignal})`; + if (this.isLogin) + return `login (score: ${this.formAnalyzer.autofillSignal}, hybrid score: ${this.formAnalyzer.hybridSignal})`; + if (this.isSignup) + return `signup (score: ${this.formAnalyzer.autofillSignal}, hybrid score: ${this.formAnalyzer.hybridSignal})`; + return "something went wrong"; + } + /** + * Checks if the form element contains the activeElement or the event target + * @return {boolean} + * @param {KeyboardEvent | null} [e] + */ + hasFocus(e) { + return this.form.contains(getActiveElement()) || this.form.contains( + /** @type HTMLElement */ + e?.target + ); + } + submitHandler(via = "unknown") { + if (this.device.globalConfig.isDDGTestMode) { + console.log("Form.submitHandler via:", via, this); + } + if (this.submitHandlerExecuted) + return; + const values = this.getValuesReadyForStorage(); + this.device.postSubmit?.(values, this); + this.submitHandlerExecuted = true; + } + /** + * Reads the values from the form without preparing to store them + * @return {InternalDataStorageObject} + */ + getRawValues() { + const formValues = [...this.inputs.credentials, ...this.inputs.identities, ...this.inputs.creditCards].reduce((output, inputEl) => { + const mainType = getInputMainType(inputEl); + const subtype = getInputSubtype(inputEl); + let value = inputEl.value || output[mainType]?.[subtype]; + if (subtype === "addressCountryCode") { + value = inferCountryCodeFromElement(inputEl); + } + if (subtype === "password" && value?.length <= 3) { + value = void 0; + } + if (value) { + output[mainType][subtype] = value; + } + return output; + }, { credentials: {}, creditCards: {}, identities: {} }); + if (!formValues.credentials.username && !formValues.identities.emailAddress) { + const hiddenFields = ( + /** @type [HTMLInputElement] */ + [...this.form.querySelectorAll("input[type=hidden]")] + ); + const probableField = hiddenFields.find((field) => { + const regex = new RegExp("email|" + this.matching.getDDGMatcherRegex("username")?.source); + const attributeText = field.id + " " + field.name; + return safeRegexTest(regex, attributeText); + }); + if (probableField?.value) { + formValues.credentials.username = probableField.value; + } else if ( + // If a form has phone + password(s) fields, save the phone as username + formValues.identities.phone && this.inputs.all.size - this.inputs.unknown.size < 4 + ) { + formValues.credentials.username = formValues.identities.phone; + } else { + this.form.querySelectorAll(this.matching.cssSelector("safeUniversalSelector")).forEach((el) => { + const elText = getTextShallow(el); + if (elText.length > 70) + return; + const emailOrUsername = elText.match( + // https://www.emailregex.com/ + /[a-zA-Z\d.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z\d-]+(?:\.[a-zA-Z\d-]+)*/ + )?.[0]; + if (emailOrUsername) { + formValues.credentials.username = emailOrUsername; + } + }); + } + } + return formValues; + } + /** + * Return form values ready for storage + * @returns {DataStorageObject} + */ + getValuesReadyForStorage() { + const formValues = this.getRawValues(); + return prepareFormValuesForStorage(formValues); + } + /** + * Determine if the form has values we want to store in the device + * @param {DataStorageObject} [values] + * @return {boolean} + */ + hasValues(values) { + const { credentials, creditCards, identities } = values || this.getValuesReadyForStorage(); + return Boolean(credentials || creditCards || identities); + } + async removeTooltip() { + const tooltip = this.device.isTooltipActive(); + if (this.isAutofilling || !tooltip) { + return; + } + await this.device.removeTooltip(); + this.intObs?.disconnect(); + } + showingTooltip(input) { + this.intObs?.observe(input); + } + removeInputHighlight(input) { + if (!input.classList.contains("ddg-autofilled")) + return; + removeInlineStyles(input, getIconStylesAutofilled(input, this)); + removeInlineStyles(input, { "cursor": "pointer" }); + input.classList.remove("ddg-autofilled"); + this.addAutofillStyles(input); + } + resetIconStylesToInitial() { + const input = this.activeInput; + if (input) { + const initialStyles = getIconStylesBase(input, this); + addInlineStyles(input, initialStyles); + } + } + removeAllHighlights(e, dataType) { + if (e && !e.isTrusted) + return; + this.resetShouldPromptToStoreData(); + this.execOnInputs((input) => this.removeInputHighlight(input), dataType); + } + removeInputDecoration(input) { + removeInlineStyles(input, getIconStylesBase(input, this)); + removeInlineStyles(input, getIconStylesAlternate(input, this)); + input.removeAttribute(ATTR_AUTOFILL); + input.removeAttribute(ATTR_INPUT_TYPE2); + } + removeAllDecorations() { + this.execOnInputs((input) => this.removeInputDecoration(input)); + this.listeners.forEach(({ el, type, fn, opts }) => el.removeEventListener(type, fn, opts)); + } + redecorateAllInputs() { + this.removeAllDecorations(); + this.execOnInputs((input) => { + if (input instanceof HTMLInputElement) { + this.decorateInput(input); + } + }); + } + /** + * Removes all scoring attributes from the inputs and deletes them from memory + */ + forgetAllInputs() { + this.execOnInputs((input) => { + input.removeAttribute(ATTR_AUTOFILL); + input.removeAttribute(ATTR_INPUT_TYPE2); + }); + Object.values(this.inputs).forEach((inputSet) => inputSet.clear()); + } + /** + * Resets our input scoring and starts from scratch + */ + recategorizeAllInputs() { + if (this.rescanCount >= MAX_FORM_RESCANS) { + this.mutObs.disconnect(); + return; + } + this.rescanCount++; + this.initialScanComplete = false; + this.removeAllDecorations(); + this.forgetAllInputs(); + this.initFormListeners(); + this.categorizeInputs(); + } + resetAllInputs() { + this.execOnInputs((input) => { + setValue(input, "", this.device.globalConfig); + this.removeInputHighlight(input); + }); + if (this.activeInput) + this.activeInput.focus(); + this.matching.clear(); + } + resetShouldPromptToStoreData() { + this.shouldPromptToStoreData = this.device.settings.featureToggles.credentials_saving; + } + dismissTooltip() { + this.removeTooltip(); + } + // This removes all listeners to avoid memory leaks and weird behaviours + destroy() { + this.mutObs.disconnect(); + this.removeAllDecorations(); + this.removeTooltip(); + this.forgetAllInputs(); + this.matching.clear(); + this.intObs = null; + this.device.scanner.forms.delete(this.form); + } + initFormListeners() { + this.addListener(this.form, "input", () => { + if (!this.isAutofilling) { + this.submitHandlerExecuted = false; + this.resetShouldPromptToStoreData(); + } + }); + if (this.form instanceof HTMLFormElement && this.form.getRootNode()) { + this.addListener(this.form, "submit", () => { + this.submitHandler("in-form submit handler"); + }, { capture: true }); + } + } + categorizeInputs() { + const selector = this.matching.cssSelector("formInputsSelector"); + if (this.form.matches(selector)) { + this.addInput(this.form); + } else { + let foundInputs = []; + if (this.form instanceof HTMLFormElement) { + foundInputs = [...this.form.elements].filter((el) => el.matches(selector)); + } else { + foundInputs = this.form.querySelectorAll(selector); + } + if (foundInputs.length < MAX_INPUTS_PER_FORM) { + foundInputs.forEach((input) => this.addInput(input)); + } else { + this.device.scanner.stopScanner(`The form has too many inputs (${foundInputs.length}), bailing.`); + return; + } + } + this.initialScanComplete = true; + if (this.form !== document.body) { + this.mutObs.observe(this.form, this.mutObsConfig); + } + } + get submitButtons() { + const selector = this.matching.cssSelector("submitButtonSelector"); + const allButtons = ( + /** @type {HTMLElement[]} */ + [...this.form.querySelectorAll(selector)] + ); + return allButtons.filter( + (btn) => isPotentiallyViewable(btn) && isLikelyASubmitButton(btn, this.matching) && buttonMatchesFormType(btn, this) + ); + } + attemptSubmissionIfNeeded() { + if (!this.isLogin || // Only submit login forms + this.submitButtons.length > 1) + return; + let isThereAnEmptyVisibleField = false; + this.execOnInputs((input) => { + if (input.value === "" && isPotentiallyViewable(input)) + isThereAnEmptyVisibleField = true; + }, "all", false); + if (isThereAnEmptyVisibleField) + return; + this.submitButtons.forEach((button) => { + if (isPotentiallyViewable(button)) { + button.click(); + } + }); + } + /** + * Executes a function on input elements. Can be limited to certain element types + * @param {(input: HTMLInputElement|HTMLSelectElement) => void} fn + * @param {'all' | SupportedMainTypes} inputType + * @param {boolean} shouldCheckForDecorate + */ + execOnInputs(fn, inputType = "all", shouldCheckForDecorate = true) { + const inputs = this.inputs[inputType]; + for (const input of inputs) { + let canExecute = true; + if (shouldCheckForDecorate) { + canExecute = isFieldDecorated(input); + } + if (canExecute) + fn(input); + } + } + addInput(input) { + if (this.inputs.all.has(input)) + return this; + if (this.inputs.all.size > MAX_INPUTS_PER_FORM) { + this.device.scanner.stopScanner("The form has too many inputs, bailing."); + return this; + } + if (this.initialScanComplete && this.rescanCount < MAX_FORM_RESCANS) { + this.formAnalyzer = new FormAnalyzer_default(this.form, input, this.matching); + this.recategorizeAllInputs(); + return this; + } + if (input.maxLength === 1) + return this; + this.inputs.all.add(input); + const opts = { + isLogin: this.isLogin, + isHybrid: this.isHybrid, + isCCForm: this.isCCForm, + hasCredentials: Boolean(this.device.settings.availableInputTypes.credentials?.username), + supportsIdentitiesAutofill: this.device.settings.featureToggles.inputType_identities + }; + this.matching.setInputType(input, this.form, opts); + const mainInputType = getInputMainType(input); + this.inputs[mainInputType].add(input); + this.decorateInput(input); + return this; + } + /** + * Adds event listeners and keeps track of them for subsequent removal + * @param {HTMLElement} el + * @param {Event['type']} type + * @param {(Event) => void} fn + * @param {AddEventListenerOptions} [opts] + */ + addListener(el, type, fn, opts) { + el.addEventListener(type, fn, opts); + this.listeners.add({ el, type, fn, opts }); + } + addAutofillStyles(input) { + const initialStyles = getIconStylesBase(input, this); + const activeStyles = getIconStylesAlternate(input, this); + addInlineStyles(input, initialStyles); + return { + onMouseMove: activeStyles, + onMouseLeave: initialStyles + }; + } + /** + * Decorate here means adding listeners and an optional icon + * @param {HTMLInputElement} input + * @returns {Promise
} + */ + async decorateInput(input) { + const config = getInputConfig(input); + const shouldDecorate = await config.shouldDecorate(input, this); + if (!shouldDecorate) + return this; + input.setAttribute(ATTR_AUTOFILL, "true"); + const hasIcon = !!config.getIconBase(input, this); + if (hasIcon) { + const { onMouseMove, onMouseLeave } = this.addAutofillStyles(input); + this.addListener(input, "mousemove", (e) => { + if (wasAutofilledByChrome(input)) + return; + if (isEventWithinDax(e, e.target)) { + addInlineStyles(e.target, { + "cursor": "pointer", + ...onMouseMove + }); + } else { + removeInlineStyles(e.target, { "cursor": "pointer" }); + if (!this.device.isTooltipActive()) { + addInlineStyles(e.target, { ...onMouseLeave }); + } + } + }); + this.addListener(input, "mouseleave", (e) => { + removeInlineStyles(e.target, { "cursor": "pointer" }); + if (!this.device.isTooltipActive()) { + addInlineStyles(e.target, { ...onMouseLeave }); + } + }); + } + function getMainClickCoords(e) { + if (!e.isTrusted) + return; + const isMainMouseButton = e.button === 0; + if (!isMainMouseButton) + return; return { - valid: false + x: e.clientX, + y: e.clientY }; } - newArray.push(sharedValue.data); + function getClickCoords(e, storedClickCoords2) { + if (e.type === "pointerdown") { + return getMainClickCoords( + /** @type {PointerEvent} */ + e + ) || null; + } + const click = storedClickCoords2.get(input); + storedClickCoords2.delete(input); + return click || null; + } + let storedClickCoords = /* @__PURE__ */ new WeakMap(); + let timeout = null; + const handlerLabel = (e) => { + const control = ( + /** @type HTMLElement */ + e.target?.closest("label")?.control + ); + if (!control) + return; + if (e.isTrusted) { + storedClickCoords.set(control, getMainClickCoords(e)); + } + clearTimeout(timeout); + timeout = setTimeout(() => { + storedClickCoords = /* @__PURE__ */ new WeakMap(); + }, 1e3); + }; + const handler = (e) => { + if (this.isAutofilling || this.device.isTooltipActive()) { + return; + } + const isLabel = e.target instanceof HTMLLabelElement; + const input2 = isLabel ? e.target.control : e.target; + if (!input2 || !this.inputs.all.has(input2)) + return; + if (wasAutofilledByChrome(input2)) + return; + if (!canBeInteractedWith(input2)) + return; + const clickCoords = getClickCoords(e, storedClickCoords); + if (e.type === "pointerdown") { + if (!e.isTrusted || !clickCoords) + return; + } + if (this.shouldOpenTooltip(e, input2)) { + const iconClicked = isEventWithinDax(e, input2); + if ((this.device.globalConfig.isMobileApp || this.device.globalConfig.isExtension) && // Avoid the icon capturing clicks on small fields making it impossible to focus + input2.offsetWidth > 50 && iconClicked) { + e.preventDefault(); + e.stopImmediatePropagation(); + input2.blur(); + } + this.touched.add(input2); + this.device.attachTooltip({ + form: this, + input: input2, + click: clickCoords, + trigger: "userInitiated", + triggerMetaData: { + // An 'icon' click is very different to a field click or focus. + // It indicates an explicit opt-in to the feature. + type: iconClicked ? "explicit-opt-in" : "implicit-opt-in" + } + }); + const activeStyles = getIconStylesAlternate(input2, this); + addInlineStyles(input2, activeStyles); + } + }; + if (!(input instanceof HTMLSelectElement)) { + const events = ["pointerdown"]; + if (!this.device.globalConfig.isMobileApp) + events.push("focus"); + input.labels?.forEach((label) => { + if (this.device.globalConfig.isMobileApp) { + this.addListener(label, "pointerdown", handler); + } else { + this.addListener(label, "pointerdown", handlerLabel); + } + }); + events.forEach((ev) => this.addListener(input, ev, handler)); + } + return this; } - return { - valid: true, - data: newArray + shouldOpenTooltip(e, input) { + if (!isPotentiallyViewable(input)) + return false; + if (isEventWithinDax(e, input)) + return true; + if (this.device.globalConfig.isWindows) + return true; + const subtype = getInputSubtype(input); + const isIncontextSignupAvailable = this.device.inContextSignup?.isAvailable(subtype); + if (this.device.globalConfig.isApp) { + const mainType = getInputMainType(input); + const hasSavedDetails = this.device.settings.canAutofillType({ mainType, subtype }, null); + if (!hasSavedDetails && isIncontextSignupAvailable) + return false; + return true; + } + if (this.device.globalConfig.isExtension || this.device.globalConfig.isMobileApp) { + if (isIncontextSignupAvailable) + return false; + } + return !this.touched.has(input) && !input.classList.contains("ddg-autofilled"); + } + autofillInput(input, string, dataType) { + if (input instanceof HTMLInputElement && !isPotentiallyViewable(input)) + return; + if (!canBeInteractedWith(input)) + return; + if (dataType !== "creditCards" && // creditCards always override, the others only when we're focusing the input + input.nodeName !== "SELECT" && input.value !== "" && // if the input is not empty + this.activeInput !== input) + return; + if (input.value === string) + return; + const successful = setValue(input, string, this.device.globalConfig); + if (!successful) + return; + input.classList.add("ddg-autofilled"); + addInlineStyles(input, getIconStylesAutofilled(input, this)); + this.touched.add(input); + input.addEventListener("input", (e) => this.removeAllHighlights(e, dataType), { once: true }); + } + /** + * Autofill method for email protection only + * @param {string} alias + * @param {'all' | SupportedMainTypes} dataType + */ + autofillEmail(alias, dataType = "identities") { + this.isAutofilling = true; + this.execOnInputs( + (input) => { + const inputSubtype = getInputSubtype(input); + if (inputSubtype === "emailAddress") { + this.autofillInput(input, alias, dataType); + } + }, + dataType + ); + this.isAutofilling = false; + this.removeTooltip(); + } + autofillData(data, dataType) { + this.isAutofilling = true; + this.execOnInputs((input) => { + const inputSubtype = getInputSubtype(input); + let autofillData = data[inputSubtype]; + if (inputSubtype === "expiration" && input instanceof HTMLInputElement) { + autofillData = getUnifiedExpiryDate(input, data.expirationMonth, data.expirationYear, this); + } + if (inputSubtype === "expirationYear" && input instanceof HTMLInputElement) { + autofillData = formatCCYear(input, autofillData, this); + } + if (inputSubtype === "addressCountryCode") { + autofillData = getCountryName(input, data); + } + if (autofillData) { + const variant = getInputVariant(input); + if (!variant) { + return this.autofillInput(input, autofillData, dataType); + } + if (variant === "new" && AUTOGENERATED_KEY in data) { + return this.autofillInput(input, autofillData, dataType); + } + if (variant === "current" && !(AUTOGENERATED_KEY in data)) { + return this.autofillInput(input, autofillData, dataType); + } + } + }, dataType); + this.isAutofilling = false; + const formValues = this.getValuesReadyForStorage(); + const areAllFormValuesKnown = Object.keys(formValues[dataType] || {}).every((subtype) => formValues[dataType][subtype] === data[subtype]); + if (areAllFormValuesKnown) { + this.shouldPromptToStoreData = false; + this.shouldAutoSubmit = this.device.globalConfig.isMobileApp; + } else { + this.shouldAutoSubmit = false; + } + this.device.postAutofill?.(data, dataType, this); + this.removeTooltip(); + } + /** + * Set all inputs of the data type to "touched" + * @param {'all' | SupportedMainTypes} dataType + */ + touchAllInputs(dataType = "all") { + this.execOnInputs( + (input) => this.touched.add(input), + dataType + ); + } + getFirstViableCredentialsInput() { + return [...this.inputs.credentials].find((input) => canBeInteractedWith(input) && isPotentiallyViewable(input)); + } + async promptLoginIfNeeded() { + if (document.visibilityState !== "visible" || !this.isLogin) + return; + const firstCredentialInput = this.getFirstViableCredentialsInput(); + const input = this.activeInput || firstCredentialInput; + if (!input) + return; + const mainType = getInputMainType(input); + const subtype = getInputSubtype(input); + await this.device.settings.populateDataIfNeeded({ mainType, subtype }); + if (this.device.settings.canAutofillType({ mainType, subtype }, this.device.inContextSignup)) { + setTimeout(() => { + safeExecute(this.form, () => { + const { x, y, width, height } = this.form.getBoundingClientRect(); + const elHCenter = x + width / 2; + const elVCenter = y + height / 2; + const topMostElementFromPoint = document.elementFromPoint(elHCenter, elVCenter); + if (this.form.contains(topMostElementFromPoint)) { + this.execOnInputs((input2) => { + if (isPotentiallyViewable(input2)) { + this.touched.add(input2); + } + }, "credentials"); + this.device.attachTooltip({ + form: this, + input, + click: null, + trigger: "autoprompt", + triggerMetaData: { + type: "implicit-opt-in" + } + }); + } + }); + }, 200); + } + } + }; + + // node_modules/zod/lib/index.mjs + var util; + (function(util2) { + util2.assertEqual = (val) => val; + function assertIs(_arg) { + } + util2.assertIs = assertIs; + function assertNever(_x) { + throw new Error(); + } + util2.assertNever = assertNever; + util2.arrayToEnum = (items) => { + const obj = {}; + for (const item of items) { + obj[item] = item; + } + return obj; }; - } else if (aType === util_1.ZodParsedType.date && bType === util_1.ZodParsedType.date && +a === +b) { - return { - valid: true, - data: a + util2.getValidEnumValues = (obj) => { + const validKeys = util2.objectKeys(obj).filter((k) => typeof obj[obj[k]] !== "number"); + const filtered = {}; + for (const k of validKeys) { + filtered[k] = obj[k]; + } + return util2.objectValues(filtered); }; - } else { - return { - valid: false + util2.objectValues = (obj) => { + return util2.objectKeys(obj).map(function(e) { + return obj[e]; + }); }; - } -} -class ZodIntersection extends ZodType { - _parse(input) { - const { - status, - ctx - } = this._processInputParams(input); - const handleParsed = (parsedLeft, parsedRight) => { - if ((0, parseUtil_1.isAborted)(parsedLeft) || (0, parseUtil_1.isAborted)(parsedRight)) { - return parseUtil_1.INVALID; - } - const merged = mergeValues(parsedLeft.value, parsedRight.value); - if (!merged.valid) { - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_intersection_types - }); - return parseUtil_1.INVALID; + util2.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (object) => { + const keys = []; + for (const key in object) { + if (Object.prototype.hasOwnProperty.call(object, key)) { + keys.push(key); + } } - if ((0, parseUtil_1.isDirty)(parsedLeft) || (0, parseUtil_1.isDirty)(parsedRight)) { - status.dirty(); + return keys; + }; + util2.find = (arr, checker) => { + for (const item of arr) { + if (checker(item)) + return item; } + return void 0; + }; + util2.isInteger = typeof Number.isInteger === "function" ? (val) => Number.isInteger(val) : (val) => typeof val === "number" && isFinite(val) && Math.floor(val) === val; + function joinValues(array, separator = " | ") { + return array.map((val) => typeof val === "string" ? `'${val}'` : val).join(separator); + } + util2.joinValues = joinValues; + util2.jsonStringifyReplacer = (_, value) => { + if (typeof value === "bigint") { + return value.toString(); + } + return value; + }; + })(util || (util = {})); + var objectUtil; + (function(objectUtil2) { + objectUtil2.mergeShapes = (first, second) => { return { - status: status.value, - value: merged.data + ...first, + ...second + // second overwrites first }; }; - if (ctx.common.async) { - return Promise.all([this._def.left._parseAsync({ - data: ctx.data, - path: ctx.path, - parent: ctx - }), this._def.right._parseAsync({ - data: ctx.data, - path: ctx.path, - parent: ctx - })]).then(_ref => { - let [left, right] = _ref; - return handleParsed(left, right); - }); - } else { - return handleParsed(this._def.left._parseSync({ - data: ctx.data, - path: ctx.path, - parent: ctx - }), this._def.right._parseSync({ - data: ctx.data, - path: ctx.path, - parent: ctx - })); + })(objectUtil || (objectUtil = {})); + var ZodParsedType = util.arrayToEnum([ + "string", + "nan", + "number", + "integer", + "float", + "boolean", + "date", + "bigint", + "symbol", + "function", + "undefined", + "null", + "array", + "object", + "unknown", + "promise", + "void", + "never", + "map", + "set" + ]); + var getParsedType = (data) => { + const t = typeof data; + switch (t) { + case "undefined": + return ZodParsedType.undefined; + case "string": + return ZodParsedType.string; + case "number": + return isNaN(data) ? ZodParsedType.nan : ZodParsedType.number; + case "boolean": + return ZodParsedType.boolean; + case "function": + return ZodParsedType.function; + case "bigint": + return ZodParsedType.bigint; + case "symbol": + return ZodParsedType.symbol; + case "object": + if (Array.isArray(data)) { + return ZodParsedType.array; + } + if (data === null) { + return ZodParsedType.null; + } + if (data.then && typeof data.then === "function" && data.catch && typeof data.catch === "function") { + return ZodParsedType.promise; + } + if (typeof Map !== "undefined" && data instanceof Map) { + return ZodParsedType.map; + } + if (typeof Set !== "undefined" && data instanceof Set) { + return ZodParsedType.set; + } + if (typeof Date !== "undefined" && data instanceof Date) { + return ZodParsedType.date; + } + return ZodParsedType.object; + default: + return ZodParsedType.unknown; } - } -} -exports.ZodIntersection = ZodIntersection; -ZodIntersection.create = (left, right, params) => { - return new ZodIntersection({ - left: left, - right: right, - typeName: ZodFirstPartyTypeKind.ZodIntersection, - ...processCreateParams(params) - }); -}; -class ZodTuple extends ZodType { - _parse(input) { - const { - status, - ctx - } = this._processInputParams(input); - if (ctx.parsedType !== util_1.ZodParsedType.array) { - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_type, - expected: util_1.ZodParsedType.array, - received: ctx.parsedType - }); - return parseUtil_1.INVALID; + }; + var ZodIssueCode = util.arrayToEnum([ + "invalid_type", + "invalid_literal", + "custom", + "invalid_union", + "invalid_union_discriminator", + "invalid_enum_value", + "unrecognized_keys", + "invalid_arguments", + "invalid_return_type", + "invalid_date", + "invalid_string", + "too_small", + "too_big", + "invalid_intersection_types", + "not_multiple_of", + "not_finite" + ]); + var quotelessJson = (obj) => { + const json = JSON.stringify(obj, null, 2); + return json.replace(/"([^"]+)":/g, "$1:"); + }; + var ZodError = class extends Error { + constructor(issues) { + super(); + this.issues = []; + this.addIssue = (sub) => { + this.issues = [...this.issues, sub]; + }; + this.addIssues = (subs = []) => { + this.issues = [...this.issues, ...subs]; + }; + const actualProto = new.target.prototype; + if (Object.setPrototypeOf) { + Object.setPrototypeOf(this, actualProto); + } else { + this.__proto__ = actualProto; + } + this.name = "ZodError"; + this.issues = issues; } - if (ctx.data.length < this._def.items.length) { - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.too_small, - minimum: this._def.items.length, - inclusive: true, - exact: false, - type: "array" - }); - return parseUtil_1.INVALID; + get errors() { + return this.issues; } - const rest = this._def.rest; - if (!rest && ctx.data.length > this._def.items.length) { - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.too_big, - maximum: this._def.items.length, - inclusive: true, - exact: false, - type: "array" - }); - status.dirty(); - } - const items = [...ctx.data].map((item, itemIndex) => { - const schema = this._def.items[itemIndex] || this._def.rest; - if (!schema) return null; - return schema._parse(new ParseInputLazyPath(ctx, item, ctx.path, itemIndex)); - }).filter(x => !!x); // filter nulls - if (ctx.common.async) { - return Promise.all(items).then(results => { - return parseUtil_1.ParseStatus.mergeArray(status, results); - }); - } else { - return parseUtil_1.ParseStatus.mergeArray(status, items); + format(_mapper) { + const mapper = _mapper || function(issue) { + return issue.message; + }; + const fieldErrors = { _errors: [] }; + const processError = (error) => { + for (const issue of error.issues) { + if (issue.code === "invalid_union") { + issue.unionErrors.map(processError); + } else if (issue.code === "invalid_return_type") { + processError(issue.returnTypeError); + } else if (issue.code === "invalid_arguments") { + processError(issue.argumentsError); + } else if (issue.path.length === 0) { + fieldErrors._errors.push(mapper(issue)); + } else { + let curr = fieldErrors; + let i = 0; + while (i < issue.path.length) { + const el = issue.path[i]; + const terminal = i === issue.path.length - 1; + if (!terminal) { + curr[el] = curr[el] || { _errors: [] }; + } else { + curr[el] = curr[el] || { _errors: [] }; + curr[el]._errors.push(mapper(issue)); + } + curr = curr[el]; + i++; + } + } + } + }; + processError(this); + return fieldErrors; } - } - get items() { - return this._def.items; - } - rest(rest) { - return new ZodTuple({ - ...this._def, - rest - }); - } -} -exports.ZodTuple = ZodTuple; -ZodTuple.create = (schemas, params) => { - if (!Array.isArray(schemas)) { - throw new Error("You must pass an array of schemas to z.tuple([ ... ])"); - } - return new ZodTuple({ - items: schemas, - typeName: ZodFirstPartyTypeKind.ZodTuple, - rest: null, - ...processCreateParams(params) - }); -}; -class ZodRecord extends ZodType { - get keySchema() { - return this._def.keyType; - } - get valueSchema() { - return this._def.valueType; - } - _parse(input) { - const { - status, - ctx - } = this._processInputParams(input); - if (ctx.parsedType !== util_1.ZodParsedType.object) { - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_type, - expected: util_1.ZodParsedType.object, - received: ctx.parsedType - }); - return parseUtil_1.INVALID; - } - const pairs = []; - const keyType = this._def.keyType; - const valueType = this._def.valueType; - for (const key in ctx.data) { - pairs.push({ - key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, key)), - value: valueType._parse(new ParseInputLazyPath(ctx, ctx.data[key], ctx.path, key)) - }); + toString() { + return this.message; } - if (ctx.common.async) { - return parseUtil_1.ParseStatus.mergeObjectAsync(status, pairs); - } else { - return parseUtil_1.ParseStatus.mergeObjectSync(status, pairs); + get message() { + return JSON.stringify(this.issues, util.jsonStringifyReplacer, 2); } - } - get element() { - return this._def.valueType; - } - static create(first, second, third) { - if (second instanceof ZodType) { - return new ZodRecord({ - keyType: first, - valueType: second, - typeName: ZodFirstPartyTypeKind.ZodRecord, - ...processCreateParams(third) - }); + get isEmpty() { + return this.issues.length === 0; } - return new ZodRecord({ - keyType: ZodString.create(), - valueType: first, - typeName: ZodFirstPartyTypeKind.ZodRecord, - ...processCreateParams(second) - }); - } -} -exports.ZodRecord = ZodRecord; -class ZodMap extends ZodType { - get keySchema() { - return this._def.keyType; - } - get valueSchema() { - return this._def.valueType; - } - _parse(input) { - const { - status, - ctx - } = this._processInputParams(input); - if (ctx.parsedType !== util_1.ZodParsedType.map) { - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_type, - expected: util_1.ZodParsedType.map, - received: ctx.parsedType - }); - return parseUtil_1.INVALID; + flatten(mapper = (issue) => issue.message) { + const fieldErrors = {}; + const formErrors = []; + for (const sub of this.issues) { + if (sub.path.length > 0) { + fieldErrors[sub.path[0]] = fieldErrors[sub.path[0]] || []; + fieldErrors[sub.path[0]].push(mapper(sub)); + } else { + formErrors.push(mapper(sub)); + } + } + return { formErrors, fieldErrors }; } - const keyType = this._def.keyType; - const valueType = this._def.valueType; - const pairs = [...ctx.data.entries()].map((_ref2, index) => { - let [key, value] = _ref2; - return { - key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, [index, "key"])), - value: valueType._parse(new ParseInputLazyPath(ctx, value, ctx.path, [index, "value"])) - }; - }); - if (ctx.common.async) { - const finalMap = new Map(); - return Promise.resolve().then(async () => { - for (const pair of pairs) { - const key = await pair.key; - const value = await pair.value; - if (key.status === "aborted" || value.status === "aborted") { - return parseUtil_1.INVALID; - } - if (key.status === "dirty" || value.status === "dirty") { - status.dirty(); - } - finalMap.set(key.value, value.value); + get formErrors() { + return this.flatten(); + } + }; + ZodError.create = (issues) => { + const error = new ZodError(issues); + return error; + }; + var errorMap = (issue, _ctx) => { + let message; + switch (issue.code) { + case ZodIssueCode.invalid_type: + if (issue.received === ZodParsedType.undefined) { + message = "Required"; + } else { + message = `Expected ${issue.expected}, received ${issue.received}`; } - return { - status: status.value, - value: finalMap - }; - }); - } else { - const finalMap = new Map(); - for (const pair of pairs) { - const key = pair.key; - const value = pair.value; - if (key.status === "aborted" || value.status === "aborted") { - return parseUtil_1.INVALID; + break; + case ZodIssueCode.invalid_literal: + message = `Invalid literal value, expected ${JSON.stringify(issue.expected, util.jsonStringifyReplacer)}`; + break; + case ZodIssueCode.unrecognized_keys: + message = `Unrecognized key(s) in object: ${util.joinValues(issue.keys, ", ")}`; + break; + case ZodIssueCode.invalid_union: + message = `Invalid input`; + break; + case ZodIssueCode.invalid_union_discriminator: + message = `Invalid discriminator value. Expected ${util.joinValues(issue.options)}`; + break; + case ZodIssueCode.invalid_enum_value: + message = `Invalid enum value. Expected ${util.joinValues(issue.options)}, received '${issue.received}'`; + break; + case ZodIssueCode.invalid_arguments: + message = `Invalid function arguments`; + break; + case ZodIssueCode.invalid_return_type: + message = `Invalid function return type`; + break; + case ZodIssueCode.invalid_date: + message = `Invalid date`; + break; + case ZodIssueCode.invalid_string: + if (typeof issue.validation === "object") { + if ("includes" in issue.validation) { + message = `Invalid input: must include "${issue.validation.includes}"`; + if (typeof issue.validation.position === "number") { + message = `${message} at one or more positions greater than or equal to ${issue.validation.position}`; + } + } else if ("startsWith" in issue.validation) { + message = `Invalid input: must start with "${issue.validation.startsWith}"`; + } else if ("endsWith" in issue.validation) { + message = `Invalid input: must end with "${issue.validation.endsWith}"`; + } else { + util.assertNever(issue.validation); + } + } else if (issue.validation !== "regex") { + message = `Invalid ${issue.validation}`; + } else { + message = "Invalid"; } - if (key.status === "dirty" || value.status === "dirty") { + break; + case ZodIssueCode.too_small: + if (issue.type === "array") + message = `Array must contain ${issue.exact ? "exactly" : issue.inclusive ? `at least` : `more than`} ${issue.minimum} element(s)`; + else if (issue.type === "string") + message = `String must contain ${issue.exact ? "exactly" : issue.inclusive ? `at least` : `over`} ${issue.minimum} character(s)`; + else if (issue.type === "number") + message = `Number must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${issue.minimum}`; + else if (issue.type === "date") + message = `Date must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${new Date(Number(issue.minimum))}`; + else + message = "Invalid input"; + break; + case ZodIssueCode.too_big: + if (issue.type === "array") + message = `Array must contain ${issue.exact ? `exactly` : issue.inclusive ? `at most` : `less than`} ${issue.maximum} element(s)`; + else if (issue.type === "string") + message = `String must contain ${issue.exact ? `exactly` : issue.inclusive ? `at most` : `under`} ${issue.maximum} character(s)`; + else if (issue.type === "number") + message = `Number must be ${issue.exact ? `exactly` : issue.inclusive ? `less than or equal to` : `less than`} ${issue.maximum}`; + else if (issue.type === "bigint") + message = `BigInt must be ${issue.exact ? `exactly` : issue.inclusive ? `less than or equal to` : `less than`} ${issue.maximum}`; + else if (issue.type === "date") + message = `Date must be ${issue.exact ? `exactly` : issue.inclusive ? `smaller than or equal to` : `smaller than`} ${new Date(Number(issue.maximum))}`; + else + message = "Invalid input"; + break; + case ZodIssueCode.custom: + message = `Invalid input`; + break; + case ZodIssueCode.invalid_intersection_types: + message = `Intersection results could not be merged`; + break; + case ZodIssueCode.not_multiple_of: + message = `Number must be a multiple of ${issue.multipleOf}`; + break; + case ZodIssueCode.not_finite: + message = "Number must be finite"; + break; + default: + message = _ctx.defaultError; + util.assertNever(issue); + } + return { message }; + }; + var overrideErrorMap = errorMap; + function setErrorMap(map) { + overrideErrorMap = map; + } + function getErrorMap() { + return overrideErrorMap; + } + var makeIssue = (params) => { + const { data, path, errorMaps, issueData } = params; + const fullPath = [...path, ...issueData.path || []]; + const fullIssue = { + ...issueData, + path: fullPath + }; + let errorMessage = ""; + const maps = errorMaps.filter((m) => !!m).slice().reverse(); + for (const map of maps) { + errorMessage = map(fullIssue, { data, defaultError: errorMessage }).message; + } + return { + ...issueData, + path: fullPath, + message: issueData.message || errorMessage + }; + }; + var EMPTY_PATH = []; + function addIssueToContext(ctx, issueData) { + const issue = makeIssue({ + issueData, + data: ctx.data, + path: ctx.path, + errorMaps: [ + ctx.common.contextualErrorMap, + ctx.schemaErrorMap, + getErrorMap(), + errorMap + // then global default map + ].filter((x) => !!x) + }); + ctx.common.issues.push(issue); + } + var ParseStatus = class { + constructor() { + this.value = "valid"; + } + dirty() { + if (this.value === "valid") + this.value = "dirty"; + } + abort() { + if (this.value !== "aborted") + this.value = "aborted"; + } + static mergeArray(status, results) { + const arrayValue = []; + for (const s of results) { + if (s.status === "aborted") + return INVALID; + if (s.status === "dirty") status.dirty(); - } - finalMap.set(key.value, value.value); + arrayValue.push(s.value); } - return { - status: status.value, - value: finalMap - }; + return { status: status.value, value: arrayValue }; } - } -} -exports.ZodMap = ZodMap; -ZodMap.create = (keyType, valueType, params) => { - return new ZodMap({ - valueType, - keyType, - typeName: ZodFirstPartyTypeKind.ZodMap, - ...processCreateParams(params) - }); -}; -class ZodSet extends ZodType { - _parse(input) { - const { - status, - ctx - } = this._processInputParams(input); - if (ctx.parsedType !== util_1.ZodParsedType.set) { - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_type, - expected: util_1.ZodParsedType.set, - received: ctx.parsedType - }); - return parseUtil_1.INVALID; - } - const def = this._def; - if (def.minSize !== null) { - if (ctx.data.size < def.minSize.value) { - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.too_small, - minimum: def.minSize.value, - type: "set", - inclusive: true, - exact: false, - message: def.minSize.message + static async mergeObjectAsync(status, pairs) { + const syncPairs = []; + for (const pair of pairs) { + syncPairs.push({ + key: await pair.key, + value: await pair.value }); - status.dirty(); } + return ParseStatus.mergeObjectSync(status, syncPairs); } - if (def.maxSize !== null) { - if (ctx.data.size > def.maxSize.value) { - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.too_big, - maximum: def.maxSize.value, - type: "set", - inclusive: true, - exact: false, - message: def.maxSize.message - }); - status.dirty(); + static mergeObjectSync(status, pairs) { + const finalObject = {}; + for (const pair of pairs) { + const { key, value } = pair; + if (key.status === "aborted") + return INVALID; + if (value.status === "aborted") + return INVALID; + if (key.status === "dirty") + status.dirty(); + if (value.status === "dirty") + status.dirty(); + if (key.value !== "__proto__" && (typeof value.value !== "undefined" || pair.alwaysSet)) { + finalObject[key.value] = value.value; + } + } + return { status: status.value, value: finalObject }; + } + }; + var INVALID = Object.freeze({ + status: "aborted" + }); + var DIRTY = (value) => ({ status: "dirty", value }); + var OK = (value) => ({ status: "valid", value }); + var isAborted = (x) => x.status === "aborted"; + var isDirty = (x) => x.status === "dirty"; + var isValid = (x) => x.status === "valid"; + var isAsync = (x) => typeof Promise !== "undefined" && x instanceof Promise; + var errorUtil; + (function(errorUtil2) { + errorUtil2.errToObj = (message) => typeof message === "string" ? { message } : message || {}; + errorUtil2.toString = (message) => typeof message === "string" ? message : message === null || message === void 0 ? void 0 : message.message; + })(errorUtil || (errorUtil = {})); + var ParseInputLazyPath = class { + constructor(parent, value, path, key) { + this._cachedPath = []; + this.parent = parent; + this.data = value; + this._path = path; + this._key = key; + } + get path() { + if (!this._cachedPath.length) { + if (this._key instanceof Array) { + this._cachedPath.push(...this._path, ...this._key); + } else { + this._cachedPath.push(...this._path, this._key); + } } + return this._cachedPath; } - const valueType = this._def.valueType; - function finalizeSet(elements) { - const parsedSet = new Set(); - for (const element of elements) { - if (element.status === "aborted") return parseUtil_1.INVALID; - if (element.status === "dirty") status.dirty(); - parsedSet.add(element.value); + }; + var handleResult = (ctx, result) => { + if (isValid(result)) { + return { success: true, data: result.value }; + } else { + if (!ctx.common.issues.length) { + throw new Error("Validation failed but no issues detected."); } return { - status: status.value, - value: parsedSet + success: false, + get error() { + if (this._error) + return this._error; + const error = new ZodError(ctx.common.issues); + this._error = error; + return this._error; + } }; } - const elements = [...ctx.data.values()].map((item, i) => valueType._parse(new ParseInputLazyPath(ctx, item, ctx.path, i))); - if (ctx.common.async) { - return Promise.all(elements).then(elements => finalizeSet(elements)); - } else { - return finalizeSet(elements); + }; + function processCreateParams(params) { + if (!params) + return {}; + const { errorMap: errorMap2, invalid_type_error, required_error, description } = params; + if (errorMap2 && (invalid_type_error || required_error)) { + throw new Error(`Can't use "invalid_type_error" or "required_error" in conjunction with custom error map.`); + } + if (errorMap2) + return { errorMap: errorMap2, description }; + const customMap = (iss, ctx) => { + if (iss.code !== "invalid_type") + return { message: ctx.defaultError }; + if (typeof ctx.data === "undefined") { + return { message: required_error !== null && required_error !== void 0 ? required_error : ctx.defaultError }; + } + return { message: invalid_type_error !== null && invalid_type_error !== void 0 ? invalid_type_error : ctx.defaultError }; + }; + return { errorMap: customMap, description }; + } + var ZodType = class { + constructor(def) { + this.spa = this.safeParseAsync; + this._def = def; + this.parse = this.parse.bind(this); + this.safeParse = this.safeParse.bind(this); + this.parseAsync = this.parseAsync.bind(this); + this.safeParseAsync = this.safeParseAsync.bind(this); + this.spa = this.spa.bind(this); + this.refine = this.refine.bind(this); + this.refinement = this.refinement.bind(this); + this.superRefine = this.superRefine.bind(this); + this.optional = this.optional.bind(this); + this.nullable = this.nullable.bind(this); + this.nullish = this.nullish.bind(this); + this.array = this.array.bind(this); + this.promise = this.promise.bind(this); + this.or = this.or.bind(this); + this.and = this.and.bind(this); + this.transform = this.transform.bind(this); + this.brand = this.brand.bind(this); + this.default = this.default.bind(this); + this.catch = this.catch.bind(this); + this.describe = this.describe.bind(this); + this.pipe = this.pipe.bind(this); + this.readonly = this.readonly.bind(this); + this.isNullable = this.isNullable.bind(this); + this.isOptional = this.isOptional.bind(this); + } + get description() { + return this._def.description; + } + _getType(input) { + return getParsedType(input.data); + } + _getOrReturnCtx(input, ctx) { + return ctx || { + common: input.parent.common, + data: input.data, + parsedType: getParsedType(input.data), + schemaErrorMap: this._def.errorMap, + path: input.path, + parent: input.parent + }; } - } - min(minSize, message) { - return new ZodSet({ - ...this._def, - minSize: { - value: minSize, - message: errorUtil_1.errorUtil.toString(message) - } - }); - } - max(maxSize, message) { - return new ZodSet({ - ...this._def, - maxSize: { - value: maxSize, - message: errorUtil_1.errorUtil.toString(message) - } - }); - } - size(size, message) { - return this.min(size, message).max(size, message); - } - nonempty(message) { - return this.min(1, message); - } -} -exports.ZodSet = ZodSet; -ZodSet.create = (valueType, params) => { - return new ZodSet({ - valueType, - minSize: null, - maxSize: null, - typeName: ZodFirstPartyTypeKind.ZodSet, - ...processCreateParams(params) - }); -}; -class ZodFunction extends ZodType { - constructor() { - super(...arguments); - this.validate = this.implement; - } - _parse(input) { - const { - ctx - } = this._processInputParams(input); - if (ctx.parsedType !== util_1.ZodParsedType.function) { - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_type, - expected: util_1.ZodParsedType.function, - received: ctx.parsedType - }); - return parseUtil_1.INVALID; + _processInputParams(input) { + return { + status: new ParseStatus(), + ctx: { + common: input.parent.common, + data: input.data, + parsedType: getParsedType(input.data), + schemaErrorMap: this._def.errorMap, + path: input.path, + parent: input.parent + } + }; } - function makeArgsIssue(args, error) { - return (0, parseUtil_1.makeIssue)({ - data: args, - path: ctx.path, - errorMaps: [ctx.common.contextualErrorMap, ctx.schemaErrorMap, (0, errors_1.getErrorMap)(), errors_1.defaultErrorMap].filter(x => !!x), - issueData: { - code: ZodError_1.ZodIssueCode.invalid_arguments, - argumentsError: error + _parseSync(input) { + const result = this._parse(input); + if (isAsync(result)) { + throw new Error("Synchronous parse encountered promise."); + } + return result; + } + _parseAsync(input) { + const result = this._parse(input); + return Promise.resolve(result); + } + parse(data, params) { + const result = this.safeParse(data, params); + if (result.success) + return result.data; + throw result.error; + } + safeParse(data, params) { + var _a; + const ctx = { + common: { + issues: [], + async: (_a = params === null || params === void 0 ? void 0 : params.async) !== null && _a !== void 0 ? _a : false, + contextualErrorMap: params === null || params === void 0 ? void 0 : params.errorMap + }, + path: (params === null || params === void 0 ? void 0 : params.path) || [], + schemaErrorMap: this._def.errorMap, + parent: null, + data, + parsedType: getParsedType(data) + }; + const result = this._parseSync({ data, path: ctx.path, parent: ctx }); + return handleResult(ctx, result); + } + async parseAsync(data, params) { + const result = await this.safeParseAsync(data, params); + if (result.success) + return result.data; + throw result.error; + } + async safeParseAsync(data, params) { + const ctx = { + common: { + issues: [], + contextualErrorMap: params === null || params === void 0 ? void 0 : params.errorMap, + async: true + }, + path: (params === null || params === void 0 ? void 0 : params.path) || [], + schemaErrorMap: this._def.errorMap, + parent: null, + data, + parsedType: getParsedType(data) + }; + const maybeAsyncResult = this._parse({ data, path: ctx.path, parent: ctx }); + const result = await (isAsync(maybeAsyncResult) ? maybeAsyncResult : Promise.resolve(maybeAsyncResult)); + return handleResult(ctx, result); + } + refine(check, message) { + const getIssueProperties = (val) => { + if (typeof message === "string" || typeof message === "undefined") { + return { message }; + } else if (typeof message === "function") { + return message(val); + } else { + return message; + } + }; + return this._refinement((val, ctx) => { + const result = check(val); + const setError = () => ctx.addIssue({ + code: ZodIssueCode.custom, + ...getIssueProperties(val) + }); + if (typeof Promise !== "undefined" && result instanceof Promise) { + return result.then((data) => { + if (!data) { + setError(); + return false; + } else { + return true; + } + }); + } + if (!result) { + setError(); + return false; + } else { + return true; } }); } - function makeReturnsIssue(returns, error) { - return (0, parseUtil_1.makeIssue)({ - data: returns, - path: ctx.path, - errorMaps: [ctx.common.contextualErrorMap, ctx.schemaErrorMap, (0, errors_1.getErrorMap)(), errors_1.defaultErrorMap].filter(x => !!x), - issueData: { - code: ZodError_1.ZodIssueCode.invalid_return_type, - returnTypeError: error + refinement(check, refinementData) { + return this._refinement((val, ctx) => { + if (!check(val)) { + ctx.addIssue(typeof refinementData === "function" ? refinementData(val, ctx) : refinementData); + return false; + } else { + return true; } }); } - const params = { - errorMap: ctx.common.contextualErrorMap - }; - const fn = ctx.data; - if (this._def.returns instanceof ZodPromise) { - // Would love a way to avoid disabling this rule, but we need - // an alias (using an arrow function was what caused 2651). - // eslint-disable-next-line @typescript-eslint/no-this-alias - const me = this; - return (0, parseUtil_1.OK)(async function () { - for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { - args[_key] = arguments[_key]; - } - const error = new ZodError_1.ZodError([]); - const parsedArgs = await me._def.args.parseAsync(args, params).catch(e => { - error.addIssue(makeArgsIssue(args, e)); - throw error; - }); - const result = await Reflect.apply(fn, this, parsedArgs); - const parsedReturns = await me._def.returns._def.type.parseAsync(result, params).catch(e => { - error.addIssue(makeReturnsIssue(result, e)); - throw error; - }); - return parsedReturns; - }); - } else { - // Would love a way to avoid disabling this rule, but we need - // an alias (using an arrow function was what caused 2651). - // eslint-disable-next-line @typescript-eslint/no-this-alias - const me = this; - return (0, parseUtil_1.OK)(function () { - for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { - args[_key2] = arguments[_key2]; - } - const parsedArgs = me._def.args.safeParse(args, params); - if (!parsedArgs.success) { - throw new ZodError_1.ZodError([makeArgsIssue(args, parsedArgs.error)]); - } - const result = Reflect.apply(fn, this, parsedArgs.data); - const parsedReturns = me._def.returns.safeParse(result, params); - if (!parsedReturns.success) { - throw new ZodError_1.ZodError([makeReturnsIssue(result, parsedReturns.error)]); - } - return parsedReturns.data; + _refinement(refinement) { + return new ZodEffects({ + schema: this, + typeName: ZodFirstPartyTypeKind.ZodEffects, + effect: { type: "refinement", refinement } }); } - } - parameters() { - return this._def.args; - } - returnType() { - return this._def.returns; - } - args() { - for (var _len3 = arguments.length, items = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { - items[_key3] = arguments[_key3]; + superRefine(refinement) { + return this._refinement(refinement); } - return new ZodFunction({ - ...this._def, - args: ZodTuple.create(items).rest(ZodUnknown.create()) - }); - } - returns(returnType) { - return new ZodFunction({ - ...this._def, - returns: returnType - }); - } - implement(func) { - const validatedFunc = this.parse(func); - return validatedFunc; - } - strictImplement(func) { - const validatedFunc = this.parse(func); - return validatedFunc; - } - static create(args, returns, params) { - return new ZodFunction({ - args: args ? args : ZodTuple.create([]).rest(ZodUnknown.create()), - returns: returns || ZodUnknown.create(), - typeName: ZodFirstPartyTypeKind.ZodFunction, - ...processCreateParams(params) - }); - } -} -exports.ZodFunction = ZodFunction; -class ZodLazy extends ZodType { - get schema() { - return this._def.getter(); - } - _parse(input) { - const { - ctx - } = this._processInputParams(input); - const lazySchema = this._def.getter(); - return lazySchema._parse({ - data: ctx.data, - path: ctx.path, - parent: ctx - }); - } -} -exports.ZodLazy = ZodLazy; -ZodLazy.create = (getter, params) => { - return new ZodLazy({ - getter: getter, - typeName: ZodFirstPartyTypeKind.ZodLazy, - ...processCreateParams(params) - }); -}; -class ZodLiteral extends ZodType { - _parse(input) { - if (input.data !== this._def.value) { - const ctx = this._getOrReturnCtx(input); - (0, parseUtil_1.addIssueToContext)(ctx, { - received: ctx.data, - code: ZodError_1.ZodIssueCode.invalid_literal, - expected: this._def.value - }); - return parseUtil_1.INVALID; + optional() { + return ZodOptional.create(this, this._def); } - return { - status: "valid", - value: input.data - }; - } - get value() { - return this._def.value; - } -} -exports.ZodLiteral = ZodLiteral; -ZodLiteral.create = (value, params) => { - return new ZodLiteral({ - value: value, - typeName: ZodFirstPartyTypeKind.ZodLiteral, - ...processCreateParams(params) - }); -}; -function createZodEnum(values, params) { - return new ZodEnum({ - values, - typeName: ZodFirstPartyTypeKind.ZodEnum, - ...processCreateParams(params) - }); -} -class ZodEnum extends ZodType { - _parse(input) { - if (typeof input.data !== "string") { - const ctx = this._getOrReturnCtx(input); - const expectedValues = this._def.values; - (0, parseUtil_1.addIssueToContext)(ctx, { - expected: util_1.util.joinValues(expectedValues), - received: ctx.parsedType, - code: ZodError_1.ZodIssueCode.invalid_type - }); - return parseUtil_1.INVALID; + nullable() { + return ZodNullable.create(this, this._def); } - if (this._def.values.indexOf(input.data) === -1) { - const ctx = this._getOrReturnCtx(input); - const expectedValues = this._def.values; - (0, parseUtil_1.addIssueToContext)(ctx, { - received: ctx.data, - code: ZodError_1.ZodIssueCode.invalid_enum_value, - options: expectedValues - }); - return parseUtil_1.INVALID; + nullish() { + return this.nullable().optional(); } - return (0, parseUtil_1.OK)(input.data); - } - get options() { - return this._def.values; - } - get enum() { - const enumValues = {}; - for (const val of this._def.values) { - enumValues[val] = val; + array() { + return ZodArray.create(this, this._def); } - return enumValues; - } - get Values() { - const enumValues = {}; - for (const val of this._def.values) { - enumValues[val] = val; + promise() { + return ZodPromise.create(this, this._def); } - return enumValues; - } - get Enum() { - const enumValues = {}; - for (const val of this._def.values) { - enumValues[val] = val; + or(option) { + return ZodUnion.create([this, option], this._def); } - return enumValues; - } - extract(values) { - return ZodEnum.create(values); - } - exclude(values) { - return ZodEnum.create(this.options.filter(opt => !values.includes(opt))); - } -} -exports.ZodEnum = ZodEnum; -ZodEnum.create = createZodEnum; -class ZodNativeEnum extends ZodType { - _parse(input) { - const nativeEnumValues = util_1.util.getValidEnumValues(this._def.values); - const ctx = this._getOrReturnCtx(input); - if (ctx.parsedType !== util_1.ZodParsedType.string && ctx.parsedType !== util_1.ZodParsedType.number) { - const expectedValues = util_1.util.objectValues(nativeEnumValues); - (0, parseUtil_1.addIssueToContext)(ctx, { - expected: util_1.util.joinValues(expectedValues), - received: ctx.parsedType, - code: ZodError_1.ZodIssueCode.invalid_type + and(incoming) { + return ZodIntersection.create(this, incoming, this._def); + } + transform(transform) { + return new ZodEffects({ + ...processCreateParams(this._def), + schema: this, + typeName: ZodFirstPartyTypeKind.ZodEffects, + effect: { type: "transform", transform } }); - return parseUtil_1.INVALID; - } - if (nativeEnumValues.indexOf(input.data) === -1) { - const expectedValues = util_1.util.objectValues(nativeEnumValues); - (0, parseUtil_1.addIssueToContext)(ctx, { - received: ctx.data, - code: ZodError_1.ZodIssueCode.invalid_enum_value, - options: expectedValues + } + default(def) { + const defaultValueFunc = typeof def === "function" ? def : () => def; + return new ZodDefault({ + ...processCreateParams(this._def), + innerType: this, + defaultValue: defaultValueFunc, + typeName: ZodFirstPartyTypeKind.ZodDefault }); - return parseUtil_1.INVALID; } - return (0, parseUtil_1.OK)(input.data); - } - get enum() { - return this._def.values; - } -} -exports.ZodNativeEnum = ZodNativeEnum; -ZodNativeEnum.create = (values, params) => { - return new ZodNativeEnum({ - values: values, - typeName: ZodFirstPartyTypeKind.ZodNativeEnum, - ...processCreateParams(params) - }); -}; -class ZodPromise extends ZodType { - unwrap() { - return this._def.type; - } - _parse(input) { - const { - ctx - } = this._processInputParams(input); - if (ctx.parsedType !== util_1.ZodParsedType.promise && ctx.common.async === false) { - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_type, - expected: util_1.ZodParsedType.promise, - received: ctx.parsedType + brand() { + return new ZodBranded({ + typeName: ZodFirstPartyTypeKind.ZodBranded, + type: this, + ...processCreateParams(this._def) }); - return parseUtil_1.INVALID; } - const promisified = ctx.parsedType === util_1.ZodParsedType.promise ? ctx.data : Promise.resolve(ctx.data); - return (0, parseUtil_1.OK)(promisified.then(data => { - return this._def.type.parseAsync(data, { - path: ctx.path, - errorMap: ctx.common.contextualErrorMap + catch(def) { + const catchValueFunc = typeof def === "function" ? def : () => def; + return new ZodCatch({ + ...processCreateParams(this._def), + innerType: this, + catchValue: catchValueFunc, + typeName: ZodFirstPartyTypeKind.ZodCatch }); - })); - } -} -exports.ZodPromise = ZodPromise; -ZodPromise.create = (schema, params) => { - return new ZodPromise({ - type: schema, - typeName: ZodFirstPartyTypeKind.ZodPromise, - ...processCreateParams(params) - }); -}; -class ZodEffects extends ZodType { - innerType() { - return this._def.schema; - } - sourceType() { - return this._def.schema._def.typeName === ZodFirstPartyTypeKind.ZodEffects ? this._def.schema.sourceType() : this._def.schema; - } - _parse(input) { - const { - status, - ctx - } = this._processInputParams(input); - const effect = this._def.effect || null; - const checkCtx = { - addIssue: arg => { - (0, parseUtil_1.addIssueToContext)(ctx, arg); - if (arg.fatal) { - status.abort(); - } else { - status.dirty(); - } - }, - get path() { - return ctx.path; - } - }; - checkCtx.addIssue = checkCtx.addIssue.bind(checkCtx); - if (effect.type === "preprocess") { - const processed = effect.transform(ctx.data, checkCtx); - if (ctx.common.issues.length) { - return { - status: "dirty", - value: ctx.data - }; - } - if (ctx.common.async) { - return Promise.resolve(processed).then(processed => { - return this._def.schema._parseAsync({ - data: processed, - path: ctx.path, - parent: ctx - }); - }); + } + describe(description) { + const This = this.constructor; + return new This({ + ...this._def, + description + }); + } + pipe(target) { + return ZodPipeline.create(this, target); + } + readonly() { + return ZodReadonly.create(this); + } + isOptional() { + return this.safeParse(void 0).success; + } + isNullable() { + return this.safeParse(null).success; + } + }; + var cuidRegex = /^c[^\s-]{8,}$/i; + var cuid2Regex = /^[a-z][a-z0-9]*$/; + var ulidRegex = /^[0-9A-HJKMNP-TV-Z]{26}$/; + var uuidRegex = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/i; + var emailRegex = /^(?!\.)(?!.*\.\.)([A-Z0-9_+-\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i; + var _emojiRegex = `^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$`; + var emojiRegex; + var ipv4Regex = /^(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))$/; + var ipv6Regex = /^(([a-f0-9]{1,4}:){7}|::([a-f0-9]{1,4}:){0,6}|([a-f0-9]{1,4}:){1}:([a-f0-9]{1,4}:){0,5}|([a-f0-9]{1,4}:){2}:([a-f0-9]{1,4}:){0,4}|([a-f0-9]{1,4}:){3}:([a-f0-9]{1,4}:){0,3}|([a-f0-9]{1,4}:){4}:([a-f0-9]{1,4}:){0,2}|([a-f0-9]{1,4}:){5}:([a-f0-9]{1,4}:){0,1})([a-f0-9]{1,4}|(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2})))$/; + var datetimeRegex = (args) => { + if (args.precision) { + if (args.offset) { + return new RegExp(`^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{${args.precision}}(([+-]\\d{2}(:?\\d{2})?)|Z)$`); } else { - return this._def.schema._parseSync({ - data: processed, - path: ctx.path, - parent: ctx - }); + return new RegExp(`^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{${args.precision}}Z$`); } - } - if (effect.type === "refinement") { - const executeRefinement = (acc - // effect: RefinementEffect - ) => { - const result = effect.refinement(acc, checkCtx); - if (ctx.common.async) { - return Promise.resolve(result); - } - if (result instanceof Promise) { - throw new Error("Async refinement encountered during synchronous parse operation. Use .parseAsync instead."); - } - return acc; - }; - if (ctx.common.async === false) { - const inner = this._def.schema._parseSync({ - data: ctx.data, - path: ctx.path, - parent: ctx - }); - if (inner.status === "aborted") return parseUtil_1.INVALID; - if (inner.status === "dirty") status.dirty(); - // return value is ignored - executeRefinement(inner.value); - return { - status: status.value, - value: inner.value - }; + } else if (args.precision === 0) { + if (args.offset) { + return new RegExp(`^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(([+-]\\d{2}(:?\\d{2})?)|Z)$`); } else { - return this._def.schema._parseAsync({ - data: ctx.data, - path: ctx.path, - parent: ctx - }).then(inner => { - if (inner.status === "aborted") return parseUtil_1.INVALID; - if (inner.status === "dirty") status.dirty(); - return executeRefinement(inner.value).then(() => { - return { - status: status.value, - value: inner.value - }; - }); - }); + return new RegExp(`^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$`); } - } - if (effect.type === "transform") { - if (ctx.common.async === false) { - const base = this._def.schema._parseSync({ - data: ctx.data, - path: ctx.path, - parent: ctx - }); - if (!(0, parseUtil_1.isValid)(base)) return base; - const result = effect.transform(base.value, checkCtx); - if (result instanceof Promise) { - throw new Error(`Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.`); - } - return { - status: status.value, - value: result - }; + } else { + if (args.offset) { + return new RegExp(`^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(([+-]\\d{2}(:?\\d{2})?)|Z)$`); } else { - return this._def.schema._parseAsync({ - data: ctx.data, - path: ctx.path, - parent: ctx - }).then(base => { - if (!(0, parseUtil_1.isValid)(base)) return base; - return Promise.resolve(effect.transform(base.value, checkCtx)).then(result => ({ - status: status.value, - value: result - })); - }); + return new RegExp(`^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?Z$`); } } - util_1.util.assertNever(effect); - } -} -exports.ZodEffects = ZodEffects; -exports.ZodTransformer = ZodEffects; -ZodEffects.create = (schema, effect, params) => { - return new ZodEffects({ - schema, - typeName: ZodFirstPartyTypeKind.ZodEffects, - effect, - ...processCreateParams(params) - }); -}; -ZodEffects.createWithPreprocess = (preprocess, schema, params) => { - return new ZodEffects({ - schema, - effect: { - type: "preprocess", - transform: preprocess - }, - typeName: ZodFirstPartyTypeKind.ZodEffects, - ...processCreateParams(params) - }); -}; -class ZodOptional extends ZodType { - _parse(input) { - const parsedType = this._getType(input); - if (parsedType === util_1.ZodParsedType.undefined) { - return (0, parseUtil_1.OK)(undefined); - } - return this._def.innerType._parse(input); - } - unwrap() { - return this._def.innerType; - } -} -exports.ZodOptional = ZodOptional; -ZodOptional.create = (type, params) => { - return new ZodOptional({ - innerType: type, - typeName: ZodFirstPartyTypeKind.ZodOptional, - ...processCreateParams(params) - }); -}; -class ZodNullable extends ZodType { - _parse(input) { - const parsedType = this._getType(input); - if (parsedType === util_1.ZodParsedType.null) { - return (0, parseUtil_1.OK)(null); - } - return this._def.innerType._parse(input); - } - unwrap() { - return this._def.innerType; - } -} -exports.ZodNullable = ZodNullable; -ZodNullable.create = (type, params) => { - return new ZodNullable({ - innerType: type, - typeName: ZodFirstPartyTypeKind.ZodNullable, - ...processCreateParams(params) - }); -}; -class ZodDefault extends ZodType { - _parse(input) { - const { - ctx - } = this._processInputParams(input); - let data = ctx.data; - if (ctx.parsedType === util_1.ZodParsedType.undefined) { - data = this._def.defaultValue(); - } - return this._def.innerType._parse({ - data, - path: ctx.path, - parent: ctx - }); - } - removeDefault() { - return this._def.innerType; - } -} -exports.ZodDefault = ZodDefault; -ZodDefault.create = (type, params) => { - return new ZodDefault({ - innerType: type, - typeName: ZodFirstPartyTypeKind.ZodDefault, - defaultValue: typeof params.default === "function" ? params.default : () => params.default, - ...processCreateParams(params) - }); -}; -class ZodCatch extends ZodType { - _parse(input) { - const { - ctx - } = this._processInputParams(input); - // newCtx is used to not collect issues from inner types in ctx - const newCtx = { - ...ctx, - common: { - ...ctx.common, - issues: [] - } - }; - const result = this._def.innerType._parse({ - data: newCtx.data, - path: newCtx.path, - parent: { - ...newCtx - } - }); - if ((0, parseUtil_1.isAsync)(result)) { - return result.then(result => { - return { - status: "valid", - value: result.status === "valid" ? result.value : this._def.catchValue({ - get error() { - return new ZodError_1.ZodError(newCtx.common.issues); - }, - input: newCtx.data - }) - }; - }); - } else { - return { - status: "valid", - value: result.status === "valid" ? result.value : this._def.catchValue({ - get error() { - return new ZodError_1.ZodError(newCtx.common.issues); - }, - input: newCtx.data - }) - }; + }; + function isValidIP(ip, version) { + if ((version === "v4" || !version) && ipv4Regex.test(ip)) { + return true; } - } - removeCatch() { - return this._def.innerType; - } -} -exports.ZodCatch = ZodCatch; -ZodCatch.create = (type, params) => { - return new ZodCatch({ - innerType: type, - typeName: ZodFirstPartyTypeKind.ZodCatch, - catchValue: typeof params.catch === "function" ? params.catch : () => params.catch, - ...processCreateParams(params) - }); -}; -class ZodNaN extends ZodType { - _parse(input) { - const parsedType = this._getType(input); - if (parsedType !== util_1.ZodParsedType.nan) { - const ctx = this._getOrReturnCtx(input); - (0, parseUtil_1.addIssueToContext)(ctx, { - code: ZodError_1.ZodIssueCode.invalid_type, - expected: util_1.ZodParsedType.nan, - received: ctx.parsedType - }); - return parseUtil_1.INVALID; + if ((version === "v6" || !version) && ipv6Regex.test(ip)) { + return true; } - return { - status: "valid", - value: input.data - }; - } -} -exports.ZodNaN = ZodNaN; -ZodNaN.create = params => { - return new ZodNaN({ - typeName: ZodFirstPartyTypeKind.ZodNaN, - ...processCreateParams(params) - }); -}; -exports.BRAND = Symbol("zod_brand"); -class ZodBranded extends ZodType { - _parse(input) { - const { - ctx - } = this._processInputParams(input); - const data = ctx.data; - return this._def.type._parse({ - data, - path: ctx.path, - parent: ctx - }); - } - unwrap() { - return this._def.type; + return false; } -} -exports.ZodBranded = ZodBranded; -class ZodPipeline extends ZodType { - _parse(input) { - const { - status, - ctx - } = this._processInputParams(input); - if (ctx.common.async) { - const handleAsync = async () => { - const inResult = await this._def.in._parseAsync({ - data: ctx.data, - path: ctx.path, - parent: ctx - }); - if (inResult.status === "aborted") return parseUtil_1.INVALID; - if (inResult.status === "dirty") { - status.dirty(); - return (0, parseUtil_1.DIRTY)(inResult.value); + var ZodString = class extends ZodType { + _parse(input) { + if (this._def.coerce) { + input.data = String(input.data); + } + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.string) { + const ctx2 = this._getOrReturnCtx(input); + addIssueToContext( + ctx2, + { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.string, + received: ctx2.parsedType + } + // + ); + return INVALID; + } + const status = new ParseStatus(); + let ctx = void 0; + for (const check of this._def.checks) { + if (check.kind === "min") { + if (input.data.length < check.value) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: check.value, + type: "string", + inclusive: true, + exact: false, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "max") { + if (input.data.length > check.value) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: check.value, + type: "string", + inclusive: true, + exact: false, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "length") { + const tooBig = input.data.length > check.value; + const tooSmall = input.data.length < check.value; + if (tooBig || tooSmall) { + ctx = this._getOrReturnCtx(input, ctx); + if (tooBig) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: check.value, + type: "string", + inclusive: true, + exact: true, + message: check.message + }); + } else if (tooSmall) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: check.value, + type: "string", + inclusive: true, + exact: true, + message: check.message + }); + } + status.dirty(); + } + } else if (check.kind === "email") { + if (!emailRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "email", + code: ZodIssueCode.invalid_string, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "emoji") { + if (!emojiRegex) { + emojiRegex = new RegExp(_emojiRegex, "u"); + } + if (!emojiRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "emoji", + code: ZodIssueCode.invalid_string, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "uuid") { + if (!uuidRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "uuid", + code: ZodIssueCode.invalid_string, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "cuid") { + if (!cuidRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "cuid", + code: ZodIssueCode.invalid_string, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "cuid2") { + if (!cuid2Regex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "cuid2", + code: ZodIssueCode.invalid_string, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "ulid") { + if (!ulidRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "ulid", + code: ZodIssueCode.invalid_string, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "url") { + try { + new URL(input.data); + } catch (_a) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "url", + code: ZodIssueCode.invalid_string, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "regex") { + check.regex.lastIndex = 0; + const testResult = check.regex.test(input.data); + if (!testResult) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "regex", + code: ZodIssueCode.invalid_string, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "trim") { + input.data = input.data.trim(); + } else if (check.kind === "includes") { + if (!input.data.includes(check.value, check.position)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_string, + validation: { includes: check.value, position: check.position }, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "toLowerCase") { + input.data = input.data.toLowerCase(); + } else if (check.kind === "toUpperCase") { + input.data = input.data.toUpperCase(); + } else if (check.kind === "startsWith") { + if (!input.data.startsWith(check.value)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_string, + validation: { startsWith: check.value }, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "endsWith") { + if (!input.data.endsWith(check.value)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_string, + validation: { endsWith: check.value }, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "datetime") { + const regex = datetimeRegex(check); + if (!regex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_string, + validation: "datetime", + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "ip") { + if (!isValidIP(input.data, check.version)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "ip", + code: ZodIssueCode.invalid_string, + message: check.message + }); + status.dirty(); + } } else { - return this._def.out._parseAsync({ - data: inResult.value, - path: ctx.path, - parent: ctx - }); + util.assertNever(check); } - }; - return handleAsync(); - } else { - const inResult = this._def.in._parseSync({ - data: ctx.data, - path: ctx.path, - parent: ctx - }); - if (inResult.status === "aborted") return parseUtil_1.INVALID; - if (inResult.status === "dirty") { - status.dirty(); - return { - status: "dirty", - value: inResult.value - }; - } else { - return this._def.out._parseSync({ - data: inResult.value, - path: ctx.path, - parent: ctx - }); } + return { status: status.value, value: input.data }; } - } - static create(a, b) { - return new ZodPipeline({ - in: a, - out: b, - typeName: ZodFirstPartyTypeKind.ZodPipeline - }); - } -} -exports.ZodPipeline = ZodPipeline; -class ZodReadonly extends ZodType { - _parse(input) { - const result = this._def.innerType._parse(input); - if ((0, parseUtil_1.isValid)(result)) { - result.value = Object.freeze(result.value); - } - return result; - } -} -exports.ZodReadonly = ZodReadonly; -ZodReadonly.create = (type, params) => { - return new ZodReadonly({ - innerType: type, - typeName: ZodFirstPartyTypeKind.ZodReadonly, - ...processCreateParams(params) - }); -}; -const custom = function (check) { - let params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - let - /** - * @deprecated - * - * Pass `fatal` into the params object instead: - * - * ```ts - * z.string().custom((val) => val.length > 5, { fatal: false }) - * ``` - * - */ - fatal = arguments.length > 2 ? arguments[2] : undefined; - if (check) return ZodAny.create().superRefine((data, ctx) => { - var _a, _b; - if (!check(data)) { - const p = typeof params === "function" ? params(data) : typeof params === "string" ? { - message: params - } : params; - const _fatal = (_b = (_a = p.fatal) !== null && _a !== void 0 ? _a : fatal) !== null && _b !== void 0 ? _b : true; - const p2 = typeof p === "string" ? { - message: p - } : p; - ctx.addIssue({ - code: "custom", - ...p2, - fatal: _fatal + _regex(regex, validation, message) { + return this.refinement((data) => regex.test(data), { + validation, + code: ZodIssueCode.invalid_string, + ...errorUtil.errToObj(message) }); } - }); - return ZodAny.create(); -}; -exports.custom = custom; -exports.late = { - object: ZodObject.lazycreate -}; -var ZodFirstPartyTypeKind; -(function (ZodFirstPartyTypeKind) { - ZodFirstPartyTypeKind["ZodString"] = "ZodString"; - ZodFirstPartyTypeKind["ZodNumber"] = "ZodNumber"; - ZodFirstPartyTypeKind["ZodNaN"] = "ZodNaN"; - ZodFirstPartyTypeKind["ZodBigInt"] = "ZodBigInt"; - ZodFirstPartyTypeKind["ZodBoolean"] = "ZodBoolean"; - ZodFirstPartyTypeKind["ZodDate"] = "ZodDate"; - ZodFirstPartyTypeKind["ZodSymbol"] = "ZodSymbol"; - ZodFirstPartyTypeKind["ZodUndefined"] = "ZodUndefined"; - ZodFirstPartyTypeKind["ZodNull"] = "ZodNull"; - ZodFirstPartyTypeKind["ZodAny"] = "ZodAny"; - ZodFirstPartyTypeKind["ZodUnknown"] = "ZodUnknown"; - ZodFirstPartyTypeKind["ZodNever"] = "ZodNever"; - ZodFirstPartyTypeKind["ZodVoid"] = "ZodVoid"; - ZodFirstPartyTypeKind["ZodArray"] = "ZodArray"; - ZodFirstPartyTypeKind["ZodObject"] = "ZodObject"; - ZodFirstPartyTypeKind["ZodUnion"] = "ZodUnion"; - ZodFirstPartyTypeKind["ZodDiscriminatedUnion"] = "ZodDiscriminatedUnion"; - ZodFirstPartyTypeKind["ZodIntersection"] = "ZodIntersection"; - ZodFirstPartyTypeKind["ZodTuple"] = "ZodTuple"; - ZodFirstPartyTypeKind["ZodRecord"] = "ZodRecord"; - ZodFirstPartyTypeKind["ZodMap"] = "ZodMap"; - ZodFirstPartyTypeKind["ZodSet"] = "ZodSet"; - ZodFirstPartyTypeKind["ZodFunction"] = "ZodFunction"; - ZodFirstPartyTypeKind["ZodLazy"] = "ZodLazy"; - ZodFirstPartyTypeKind["ZodLiteral"] = "ZodLiteral"; - ZodFirstPartyTypeKind["ZodEnum"] = "ZodEnum"; - ZodFirstPartyTypeKind["ZodEffects"] = "ZodEffects"; - ZodFirstPartyTypeKind["ZodNativeEnum"] = "ZodNativeEnum"; - ZodFirstPartyTypeKind["ZodOptional"] = "ZodOptional"; - ZodFirstPartyTypeKind["ZodNullable"] = "ZodNullable"; - ZodFirstPartyTypeKind["ZodDefault"] = "ZodDefault"; - ZodFirstPartyTypeKind["ZodCatch"] = "ZodCatch"; - ZodFirstPartyTypeKind["ZodPromise"] = "ZodPromise"; - ZodFirstPartyTypeKind["ZodBranded"] = "ZodBranded"; - ZodFirstPartyTypeKind["ZodPipeline"] = "ZodPipeline"; - ZodFirstPartyTypeKind["ZodReadonly"] = "ZodReadonly"; -})(ZodFirstPartyTypeKind = exports.ZodFirstPartyTypeKind || (exports.ZodFirstPartyTypeKind = {})); -// requires TS 4.4+ -class Class { - constructor() {} -} -const instanceOfType = function ( -// const instanceOfType = any>( -cls) { - let params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { - message: `Input not instance of ${cls.name}` - }; - return (0, exports.custom)(data => data instanceof cls, params); -}; -exports.instanceof = instanceOfType; -const stringType = ZodString.create; -exports.string = stringType; -const numberType = ZodNumber.create; -exports.number = numberType; -const nanType = ZodNaN.create; -exports.nan = nanType; -const bigIntType = ZodBigInt.create; -exports.bigint = bigIntType; -const booleanType = ZodBoolean.create; -exports.boolean = booleanType; -const dateType = ZodDate.create; -exports.date = dateType; -const symbolType = ZodSymbol.create; -exports.symbol = symbolType; -const undefinedType = ZodUndefined.create; -exports.undefined = undefinedType; -const nullType = ZodNull.create; -exports.null = nullType; -const anyType = ZodAny.create; -exports.any = anyType; -const unknownType = ZodUnknown.create; -exports.unknown = unknownType; -const neverType = ZodNever.create; -exports.never = neverType; -const voidType = ZodVoid.create; -exports.void = voidType; -const arrayType = ZodArray.create; -exports.array = arrayType; -const objectType = ZodObject.create; -exports.object = objectType; -const strictObjectType = ZodObject.strictCreate; -exports.strictObject = strictObjectType; -const unionType = ZodUnion.create; -exports.union = unionType; -const discriminatedUnionType = ZodDiscriminatedUnion.create; -exports.discriminatedUnion = discriminatedUnionType; -const intersectionType = ZodIntersection.create; -exports.intersection = intersectionType; -const tupleType = ZodTuple.create; -exports.tuple = tupleType; -const recordType = ZodRecord.create; -exports.record = recordType; -const mapType = ZodMap.create; -exports.map = mapType; -const setType = ZodSet.create; -exports.set = setType; -const functionType = ZodFunction.create; -exports.function = functionType; -const lazyType = ZodLazy.create; -exports.lazy = lazyType; -const literalType = ZodLiteral.create; -exports.literal = literalType; -const enumType = ZodEnum.create; -exports.enum = enumType; -const nativeEnumType = ZodNativeEnum.create; -exports.nativeEnum = nativeEnumType; -const promiseType = ZodPromise.create; -exports.promise = promiseType; -const effectsType = ZodEffects.create; -exports.effect = effectsType; -exports.transformer = effectsType; -const optionalType = ZodOptional.create; -exports.optional = optionalType; -const nullableType = ZodNullable.create; -exports.nullable = nullableType; -const preprocessType = ZodEffects.createWithPreprocess; -exports.preprocess = preprocessType; -const pipelineType = ZodPipeline.create; -exports.pipeline = pipelineType; -const ostring = () => stringType().optional(); -exports.ostring = ostring; -const onumber = () => numberType().optional(); -exports.onumber = onumber; -const oboolean = () => booleanType().optional(); -exports.oboolean = oboolean; -exports.coerce = { - string: arg => ZodString.create({ - ...arg, - coerce: true - }), - number: arg => ZodNumber.create({ - ...arg, - coerce: true - }), - boolean: arg => ZodBoolean.create({ - ...arg, - coerce: true - }), - bigint: arg => ZodBigInt.create({ - ...arg, - coerce: true - }), - date: arg => ZodDate.create({ - ...arg, - coerce: true - }) -}; -exports.NEVER = parseUtil_1.INVALID; - -},{"./ZodError":2,"./errors":3,"./helpers/errorUtil":5,"./helpers/parseUtil":6,"./helpers/util":8}],12:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -Object.defineProperty(exports, "DeviceApi", { - enumerable: true, - get: function () { - return _deviceApi.DeviceApi; - } -}); -Object.defineProperty(exports, "DeviceApiCall", { - enumerable: true, - get: function () { - return _deviceApiCall.DeviceApiCall; - } -}); -Object.defineProperty(exports, "DeviceApiTransport", { - enumerable: true, - get: function () { - return _deviceApi.DeviceApiTransport; - } -}); -Object.defineProperty(exports, "createNotification", { - enumerable: true, - get: function () { - return _deviceApiCall.createNotification; - } -}); -Object.defineProperty(exports, "createRequest", { - enumerable: true, - get: function () { - return _deviceApiCall.createRequest; - } -}); -Object.defineProperty(exports, "validate", { - enumerable: true, - get: function () { - return _deviceApiCall.validate; - } -}); -var _deviceApiCall = require("./lib/device-api-call.js"); -var _deviceApi = require("./lib/device-api.js"); - -},{"./lib/device-api-call.js":13,"./lib/device-api.js":14}],13:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.SchemaValidationError = exports.DeviceApiCallError = exports.DeviceApiCall = void 0; -exports.createDeviceApiCall = createDeviceApiCall; -exports.createNotification = void 0; -exports.createRequest = createRequest; -exports.validate = validate; -/** - * This roughly follows https://www.jsonrpc.org/specification - * @template {import("zod").ZodType} Params=import("zod").ZodType - * @template {import("zod").ZodType} Result=import("zod").ZodType - */ -class DeviceApiCall { - /** @type {string} */ - method = 'unknown'; - /** - * An optional 'id' - used to indicate if a request requires a response. - * @type {string|null} - */ - id = null; - /** @type {Params | null | undefined} */ - paramsValidator = null; - /** @type {Result | null | undefined} */ - resultValidator = null; - /** @type {import("zod").infer} */ - params; - /** - * This is a carve-out for legacy messages that are not typed yet. - * If you set this to 'true', then the response will not be checked to conform - * to any shape - * @deprecated this is here to aid migration, should be removed ASAP - * @type {boolean} - */ - throwOnResultKeysMissing = true; - /** - * New messages should be in a particular format, eg: { success: T }, - * but you can set this to false if you want to access the result as-is, - * without any unwrapping logic - * @deprecated this is here to aid migration, should be removed ASAP - * @type {boolean} - */ - unwrapResult = true; - /** - * @param {import("zod").infer} data - */ - constructor(data) { - this.params = data; - } - - /** - * @returns {import("zod").infer|undefined} - */ - validateParams() { - if (this.params === undefined) { - return undefined; - } - this._validate(this.params, this.paramsValidator); - return this.params; - } - - /** - * @param {any|null} incoming - * @returns {import("zod").infer} - */ - validateResult(incoming) { - this._validate(incoming, this.resultValidator); - if (!incoming) { - return incoming; - } - if (!this.unwrapResult) { - return incoming; + _addCheck(check) { + return new ZodString({ + ...this._def, + checks: [...this._def.checks, check] + }); } - if ('data' in incoming) { - console.warn('response had `data` property. Please migrate to `success`'); - return incoming.data; + email(message) { + return this._addCheck({ kind: "email", ...errorUtil.errToObj(message) }); } - if ('success' in incoming) { - return incoming.success; + url(message) { + return this._addCheck({ kind: "url", ...errorUtil.errToObj(message) }); } - if ('error' in incoming) { - if (typeof incoming.error.message === 'string') { - throw new DeviceApiCallError(`${this.method}: ${incoming.error.message}`); - } + emoji(message) { + return this._addCheck({ kind: "emoji", ...errorUtil.errToObj(message) }); } - if (this.throwOnResultKeysMissing) { - throw new Error('unreachable. Response did not contain `success` or `data`'); + uuid(message) { + return this._addCheck({ kind: "uuid", ...errorUtil.errToObj(message) }); } - return incoming; - } - - /** - * @param {any} data - * @param {import("zod").ZodType|undefined|null} [validator] - * @private - */ - _validate(data, validator) { - if (!validator) return data; - if (validator) { - const result = validator?.safeParse(data); - if (!result) { - throw new Error('unreachable, data failure', data); - } - if (!result.success) { - if ('error' in result) { - this.throwError(result.error.issues); - } else { - console.error('unknown error from validate'); - } - } + cuid(message) { + return this._addCheck({ kind: "cuid", ...errorUtil.errToObj(message) }); } - } - - /** - * @param {import('zod').ZodIssue[]} errors - */ - throwError(errors) { - const error = SchemaValidationError.fromZodErrors(errors, this.constructor.name); - throw error; - } - - /** - * Use this helper for creating stand-in response messages that are typed correctly. - * - * @examples - * - * ```js - * const msg = new Message(); - * const response = msg.response({}) // <-- This argument will be typed correctly - * ``` - * - * @param {import("zod").infer} response - * @returns {import("zod").infer} - */ - result(response) { - return response; - } - /** - * @returns {import("zod").infer} - */ - preResultValidation(response) { - return response; - } -} -exports.DeviceApiCall = DeviceApiCall; -class DeviceApiCallError extends Error {} - -/** - * Check for this error if you'd like to - */ -exports.DeviceApiCallError = DeviceApiCallError; -class SchemaValidationError extends Error { - /** @type {import("zod").ZodIssue[]} */ - validationErrors = []; - - /** - * @param {import("zod").ZodIssue[]} errors - * @param {string} name - * @returns {SchemaValidationError} - */ - static fromZodErrors(errors, name) { - const heading = `${errors.length} SchemaValidationError(s) errors for ` + name; - function log(issue) { - switch (issue.code) { - case 'invalid_literal': - case 'invalid_type': - { - console.log(`${name}. Path: '${issue.path.join('.')}', Error: '${issue.message}'`); - break; - } - case 'invalid_union': - { - for (let unionError of issue.unionErrors) { - for (let issue1 of unionError.issues) { - log(issue1); - } - } - break; - } - default: - { - console.log(name, 'other issue:', issue); - } - } + cuid2(message) { + return this._addCheck({ kind: "cuid2", ...errorUtil.errToObj(message) }); } - for (let error of errors) { - log(error); + ulid(message) { + return this._addCheck({ kind: "ulid", ...errorUtil.errToObj(message) }); } - const message = [heading, 'please see the details above'].join('\n '); - const error = new SchemaValidationError(message); - error.validationErrors = errors; - return error; - } -} - -/** - * Creates an instance of `DeviceApiCall` from only a name and 'params' - * and optional validators. Use this to help migrate existing messages. - * - * @template {import("zod").ZodType} Params - * @template {import("zod").ZodType} Result - * @param {string} method - * @param {import("zod").infer} [params] - * @param {Params|null} [paramsValidator] - * @param {Result|null} [resultValidator] - * @returns {DeviceApiCall} - */ -exports.SchemaValidationError = SchemaValidationError; -function createDeviceApiCall(method, params) { - let paramsValidator = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; - let resultValidator = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; - /** @type {DeviceApiCall} */ - const deviceApiCall = new DeviceApiCall(params); - deviceApiCall.paramsValidator = paramsValidator; - deviceApiCall.resultValidator = resultValidator; - deviceApiCall.method = method; - deviceApiCall.throwOnResultKeysMissing = false; - deviceApiCall.unwrapResult = false; - return deviceApiCall; -} - -/** - * Creates an instance of `DeviceApiCall` from only a name and 'params' - * and optional validators. Use this to help migrate existing messages. - * - * Note: This creates a regular DeviceApiCall, but adds the 'id' as a string - * so that transports know that it expects a response. - * - * @template {import("zod").ZodType} Params - * @template {import("zod").ZodType} Result - * @param {string} method - * @param {import("zod").infer} [params] - * @param {string} [id] - * @param {Params|null} [paramsValidator] - * @param {Result|null} [resultValidator] - * @returns {DeviceApiCall} - */ -function createRequest(method, params) { - let id = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'n/a'; - let paramsValidator = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; - let resultValidator = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null; - const call = createDeviceApiCall(method, params, paramsValidator, resultValidator); - call.id = id; - return call; -} -const createNotification = exports.createNotification = createDeviceApiCall; - -/** - * Validate any arbitrary data with any Zod validator - * - * @template {import("zod").ZodType} Validator - * @param {any} data - * @param {Validator | null} [validator] - * @returns {import("zod").infer} - */ -function validate(data) { - let validator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; - if (validator) { - return validator.parse(data); - } - return data; -} - -},{}],14:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.DeviceApiTransport = exports.DeviceApi = void 0; -/** - * Platforms should only need to implement this `send` method - */ -class DeviceApiTransport { - /** - * @param {import("./device-api-call.js").DeviceApiCall} _deviceApiCall - * @param {CallOptions} [_options] - * @returns {Promise} - */ - async send(_deviceApiCall, _options) { - return undefined; - } -} - -/** - * This is the base Sender class that platforms can will implement. - * - * Note: The 'handle' method must be implemented, unless you also implement 'send' - * - * @typedef CallOptions - * @property {AbortSignal} [signal] - */ -exports.DeviceApiTransport = DeviceApiTransport; -class DeviceApi { - /** @type {DeviceApiTransport} */ - transport; - /** @param {DeviceApiTransport} transport */ - constructor(transport) { - this.transport = transport; - } - /** - * @template {import("./device-api-call").DeviceApiCall} D - * @param {D} deviceApiCall - * @param {CallOptions} [options] - * @returns {Promise['success']>>} - */ - async request(deviceApiCall, options) { - deviceApiCall.validateParams(); - let result = await this.transport.send(deviceApiCall, options); - let processed = deviceApiCall.preResultValidation(result); - return deviceApiCall.validateResult(processed); - } - /** - * @template {import("./device-api-call").DeviceApiCall} P - * @param {P} deviceApiCall - * @param {CallOptions} [options] - * @returns {Promise} - */ - async notify(deviceApiCall, options) { - deviceApiCall.validateParams(); - return this.transport.send(deviceApiCall, options); - } -} -exports.DeviceApi = DeviceApi; - -},{}],15:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.MissingHandler = exports.MessagingTransport = exports.Messaging = void 0; -Object.defineProperty(exports, "WebkitMessagingConfig", { - enumerable: true, - get: function () { - return _webkit.WebkitMessagingConfig; - } -}); -var _webkit = require("./webkit.js"); -/** - * @module Messaging - * - * @description - * - * An abstraction for communications between JavaScript and host platforms. - * - * 1) First you construct your platform-specific configuration (eg: {@link WebkitMessagingConfig}) - * 2) Then use that to get an instance of the Messaging utility which allows - * you to send and receive data in a unified way - * 3) Each platform implements {@link MessagingTransport} along with its own Configuration - * - For example, to learn what configuration is required for Webkit, see: {@link "Webkit Messaging".WebkitMessagingConfig} - * - Or, to learn about how messages are sent and received in Webkit, see {@link "Webkit Messaging".WebkitMessagingTransport} - * - * @example Webkit Messaging - * - * ```js - * import { Messaging, WebkitMessagingConfig } from "@duckduckgo/content-scope-scripts/lib/messaging.js" - * - * // This config would be injected into the UserScript - * const injectedConfig = { - * hasModernWebkitAPI: true, - * webkitMessageHandlerNames: ["foo", "bar", "baz"], - * secret: "dax", - * }; - * - * // Then use that config to construct platform-specific configuration - * const config = new WebkitMessagingConfig(injectedConfig); - * - * // finally, get an instance of Messaging and start sending messages in a unified way 🚀 - * const messaging = new Messaging(config); - * messaging.notify("hello world!", {foo: "bar"}) - * - * ``` - * - * @example Windows Messaging - * - * ```js - * import { Messaging, WindowsMessagingConfig } from "@duckduckgo/content-scope-scripts/lib/messaging.js" - * - * // Messaging on Windows is namespaced, so you can create multiple messaging instances - * const autofillConfig = new WindowsMessagingConfig({ featureName: "Autofill" }); - * const debugConfig = new WindowsMessagingConfig({ featureName: "Debugging" }); - * - * const autofillMessaging = new Messaging(autofillConfig); - * const debugMessaging = new Messaging(debugConfig); - * - * // Now send messages to both features as needed 🚀 - * autofillMessaging.notify("storeFormData", { "username": "dax" }) - * debugMessaging.notify("pageLoad", { time: window.performance.now() }) - * ``` - */ - -/** - * @implements {MessagingTransport} - */ -class Messaging { - /** - * @param {WebkitMessagingConfig} config - */ - constructor(config) { - this.transport = getTransport(config); - } - /** - * Send a 'fire-and-forget' message. - * @throws {Error} - * {@link MissingHandler} - * - * @example - * - * ``` - * const messaging = new Messaging(config) - * messaging.notify("foo", {bar: "baz"}) - * ``` - * @param {string} name - * @param {Record} [data] - */ - notify(name) { - let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - this.transport.notify(name, data); - } - /** - * Send a request, and wait for a response - * @throws {Error} - * {@link MissingHandler} - * - * @example - * ``` - * const messaging = new Messaging(config) - * const response = await messaging.request("foo", {bar: "baz"}) - * ``` - * - * @param {string} name - * @param {Record} [data] - * @return {Promise} - */ - request(name) { - let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - return this.transport.request(name, data); - } -} - -/** - * @interface - */ -exports.Messaging = Messaging; -class MessagingTransport { - /** - * @param {string} name - * @param {Record} [data] - * @returns {void} - */ - // @ts-ignore - ignoring a no-unused ts error, this is only an interface. - notify(name) { - let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - throw new Error("must implement 'notify'"); - } - /** - * @param {string} name - * @param {Record} [data] - * @return {Promise} - */ - // @ts-ignore - ignoring a no-unused ts error, this is only an interface. - request(name) { - let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - throw new Error('must implement'); - } -} - -/** - * @param {WebkitMessagingConfig} config - * @returns {MessagingTransport} - */ -exports.MessagingTransport = MessagingTransport; -function getTransport(config) { - if (config instanceof _webkit.WebkitMessagingConfig) { - return new _webkit.WebkitMessagingTransport(config); - } - throw new Error('unreachable'); -} - -/** - * Thrown when a handler cannot be found - */ -class MissingHandler extends Error { - /** - * @param {string} message - * @param {string} handlerName - */ - constructor(message, handlerName) { - super(message); - this.handlerName = handlerName; - } -} - -/** - * Some re-exports for convenience - */ -exports.MissingHandler = MissingHandler; - -},{"./webkit.js":16}],16:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.WebkitMessagingTransport = exports.WebkitMessagingConfig = exports.SecureMessagingParams = void 0; -var _messaging = require("./messaging.js"); -/** - * @module Webkit Messaging - * - * @description - * - * A wrapper for messaging on WebKit platforms. It supports modern WebKit messageHandlers - * along with encryption for older versions (like macOS Catalina) - * - * Note: If you wish to support Catalina then you'll need to implement the native - * part of the message handling, see {@link WebkitMessagingTransport} for details. - * - * ```js - * import { Messaging, WebkitMessagingConfig } from "@duckduckgo/content-scope-scripts/lib/messaging.js" - * - * // This config would be injected into the UserScript - * const injectedConfig = { - * hasModernWebkitAPI: true, - * webkitMessageHandlerNames: ["foo", "bar", "baz"], - * secret: "dax", - * }; - * - * // Then use that config to construct platform-specific configuration - * const config = new WebkitMessagingConfig(injectedConfig); - * - * // finally, get an instance of Messaging and start sending messages in a unified way 🚀 - * const messaging = new Messaging(config); - * messaging.notify("hello world!", {foo: "bar"}) - * - * ``` - */ - -/** - * @typedef {import("./messaging").MessagingTransport} MessagingTransport - */ - -/** - * @example - * On macOS 11+, this will just call through to `window.webkit.messageHandlers.x.postMessage` - * - * Eg: for a `foo` message defined in Swift that accepted the payload `{"bar": "baz"}`, the following - * would occur: - * - * ```js - * const json = await window.webkit.messageHandlers.foo.postMessage({ bar: "baz" }); - * const response = JSON.parse(json) - * ``` - * - * @example - * On macOS 10 however, the process is a little more involved. A method will be appended to `window` - * that allows the response to be delivered there instead. It's not exactly this, but you can visualize the flow - * as being something along the lines of: - * - * ```js - * // add the window method - * window["_0123456"] = (response) => { - * // decrypt `response` and deliver the result to the caller here - * // then remove the temporary method - * delete window["_0123456"] - * }; - * - * // send the data + `messageHanding` values - * window.webkit.messageHandlers.foo.postMessage({ - * bar: "baz", - * messagingHandling: { - * methodName: "_0123456", - * secret: "super-secret", - * key: [1, 2, 45, 2], - * iv: [34, 4, 43], - * } - * }); - * - * // later in swift, the following JavaScript snippet will be executed - * (() => { - * window["_0123456"]({ - * ciphertext: [12, 13, 4], - * tag: [3, 5, 67, 56] - * }) - * })() - * ``` - * @implements {MessagingTransport} - */ -class WebkitMessagingTransport { - /** @type {WebkitMessagingConfig} */ - config; - globals; - /** - * @param {WebkitMessagingConfig} config - */ - constructor(config) { - this.config = config; - this.globals = captureGlobals(); - if (!this.config.hasModernWebkitAPI) { - this.captureWebkitHandlers(this.config.webkitMessageHandlerNames); + ip(options) { + return this._addCheck({ kind: "ip", ...errorUtil.errToObj(options) }); } - } - /** - * Sends message to the webkit layer (fire and forget) - * @param {String} handler - * @param {*} data - * @internal - */ - wkSend(handler) { - let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - if (!(handler in this.globals.window.webkit.messageHandlers)) { - throw new _messaging.MissingHandler(`Missing webkit handler: '${handler}'`, handler); - } - const outgoing = { - ...data, - messageHandling: { - ...data.messageHandling, - secret: this.config.secret - } - }; - if (!this.config.hasModernWebkitAPI) { - if (!(handler in this.globals.capturedWebkitHandlers)) { - throw new _messaging.MissingHandler(`cannot continue, method ${handler} not captured on macos < 11`, handler); - } else { - return this.globals.capturedWebkitHandlers[handler](outgoing); + datetime(options) { + var _a; + if (typeof options === "string") { + return this._addCheck({ + kind: "datetime", + precision: null, + offset: false, + message: options + }); } + return this._addCheck({ + kind: "datetime", + precision: typeof (options === null || options === void 0 ? void 0 : options.precision) === "undefined" ? null : options === null || options === void 0 ? void 0 : options.precision, + offset: (_a = options === null || options === void 0 ? void 0 : options.offset) !== null && _a !== void 0 ? _a : false, + ...errorUtil.errToObj(options === null || options === void 0 ? void 0 : options.message) + }); } - return this.globals.window.webkit.messageHandlers[handler].postMessage?.(outgoing); - } - - /** - * Sends message to the webkit layer and waits for the specified response - * @param {String} handler - * @param {*} data - * @returns {Promise<*>} - * @internal - */ - async wkSendAndWait(handler) { - let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - if (this.config.hasModernWebkitAPI) { - const response = await this.wkSend(handler, data); - return this.globals.JSONparse(response || '{}'); - } - try { - const randMethodName = this.createRandMethodName(); - const key = await this.createRandKey(); - const iv = this.createRandIv(); - const { - ciphertext, - tag - } = await new this.globals.Promise(( /** @type {any} */resolve) => { - this.generateRandomMethod(randMethodName, resolve); - data.messageHandling = new SecureMessagingParams({ - methodName: randMethodName, - secret: this.config.secret, - key: this.globals.Arrayfrom(key), - iv: this.globals.Arrayfrom(iv) - }); - this.wkSend(handler, data); + regex(regex, message) { + return this._addCheck({ + kind: "regex", + regex, + ...errorUtil.errToObj(message) }); - const cipher = new this.globals.Uint8Array([...ciphertext, ...tag]); - const decrypted = await this.decrypt(cipher, key, iv); - return this.globals.JSONparse(decrypted || '{}'); - } catch (e) { - // re-throw when the error is just a 'MissingHandler' - if (e instanceof _messaging.MissingHandler) { - throw e; - } else { - console.error('decryption failed', e); - console.error(e); - return { - error: e - }; - } } - } - /** - * @param {string} name - * @param {Record} [data] - */ - notify(name) { - let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - this.wkSend(name, data); - } - /** - * @param {string} name - * @param {Record} [data] - */ - request(name) { - let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - return this.wkSendAndWait(name, data); - } - /** - * Generate a random method name and adds it to the global scope - * The native layer will use this method to send the response - * @param {string | number} randomMethodName - * @param {Function} callback - */ - generateRandomMethod(randomMethodName, callback) { - var _this = this; - this.globals.ObjectDefineProperty(this.globals.window, randomMethodName, { - enumerable: false, - // configurable, To allow for deletion later - configurable: true, - writable: false, - /** - * @param {any[]} args - */ - value: function () { - callback(...arguments); - // @ts-ignore - we want this to throw if it fails as it would indicate a fatal error. - delete _this.globals.window[randomMethodName]; - } - }); - } - randomString() { - return '' + this.globals.getRandomValues(new this.globals.Uint32Array(1))[0]; - } - createRandMethodName() { - return '_' + this.randomString(); - } - - /** - * @type {{name: string, length: number}} - */ - algoObj = { - name: 'AES-GCM', - length: 256 - }; - - /** - * @returns {Promise} - */ - async createRandKey() { - const key = await this.globals.generateKey(this.algoObj, true, ['encrypt', 'decrypt']); - const exportedKey = await this.globals.exportKey('raw', key); - return new this.globals.Uint8Array(exportedKey); - } - - /** - * @returns {Uint8Array} - */ - createRandIv() { - return this.globals.getRandomValues(new this.globals.Uint8Array(12)); - } - - /** - * @param {BufferSource} ciphertext - * @param {BufferSource} key - * @param {Uint8Array} iv - * @returns {Promise} - */ - async decrypt(ciphertext, key, iv) { - const cryptoKey = await this.globals.importKey('raw', key, 'AES-GCM', false, ['decrypt']); - const algo = { - name: 'AES-GCM', - iv - }; - let decrypted = await this.globals.decrypt(algo, cryptoKey, ciphertext); - let dec = new this.globals.TextDecoder(); - return dec.decode(decrypted); - } - - /** - * When required (such as on macos 10.x), capture the `postMessage` method on - * each webkit messageHandler - * - * @param {string[]} handlerNames - */ - captureWebkitHandlers(handlerNames) { - const handlers = window.webkit.messageHandlers; - if (!handlers) throw new _messaging.MissingHandler('window.webkit.messageHandlers was absent', 'all'); - for (let webkitMessageHandlerName of handlerNames) { - if (typeof handlers[webkitMessageHandlerName]?.postMessage === 'function') { - /** - * `bind` is used here to ensure future calls to the captured - * `postMessage` have the correct `this` context - */ - const original = handlers[webkitMessageHandlerName]; - const bound = handlers[webkitMessageHandlerName].postMessage?.bind(original); - this.globals.capturedWebkitHandlers[webkitMessageHandlerName] = bound; - delete handlers[webkitMessageHandlerName].postMessage; - } + includes(value, options) { + return this._addCheck({ + kind: "includes", + value, + position: options === null || options === void 0 ? void 0 : options.position, + ...errorUtil.errToObj(options === null || options === void 0 ? void 0 : options.message) + }); } - } -} - -/** - * Use this configuration to create an instance of {@link Messaging} for WebKit - * - * ```js - * import { fromConfig, WebkitMessagingConfig } from "@duckduckgo/content-scope-scripts/lib/messaging.js" - * - * const config = new WebkitMessagingConfig({ - * hasModernWebkitAPI: true, - * webkitMessageHandlerNames: ["foo", "bar", "baz"], - * secret: "dax", - * }); - * - * const messaging = new Messaging(config) - * const resp = await messaging.request("debugConfig") - * ``` - */ -exports.WebkitMessagingTransport = WebkitMessagingTransport; -class WebkitMessagingConfig { - /** - * @param {object} params - * @param {boolean} params.hasModernWebkitAPI - * @param {string[]} params.webkitMessageHandlerNames - * @param {string} params.secret - */ - constructor(params) { - /** - * Whether or not the current WebKit Platform supports secure messaging - * by default (eg: macOS 11+) - */ - this.hasModernWebkitAPI = params.hasModernWebkitAPI; - /** - * A list of WebKit message handler names that a user script can send - */ - this.webkitMessageHandlerNames = params.webkitMessageHandlerNames; - /** - * A string provided by native platforms to be sent with future outgoing - * messages - */ - this.secret = params.secret; - } -} - -/** - * This is the additional payload that gets appended to outgoing messages. - * It's used in the Swift side to encrypt the response that comes back - */ -exports.WebkitMessagingConfig = WebkitMessagingConfig; -class SecureMessagingParams { - /** - * @param {object} params - * @param {string} params.methodName - * @param {string} params.secret - * @param {number[]} params.key - * @param {number[]} params.iv - */ - constructor(params) { - /** - * The method that's been appended to `window` to be called later - */ - this.methodName = params.methodName; - /** - * The secret used to ensure message sender validity - */ - this.secret = params.secret; - /** - * The CipherKey as number[] - */ - this.key = params.key; - /** - * The Initial Vector as number[] - */ - this.iv = params.iv; - } -} - -/** - * Capture some globals used for messaging handling to prevent page - * scripts from tampering with this - */ -exports.SecureMessagingParams = SecureMessagingParams; -function captureGlobals() { - // Creat base with null prototype - return { - window, - // Methods must be bound to their interface, otherwise they throw Illegal invocation - encrypt: window.crypto.subtle.encrypt.bind(window.crypto.subtle), - decrypt: window.crypto.subtle.decrypt.bind(window.crypto.subtle), - generateKey: window.crypto.subtle.generateKey.bind(window.crypto.subtle), - exportKey: window.crypto.subtle.exportKey.bind(window.crypto.subtle), - importKey: window.crypto.subtle.importKey.bind(window.crypto.subtle), - getRandomValues: window.crypto.getRandomValues.bind(window.crypto), - TextEncoder, - TextDecoder, - Uint8Array, - Uint16Array, - Uint32Array, - JSONstringify: window.JSON.stringify, - JSONparse: window.JSON.parse, - Arrayfrom: window.Array.from, - Promise: window.Promise, - ObjectDefineProperty: window.Object.defineProperty, - addEventListener: window.addEventListener.bind(window), - /** @type {Record} */ - capturedWebkitHandlers: {} - }; -} - -},{"./messaging.js":15}],17:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.HostnameInputError = void 0; -Object.defineProperty(exports, "ParserError", { - enumerable: true, - get: function () { - return _rulesParser.ParserError; - } -}); -exports._selectPasswordRules = _selectPasswordRules; -Object.defineProperty(exports, "constants", { - enumerable: true, - get: function () { - return _constants.constants; - } -}); -exports.generate = generate; -var _applePassword = require("./lib/apple.password.js"); -var _rulesParser = require("./lib/rules-parser.js"); -var _constants = require("./lib/constants.js"); -/** - * @typedef {{ - * domain?: string | null | undefined; - * input?: string | null | undefined; - * rules?: RulesFormat | null | undefined; - * onError?: ((error: unknown) => void) | null | undefined; - * }} GenerateOptions - */ -/** - * Generate a random password based on the following attempts - * - * 1) using `options.input` if provided -> falling back to default ruleset - * 2) using `options.domain` if provided -> falling back to default ruleset - * 3) using default ruleset - * - * Note: This API is designed to never throw - if you want to observe errors - * during development, you can provide an `onError` callback - * - * @param {GenerateOptions} [options] - */ -function generate() { - let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - try { - if (typeof options?.input === 'string') { - return _applePassword.Password.generateOrThrow(options.input); - } - if (typeof options?.domain === 'string') { - if (options?.rules) { - const rules = _selectPasswordRules(options.domain, options.rules); - if (rules) { - return _applePassword.Password.generateOrThrow(rules); - } - } - } - } catch (e) { - // if an 'onError' callback was provided, forward all errors - if (options?.onError && typeof options?.onError === 'function') { - options.onError(e); - } else { - // otherwise, only console.error unknown errors (which could be implementation bugs) - const isKnownError = e instanceof _rulesParser.ParserError || e instanceof HostnameInputError; - if (!isKnownError) { - console.error(e); - } + startsWith(value, message) { + return this._addCheck({ + kind: "startsWith", + value, + ...errorUtil.errToObj(message) + }); } - } - - // At this point, we have to trust the generation will not throw - // as it is NOT using any user/page-provided data - return _applePassword.Password.generateDefault(); -} - -// An extension type to differentiate between known errors -class HostnameInputError extends Error {} - -/** - * @typedef {Record} RulesFormat - */ - -/** - * @private - * @param {string} inputHostname - * @param {RulesFormat} rules - * @returns {string | undefined} - * @throws {HostnameInputError} - */ -exports.HostnameInputError = HostnameInputError; -function _selectPasswordRules(inputHostname, rules) { - const hostname = _safeHostname(inputHostname); - // direct match - if (rules[hostname]) { - return rules[hostname]['password-rules']; - } - - // otherwise, start chopping off subdomains and re-joining to compare - const pieces = hostname.split('.'); - while (pieces.length > 1) { - pieces.shift(); - const joined = pieces.join('.'); - if (rules[joined]) { - return rules[joined]['password-rules']; + endsWith(value, message) { + return this._addCheck({ + kind: "endsWith", + value, + ...errorUtil.errToObj(message) + }); + } + min(minLength, message) { + return this._addCheck({ + kind: "min", + value: minLength, + ...errorUtil.errToObj(message) + }); + } + max(maxLength, message) { + return this._addCheck({ + kind: "max", + value: maxLength, + ...errorUtil.errToObj(message) + }); + } + length(len, message) { + return this._addCheck({ + kind: "length", + value: len, + ...errorUtil.errToObj(message) + }); } - } - return undefined; -} - -/** - * @private - * @param {string} inputHostname; - * @throws {HostnameInputError} - * @returns {string} - */ -function _safeHostname(inputHostname) { - if (inputHostname.startsWith('http:') || inputHostname.startsWith('https:')) { - throw new HostnameInputError('invalid input, you can only provide a hostname but you gave a scheme'); - } - if (inputHostname.includes(':')) { - throw new HostnameInputError('invalid input, you can only provide a hostname but you gave a :port'); - } - try { - const asUrl = new URL('https://' + inputHostname); - return asUrl.hostname; - } catch (e) { - throw new HostnameInputError(`could not instantiate a URL from that hostname ${inputHostname}`); - } -} - -},{"./lib/apple.password.js":18,"./lib/constants.js":19,"./lib/rules-parser.js":20}],18:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.Password = void 0; -var parser = _interopRequireWildcard(require("./rules-parser.js")); -var _constants = require("./constants.js"); -function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } -function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } -/* - * - * NOTE: - * - * This file was created with inspiration from https://developer.apple.com/password-rules - * - * * The changes made by DuckDuckGo employees are: - * - * 1) removed all logic relating to 'more typeable passwords' - * 2) reduced the number of password styles from 4 to only the 1 which suits our needs - * 2) added JSDoc comments (for Typescript checking) - * - */ - -/** - * @typedef {{ - * PasswordAllowedCharacters?: string, - * PasswordRequiredCharacters?: string[], - * PasswordRepeatedCharacterLimit?: number, - * PasswordConsecutiveCharacterLimit?: number, - * PasswordMinLength?: number, - * PasswordMaxLength?: number, - * }} Requirements - */ -/** - * @typedef {{ - * NumberOfRequiredRandomCharacters: number, - * PasswordAllowedCharacters: string, - * RequiredCharacterSets: string[] - * }} PasswordParameters - */ -const defaults = Object.freeze({ - SCAN_SET_ORDER: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-~!@#$%^&*_+=`|(){}[:;\\\"'<>,.?/ ]", - defaultUnambiguousCharacters: 'abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ0123456789', - defaultPasswordLength: _constants.constants.DEFAULT_MIN_LENGTH, - defaultPasswordRules: _constants.constants.DEFAULT_PASSWORD_RULES, - defaultRequiredCharacterSets: ['abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', '0123456789'], - /** - * @type {typeof window.crypto.getRandomValues | null} - */ - getRandomValues: null -}); - -/** - * This is added here to ensure: - * - * 1) `getRandomValues` is called with the correct prototype chain - * 2) `window` is not accessed when in a node environment - * 3) `bind` is not called in a hot code path - * - * @type {{ getRandomValues: typeof window.crypto.getRandomValues }} - */ -const safeGlobals = {}; -if (typeof window !== 'undefined') { - safeGlobals.getRandomValues = window.crypto.getRandomValues.bind(window.crypto); -} -class Password { - /** - * @param {Partial} [options] - */ - constructor() { - let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; /** - * @type {typeof defaults} + * @deprecated Use z.string().min(1) instead. + * @see {@link ZodString.min} */ - this.options = { - ...defaults, - ...options - }; - return this; - } - static get defaults() { - return defaults; - } - - /** - * Generates a password from the given input. - * - * Note: This method will throw an error if parsing fails - use with caution - * - * @example - * - * ```javascript - * const password = Password.generateOrThrow("minlength: 20") - * ``` - * @public - * @param {string} inputString - * @param {Partial} [options] - * @throws {ParserError|Error} - * @returns {string} - */ - static generateOrThrow(inputString) { - let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - return new Password(options).parse(inputString).generate(); - } - /** - * Generates a password using the default ruleset. - * - * @example - * - * ```javascript - * const password = Password.generateDefault() - * ``` - * - * @public - * @param {Partial} [options] - * @returns {string} - */ - static generateDefault() { - let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - return new Password(options).parse(Password.defaults.defaultPasswordRules).generate(); - } - - /** - * Convert a ruleset into it's internally-used component pieces. - * - * @param {string} inputString - * @throws {parser.ParserError|Error} - * @returns {{ - * requirements: Requirements; - * parameters: PasswordParameters; - * rules: parser.Rule[], - * get entropy(): number; - * generate: () => string; - * }} - */ - parse(inputString) { - const rules = parser.parsePasswordRules(inputString); - const requirements = this._requirementsFromRules(rules); - if (!requirements) throw new Error('could not generate requirements for ' + JSON.stringify(inputString)); - const parameters = this._passwordGenerationParametersDictionary(requirements); - return { - requirements, - parameters, - rules, - get entropy() { - return Math.log2(parameters.PasswordAllowedCharacters.length ** parameters.NumberOfRequiredRandomCharacters); - }, - generate: () => { - const password = this._generatedPasswordMatchingRequirements(requirements, parameters); - /** - * The following is unreachable because if user input was incorrect then - * the parsing phase would throw. The following lines is to satisfy Typescript - */ - if (password === '') throw new Error('unreachable'); - return password; - } - }; - } - - /** - * Given an array of `Rule's`, convert into `Requirements` - * - * @param {parser.Rule[]} passwordRules - * @returns {Requirements | null} - */ - _requirementsFromRules(passwordRules) { - /** @type {Requirements} */ - const requirements = {}; - for (let rule of passwordRules) { - if (rule.name === parser.RuleName.ALLOWED) { - console.assert(!('PasswordAllowedCharacters' in requirements)); - const chars = this._charactersFromCharactersClasses(rule.value); - const scanSet = this._canonicalizedScanSetFromCharacters(chars); - if (scanSet) { - requirements.PasswordAllowedCharacters = scanSet; - } - } else if (rule.name === parser.RuleName.MAX_CONSECUTIVE) { - console.assert(!('PasswordRepeatedCharacterLimit' in requirements)); - requirements.PasswordRepeatedCharacterLimit = rule.value; - } else if (rule.name === parser.RuleName.REQUIRED) { - let requiredCharacters = requirements.PasswordRequiredCharacters; - if (!requiredCharacters) { - requiredCharacters = requirements.PasswordRequiredCharacters = []; - } - requiredCharacters.push(this._canonicalizedScanSetFromCharacters(this._charactersFromCharactersClasses(rule.value))); - } else if (rule.name === parser.RuleName.MIN_LENGTH) { - requirements.PasswordMinLength = rule.value; - } else if (rule.name === parser.RuleName.MAX_LENGTH) { - requirements.PasswordMaxLength = rule.value; - } + nonempty(message) { + return this.min(1, errorUtil.errToObj(message)); } - - // Only include an allowed rule matching SCAN_SET_ORDER (all characters) when a required rule is also present. - if (requirements.PasswordAllowedCharacters === this.options.SCAN_SET_ORDER && !requirements.PasswordRequiredCharacters) { - delete requirements.PasswordAllowedCharacters; + trim() { + return new ZodString({ + ...this._def, + checks: [...this._def.checks, { kind: "trim" }] + }); } - - // Fix up PasswordRequiredCharacters, if needed. - if (requirements.PasswordRequiredCharacters && requirements.PasswordRequiredCharacters.length === 1 && requirements.PasswordRequiredCharacters[0] === this.options.SCAN_SET_ORDER) { - delete requirements.PasswordRequiredCharacters; + toLowerCase() { + return new ZodString({ + ...this._def, + checks: [...this._def.checks, { kind: "toLowerCase" }] + }); } - return Object.keys(requirements).length ? requirements : null; - } - - /** - * @param {number} range - * @returns {number} - */ - _randomNumberWithUniformDistribution(range) { - const getRandomValues = this.options.getRandomValues || safeGlobals.getRandomValues; - // Based on the algorithm described in https://pthree.org/2018/06/13/why-the-multiply-and-floor-rng-method-is-biased/ - const max = Math.floor(2 ** 32 / range) * range; - let x; - do { - x = getRandomValues(new Uint32Array(1))[0]; - } while (x >= max); - return x % range; - } - - /** - * @param {number} numberOfRequiredRandomCharacters - * @param {string} allowedCharacters - */ - _classicPassword(numberOfRequiredRandomCharacters, allowedCharacters) { - const length = allowedCharacters.length; - const randomCharArray = Array(numberOfRequiredRandomCharacters); - for (let i = 0; i < numberOfRequiredRandomCharacters; i++) { - const index = this._randomNumberWithUniformDistribution(length); - randomCharArray[i] = allowedCharacters[index]; - } - return randomCharArray.join(''); - } - - /** - * @param {string} password - * @param {number} consecutiveCharLimit - * @returns {boolean} - */ - _passwordHasNotExceededConsecutiveCharLimit(password, consecutiveCharLimit) { - let longestConsecutiveCharLength = 1; - let firstConsecutiveCharIndex = 0; - // Both "123" or "abc" and "321" or "cba" are considered consecutive. - let isSequenceAscending; - for (let i = 1; i < password.length; i++) { - const currCharCode = password.charCodeAt(i); - const prevCharCode = password.charCodeAt(i - 1); - if (isSequenceAscending) { - // If `isSequenceAscending` is defined, then we know that we are in the middle of an existing - // pattern. Check if the pattern continues based on whether the previous pattern was - // ascending or descending. - if (isSequenceAscending.valueOf() && currCharCode === prevCharCode + 1 || !isSequenceAscending.valueOf() && currCharCode === prevCharCode - 1) { - continue; - } - - // Take into account the case when the sequence transitions from descending - // to ascending. - if (currCharCode === prevCharCode + 1) { - firstConsecutiveCharIndex = i - 1; - isSequenceAscending = Boolean(true); - continue; - } - - // Take into account the case when the sequence transitions from ascending - // to descending. - if (currCharCode === prevCharCode - 1) { - firstConsecutiveCharIndex = i - 1; - isSequenceAscending = Boolean(false); - continue; + toUpperCase() { + return new ZodString({ + ...this._def, + checks: [...this._def.checks, { kind: "toUpperCase" }] + }); + } + get isDatetime() { + return !!this._def.checks.find((ch) => ch.kind === "datetime"); + } + get isEmail() { + return !!this._def.checks.find((ch) => ch.kind === "email"); + } + get isURL() { + return !!this._def.checks.find((ch) => ch.kind === "url"); + } + get isEmoji() { + return !!this._def.checks.find((ch) => ch.kind === "emoji"); + } + get isUUID() { + return !!this._def.checks.find((ch) => ch.kind === "uuid"); + } + get isCUID() { + return !!this._def.checks.find((ch) => ch.kind === "cuid"); + } + get isCUID2() { + return !!this._def.checks.find((ch) => ch.kind === "cuid2"); + } + get isULID() { + return !!this._def.checks.find((ch) => ch.kind === "ulid"); + } + get isIP() { + return !!this._def.checks.find((ch) => ch.kind === "ip"); + } + get minLength() { + let min = null; + for (const ch of this._def.checks) { + if (ch.kind === "min") { + if (min === null || ch.value > min) + min = ch.value; } - isSequenceAscending = null; - } else if (currCharCode === prevCharCode + 1) { - isSequenceAscending = Boolean(true); - continue; - } else if (currCharCode === prevCharCode - 1) { - isSequenceAscending = Boolean(false); - continue; } - const currConsecutiveCharLength = i - firstConsecutiveCharIndex; - if (currConsecutiveCharLength > longestConsecutiveCharLength) { - longestConsecutiveCharLength = currConsecutiveCharLength; - } - firstConsecutiveCharIndex = i; + return min; } - if (isSequenceAscending) { - const currConsecutiveCharLength = password.length - firstConsecutiveCharIndex; - if (currConsecutiveCharLength > longestConsecutiveCharLength) { - longestConsecutiveCharLength = currConsecutiveCharLength; + get maxLength() { + let max = null; + for (const ch of this._def.checks) { + if (ch.kind === "max") { + if (max === null || ch.value < max) + max = ch.value; + } } + return max; } - return longestConsecutiveCharLength <= consecutiveCharLimit; - } - - /** - * @param {string} password - * @param {number} repeatedCharLimit - * @returns {boolean} - */ - _passwordHasNotExceededRepeatedCharLimit(password, repeatedCharLimit) { - let longestRepeatedCharLength = 1; - let lastRepeatedChar = password.charAt(0); - let lastRepeatedCharIndex = 0; - for (let i = 1; i < password.length; i++) { - const currChar = password.charAt(i); - if (currChar === lastRepeatedChar) { - continue; - } - const currRepeatedCharLength = i - lastRepeatedCharIndex; - if (currRepeatedCharLength > longestRepeatedCharLength) { - longestRepeatedCharLength = currRepeatedCharLength; + }; + ZodString.create = (params) => { + var _a; + return new ZodString({ + checks: [], + typeName: ZodFirstPartyTypeKind.ZodString, + coerce: (_a = params === null || params === void 0 ? void 0 : params.coerce) !== null && _a !== void 0 ? _a : false, + ...processCreateParams(params) + }); + }; + function floatSafeRemainder(val, step) { + const valDecCount = (val.toString().split(".")[1] || "").length; + const stepDecCount = (step.toString().split(".")[1] || "").length; + const decCount = valDecCount > stepDecCount ? valDecCount : stepDecCount; + const valInt = parseInt(val.toFixed(decCount).replace(".", "")); + const stepInt = parseInt(step.toFixed(decCount).replace(".", "")); + return valInt % stepInt / Math.pow(10, decCount); + } + var ZodNumber = class extends ZodType { + constructor() { + super(...arguments); + this.min = this.gte; + this.max = this.lte; + this.step = this.multipleOf; + } + _parse(input) { + if (this._def.coerce) { + input.data = Number(input.data); + } + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.number) { + const ctx2 = this._getOrReturnCtx(input); + addIssueToContext(ctx2, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.number, + received: ctx2.parsedType + }); + return INVALID; + } + let ctx = void 0; + const status = new ParseStatus(); + for (const check of this._def.checks) { + if (check.kind === "int") { + if (!util.isInteger(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: "integer", + received: "float", + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "min") { + const tooSmall = check.inclusive ? input.data < check.value : input.data <= check.value; + if (tooSmall) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: check.value, + type: "number", + inclusive: check.inclusive, + exact: false, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "max") { + const tooBig = check.inclusive ? input.data > check.value : input.data >= check.value; + if (tooBig) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: check.value, + type: "number", + inclusive: check.inclusive, + exact: false, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "multipleOf") { + if (floatSafeRemainder(input.data, check.value) !== 0) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.not_multiple_of, + multipleOf: check.value, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "finite") { + if (!Number.isFinite(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.not_finite, + message: check.message + }); + status.dirty(); + } + } else { + util.assertNever(check); + } } - lastRepeatedChar = currChar; - lastRepeatedCharIndex = i; + return { status: status.value, value: input.data }; } - return longestRepeatedCharLength <= repeatedCharLimit; - } - - /** - * @param {string} password - * @param {string[]} requiredCharacterSets - * @returns {boolean} - */ - _passwordContainsRequiredCharacters(password, requiredCharacterSets) { - const requiredCharacterSetsLength = requiredCharacterSets.length; - const passwordLength = password.length; - for (let i = 0; i < requiredCharacterSetsLength; i++) { - const requiredCharacterSet = requiredCharacterSets[i]; - let hasRequiredChar = false; - for (let j = 0; j < passwordLength; j++) { - const char = password.charAt(j); - if (requiredCharacterSet.indexOf(char) !== -1) { - hasRequiredChar = true; - break; + gte(value, message) { + return this.setLimit("min", value, true, errorUtil.toString(message)); + } + gt(value, message) { + return this.setLimit("min", value, false, errorUtil.toString(message)); + } + lte(value, message) { + return this.setLimit("max", value, true, errorUtil.toString(message)); + } + lt(value, message) { + return this.setLimit("max", value, false, errorUtil.toString(message)); + } + setLimit(kind, value, inclusive, message) { + return new ZodNumber({ + ...this._def, + checks: [ + ...this._def.checks, + { + kind, + value, + inclusive, + message: errorUtil.toString(message) + } + ] + }); + } + _addCheck(check) { + return new ZodNumber({ + ...this._def, + checks: [...this._def.checks, check] + }); + } + int(message) { + return this._addCheck({ + kind: "int", + message: errorUtil.toString(message) + }); + } + positive(message) { + return this._addCheck({ + kind: "min", + value: 0, + inclusive: false, + message: errorUtil.toString(message) + }); + } + negative(message) { + return this._addCheck({ + kind: "max", + value: 0, + inclusive: false, + message: errorUtil.toString(message) + }); + } + nonpositive(message) { + return this._addCheck({ + kind: "max", + value: 0, + inclusive: true, + message: errorUtil.toString(message) + }); + } + nonnegative(message) { + return this._addCheck({ + kind: "min", + value: 0, + inclusive: true, + message: errorUtil.toString(message) + }); + } + multipleOf(value, message) { + return this._addCheck({ + kind: "multipleOf", + value, + message: errorUtil.toString(message) + }); + } + finite(message) { + return this._addCheck({ + kind: "finite", + message: errorUtil.toString(message) + }); + } + safe(message) { + return this._addCheck({ + kind: "min", + inclusive: true, + value: Number.MIN_SAFE_INTEGER, + message: errorUtil.toString(message) + })._addCheck({ + kind: "max", + inclusive: true, + value: Number.MAX_SAFE_INTEGER, + message: errorUtil.toString(message) + }); + } + get minValue() { + let min = null; + for (const ch of this._def.checks) { + if (ch.kind === "min") { + if (min === null || ch.value > min) + min = ch.value; } } - if (!hasRequiredChar) { - return false; + return min; + } + get maxValue() { + let max = null; + for (const ch of this._def.checks) { + if (ch.kind === "max") { + if (max === null || ch.value < max) + max = ch.value; + } } + return max; } - return true; - } - - /** - * @param {string} string1 - * @param {string} string2 - * @returns {boolean} - */ - _stringsHaveAtLeastOneCommonCharacter(string1, string2) { - const string2Length = string2.length; - for (let i = 0; i < string2Length; i++) { - const char = string2.charAt(i); - if (string1.indexOf(char) !== -1) { - return true; + get isInt() { + return !!this._def.checks.find((ch) => ch.kind === "int" || ch.kind === "multipleOf" && util.isInteger(ch.value)); + } + get isFinite() { + let max = null, min = null; + for (const ch of this._def.checks) { + if (ch.kind === "finite" || ch.kind === "int" || ch.kind === "multipleOf") { + return true; + } else if (ch.kind === "min") { + if (min === null || ch.value > min) + min = ch.value; + } else if (ch.kind === "max") { + if (max === null || ch.value < max) + max = ch.value; + } } + return Number.isFinite(min) && Number.isFinite(max); } - return false; - } - - /** - * @param {Requirements} requirements - * @returns {PasswordParameters} - */ - _passwordGenerationParametersDictionary(requirements) { - let minPasswordLength = requirements.PasswordMinLength; - const maxPasswordLength = requirements.PasswordMaxLength; - - // @ts-ignore - if (minPasswordLength > maxPasswordLength) { - // Resetting invalid value of min length to zero means "ignore min length parameter in password generation". - minPasswordLength = 0; - } - const requiredCharacterArray = requirements.PasswordRequiredCharacters; - let allowedCharacters = requirements.PasswordAllowedCharacters; - let requiredCharacterSets = this.options.defaultRequiredCharacterSets; - if (requiredCharacterArray) { - const mutatedRequiredCharacterSets = []; - const requiredCharacterArrayLength = requiredCharacterArray.length; - for (let i = 0; i < requiredCharacterArrayLength; i++) { - const requiredCharacters = requiredCharacterArray[i]; - if (allowedCharacters && this._stringsHaveAtLeastOneCommonCharacter(requiredCharacters, allowedCharacters)) { - mutatedRequiredCharacterSets.push(requiredCharacters); + }; + ZodNumber.create = (params) => { + return new ZodNumber({ + checks: [], + typeName: ZodFirstPartyTypeKind.ZodNumber, + coerce: (params === null || params === void 0 ? void 0 : params.coerce) || false, + ...processCreateParams(params) + }); + }; + var ZodBigInt = class extends ZodType { + constructor() { + super(...arguments); + this.min = this.gte; + this.max = this.lte; + } + _parse(input) { + if (this._def.coerce) { + input.data = BigInt(input.data); + } + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.bigint) { + const ctx2 = this._getOrReturnCtx(input); + addIssueToContext(ctx2, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.bigint, + received: ctx2.parsedType + }); + return INVALID; + } + let ctx = void 0; + const status = new ParseStatus(); + for (const check of this._def.checks) { + if (check.kind === "min") { + const tooSmall = check.inclusive ? input.data < check.value : input.data <= check.value; + if (tooSmall) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + type: "bigint", + minimum: check.value, + inclusive: check.inclusive, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "max") { + const tooBig = check.inclusive ? input.data > check.value : input.data >= check.value; + if (tooBig) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + type: "bigint", + maximum: check.value, + inclusive: check.inclusive, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "multipleOf") { + if (input.data % check.value !== BigInt(0)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.not_multiple_of, + multipleOf: check.value, + message: check.message + }); + status.dirty(); + } + } else { + util.assertNever(check); } } - requiredCharacterSets = mutatedRequiredCharacterSets; + return { status: status.value, value: input.data }; } - - // If requirements allow, we will generateOrThrow the password in default format: "xxx-xxx-xxx-xxx". - let numberOfRequiredRandomCharacters = this.options.defaultPasswordLength; - if (minPasswordLength && minPasswordLength > numberOfRequiredRandomCharacters) { - numberOfRequiredRandomCharacters = minPasswordLength; + gte(value, message) { + return this.setLimit("min", value, true, errorUtil.toString(message)); } - if (maxPasswordLength && maxPasswordLength < numberOfRequiredRandomCharacters) { - numberOfRequiredRandomCharacters = maxPasswordLength; + gt(value, message) { + return this.setLimit("min", value, false, errorUtil.toString(message)); } - if (!allowedCharacters) { - allowedCharacters = this.options.defaultUnambiguousCharacters; + lte(value, message) { + return this.setLimit("max", value, true, errorUtil.toString(message)); } - - // In default password format, we use dashes only as separators, not as symbols you can encounter at a random position. - - if (!requiredCharacterSets) { - requiredCharacterSets = this.options.defaultRequiredCharacterSets; + lt(value, message) { + return this.setLimit("max", value, false, errorUtil.toString(message)); + } + setLimit(kind, value, inclusive, message) { + return new ZodBigInt({ + ...this._def, + checks: [ + ...this._def.checks, + { + kind, + value, + inclusive, + message: errorUtil.toString(message) + } + ] + }); + } + _addCheck(check) { + return new ZodBigInt({ + ...this._def, + checks: [...this._def.checks, check] + }); + } + positive(message) { + return this._addCheck({ + kind: "min", + value: BigInt(0), + inclusive: false, + message: errorUtil.toString(message) + }); + } + negative(message) { + return this._addCheck({ + kind: "max", + value: BigInt(0), + inclusive: false, + message: errorUtil.toString(message) + }); + } + nonpositive(message) { + return this._addCheck({ + kind: "max", + value: BigInt(0), + inclusive: true, + message: errorUtil.toString(message) + }); + } + nonnegative(message) { + return this._addCheck({ + kind: "min", + value: BigInt(0), + inclusive: true, + message: errorUtil.toString(message) + }); } - - // If we have more requirements of the type "need a character from set" than the length of the password we want to generateOrThrow, then - // we will never be able to meet these requirements, and we'll end up in an infinite loop generating passwords. To avoid this, - // reset required character sets if the requirements are impossible to meet. - if (requiredCharacterSets.length > numberOfRequiredRandomCharacters) { - requiredCharacterSets = []; + multipleOf(value, message) { + return this._addCheck({ + kind: "multipleOf", + value, + message: errorUtil.toString(message) + }); } - - // Do not require any character sets that do not contain allowed characters. - const requiredCharacterSetsLength = requiredCharacterSets.length; - const mutatedRequiredCharacterSets = []; - const allowedCharactersLength = allowedCharacters.length; - for (let i = 0; i < requiredCharacterSetsLength; i++) { - const requiredCharacterSet = requiredCharacterSets[i]; - let requiredCharacterSetContainsAllowedCharacters = false; - for (let j = 0; j < allowedCharactersLength; j++) { - const character = allowedCharacters.charAt(j); - if (requiredCharacterSet.indexOf(character) !== -1) { - requiredCharacterSetContainsAllowedCharacters = true; - break; + get minValue() { + let min = null; + for (const ch of this._def.checks) { + if (ch.kind === "min") { + if (min === null || ch.value > min) + min = ch.value; } } - if (requiredCharacterSetContainsAllowedCharacters) { - mutatedRequiredCharacterSets.push(requiredCharacterSet); + return min; + } + get maxValue() { + let max = null; + for (const ch of this._def.checks) { + if (ch.kind === "max") { + if (max === null || ch.value < max) + max = ch.value; + } } + return max; } - requiredCharacterSets = mutatedRequiredCharacterSets; - return { - NumberOfRequiredRandomCharacters: numberOfRequiredRandomCharacters, - PasswordAllowedCharacters: allowedCharacters, - RequiredCharacterSets: requiredCharacterSets - }; - } - - /** - * @param {Requirements | null} requirements - * @param {PasswordParameters} [parameters] - * @returns {string} - */ - _generatedPasswordMatchingRequirements(requirements, parameters) { - requirements = requirements || {}; - parameters = parameters || this._passwordGenerationParametersDictionary(requirements); - const numberOfRequiredRandomCharacters = parameters.NumberOfRequiredRandomCharacters; - const repeatedCharLimit = requirements.PasswordRepeatedCharacterLimit; - const allowedCharacters = parameters.PasswordAllowedCharacters; - const shouldCheckRepeatedCharRequirement = !!repeatedCharLimit; - while (true) { - const password = this._classicPassword(numberOfRequiredRandomCharacters, allowedCharacters); - if (!this._passwordContainsRequiredCharacters(password, parameters.RequiredCharacterSets)) { - continue; + }; + ZodBigInt.create = (params) => { + var _a; + return new ZodBigInt({ + checks: [], + typeName: ZodFirstPartyTypeKind.ZodBigInt, + coerce: (_a = params === null || params === void 0 ? void 0 : params.coerce) !== null && _a !== void 0 ? _a : false, + ...processCreateParams(params) + }); + }; + var ZodBoolean = class extends ZodType { + _parse(input) { + if (this._def.coerce) { + input.data = Boolean(input.data); + } + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.boolean) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.boolean, + received: ctx.parsedType + }); + return INVALID; } - if (shouldCheckRepeatedCharRequirement) { - if (repeatedCharLimit !== undefined && repeatedCharLimit >= 1 && !this._passwordHasNotExceededRepeatedCharLimit(password, repeatedCharLimit)) { - continue; - } + return OK(input.data); + } + }; + ZodBoolean.create = (params) => { + return new ZodBoolean({ + typeName: ZodFirstPartyTypeKind.ZodBoolean, + coerce: (params === null || params === void 0 ? void 0 : params.coerce) || false, + ...processCreateParams(params) + }); + }; + var ZodDate = class extends ZodType { + _parse(input) { + if (this._def.coerce) { + input.data = new Date(input.data); + } + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.date) { + const ctx2 = this._getOrReturnCtx(input); + addIssueToContext(ctx2, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.date, + received: ctx2.parsedType + }); + return INVALID; } - const consecutiveCharLimit = requirements.PasswordConsecutiveCharacterLimit; - if (consecutiveCharLimit && consecutiveCharLimit >= 1) { - if (!this._passwordHasNotExceededConsecutiveCharLimit(password, consecutiveCharLimit)) { - continue; + if (isNaN(input.data.getTime())) { + const ctx2 = this._getOrReturnCtx(input); + addIssueToContext(ctx2, { + code: ZodIssueCode.invalid_date + }); + return INVALID; + } + const status = new ParseStatus(); + let ctx = void 0; + for (const check of this._def.checks) { + if (check.kind === "min") { + if (input.data.getTime() < check.value) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + message: check.message, + inclusive: true, + exact: false, + minimum: check.value, + type: "date" + }); + status.dirty(); + } + } else if (check.kind === "max") { + if (input.data.getTime() > check.value) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + message: check.message, + inclusive: true, + exact: false, + maximum: check.value, + type: "date" + }); + status.dirty(); + } + } else { + util.assertNever(check); } } - return password || ''; + return { + status: status.value, + value: new Date(input.data.getTime()) + }; } - } - - /** - * @param {parser.CustomCharacterClass | parser.NamedCharacterClass} characterClass - * @returns {string[]} - */ - _scanSetFromCharacterClass(characterClass) { - if (characterClass instanceof parser.CustomCharacterClass) { - return characterClass.characters; - } - console.assert(characterClass instanceof parser.NamedCharacterClass); - switch (characterClass.name) { - case parser.Identifier.ASCII_PRINTABLE: - case parser.Identifier.UNICODE: - return this.options.SCAN_SET_ORDER.split(''); - case parser.Identifier.DIGIT: - return this.options.SCAN_SET_ORDER.substring(this.options.SCAN_SET_ORDER.indexOf('0'), this.options.SCAN_SET_ORDER.indexOf('9') + 1).split(''); - case parser.Identifier.LOWER: - return this.options.SCAN_SET_ORDER.substring(this.options.SCAN_SET_ORDER.indexOf('a'), this.options.SCAN_SET_ORDER.indexOf('z') + 1).split(''); - case parser.Identifier.SPECIAL: - return this.options.SCAN_SET_ORDER.substring(this.options.SCAN_SET_ORDER.indexOf('-'), this.options.SCAN_SET_ORDER.indexOf(']') + 1).split(''); - case parser.Identifier.UPPER: - return this.options.SCAN_SET_ORDER.substring(this.options.SCAN_SET_ORDER.indexOf('A'), this.options.SCAN_SET_ORDER.indexOf('Z') + 1).split(''); - } - console.assert(false, parser.SHOULD_NOT_BE_REACHED); - return []; - } - - /** - * @param {(parser.CustomCharacterClass | parser.NamedCharacterClass)[]} characterClasses - */ - _charactersFromCharactersClasses(characterClasses) { - const output = []; - for (let characterClass of characterClasses) { - output.push(...this._scanSetFromCharacterClass(characterClass)); + _addCheck(check) { + return new ZodDate({ + ...this._def, + checks: [...this._def.checks, check] + }); } - return output; - } - - /** - * @param {string[]} characters - * @returns {string} - */ - _canonicalizedScanSetFromCharacters(characters) { - if (!characters.length) { - return ''; - } - let shadowCharacters = Array.prototype.slice.call(characters); - shadowCharacters.sort((a, b) => this.options.SCAN_SET_ORDER.indexOf(a) - this.options.SCAN_SET_ORDER.indexOf(b)); - let uniqueCharacters = [shadowCharacters[0]]; - for (let i = 1, length = shadowCharacters.length; i < length; ++i) { - if (shadowCharacters[i] === shadowCharacters[i - 1]) { - continue; - } - uniqueCharacters.push(shadowCharacters[i]); + min(minDate, message) { + return this._addCheck({ + kind: "min", + value: minDate.getTime(), + message: errorUtil.toString(message) + }); } - return uniqueCharacters.join(''); - } -} -exports.Password = Password; - -},{"./constants.js":19,"./rules-parser.js":20}],19:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.constants = void 0; -const DEFAULT_MIN_LENGTH = 20; -const DEFAULT_MAX_LENGTH = 30; -const DEFAULT_REQUIRED_CHARS = '-!?$&#%'; -const DEFAULT_UNAMBIGUOUS_CHARS = 'abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ0123456789'; -const DEFAULT_PASSWORD_RULES = [`minlength: ${DEFAULT_MIN_LENGTH}`, `maxlength: ${DEFAULT_MAX_LENGTH}`, `required: [${DEFAULT_REQUIRED_CHARS}]`, `allowed: [${DEFAULT_UNAMBIGUOUS_CHARS}]`].join('; '); -const constants = exports.constants = { - DEFAULT_MIN_LENGTH, - DEFAULT_MAX_LENGTH, - DEFAULT_PASSWORD_RULES, - DEFAULT_REQUIRED_CHARS, - DEFAULT_UNAMBIGUOUS_CHARS -}; - -},{}],20:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.SHOULD_NOT_BE_REACHED = exports.RuleName = exports.Rule = exports.ParserError = exports.NamedCharacterClass = exports.Identifier = exports.CustomCharacterClass = void 0; -exports.parsePasswordRules = parsePasswordRules; -// Copyright (c) 2019 - 2020 Apple Inc. Licensed under MIT License. - -/* - * - * NOTE: - * - * This file was taken as intended from https://github.com/apple/password-manager-resources. - * - * The only additions from DuckDuckGo employees are - * - * 1) exporting some identifiers - * 2) adding some JSDoc comments - * 3) making this parser throw when it cannot produce any rules - * ^ the default implementation still returns a base-line ruleset, which we didn't want. - * - */ - -const Identifier = exports.Identifier = { - ASCII_PRINTABLE: 'ascii-printable', - DIGIT: 'digit', - LOWER: 'lower', - SPECIAL: 'special', - UNICODE: 'unicode', - UPPER: 'upper' -}; -const RuleName = exports.RuleName = { - ALLOWED: 'allowed', - MAX_CONSECUTIVE: 'max-consecutive', - REQUIRED: 'required', - MIN_LENGTH: 'minlength', - MAX_LENGTH: 'maxlength' -}; -const CHARACTER_CLASS_START_SENTINEL = '['; -const CHARACTER_CLASS_END_SENTINEL = ']'; -const PROPERTY_VALUE_SEPARATOR = ','; -const PROPERTY_SEPARATOR = ';'; -const PROPERTY_VALUE_START_SENTINEL = ':'; -const SPACE_CODE_POINT = ' '.codePointAt(0); -const SHOULD_NOT_BE_REACHED = exports.SHOULD_NOT_BE_REACHED = 'Should not be reached'; -class Rule { - constructor(name, value) { - this._name = name; - this.value = value; - } - get name() { - return this._name; - } - toString() { - return JSON.stringify(this); - } -} -exports.Rule = Rule; -; -class NamedCharacterClass { - constructor(name) { - console.assert(_isValidRequiredOrAllowedPropertyValueIdentifier(name)); - this._name = name; - } - get name() { - return this._name.toLowerCase(); - } - toString() { - return this._name; - } - toHTMLString() { - return this._name; - } -} -exports.NamedCharacterClass = NamedCharacterClass; -; -class ParserError extends Error {} -exports.ParserError = ParserError; -; -class CustomCharacterClass { - constructor(characters) { - console.assert(characters instanceof Array); - this._characters = characters; - } - get characters() { - return this._characters; - } - toString() { - return `[${this._characters.join('')}]`; - } - toHTMLString() { - return `[${this._characters.join('').replace('"', '"')}]`; - } -} -exports.CustomCharacterClass = CustomCharacterClass; -; - -// MARK: Lexer functions - -function _isIdentifierCharacter(c) { - console.assert(c.length === 1); - // eslint-disable-next-line no-mixed-operators - return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c === '-'; -} -function _isASCIIDigit(c) { - console.assert(c.length === 1); - return c >= '0' && c <= '9'; -} -function _isASCIIPrintableCharacter(c) { - console.assert(c.length === 1); - return c >= ' ' && c <= '~'; -} -function _isASCIIWhitespace(c) { - console.assert(c.length === 1); - return c === ' ' || c === '\f' || c === '\n' || c === '\r' || c === '\t'; -} - -// MARK: ASCII printable character bit set and canonicalization functions - -function _bitSetIndexForCharacter(c) { - console.assert(c.length === 1); - // @ts-ignore - return c.codePointAt(0) - SPACE_CODE_POINT; -} -function _characterAtBitSetIndex(index) { - return String.fromCodePoint(index + SPACE_CODE_POINT); -} -function _markBitsForNamedCharacterClass(bitSet, namedCharacterClass) { - console.assert(bitSet instanceof Array); - console.assert(namedCharacterClass.name !== Identifier.UNICODE); - console.assert(namedCharacterClass.name !== Identifier.ASCII_PRINTABLE); - if (namedCharacterClass.name === Identifier.UPPER) { - bitSet.fill(true, _bitSetIndexForCharacter('A'), _bitSetIndexForCharacter('Z') + 1); - } else if (namedCharacterClass.name === Identifier.LOWER) { - bitSet.fill(true, _bitSetIndexForCharacter('a'), _bitSetIndexForCharacter('z') + 1); - } else if (namedCharacterClass.name === Identifier.DIGIT) { - bitSet.fill(true, _bitSetIndexForCharacter('0'), _bitSetIndexForCharacter('9') + 1); - } else if (namedCharacterClass.name === Identifier.SPECIAL) { - bitSet.fill(true, _bitSetIndexForCharacter(' '), _bitSetIndexForCharacter('/') + 1); - bitSet.fill(true, _bitSetIndexForCharacter(':'), _bitSetIndexForCharacter('@') + 1); - bitSet.fill(true, _bitSetIndexForCharacter('['), _bitSetIndexForCharacter('`') + 1); - bitSet.fill(true, _bitSetIndexForCharacter('{'), _bitSetIndexForCharacter('~') + 1); - } else { - console.assert(false, SHOULD_NOT_BE_REACHED, namedCharacterClass); - } -} -function _markBitsForCustomCharacterClass(bitSet, customCharacterClass) { - for (let character of customCharacterClass.characters) { - bitSet[_bitSetIndexForCharacter(character)] = true; - } -} -function _canonicalizedPropertyValues(propertyValues, keepCustomCharacterClassFormatCompliant) { - // @ts-ignore - let asciiPrintableBitSet = new Array('~'.codePointAt(0) - ' '.codePointAt(0) + 1); - for (let propertyValue of propertyValues) { - if (propertyValue instanceof NamedCharacterClass) { - if (propertyValue.name === Identifier.UNICODE) { - return [new NamedCharacterClass(Identifier.UNICODE)]; - } - if (propertyValue.name === Identifier.ASCII_PRINTABLE) { - return [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)]; - } - _markBitsForNamedCharacterClass(asciiPrintableBitSet, propertyValue); - } else if (propertyValue instanceof CustomCharacterClass) { - _markBitsForCustomCharacterClass(asciiPrintableBitSet, propertyValue); + max(maxDate, message) { + return this._addCheck({ + kind: "max", + value: maxDate.getTime(), + message: errorUtil.toString(message) + }); } - } - let charactersSeen = []; - function checkRange(start, end) { - let temp = []; - for (let i = _bitSetIndexForCharacter(start); i <= _bitSetIndexForCharacter(end); ++i) { - if (asciiPrintableBitSet[i]) { - temp.push(_characterAtBitSetIndex(i)); + get minDate() { + let min = null; + for (const ch of this._def.checks) { + if (ch.kind === "min") { + if (min === null || ch.value > min) + min = ch.value; + } } + return min != null ? new Date(min) : null; } - let result = temp.length === _bitSetIndexForCharacter(end) - _bitSetIndexForCharacter(start) + 1; - if (!result) { - charactersSeen = charactersSeen.concat(temp); - } - return result; - } - let hasAllUpper = checkRange('A', 'Z'); - let hasAllLower = checkRange('a', 'z'); - let hasAllDigits = checkRange('0', '9'); - - // Check for special characters, accounting for characters that are given special treatment (i.e. '-' and ']') - let hasAllSpecial = false; - let hasDash = false; - let hasRightSquareBracket = false; - let temp = []; - for (let i = _bitSetIndexForCharacter(' '); i <= _bitSetIndexForCharacter('/'); ++i) { - if (!asciiPrintableBitSet[i]) { - continue; - } - let character = _characterAtBitSetIndex(i); - if (keepCustomCharacterClassFormatCompliant && character === '-') { - hasDash = true; - } else { - temp.push(character); + get maxDate() { + let max = null; + for (const ch of this._def.checks) { + if (ch.kind === "max") { + if (max === null || ch.value < max) + max = ch.value; + } + } + return max != null ? new Date(max) : null; } - } - for (let i = _bitSetIndexForCharacter(':'); i <= _bitSetIndexForCharacter('@'); ++i) { - if (asciiPrintableBitSet[i]) { - temp.push(_characterAtBitSetIndex(i)); + }; + ZodDate.create = (params) => { + return new ZodDate({ + checks: [], + coerce: (params === null || params === void 0 ? void 0 : params.coerce) || false, + typeName: ZodFirstPartyTypeKind.ZodDate, + ...processCreateParams(params) + }); + }; + var ZodSymbol = class extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.symbol) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.symbol, + received: ctx.parsedType + }); + return INVALID; + } + return OK(input.data); } - } - for (let i = _bitSetIndexForCharacter('['); i <= _bitSetIndexForCharacter('`'); ++i) { - if (!asciiPrintableBitSet[i]) { - continue; + }; + ZodSymbol.create = (params) => { + return new ZodSymbol({ + typeName: ZodFirstPartyTypeKind.ZodSymbol, + ...processCreateParams(params) + }); + }; + var ZodUndefined = class extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.undefined) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.undefined, + received: ctx.parsedType + }); + return INVALID; + } + return OK(input.data); } - let character = _characterAtBitSetIndex(i); - if (keepCustomCharacterClassFormatCompliant && character === ']') { - hasRightSquareBracket = true; - } else { - temp.push(character); + }; + ZodUndefined.create = (params) => { + return new ZodUndefined({ + typeName: ZodFirstPartyTypeKind.ZodUndefined, + ...processCreateParams(params) + }); + }; + var ZodNull = class extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.null) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.null, + received: ctx.parsedType + }); + return INVALID; + } + return OK(input.data); } - } - for (let i = _bitSetIndexForCharacter('{'); i <= _bitSetIndexForCharacter('~'); ++i) { - if (asciiPrintableBitSet[i]) { - temp.push(_characterAtBitSetIndex(i)); + }; + ZodNull.create = (params) => { + return new ZodNull({ + typeName: ZodFirstPartyTypeKind.ZodNull, + ...processCreateParams(params) + }); + }; + var ZodAny = class extends ZodType { + constructor() { + super(...arguments); + this._any = true; } - } - if (hasDash) { - temp.unshift('-'); - } - if (hasRightSquareBracket) { - temp.push(']'); - } - let numberOfSpecialCharacters = _bitSetIndexForCharacter('/') - _bitSetIndexForCharacter(' ') + 1 + (_bitSetIndexForCharacter('@') - _bitSetIndexForCharacter(':') + 1) + (_bitSetIndexForCharacter('`') - _bitSetIndexForCharacter('[') + 1) + (_bitSetIndexForCharacter('~') - _bitSetIndexForCharacter('{') + 1); - hasAllSpecial = temp.length === numberOfSpecialCharacters; - if (!hasAllSpecial) { - charactersSeen = charactersSeen.concat(temp); - } - let result = []; - if (hasAllUpper && hasAllLower && hasAllDigits && hasAllSpecial) { - return [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)]; - } - if (hasAllUpper) { - result.push(new NamedCharacterClass(Identifier.UPPER)); - } - if (hasAllLower) { - result.push(new NamedCharacterClass(Identifier.LOWER)); - } - if (hasAllDigits) { - result.push(new NamedCharacterClass(Identifier.DIGIT)); - } - if (hasAllSpecial) { - result.push(new NamedCharacterClass(Identifier.SPECIAL)); - } - if (charactersSeen.length) { - result.push(new CustomCharacterClass(charactersSeen)); - } - return result; -} - -// MARK: Parser functions - -function _indexOfNonWhitespaceCharacter(input) { - let position = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; - console.assert(position >= 0); - console.assert(position <= input.length); - let length = input.length; - while (position < length && _isASCIIWhitespace(input[position])) { - ++position; - } - return position; -} -function _parseIdentifier(input, position) { - console.assert(position >= 0); - console.assert(position < input.length); - console.assert(_isIdentifierCharacter(input[position])); - let length = input.length; - let seenIdentifiers = []; - do { - let c = input[position]; - if (!_isIdentifierCharacter(c)) { - break; - } - seenIdentifiers.push(c); - ++position; - } while (position < length); - return [seenIdentifiers.join(''), position]; -} -function _isValidRequiredOrAllowedPropertyValueIdentifier(identifier) { - return identifier && Object.values(Identifier).includes(identifier.toLowerCase()); -} -function _parseCustomCharacterClass(input, position) { - console.assert(position >= 0); - console.assert(position < input.length); - console.assert(input[position] === CHARACTER_CLASS_START_SENTINEL); - let length = input.length; - ++position; - if (position >= length) { - // console.error('Found end-of-line instead of character class character') - return [null, position]; - } - let initialPosition = position; - let result = []; - do { - let c = input[position]; - if (!_isASCIIPrintableCharacter(c)) { - ++position; - continue; + _parse(input) { + return OK(input.data); } - if (c === '-' && position - initialPosition > 0) { - // FIXME: Should this be an error? - console.warn("Ignoring '-'; a '-' may only appear as the first character in a character class"); - ++position; - continue; + }; + ZodAny.create = (params) => { + return new ZodAny({ + typeName: ZodFirstPartyTypeKind.ZodAny, + ...processCreateParams(params) + }); + }; + var ZodUnknown = class extends ZodType { + constructor() { + super(...arguments); + this._unknown = true; } - result.push(c); - ++position; - if (c === CHARACTER_CLASS_END_SENTINEL) { - break; - } - } while (position < length); - if (position < length && input[position] !== CHARACTER_CLASS_END_SENTINEL) { - // Fix up result; we over consumed. - result.pop(); - return [result, position]; - } else if (position === length && input[position - 1] === CHARACTER_CLASS_END_SENTINEL) { - // Fix up result; we over consumed. - result.pop(); - return [result, position]; - } - if (position < length && input[position] === CHARACTER_CLASS_END_SENTINEL) { - return [result, position + 1]; - } - - // console.error('Found end-of-line instead of end of character class') - return [null, position]; -} -function _parsePasswordRequiredOrAllowedPropertyValue(input, position) { - console.assert(position >= 0); - console.assert(position < input.length); - let length = input.length; - let propertyValues = []; - while (true) { - if (_isIdentifierCharacter(input[position])) { - let identifierStartPosition = position; - // eslint-disable-next-line no-redeclare - var [propertyValue, position] = _parseIdentifier(input, position); - if (!_isValidRequiredOrAllowedPropertyValueIdentifier(propertyValue)) { - // console.error('Unrecognized property value identifier: ' + propertyValue) - return [null, identifierStartPosition]; - } - propertyValues.push(new NamedCharacterClass(propertyValue)); - } else if (input[position] === CHARACTER_CLASS_START_SENTINEL) { - // eslint-disable-next-line no-redeclare - var [propertyValue, position] = _parseCustomCharacterClass(input, position); - if (propertyValue && propertyValue.length) { - propertyValues.push(new CustomCharacterClass(propertyValue)); - } - } else { - // console.error('Failed to find start of property value: ' + input.substr(position)) - return [null, position]; + _parse(input) { + return OK(input.data); } - position = _indexOfNonWhitespaceCharacter(input, position); - if (position >= length || input[position] === PROPERTY_SEPARATOR) { - break; + }; + ZodUnknown.create = (params) => { + return new ZodUnknown({ + typeName: ZodFirstPartyTypeKind.ZodUnknown, + ...processCreateParams(params) + }); + }; + var ZodNever = class extends ZodType { + _parse(input) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.never, + received: ctx.parsedType + }); + return INVALID; } - if (input[position] === PROPERTY_VALUE_SEPARATOR) { - position = _indexOfNonWhitespaceCharacter(input, position + 1); - if (position >= length) { - // console.error('Found end-of-line instead of start of next property value') - return [null, position]; + }; + ZodNever.create = (params) => { + return new ZodNever({ + typeName: ZodFirstPartyTypeKind.ZodNever, + ...processCreateParams(params) + }); + }; + var ZodVoid = class extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.undefined) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.void, + received: ctx.parsedType + }); + return INVALID; } - continue; + return OK(input.data); } - - // console.error('Failed to find start of next property or property value: ' + input.substr(position)) - return [null, position]; - } - return [propertyValues, position]; -} - -/** - * @param input - * @param position - * @returns {[Rule|null, number, string|undefined]} - * @private - */ -function _parsePasswordRule(input, position) { - console.assert(position >= 0); - console.assert(position < input.length); - console.assert(_isIdentifierCharacter(input[position])); - let length = input.length; - var mayBeIdentifierStartPosition = position; - // eslint-disable-next-line no-redeclare - var [identifier, position] = _parseIdentifier(input, position); - if (!Object.values(RuleName).includes(identifier)) { - // console.error('Unrecognized property name: ' + identifier) - return [null, mayBeIdentifierStartPosition, undefined]; - } - if (position >= length) { - // console.error('Found end-of-line instead of start of property value') - return [null, position, undefined]; - } - if (input[position] !== PROPERTY_VALUE_START_SENTINEL) { - // console.error('Failed to find start of property value: ' + input.substr(position)) - return [null, position, undefined]; - } - let property = { - name: identifier, - value: null }; - position = _indexOfNonWhitespaceCharacter(input, position + 1); - // Empty value - if (position >= length || input[position] === PROPERTY_SEPARATOR) { - return [new Rule(property.name, property.value), position, undefined]; - } - switch (identifier) { - case RuleName.ALLOWED: - case RuleName.REQUIRED: - { - // eslint-disable-next-line no-redeclare - var [propertyValue, position] = _parsePasswordRequiredOrAllowedPropertyValue(input, position); - if (propertyValue) { - property.value = propertyValue; - } - return [new Rule(property.name, property.value), position, undefined]; + ZodVoid.create = (params) => { + return new ZodVoid({ + typeName: ZodFirstPartyTypeKind.ZodVoid, + ...processCreateParams(params) + }); + }; + var ZodArray = class extends ZodType { + _parse(input) { + const { ctx, status } = this._processInputParams(input); + const def = this._def; + if (ctx.parsedType !== ZodParsedType.array) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.array, + received: ctx.parsedType + }); + return INVALID; } - case RuleName.MAX_CONSECUTIVE: - { - // eslint-disable-next-line no-redeclare - var [propertyValue, position] = _parseMaxConsecutivePropertyValue(input, position); - if (propertyValue) { - property.value = propertyValue; + if (def.exactLength !== null) { + const tooBig = ctx.data.length > def.exactLength.value; + const tooSmall = ctx.data.length < def.exactLength.value; + if (tooBig || tooSmall) { + addIssueToContext(ctx, { + code: tooBig ? ZodIssueCode.too_big : ZodIssueCode.too_small, + minimum: tooSmall ? def.exactLength.value : void 0, + maximum: tooBig ? def.exactLength.value : void 0, + type: "array", + inclusive: true, + exact: true, + message: def.exactLength.message + }); + status.dirty(); } - return [new Rule(property.name, property.value), position, undefined]; } - case RuleName.MIN_LENGTH: - case RuleName.MAX_LENGTH: - { - // eslint-disable-next-line no-redeclare - var [propertyValue, position] = _parseMinLengthMaxLengthPropertyValue(input, position); - if (propertyValue) { - property.value = propertyValue; + if (def.minLength !== null) { + if (ctx.data.length < def.minLength.value) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: def.minLength.value, + type: "array", + inclusive: true, + exact: false, + message: def.minLength.message + }); + status.dirty(); } - return [new Rule(property.name, property.value), position, undefined]; - } - } - console.assert(false, SHOULD_NOT_BE_REACHED); - return [null, -1, undefined]; -} -function _parseMinLengthMaxLengthPropertyValue(input, position) { - return _parseInteger(input, position); -} -function _parseMaxConsecutivePropertyValue(input, position) { - return _parseInteger(input, position); -} -function _parseInteger(input, position) { - console.assert(position >= 0); - console.assert(position < input.length); - if (!_isASCIIDigit(input[position])) { - // console.error('Failed to parse value of type integer; not a number: ' + input.substr(position)) - return [null, position]; - } - let length = input.length; - // let initialPosition = position - let result = 0; - do { - result = 10 * result + parseInt(input[position], 10); - ++position; - } while (position < length && input[position] !== PROPERTY_SEPARATOR && _isASCIIDigit(input[position])); - if (position >= length || input[position] === PROPERTY_SEPARATOR) { - return [result, position]; - } - - // console.error('Failed to parse value of type integer; not a number: ' + input.substr(initialPosition)) - return [null, position]; -} - -/** - * @param input - * @returns {[Rule[]|null, string|undefined]} - * @private - */ -function _parsePasswordRulesInternal(input) { - let parsedProperties = []; - let length = input.length; - var position = _indexOfNonWhitespaceCharacter(input); - while (position < length) { - if (!_isIdentifierCharacter(input[position])) { - // console.warn('Failed to find start of property: ' + input.substr(position)) - return [parsedProperties, undefined]; - } - - // eslint-disable-next-line no-redeclare - var [parsedProperty, position, message] = _parsePasswordRule(input, position); - if (parsedProperty && parsedProperty.value) { - parsedProperties.push(parsedProperty); - } - position = _indexOfNonWhitespaceCharacter(input, position); - if (position >= length) { - break; - } - if (input[position] === PROPERTY_SEPARATOR) { - position = _indexOfNonWhitespaceCharacter(input, position + 1); - if (position >= length) { - return [parsedProperties, undefined]; } - continue; - } - - // console.error('Failed to find start of next property: ' + input.substr(position)) - return [null, message || 'Failed to find start of next property: ' + input.substr(position)]; - } - return [parsedProperties, undefined]; -} - -/** - * @param {string} input - * @param {boolean} [formatRulesForMinifiedVersion] - * @returns {Rule[]} - */ -function parsePasswordRules(input, formatRulesForMinifiedVersion) { - let [passwordRules, maybeMessage] = _parsePasswordRulesInternal(input); - if (!passwordRules) { - throw new ParserError(maybeMessage); - } - if (passwordRules.length === 0) { - throw new ParserError('No valid rules were provided'); - } - - // When formatting rules for minified version, we should keep the formatted rules - // as similar to the input as possible. Avoid copying required rules to allowed rules. - let suppressCopyingRequiredToAllowed = formatRulesForMinifiedVersion; - let requiredRules = []; - let newAllowedValues = []; - let minimumMaximumConsecutiveCharacters = null; - let maximumMinLength = 0; - let minimumMaxLength = null; - for (let rule of passwordRules) { - switch (rule.name) { - case RuleName.MAX_CONSECUTIVE: - minimumMaximumConsecutiveCharacters = minimumMaximumConsecutiveCharacters ? Math.min(rule.value, minimumMaximumConsecutiveCharacters) : rule.value; - break; - case RuleName.MIN_LENGTH: - maximumMinLength = Math.max(rule.value, maximumMinLength); - break; - case RuleName.MAX_LENGTH: - minimumMaxLength = minimumMaxLength ? Math.min(rule.value, minimumMaxLength) : rule.value; - break; - case RuleName.REQUIRED: - rule.value = _canonicalizedPropertyValues(rule.value, formatRulesForMinifiedVersion); - requiredRules.push(rule); - if (!suppressCopyingRequiredToAllowed) { - newAllowedValues = newAllowedValues.concat(rule.value); - } - break; - case RuleName.ALLOWED: - newAllowedValues = newAllowedValues.concat(rule.value); - break; - } - } - let newPasswordRules = []; - if (maximumMinLength > 0) { - newPasswordRules.push(new Rule(RuleName.MIN_LENGTH, maximumMinLength)); - } - if (minimumMaxLength !== null) { - newPasswordRules.push(new Rule(RuleName.MAX_LENGTH, minimumMaxLength)); - } - if (minimumMaximumConsecutiveCharacters !== null) { - newPasswordRules.push(new Rule(RuleName.MAX_CONSECUTIVE, minimumMaximumConsecutiveCharacters)); - } - let sortedRequiredRules = requiredRules.sort(function (a, b) { - const namedCharacterClassOrder = [Identifier.LOWER, Identifier.UPPER, Identifier.DIGIT, Identifier.SPECIAL, Identifier.ASCII_PRINTABLE, Identifier.UNICODE]; - let aIsJustOneNamedCharacterClass = a.value.length === 1 && a.value[0] instanceof NamedCharacterClass; - let bIsJustOneNamedCharacterClass = b.value.length === 1 && b.value[0] instanceof NamedCharacterClass; - if (aIsJustOneNamedCharacterClass && !bIsJustOneNamedCharacterClass) { - return -1; - } - if (!aIsJustOneNamedCharacterClass && bIsJustOneNamedCharacterClass) { - return 1; - } - if (aIsJustOneNamedCharacterClass && bIsJustOneNamedCharacterClass) { - let aIndex = namedCharacterClassOrder.indexOf(a.value[0].name); - let bIndex = namedCharacterClassOrder.indexOf(b.value[0].name); - return aIndex - bIndex; - } - return 0; - }); - newPasswordRules = newPasswordRules.concat(sortedRequiredRules); - newAllowedValues = _canonicalizedPropertyValues(newAllowedValues, suppressCopyingRequiredToAllowed); - if (!suppressCopyingRequiredToAllowed && !newAllowedValues.length) { - newAllowedValues = [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)]; - } - if (newAllowedValues.length) { - newPasswordRules.push(new Rule(RuleName.ALLOWED, newAllowedValues)); - } - return newPasswordRules; -} - -},{}],21:[function(require,module,exports){ -module.exports={ - "163.com": { - "password-rules": "minlength: 6; maxlength: 16;" - }, - "1800flowers.com": { - "password-rules": "minlength: 6; required: lower, upper; required: digit;" - }, - "access.service.gov.uk": { - "password-rules": "minlength: 10; required: lower; required: upper; required: digit; required: special;" - }, - "admiral.com": { - "password-rules": "minlength: 8; required: digit; required: [- !\"#$&'()*+,.:;<=>?@[^_`{|}~]]; allowed: lower, upper;" - }, - "ae.com": { - "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit;" - }, - "aetna.com": { - "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 2; required: upper; required: digit; allowed: lower, [-_&#@];" - }, - "airasia.com": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;" - }, - "airfrance.com": { - "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit; allowed: [-!#$&+/?@_];" - }, - "airfrance.us": { - "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit; allowed: [-!#$&+/?@_];" - }, - "ajisushionline.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; allowed: [ !#$%&*?@];" - }, - "aliexpress.com": { - "password-rules": "minlength: 6; maxlength: 20; allowed: lower, upper, digit;" - }, - "alliantcreditunion.com": { - "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower, upper; required: digit; allowed: [!#$*];" - }, - "allianz.com.br": { - "password-rules": "minlength: 4; maxlength: 4;" - }, - "americanexpress.com": { - "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 4; required: lower, upper; required: digit; allowed: [%&_?#=];" - }, - "anatel.gov.br": { - "password-rules": "minlength: 6; maxlength: 15; allowed: lower, upper, digit;" - }, - "ancestry.com": { - "password-rules": "minlength: 8; required: lower, upper; required: digit;" - }, - "angieslist.com": { - "password-rules": "minlength: 6; maxlength: 15;" - }, - "anthem.com": { - "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower, upper; required: digit; allowed: [!$*?@|];" - }, - "app.digio.in": { - "password-rules": "minlength: 8; maxlength: 15;" - }, - "app.parkmobile.io": { - "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit; required: [!@#$%^&];" - }, - "apple.com": { - "password-rules": "minlength: 8; maxlength: 63; required: lower; required: upper; required: digit; allowed: ascii-printable;" - }, - "areariservata.bancaetica.it": { - "password-rules": "minlength: 8; maxlength: 10; required: lower; required: upper; required: digit; required: [!#&*+/=@_];" - }, - "artscyclery.com": { - "password-rules": "minlength: 6; maxlength: 19;" - }, - "astonmartinf1.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: special;" - }, - "auth.readymag.com": { - "password-rules": "minlength: 8; maxlength: 128; required: lower; required: upper; allowed: special;" - }, - "autify.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!\"#$%&'()*+,./:;<=>?@[^_`{|}~]];" - }, - "axa.de": { - "password-rules": "minlength: 8; maxlength: 65; required: lower; required: upper; required: digit; allowed: [-!\"§$%&/()=?;:_+*'#];" - }, - "baidu.com": { - "password-rules": "minlength: 6; maxlength: 14;" - }, - "bancochile.cl": { - "password-rules": "minlength: 8; maxlength: 8; required: lower; required: upper; required: digit;" - }, - "bankofamerica.com": { - "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower; required: upper; required: digit; allowed: [-@#*()+={}/?~;,._];" - }, - "battle.net": { - "password-rules": "minlength: 8; maxlength: 16; required: lower, upper; allowed: digit, special;" - }, - "bcassessment.ca": { - "password-rules": "minlength: 8; maxlength: 14;" - }, - "belkin.com": { - "password-rules": "minlength: 8; required: lower, upper; required: digit; required: [$!@~_,%&];" - }, - "benefitslogin.discoverybenefits.com": { - "password-rules": "minlength: 10; required: upper; required: digit; required: [!#$%&*?@]; allowed: lower;" - }, - "benjerry.com": { - "password-rules": "required: upper; required: upper; required: digit; required: digit; required: special; required: special; allowed: lower;" - }, - "bestbuy.com": { - "password-rules": "minlength: 20; required: lower; required: upper; required: digit; required: special;" - }, - "bhphotovideo.com": { - "password-rules": "maxlength: 15;" - }, - "bilibili.com": { - "password-rules": "maxlength: 16;" - }, - "billerweb.com": { - "password-rules": "minlength: 8; max-consecutive: 2; required: digit; required: upper,lower;" - }, - "biovea.com": { - "password-rules": "maxlength: 19;" - }, - "bitly.com": { - "password-rules": "minlength: 6; required: lower; required: upper; required: digit; required: [`!@#$%^&*()+~{}'\";:<>?]];" - }, - "bloomingdales.com": { - "password-rules": "minlength: 7; maxlength: 16; required: lower, upper; required: digit; required: [`!@#$%^&*()+~{}'\";:<>?]];" - }, - "bluesguitarunleashed.com": { - "password-rules": "allowed: lower, upper, digit, [!$#@];" - }, - "bochk.com": { - "password-rules": "minlength: 8; maxlength: 12; max-consecutive: 3; required: lower; required: upper; required: digit; allowed: [#$%&()*+,.:;<=>?@_];" - }, - "box.com": { - "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: digit;" - }, - "brighthorizons.com": { - "password-rules": "minlength: 8; maxlength: 16;" - }, - "callofduty.com": { - "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 2; required: lower, upper; required: digit;" - }, - "capitalone.com": { - "password-rules": "minlength: 8; maxlength: 32; required: lower, upper; required: digit; allowed: [-_./\\@$*&!#];" - }, - "cardbenefitservices.com": { - "password-rules": "minlength: 7; maxlength: 100; required: lower, upper; required: digit;" - }, - "carrefour.it": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&*?@_];" - }, - "cb2.com": { - "password-rules": "minlength: 7; maxlength: 18; required: lower, upper; required: digit;" - }, - "ccs-grp.com": { - "password-rules": "minlength: 8; maxlength: 16; required: digit; required: upper,lower; allowed: [-!#$%&'+./=?\\^_`{|}~];" - }, - "cecredentialtrust.com": { - "password-rules": "minlength: 12; required: lower; required: upper; required: digit; required: [!#$%&*@^];" - }, - "chase.com": { - "password-rules": "minlength: 8; maxlength: 32; max-consecutive: 2; required: lower, upper; required: digit; required: [!#$%+/=@~];" - }, - "cigna.co.uk": { - "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit;" - }, - "citi.com": { - "password-rules": "minlength: 8; maxlength: 64; max-consecutive: 2; required: digit; required: upper; required: lower; required: [-~`!@#$%^&*()_\\/|];" - }, - "claimlookup.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [@#$%^&+=!];" - }, - "claro.com.br": { - "password-rules": "minlength: 8; required: lower; allowed: upper, digit, [-!@#$%&*_+=<>];" - }, - "classmates.com": { - "password-rules": "minlength: 6; maxlength: 20; allowed: lower, upper, digit, [!@#$%^&*];" - }, - "clegc-gckey.gc.ca": { - "password-rules": "minlength: 8; maxlength: 16; required: lower, upper, digit;" - }, - "clien.net": { - "password-rules": "minlength: 5; required: lower, upper; required: digit;" - }, - "cogmembers.org": { - "password-rules": "minlength: 8; maxlength: 14; required: upper; required: digit, allowed: lower;" - }, - "collectivehealth.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" - }, - "comcastpaymentcenter.com": { - "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 2;required: lower, upper; required: digit;" - }, - "comed.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [-~!@#$%^&*_+=`|(){}[:;\"'<>,.?/\\]];" - }, - "commerzbank.de": { - "password-rules": "minlength: 5; maxlength: 8; required: lower, upper; required: digit;" - }, - "consorsbank.de": { - "password-rules": "minlength: 5; maxlength: 5; required: lower, upper, digit;" - }, - "consorsfinanz.de": { - "password-rules": "minlength: 6; maxlength: 15; allowed: lower, upper, digit, [-.];" - }, - "costco.com": { - "password-rules": "minlength: 8; maxlength: 20; required: lower, upper; allowed: digit, [-!#$%&'()*+/:;=?@[^_`{|}~]];" - }, - "coursera.com": { - "password-rules": "minlength: 8; maxlength: 72;" - }, - "cox.com": { - "password-rules": "minlength: 8; maxlength: 24; required: digit; required: upper,lower; allowed: [!#$%()*@^];" - }, - "crateandbarrel.com": { - "password-rules": "minlength: 9; maxlength: 64; required: lower; required: upper; required: digit; required: [!\"#$%&()*,.:<>?@^_{|}];" - }, - "cvs.com": { - "password-rules": "minlength: 8; maxlength: 25; required: lower, upper; required: digit; allowed: [!@#$%^&*()];" - }, - "dailymail.co.uk": { - "password-rules": "minlength: 5; maxlength: 15;" - }, - "dan.org": { - "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit; required: [!@$%^&*];" - }, - "danawa.com": { - "password-rules": "minlength: 8; maxlength: 21; required: lower, upper; required: digit; required: [!@$%^&*];" - }, - "darty.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" - }, - "dbs.com.hk": { - "password-rules": "minlength: 8; maxlength: 30; required: lower; required: upper; required: digit;" - }, - "decluttr.com": { - "password-rules": "minlength: 8; maxlength: 45; required: lower; required: upper; required: digit;" - }, - "delta.com": { - "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit;" - }, - "deutsche-bank.de": { - "password-rules": "minlength: 5; maxlength: 5; required: lower, upper, digit;" - }, - "devstore.cn": { - "password-rules": "minlength: 6; maxlength: 12;" - }, - "dickssportinggoods.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&*?@^];" - }, - "dkb.de": { - "password-rules": "minlength: 8; maxlength: 38; required: lower, upper; required: digit; allowed: [-äüöÄÜÖß!$%&/()=?+#,.:];" - }, - "dmm.com": { - "password-rules": "minlength: 4; maxlength: 16; required: lower; required: upper; required: digit;" - }, - "dowjones.com": { - "password-rules": "maxlength: 15;" - }, - "ea.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: special;" - }, - "easycoop.com": { - "password-rules": "minlength: 8; required: upper; required: special; allowed: lower, digit;" - }, - "easyjet.com": { - "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: [-];" - }, - "ebrap.org": { - "password-rules": "minlength: 15; required: lower; required: lower; required: upper; required: upper; required: digit; required: digit; required: [-!@#$%^&*()_+|~=`{}[:\";'?,./.]]; required: [-!@#$%^&*()_+|~=`{}[:\";'?,./.]];" - }, - "ecompanystore.com": { - "password-rules": "minlength: 8; maxlength: 16; max-consecutive: 2; required: lower; required: upper; required: digit; required: [#$%*+.=@^_];" - }, - "eddservices.edd.ca.gov": { - "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit; required: [!@#$%^&*()];" - }, - "edistrict.kerala.gov.in": { - "password-rules": "minlength: 5; maxlength: 15; required: lower; required: upper; required: digit; required: [!@#$];" - }, - "empower-retirement.com": { - "password-rules": "minlength: 8; maxlength: 16;" - }, - "epicgames.com": { - "password-rules": "minlength: 7; required: lower; required: upper; required: digit; required: [-!\"#$%&'()*+,./:;<=>?@[^_`{|}~]];" - }, - "epicmix.com": { - "password-rules": "minlength: 8; maxlength: 16;" - }, - "equifax.com": { - "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; required: [!$*+@];" - }, - "essportal.excelityglobal.com": { - "password-rules": "minlength: 6; maxlength: 8; allowed: lower, upper, digit;" - }, - "ettoday.net": { - "password-rules": "minlength: 6; maxlength: 12;" - }, - "examservice.com.tw": { - "password-rules": "minlength: 6; maxlength: 8;" - }, - "expertflyer.com": { - "password-rules": "minlength: 5; maxlength: 16; required: lower, upper; required: digit;" - }, - "extraspace.com": { - "password-rules": "minlength: 8; maxlength: 20; allowed: lower; required: upper, digit, [!#$%&*?@];" - }, - "ezpassva.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;" - }, - "fc2.com": { - "password-rules": "minlength: 8; maxlength: 16;" - }, - "fccaccessonline.com": { - "password-rules": "minlength: 8; maxlength: 14; max-consecutive: 3; required: lower; required: upper; required: digit; required: [!#$%*^_];" - }, - "fedex.com": { - "password-rules": "minlength: 8; max-consecutive: 3; required: lower; required: upper; required: digit; allowed: [-!@#$%^&*_+=`|(){}[:;,.?]];" - }, - "fidelity.com": { - "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: [-!$%+,./:;=?@^_|]; max-consecutive: 2;" - }, - "flysas.com": { - "password-rules": "minlength: 8; maxlength: 14; required: lower; required: upper; required: digit; required: [-~!@#$%^&_+=`|(){}[:\"'<>,.?]];" - }, - "fnac.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" - }, - "fuelrewards.com": { - "password-rules": "minlength: 8; maxlength: 16; allowed: upper,lower,digit,[!#$%@];" - }, - "gamestop.com": { - "password-rules": "minlength: 8; maxlength: 225; required: lower; required: upper; required: digit; required: [!@#$%];" - }, - "garmin.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" - }, - "getflywheel.com": { - "password-rules": "minlength: 7; maxlength: 72;" - }, - "girlscouts.org": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [$#!];" - }, - "gmx.net": { - "password-rules": "minlength: 8; maxlength: 40; allowed: lower, upper, digit, [-<=>~!|()@#{}$%,.?^'&*_+`:;\"[]];" - }, - "gocurb.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [$%&#*?!@^];" - }, - "google.com": { - "password-rules": "minlength: 8; allowed: lower, upper, digit, [-!\"#$%&'()*+,./:;<=>?@[^_{|}~]];" - }, - "guardiananytime.com": { - "password-rules": "minlength: 8; maxlength: 50; max-consecutive: 2; required: lower; required: upper; required: digit, [-~!@#$%^&*_+=`|(){}[:;,.?]];" - }, - "gwl.greatwestlife.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [-!#$%_=+<>];" - }, - "hangseng.com": { - "password-rules": "minlength: 8; maxlength: 30; required: lower; required: upper; required: digit;" - }, - "hawaiianairlines.com": { - "password-rules": "maxlength: 16;" - }, - "hertz-japan.com": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz-kuwait.com": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz-saudi.com": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.at": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.be": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.bh": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.ca": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.ch": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.cn": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.co.ao": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.co.id": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.co.kr": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.co.nz": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.co.th": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.co.uk": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.au": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.bh": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.hk": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.kw": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.mt": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.pl": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.pt": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.sg": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.tw": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.cv": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.cz": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.de": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.ee": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.es": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.fi": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.fr": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.hu": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.ie": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.it": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.jo": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.lt": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.nl": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.no": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.nu": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.pl": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.pt": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.qa": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.ru": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.se": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.si": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertzcaribbean.com": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hetzner.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit, special;" - }, - "hilton.com": { - "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit;" - }, - "hkbea.com": { - "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit;" - }, - "hkexpress.com": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: special;" - }, - "hotels.com": { - "password-rules": "minlength: 6; maxlength: 20; required: digit; required: [-~#@$%&!*_?^]; allowed: lower, upper;" - }, - "hotwire.com": { - "password-rules": "minlength: 6; maxlength: 30; allowed: lower, upper, digit, [-~!@#$%^&*_+=`|(){}[:;\"'<>,.?]];" - }, - "hrblock.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [$#%!];" - }, - "hsbc.com.hk": { - "password-rules": "minlength: 6; maxlength: 30; required: lower; required: upper; required: digit; allowed: ['.@_];" - }, - "hsbc.com.my": { - "password-rules": "minlength: 8; maxlength: 30; required: lower, upper; required: digit; allowed: [-!$*.=?@_'];" - }, - "hypovereinsbank.de": { - "password-rules": "minlength: 6; maxlength: 10; required: lower, upper, digit; allowed: [!\"#$%&()*+:;<=>?@[{}~]];" - }, - "hyresbostader.se": { - "password-rules": "minlength: 6; maxlength: 20; required: lower, upper; required: digit;" - }, - "ichunqiu.com": { - "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit;" - }, - "id.sonyentertainmentnetwork.com": { - "password-rules": "minlength: 8; maxlength: 30; required: lower, upper; required: digit; allowed: [-!@#^&*=+;:];" - }, - "identitytheft.gov": { - "password-rules": "allowed: lower, upper, digit, [!#%&*@^];" - }, - "idestination.info": { - "password-rules": "maxlength: 15;" - }, - "impots.gouv.fr": { - "password-rules": "minlength: 12; maxlength: 128; required: lower; required: digit; allowed: [-!#$%&*+/=?^_'.{|}];" - }, - "indochino.com": { - "password-rules": "minlength: 6; maxlength: 15; required: upper; required: digit; allowed: lower, special;" - }, - "inntopia.travel": { - "password-rules": "minlength: 7; maxlength: 19; required: digit; allowed: upper,lower,[-];" - }, - "internationalsos.com": { - "password-rules": "required: lower; required: upper; required: digit; required: [@#$%^&+=_];" - }, - "irctc.co.in": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [!@#$%^&*()+];" - }, - "irs.gov": { - "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit; required: [!#$%&*@];" - }, - "jal.co.jp": { - "password-rules": "minlength: 8; maxlength: 16;" - }, - "japanpost.jp": { - "password-rules": "minlength: 8; maxlength: 16; required: digit; required: upper,lower;" - }, - "jordancu-onlinebanking.org": { - "password-rules": "minlength: 6; maxlength: 32; allowed: upper, lower, digit,[-!\"#$%&'()*+,.:;<=>?@[^_`{|}~]];" - }, - "keldoc.com": { - "password-rules": "minlength: 12; required: lower; required: upper; required: digit; required: [!@#$%^&*];" - }, - "key.harvard.edu": { - "password-rules": "minlength: 10; maxlength: 100; required: lower; required: upper; required: digit; allowed: [-@_#!&$`%*+()./,;~:{}|?>=<^[']];" - }, - "kfc.ca": { - "password-rules": "minlength: 6; maxlength: 15; required: lower; required: upper; required: digit; required: [!@#$%&?*];" - }, - "klm.com": { - "password-rules": "minlength: 8; maxlength: 12;" - }, - "kundenportal.edeka-smart.de": { - "password-rules": "minlength: 8; maxlength: 16; required: digit; required: upper, lower; required: [!\"§$%&#];" - }, - "la-z-boy.com": { - "password-rules": "minlength: 6; maxlength: 15; required: lower, upper; required: digit;" - }, - "labcorp.com": { - "password-rules": "minlength: 8; maxlength: 20; required: upper; required: lower; required: digit; required: [!@#$%^&*];" - }, - "ladwp.com": { - "password-rules": "minlength: 8; maxlength: 20; required: digit; allowed: lower, upper;" - }, - "launtel.net.au": { - "password-rules": "minlength: 8; required: digit; required: digit; allowed: lower, upper;" - }, - "leetchi.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&()*+,./:;<>?@\"_];" - }, - "lepida.it": { - "password-rules": "minlength: 8; maxlength: 16; max-consecutive: 2; required: lower; required: upper; required: digit; required: [-!\"#$%&'()*+,.:;<=>?@[^_`{|}~]];" - }, - "lg.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [-!#$%&'()*+,.:;=?@[^_{|}~]];" - }, - "linearity.io": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: special;" - }, - "live.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; allowed: [-@_#!&$`%*+()./,;~:{}|?>=<^'[]];" - }, - "lloydsbank.co.uk": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: digit; allowed: upper;" - }, - "lowes.com": { - "password-rules": "minlength: 8; maxlength: 128; max-consecutive: 3; required: lower, upper; required: digit;" - }, - "loyalty.accor.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&=@];" - }, - "lsacsso.b2clogin.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit, [-!#$%&*?@^_];" - }, - "lufthansa.com": { - "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit; required: [!#$%&()*+,./:;<>?@\"_];" - }, - "macys.com": { - "password-rules": "minlength: 7; maxlength: 16; allowed: lower, upper, digit, [~!@#$%^&*+`(){}[:;\"'<>?]];" - }, - "mailbox.org": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; allowed: [-!$\"%&/()=*+#.,;:@?{}[]];" - }, - "makemytrip.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [@$!%*#?&];" - }, - "marriott.com": { - "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; allowed: [$!#&@?%=];" - }, - "maybank2u.com.my": { - "password-rules": "minlength: 8; maxlength: 12; max-consecutive: 2; required: lower; required: upper; required: digit; required: [-~!@#$%^&*_+=`|(){}[:;\"'<>,.?];" - }, - "medicare.gov": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [@!$%^*()];" - }, - "member.everbridge.net": { - "password-rules": "minlength: 8; required: lower, upper; required: digit; allowed: [!@#$%^&*()];" - }, - "metlife.com": { - "password-rules": "minlength: 6; maxlength: 20;" - }, - "microsoft.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: special;" - }, - "milogin.michigan.gov": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [@#$!~&];" - }, - "mintmobile.com": { - "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; required: special; allowed: [!#$%&()*+:;=@[^_`{}~]];" - }, - "mlb.com": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;" - }, - "mpv.tickets.com": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;" - }, - "museumofflight.org": { - "password-rules": "minlength: 8; maxlength: 15;" - }, - "my.konami.net": { - "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit;" - }, - "myaccess.dmdc.osd.mil": { - "password-rules": "minlength: 9; maxlength: 20; required: lower; required: upper; required: digit; allowed: [-@_#!&$`%*+()./,;~:{}|?>=<^'[]];" - }, - "mygoodtogo.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower, upper, digit;" - }, - "myhealthrecord.com": { - "password-rules": "minlength: 8; maxlength: 20; allowed: lower, upper, digit, [_.!$*=];" - }, - "mysavings.breadfinancial.com": { - "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit; required: [+_%@!$*~];" - }, - "mysedgwick.com": { - "password-rules": "minlength: 8; maxlength: 16; allowed: lower; required: upper; required: digit; required: [@#%^&+=!]; allowed: [-~_$.,;]" - }, - "mysubaru.com": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; allowed: [!#$%()*+,./:;=?@\\^`~];" - }, - "naver.com": { - "password-rules": "minlength: 6; maxlength: 16;" - }, - "nelnet.net": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit, [!@#$&*];" - }, - "netflix.com": { - "password-rules": "minlength: 4; maxlength: 60; required: lower, upper, digit; allowed: special;" - }, - "netgear.com": { - "password-rules": "minlength: 6; maxlength: 128; allowed: lower, upper, digit, [!@#$%^&*()];" - }, - "nowinstock.net": { - "password-rules": "minlength: 6; maxlength: 20; allowed: lower, upper, digit;" - }, - "order.wendys.com": { - "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; allowed: [!#$%&()*+/=?^_{}];" - }, - "ototoy.jp": { - "password-rules": "minlength: 8; allowed: upper,lower,digit,[- .=_];" - }, - "packageconciergeadmin.com": { - "password-rules": "minlength: 4; maxlength: 4; allowed: digit;" - }, - "paypal.com": { - "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower, upper; required: digit, [!@#$%^&*()];" - }, - "payvgm.youraccountadvantage.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: special;" - }, - "pilotflyingj.com": { - "password-rules": "minlength: 7; required: digit; allowed: lower, upper;" - }, - "pixnet.cc": { - "password-rules": "minlength: 4; maxlength: 16; allowed: lower, upper;" - }, - "planetary.org": { - "password-rules": "minlength: 5; maxlength: 20; required: lower; required: upper; required: digit; allowed: ascii-printable;" - }, - "plazapremiumlounge.com": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; allowed: [!#$%&*,@^];" - }, - "portal.edd.ca.gov": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&()*@^];" - }, - "portals.emblemhealth.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&'()*+,./:;<>?@\\^_`{|}~[]];" - }, - "portlandgeneral.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [!#$%&*?@];" - }, - "poste.it": { - "password-rules": "minlength: 8; maxlength: 16; max-consecutive: 2; required: lower; required: upper; required: digit; required: special;" - }, - "posteo.de": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit, [-~!#$%&_+=|(){}[:;\"’<>,.? ]];" - }, - "powells.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [\"!@#$%^&*(){}[]];" - }, - "preferredhotels.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&()*+@^_];" - }, - "premier.ticketek.com.au": { - "password-rules": "minlength: 6; maxlength: 16;" - }, - "premierinn.com": { - "password-rules": "minlength: 8; required: upper; required: digit; allowed: lower;" - }, - "prepaid.bankofamerica.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [!@#$%^&*()+~{}'\";:<>?];" - }, - "prestocard.ca": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit,[!\"#$%&'()*+,<>?@];" - }, - "propelfuels.com": { - "password-rules": "minlength: 6; maxlength: 16;" - }, - "qdosstatusreview.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&@^];" - }, - "questdiagnostics.com": { - "password-rules": "minlength: 8; maxlength: 30; required: upper, lower; required: digit, [!#$%&()*+<>?@^_~];" - }, - "rejsekort.dk": { - "password-rules": "minlength: 7; maxlength: 15; required: lower; required: upper; required: digit;" - }, - "renaud-bray.com": { - "password-rules": "minlength: 8; maxlength: 38; allowed: upper,lower,digit;" - }, - "ring.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!@#$%^&*<>?];" - }, - "riteaid.com": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;" - }, - "robinhood.com": { - "password-rules": "minlength: 10;" - }, - "rogers.com": { - "password-rules": "minlength: 8; required: lower, upper; required: digit; required: [!@#$];" - }, - "ruc.dk": { - "password-rules": "minlength: 6; maxlength: 8; required: lower, upper; required: [-!#%&(){}*+;%/<=>?_];" - }, - "runescape.com": { - "password-rules": "minlength: 5; maxlength: 20; required: lower; required: upper; required: digit;" - }, - "ruten.com.tw": { - "password-rules": "minlength: 6; maxlength: 15; required: lower, upper;" - }, - "salslimo.com": { - "password-rules": "minlength: 8; maxlength: 50; required: upper; required: lower; required: digit; required: [!@#$&*];" - }, - "santahelenasaude.com.br": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [-!@#$%&*_+=<>];" - }, - "santander.de": { - "password-rules": "minlength: 8; maxlength: 12; required: lower, upper; required: digit; allowed: [-!#$%&'()*,.:;=?^{}];" - }, - "sbisec.co.jp": { - "password-rules": "minlength: 10; maxlength: 20; allowed: upper,lower,digit;" - }, - "secure-arborfcu.org": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [!#$%&'()+,.:?@[_`~]];" - }, - "secure.orclinic.com": { - "password-rules": "minlength: 6; maxlength: 15; required: lower; required: digit; allowed: ascii-printable;" - }, - "secure.snnow.ca": { - "password-rules": "minlength: 7; maxlength: 16; required: digit; allowed: lower, upper;" - }, - "sephora.com": { - "password-rules": "minlength: 6; maxlength: 12;" - }, - "serviziconsolari.esteri.it": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;" - }, - "servizioelettriconazionale.it": { - "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; required: [!#$%&*?@^_~];" - }, - "sfwater.org": { - "password-rules": "minlength: 10; maxlength: 30; required: digit; allowed: lower, upper, [!@#$%*()_+^}{:;?.];" - }, - "signin.ea.com": { - "password-rules": "minlength: 8; maxlength: 64; required: lower, upper; required: digit; allowed: [-!@#^&*=+;:];" - }, - "southwest.com": { - "password-rules": "minlength: 8; maxlength: 16; required: upper; required: digit; allowed: lower, [!@#$%^*(),.;:/\\];" - }, - "speedway.com": { - "password-rules": "minlength: 4; maxlength: 8; required: digit;" - }, - "spirit.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [!@#$%^&*()];" - }, - "splunk.com": { - "password-rules": "minlength: 8; maxlength: 64; required: lower; required: upper; required: digit; required: [-!@#$%&*_+=<>];" - }, - "ssa.gov": { - "password-rules": "required: lower; required: upper; required: digit; required: [~!@#$%^&*];" - }, - "store.nintendo.co.uk": { - "password-rules": "minlength: 8; maxlength: 20;" - }, - "store.nvidia.com": { - "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit; required: [-!@#$%^*~:;&><[{}|_+=?]];" - }, - "store.steampowered.com": { - "password-rules": "minlength: 6; required: lower; required: upper; required: digit; allowed: [~!@#$%^&*];" - }, - "successfactors.eu": { - "password-rules": "minlength: 8; maxlength: 18; required: lower; required: upper; required: digit,[-!\"#$%&'()*+,.:;<=>?@[^_`{|}~]];" - }, - "sulamericaseguros.com.br": { - "password-rules": "minlength: 6; maxlength: 6;" - }, - "sunlife.com": { - "password-rules": "minlength: 8; maxlength: 10; required: digit; required: lower, upper;" - }, - "t-mobile.net": { - "password-rules": "minlength: 8; maxlength: 16;" - }, - "target.com": { - "password-rules": "minlength: 8; maxlength: 20; required: lower, upper; required: digit, [-!\"#$%&'()*+,./:;=?@[\\^_`{|}~];" - }, - "tdscpc.gov.in": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [ &',;\"];" - }, - "telekom-dienste.de": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [#$%&()*+,./<=>?@_{|}~];" - }, - "thameswater.co.uk": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;" - }, - "tix.soundrink.com": { - "password-rules": "minlength: 6; maxlength: 16;" - }, - "training.confluent.io": { - "password-rules": "minlength: 6; maxlength: 16; required: lower; required: upper; required: digit; allowed: [!#$%*@^_~];" - }, - "treasurer.mo.gov": { - "password-rules": "minlength: 8; maxlength: 26; required: lower; required: upper; required: digit; required: [!#$&];" - }, - "twitch.tv": { - "password-rules": "minlength: 8; maxlength: 71;" - }, - "twitter.com": { - "password-rules": "minlength: 8;" - }, - "ubisoft.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [-]; required: [!@#$%^&*()+];" - }, - "udel.edu": { - "password-rules": "minlength: 12; maxlength: 30; required: lower; required: upper; required: digit; required: [!@#$%^&*()+];" - }, - "user.ornl.gov": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower, upper; required: digit; allowed: [!#$%./_];" - }, - "usps.com": { - "password-rules": "minlength: 8; maxlength: 50; max-consecutive: 2; required: lower; required: upper; required: digit; allowed: [-!\"#&'()+,./?@];" - }, - "vanguard.com": { - "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: digit;" - }, - "vanguardinvestor.co.uk": { - "password-rules": "minlength: 8; maxlength: 50; required: lower; required: upper; required: digit; required: digit;" - }, - "ventrachicago.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit, [!@#$%^];" - }, - "verizonwireless.com": { - "password-rules": "minlength: 8; maxlength: 20; required: lower, upper; required: digit; allowed: unicode;" - }, - "vetsfirstchoice.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; allowed: [?!@$%^+=&];" - }, - "vince.com": { - "password-rules": "minlength: 8; required: digit; required: lower; required: upper; required: [$%/(){}=?!.,_*|+~#[]];" - }, - "virginmobile.ca": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$@];" - }, - "visa.com": { - "password-rules": "minlength: 6; maxlength: 32;" - }, - "visabenefits-auth.axa-assistance.us": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!\"#$%&()*,.:<>?@^{|}];" - }, - "vivo.com.br": { - "password-rules": "maxlength: 6; max-consecutive: 3; allowed: digit;" - }, - "volaris.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;" - }, - "wa.aaa.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: ascii-printable;" - }, - "walkhighlands.co.uk": { - "password-rules": "minlength: 9; maxlength: 15; required: lower; required: upper; required: digit; allowed: special;" - }, - "walmart.com": { - "password-rules": "allowed: lower, upper, digit, [-(~!@#$%^&*_+=`|(){}[:;\"'<>,.?]];" - }, - "waze.com": { - "password-rules": "minlength: 8; maxlength: 64; required: lower, upper, digit;" - }, - "wccls.org": { - "password-rules": "minlength: 4; maxlength: 16; allowed: lower, upper, digit;" - }, - "web.de": { - "password-rules": "minlength: 8; maxlength: 40; allowed: lower, upper, digit, [-<=>~!|()@#{}$%,.?^'&*_+`:;\"[]];" - }, - "wegmans.com": { - "password-rules": "minlength: 8; required: digit; required: upper,lower; required: [!#$%&*+=?@^];" - }, - "weibo.com": { - "password-rules": "minlength: 6; maxlength: 16;" - }, - "wellsfargo.com": { - "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit;" - }, - "wmata.com": { - "password-rules": "minlength: 8; required: lower, upper; required: digit; required: digit; required: [-!@#$%^&*~/\"()_=+\\|,.?[]];" - }, - "worldstrides.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [-!#$%&*+=?@^_~];" - }, - "wsj.com": { - "password-rules": "minlength: 5; maxlength: 15; required: digit; allowed: lower, upper, [-~!@#$^*_=`|(){}[:;\"'<>,.?]];" - }, - "xfinity.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower, upper; required: digit;" - }, - "xvoucher.com": { - "password-rules": "minlength: 11; required: upper; required: digit; required: [!@#$%&_];" - }, - "yatra.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&'()+,.:?@[_`~]];" - }, - "zara.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" - }, - "zdf.de": { - "password-rules": "minlength: 8; required: upper; required: digit; allowed: lower, special;" - }, - "zoom.us": { - "password-rules": "minlength: 8; maxlength: 32; max-consecutive: 6; required: lower; required: upper; required: digit;" - } -} -},{}],22:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.createDevice = createDevice; -var _config = require("./config.js"); -var _AndroidInterface = require("./DeviceInterface/AndroidInterface.js"); -var _ExtensionInterface = require("./DeviceInterface/ExtensionInterface.js"); -var _AppleDeviceInterface = require("./DeviceInterface/AppleDeviceInterface.js"); -var _AppleOverlayDeviceInterface = require("./DeviceInterface/AppleOverlayDeviceInterface.js"); -var _transports = require("./deviceApiCalls/transports/transports.js"); -var _index = require("../packages/device-api/index.js"); -var _Settings = require("./Settings.js"); -var _WindowsInterface = require("./DeviceInterface/WindowsInterface.js"); -var _WindowsOverlayDeviceInterface = require("./DeviceInterface/WindowsOverlayDeviceInterface.js"); -function createDevice() { - const globalConfig = (0, _config.createGlobalConfig)(); - const transport = (0, _transports.createTransport)(globalConfig); - - /** - * A wrapper around transports to assist in debugging/integrations - * @type {import("../packages/device-api").DeviceApiTransport} - */ - const loggingTransport = { - async send(deviceApiCall) { - console.log('[->outgoing]', 'id:', deviceApiCall.method, deviceApiCall.params || null); - const result = await transport.send(deviceApiCall); - console.log('[<-incoming]', 'id:', deviceApiCall.method, result || null); - return result; + if (def.maxLength !== null) { + if (ctx.data.length > def.maxLength.value) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: def.maxLength.value, + type: "array", + inclusive: true, + exact: false, + message: def.maxLength.message + }); + status.dirty(); + } + } + if (ctx.common.async) { + return Promise.all([...ctx.data].map((item, i) => { + return def.type._parseAsync(new ParseInputLazyPath(ctx, item, ctx.path, i)); + })).then((result2) => { + return ParseStatus.mergeArray(status, result2); + }); + } + const result = [...ctx.data].map((item, i) => { + return def.type._parseSync(new ParseInputLazyPath(ctx, item, ctx.path, i)); + }); + return ParseStatus.mergeArray(status, result); } - }; - - // Create the DeviceAPI + Setting - let deviceApi = new _index.DeviceApi(globalConfig.isDDGTestMode ? loggingTransport : transport); - const settings = new _Settings.Settings(globalConfig, deviceApi); - if (globalConfig.isWindows) { - if (globalConfig.isTopFrame) { - return new _WindowsOverlayDeviceInterface.WindowsOverlayDeviceInterface(globalConfig, deviceApi, settings); - } - return new _WindowsInterface.WindowsInterface(globalConfig, deviceApi, settings); - } - if (globalConfig.isDDGApp) { - if (globalConfig.isAndroid) { - return new _AndroidInterface.AndroidInterface(globalConfig, deviceApi, settings); + get element() { + return this._def.type; } - if (globalConfig.isTopFrame) { - return new _AppleOverlayDeviceInterface.AppleOverlayDeviceInterface(globalConfig, deviceApi, settings); + min(minLength, message) { + return new ZodArray({ + ...this._def, + minLength: { value: minLength, message: errorUtil.toString(message) } + }); } - return new _AppleDeviceInterface.AppleDeviceInterface(globalConfig, deviceApi, settings); - } - globalConfig.isExtension = true; - return new _ExtensionInterface.ExtensionInterface(globalConfig, deviceApi, settings); -} - -},{"../packages/device-api/index.js":12,"./DeviceInterface/AndroidInterface.js":23,"./DeviceInterface/AppleDeviceInterface.js":24,"./DeviceInterface/AppleOverlayDeviceInterface.js":25,"./DeviceInterface/ExtensionInterface.js":26,"./DeviceInterface/WindowsInterface.js":28,"./DeviceInterface/WindowsOverlayDeviceInterface.js":29,"./Settings.js":50,"./config.js":64,"./deviceApiCalls/transports/transports.js":72}],23:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.AndroidInterface = void 0; -var _InterfacePrototype = _interopRequireDefault(require("./InterfacePrototype.js")); -var _autofillUtils = require("../autofill-utils.js"); -var _NativeUIController = require("../UI/controllers/NativeUIController.js"); -var _InContextSignup = require("../InContextSignup.js"); -var _deviceApiCalls = require("../deviceApiCalls/__generated__/deviceApiCalls.js"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -class AndroidInterface extends _InterfacePrototype.default { - inContextSignup = new _InContextSignup.InContextSignup(this); - - /** - * @returns {Promise} - */ - async getAlias() { - const { - alias - } = await (0, _autofillUtils.sendAndWaitForAnswer)(async () => { - if (this.inContextSignup.isAvailable()) { - const { - isSignedIn - } = await this.deviceApi.request(new _deviceApiCalls.ShowInContextEmailProtectionSignupPromptCall(null)); - // On Android we can't get the input type data again without - // refreshing the page, so instead we can mutate it now that we - // know the user has Email Protection available. - if (this.globalConfig.availableInputTypes) { - this.globalConfig.availableInputTypes.email = isSignedIn; - } - this.updateForStateChange(); - this.onFinishedAutofill(); - } - return window.EmailInterface.showTooltip(); - }, 'getAliasResponse'); - return alias; - } - - /** - * @override - */ - createUIController() { - return new _NativeUIController.NativeUIController(); - } - - /** - * @deprecated use `this.settings.availableInputTypes.email` in the future - * @returns {boolean} - */ - isDeviceSignedIn() { - // on DDG domains, always check via `window.EmailInterface.isSignedIn()` - if (this.globalConfig.isDDGDomain) { - return window.EmailInterface.isSignedIn() === 'true'; + max(maxLength, message) { + return new ZodArray({ + ...this._def, + maxLength: { value: maxLength, message: errorUtil.toString(message) } + }); } - - // on non-DDG domains, where `availableInputTypes.email` is present, use it - if (typeof this.globalConfig.availableInputTypes?.email === 'boolean') { - return this.globalConfig.availableInputTypes.email; + length(len, message) { + return new ZodArray({ + ...this._def, + exactLength: { value: len, message: errorUtil.toString(message) } + }); } - - // ...on other domains we assume true because the script wouldn't exist otherwise - return true; - } - async setupAutofill() { - await this.inContextSignup.init(); - } - - /** - * Used by the email web app - * Settings page displays data of the logged in user data - */ - getUserData() { - let userData = null; - try { - userData = JSON.parse(window.EmailInterface.getUserData()); - } catch (e) { - if (this.globalConfig.isDDGTestMode) { - console.error(e); - } + nonempty(message) { + return this.min(1, message); } - return Promise.resolve(userData); - } - - /** - * Used by the email web app - * Device capabilities determine which functionality is available to the user - */ - getEmailProtectionCapabilities() { - let deviceCapabilities = null; - try { - deviceCapabilities = JSON.parse(window.EmailInterface.getDeviceCapabilities()); - } catch (e) { - if (this.globalConfig.isDDGTestMode) { - console.error(e); + }; + ZodArray.create = (schema, params) => { + return new ZodArray({ + type: schema, + minLength: null, + maxLength: null, + exactLength: null, + typeName: ZodFirstPartyTypeKind.ZodArray, + ...processCreateParams(params) + }); + }; + function deepPartialify(schema) { + if (schema instanceof ZodObject) { + const newShape = {}; + for (const key in schema.shape) { + const fieldSchema = schema.shape[key]; + newShape[key] = ZodOptional.create(deepPartialify(fieldSchema)); + } + return new ZodObject({ + ...schema._def, + shape: () => newShape + }); + } else if (schema instanceof ZodArray) { + return new ZodArray({ + ...schema._def, + type: deepPartialify(schema.element) + }); + } else if (schema instanceof ZodOptional) { + return ZodOptional.create(deepPartialify(schema.unwrap())); + } else if (schema instanceof ZodNullable) { + return ZodNullable.create(deepPartialify(schema.unwrap())); + } else if (schema instanceof ZodTuple) { + return ZodTuple.create(schema.items.map((item) => deepPartialify(item))); + } else { + return schema; + } + } + var ZodObject = class extends ZodType { + constructor() { + super(...arguments); + this._cached = null; + this.nonstrict = this.passthrough; + this.augment = this.extend; + } + _getCached() { + if (this._cached !== null) + return this._cached; + const shape = this._def.shape(); + const keys = util.objectKeys(shape); + return this._cached = { shape, keys }; + } + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.object) { + const ctx2 = this._getOrReturnCtx(input); + addIssueToContext(ctx2, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.object, + received: ctx2.parsedType + }); + return INVALID; + } + const { status, ctx } = this._processInputParams(input); + const { shape, keys: shapeKeys } = this._getCached(); + const extraKeys = []; + if (!(this._def.catchall instanceof ZodNever && this._def.unknownKeys === "strip")) { + for (const key in ctx.data) { + if (!shapeKeys.includes(key)) { + extraKeys.push(key); + } + } } - } - return Promise.resolve(deviceCapabilities); - } - storeUserData(_ref) { - let { - addUserData: { - token, - userName, - cohort - } - } = _ref; - return window.EmailInterface.storeCredentials(token, userName, cohort); - } - - /** - * Used by the email web app - * Provides functionality to log the user out - */ - removeUserData() { - try { - return window.EmailInterface.removeCredentials(); - } catch (e) { - if (this.globalConfig.isDDGTestMode) { - console.error(e); + const pairs = []; + for (const key of shapeKeys) { + const keyValidator = shape[key]; + const value = ctx.data[key]; + pairs.push({ + key: { status: "valid", value: key }, + value: keyValidator._parse(new ParseInputLazyPath(ctx, value, ctx.path, key)), + alwaysSet: key in ctx.data + }); } - } - } - - /** - * Used by the email web app - * Provides functionality to close the window after in-context sign-up or sign-in - */ - closeEmailProtection() { - this.deviceApi.request(new _deviceApiCalls.CloseEmailProtectionTabCall(null)); - } - addLogoutListener(handler) { - // Only deal with logging out if we're in the email web app - if (!this.globalConfig.isDDGDomain) return; - window.addEventListener('message', e => { - if (this.globalConfig.isDDGDomain && e.data.emailProtectionSignedOut) { - handler(); + if (this._def.catchall instanceof ZodNever) { + const unknownKeys = this._def.unknownKeys; + if (unknownKeys === "passthrough") { + for (const key of extraKeys) { + pairs.push({ + key: { status: "valid", value: key }, + value: { status: "valid", value: ctx.data[key] } + }); + } + } else if (unknownKeys === "strict") { + if (extraKeys.length > 0) { + addIssueToContext(ctx, { + code: ZodIssueCode.unrecognized_keys, + keys: extraKeys + }); + status.dirty(); + } + } else if (unknownKeys === "strip") + ; + else { + throw new Error(`Internal ZodObject error: invalid unknownKeys value.`); + } + } else { + const catchall = this._def.catchall; + for (const key of extraKeys) { + const value = ctx.data[key]; + pairs.push({ + key: { status: "valid", value: key }, + value: catchall._parse( + new ParseInputLazyPath(ctx, value, ctx.path, key) + //, ctx.child(key), value, getParsedType(value) + ), + alwaysSet: key in ctx.data + }); + } } - }); - } - - /** Noop */ - firePixel(_pixelParam) {} -} -exports.AndroidInterface = AndroidInterface; - -},{"../InContextSignup.js":44,"../UI/controllers/NativeUIController.js":57,"../autofill-utils.js":62,"../deviceApiCalls/__generated__/deviceApiCalls.js":66,"./InterfacePrototype.js":27}],24:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.AppleDeviceInterface = void 0; -var _InterfacePrototype = _interopRequireDefault(require("./InterfacePrototype.js")); -var _autofillUtils = require("../autofill-utils.js"); -var _HTMLTooltip = require("../UI/HTMLTooltip.js"); -var _HTMLTooltipUIController = require("../UI/controllers/HTMLTooltipUIController.js"); -var _OverlayUIController = require("../UI/controllers/OverlayUIController.js"); -var _index = require("../../packages/device-api/index.js"); -var _additionalDeviceApiCalls = require("../deviceApiCalls/additionalDeviceApiCalls.js"); -var _NativeUIController = require("../UI/controllers/NativeUIController.js"); -var _deviceApiCalls = require("../deviceApiCalls/__generated__/deviceApiCalls.js"); -var _matching = require("../Form/matching.js"); -var _InContextSignup = require("../InContextSignup.js"); -var _ThirdPartyProvider = require("../ThirdPartyProvider.js"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -/** - * @typedef {import('../deviceApiCalls/__generated__/validators-ts').GetAutofillDataRequest} GetAutofillDataRequest - */ - -class AppleDeviceInterface extends _InterfacePrototype.default { - inContextSignup = new _InContextSignup.InContextSignup(this); - - /** @override */ - initialSetupDelayMs = 300; - thirdPartyProvider = new _ThirdPartyProvider.ThirdPartyProvider(this); - - /** - * The default functionality of this class is to operate as an 'overlay controller' - - * which means it's purpose is to message the native layer about when to open/close the overlay. - * - * There is an additional use-case though, when running on older macOS versions, we just display the - * HTMLTooltip in-page (like the extension does). This is why the `!this.globalConfig.supportsTopFrame` - * check exists below - if we know we don't support the overlay, we fall back to in-page. - * - * @override - * @returns {import("../UI/controllers/UIController.js").UIController} - */ - createUIController() { - if (this.globalConfig.userPreferences?.platform?.name === 'ios') { - return new _NativeUIController.NativeUIController(); + if (ctx.common.async) { + return Promise.resolve().then(async () => { + const syncPairs = []; + for (const pair of pairs) { + const key = await pair.key; + syncPairs.push({ + key, + value: await pair.value, + alwaysSet: pair.alwaysSet + }); + } + return syncPairs; + }).then((syncPairs) => { + return ParseStatus.mergeObjectSync(status, syncPairs); + }); + } else { + return ParseStatus.mergeObjectSync(status, pairs); + } + } + get shape() { + return this._def.shape(); + } + strict(message) { + errorUtil.errToObj; + return new ZodObject({ + ...this._def, + unknownKeys: "strict", + ...message !== void 0 ? { + errorMap: (issue, ctx) => { + var _a, _b, _c, _d; + const defaultError = (_c = (_b = (_a = this._def).errorMap) === null || _b === void 0 ? void 0 : _b.call(_a, issue, ctx).message) !== null && _c !== void 0 ? _c : ctx.defaultError; + if (issue.code === "unrecognized_keys") + return { + message: (_d = errorUtil.errToObj(message).message) !== null && _d !== void 0 ? _d : defaultError + }; + return { + message: defaultError + }; + } + } : {} + }); } - if (!this.globalConfig.supportsTopFrame) { - const options = { - ..._HTMLTooltip.defaultOptions, - testMode: this.isTestMode() - }; - return new _HTMLTooltipUIController.HTMLTooltipUIController({ - device: this, - tooltipKind: 'modern' - }, options); + strip() { + return new ZodObject({ + ...this._def, + unknownKeys: "strip" + }); + } + passthrough() { + return new ZodObject({ + ...this._def, + unknownKeys: "passthrough" + }); + } + // const AugmentFactory = + // (def: Def) => + // ( + // augmentation: Augmentation + // ): ZodObject< + // extendShape, Augmentation>, + // Def["unknownKeys"], + // Def["catchall"] + // > => { + // return new ZodObject({ + // ...def, + // shape: () => ({ + // ...def.shape(), + // ...augmentation, + // }), + // }) as any; + // }; + extend(augmentation) { + return new ZodObject({ + ...this._def, + shape: () => ({ + ...this._def.shape(), + ...augmentation + }) + }); } - /** - * If we get here, we're just a controller for an overlay + * Prior to zod@1.0.12 there was a bug in the + * inferred type of merged objects. Please + * upgrade if you are experiencing issues. */ - return new _OverlayUIController.OverlayUIController({ - remove: async () => this._closeAutofillParent(), - show: async details => this._show(details) - }); - } - - /** - * For now, this could be running - * 1) on iOS - * 2) on macOS + Overlay - * 3) on macOS + in-page HTMLTooltip - * - * @override - * @returns {Promise} - */ - async setupAutofill() { - if (!this.globalConfig.supportsTopFrame) { - await this._getAutofillInitData(); + merge(merging) { + const merged = new ZodObject({ + unknownKeys: merging._def.unknownKeys, + catchall: merging._def.catchall, + shape: () => ({ + ...this._def.shape(), + ...merging._def.shape() + }), + typeName: ZodFirstPartyTypeKind.ZodObject + }); + return merged; } - await this.inContextSignup.init(); - const signedIn = await this._checkDeviceSignedIn(); - if (signedIn) { - if (this.globalConfig.isApp) { - await this.getAddresses(); - } + // merge< + // Incoming extends AnyZodObject, + // Augmentation extends Incoming["shape"], + // NewOutput extends { + // [k in keyof Augmentation | keyof Output]: k extends keyof Augmentation + // ? Augmentation[k]["_output"] + // : k extends keyof Output + // ? Output[k] + // : never; + // }, + // NewInput extends { + // [k in keyof Augmentation | keyof Input]: k extends keyof Augmentation + // ? Augmentation[k]["_input"] + // : k extends keyof Input + // ? Input[k] + // : never; + // } + // >( + // merging: Incoming + // ): ZodObject< + // extendShape>, + // Incoming["_def"]["unknownKeys"], + // Incoming["_def"]["catchall"], + // NewOutput, + // NewInput + // > { + // const merged: any = new ZodObject({ + // unknownKeys: merging._def.unknownKeys, + // catchall: merging._def.catchall, + // shape: () => + // objectUtil.mergeShapes(this._def.shape(), merging._def.shape()), + // typeName: ZodFirstPartyTypeKind.ZodObject, + // }) as any; + // return merged; + // } + setKey(key, schema) { + return this.augment({ [key]: schema }); + } + // merge( + // merging: Incoming + // ): //ZodObject = (merging) => { + // ZodObject< + // extendShape>, + // Incoming["_def"]["unknownKeys"], + // Incoming["_def"]["catchall"] + // > { + // // const mergedShape = objectUtil.mergeShapes( + // // this._def.shape(), + // // merging._def.shape() + // // ); + // const merged: any = new ZodObject({ + // unknownKeys: merging._def.unknownKeys, + // catchall: merging._def.catchall, + // shape: () => + // objectUtil.mergeShapes(this._def.shape(), merging._def.shape()), + // typeName: ZodFirstPartyTypeKind.ZodObject, + // }) as any; + // return merged; + // } + catchall(index) { + return new ZodObject({ + ...this._def, + catchall: index + }); } - } - - /** - * Used by the email web app - * Settings page displays data of the logged in user data - */ - getUserData() { - return this.deviceApi.request((0, _index.createRequest)('emailHandlerGetUserData')); - } - - /** - * Used by the email web app - * Device capabilities determine which functionality is available to the user - */ - getEmailProtectionCapabilities() { - return this.deviceApi.request((0, _index.createRequest)('emailHandlerGetCapabilities')); - } - - /** - */ - async getSelectedCredentials() { - return this.deviceApi.request((0, _index.createRequest)('getSelectedCredentials')); - } - - /** - * The data format provided here for `parentArgs` matches Window now. - * @param {GetAutofillDataRequest} parentArgs - */ - async _showAutofillParent(parentArgs) { - const applePayload = { - ...parentArgs.triggerContext, - serializedInputContext: parentArgs.serializedInputContext - }; - return this.deviceApi.notify((0, _index.createNotification)('showAutofillParent', applePayload)); - } - - /** - * @returns {Promise} - */ - async _closeAutofillParent() { - return this.deviceApi.notify((0, _index.createNotification)('closeAutofillParent', {})); - } - - /** - * @param {GetAutofillDataRequest} details - */ - async _show(details) { - await this._showAutofillParent(details); - this._listenForSelectedCredential(async response => { - if (!response) return; - if ('configType' in response) { - this.selectedDetail(response.data, response.configType); - } else if ('stop' in response) { - await this.onFinishedAutofill(); - } else if ('stateChange' in response) { - await this.updateForStateChange(); - } - }); - } - async refreshData() { - await super.refreshData(); - await this._checkDeviceSignedIn(); - } - async getAddresses() { - if (!this.globalConfig.isApp) return this.getAlias(); - const { - addresses - } = await this.deviceApi.request((0, _index.createRequest)('emailHandlerGetAddresses')); - this.storeLocalAddresses(addresses); - return addresses; - } - async refreshAlias() { - await this.deviceApi.notify((0, _index.createNotification)('emailHandlerRefreshAlias')); - // On macOS we also update the addresses stored locally - if (this.globalConfig.isApp) this.getAddresses(); - } - async _checkDeviceSignedIn() { - const { - isAppSignedIn - } = await this.deviceApi.request((0, _index.createRequest)('emailHandlerCheckAppSignedInStatus')); - this.isDeviceSignedIn = () => !!isAppSignedIn; - return !!isAppSignedIn; - } - storeUserData(_ref) { - let { - addUserData: { - token, - userName, - cohort - } - } = _ref; - return this.deviceApi.notify((0, _index.createNotification)('emailHandlerStoreToken', { - token, - username: userName, - cohort - })); - } - - /** - * Used by the email web app - * Provides functionality to log the user out - */ - removeUserData() { - this.deviceApi.notify((0, _index.createNotification)('emailHandlerRemoveToken')); - } - - /** - * Used by the email web app - * Provides functionality to close the window after in-context sign-up or sign-in - */ - closeEmailProtection() { - this.deviceApi.request(new _deviceApiCalls.CloseEmailProtectionTabCall(null)); - } - - /** - * PM endpoints - */ - - /** - * Gets the init data from the device - * @returns {APIResponse} - */ - async _getAutofillInitData() { - const response = await this.deviceApi.request((0, _index.createRequest)('pmHandlerGetAutofillInitData')); - this.storeLocalData(response.success); - return response; - } - - /** - * Gets credentials ready for autofill - * @param {CredentialsObject['id']} id - the credential id - * @returns {APIResponseSingle} - */ - getAutofillCredentials(id) { - return this.deviceApi.request((0, _index.createRequest)('pmHandlerGetAutofillCredentials', { - id - })); - } - - /** - * Opens the native UI for managing passwords - */ - openManagePasswords() { - return this.deviceApi.notify((0, _index.createNotification)('pmHandlerOpenManagePasswords')); - } - - /** - * Gets a single identity obj once the user requests it - * @param {IdentityObject['id']} id - * @returns {Promise<{success: IdentityObject|undefined}>} - */ - getAutofillIdentity(id) { - const identity = this.getLocalIdentities().find(_ref2 => { - let { - id: identityId - } = _ref2; - return `${identityId}` === `${id}`; + pick(mask) { + const shape = {}; + util.objectKeys(mask).forEach((key) => { + if (mask[key] && this.shape[key]) { + shape[key] = this.shape[key]; + } + }); + return new ZodObject({ + ...this._def, + shape: () => shape + }); + } + omit(mask) { + const shape = {}; + util.objectKeys(this.shape).forEach((key) => { + if (!mask[key]) { + shape[key] = this.shape[key]; + } + }); + return new ZodObject({ + ...this._def, + shape: () => shape + }); + } + /** + * @deprecated + */ + deepPartial() { + return deepPartialify(this); + } + partial(mask) { + const newShape = {}; + util.objectKeys(this.shape).forEach((key) => { + const fieldSchema = this.shape[key]; + if (mask && !mask[key]) { + newShape[key] = fieldSchema; + } else { + newShape[key] = fieldSchema.optional(); + } + }); + return new ZodObject({ + ...this._def, + shape: () => newShape + }); + } + required(mask) { + const newShape = {}; + util.objectKeys(this.shape).forEach((key) => { + if (mask && !mask[key]) { + newShape[key] = this.shape[key]; + } else { + const fieldSchema = this.shape[key]; + let newField = fieldSchema; + while (newField instanceof ZodOptional) { + newField = newField._def.innerType; + } + newShape[key] = newField; + } + }); + return new ZodObject({ + ...this._def, + shape: () => newShape + }); + } + keyof() { + return createZodEnum(util.objectKeys(this.shape)); + } + }; + ZodObject.create = (shape, params) => { + return new ZodObject({ + shape: () => shape, + unknownKeys: "strip", + catchall: ZodNever.create(), + typeName: ZodFirstPartyTypeKind.ZodObject, + ...processCreateParams(params) }); - return Promise.resolve({ - success: identity + }; + ZodObject.strictCreate = (shape, params) => { + return new ZodObject({ + shape: () => shape, + unknownKeys: "strict", + catchall: ZodNever.create(), + typeName: ZodFirstPartyTypeKind.ZodObject, + ...processCreateParams(params) }); - } - - /** - * Gets a single complete credit card obj once the user requests it - * @param {CreditCardObject['id']} id - * @returns {APIResponse} - */ - getAutofillCreditCard(id) { - return this.deviceApi.request((0, _index.createRequest)('pmHandlerGetCreditCard', { - id - })); - } - getCurrentInputType() { - const topContextData = this.getTopContextData(); - return topContextData?.inputType ? topContextData.inputType : (0, _matching.getInputType)(this.activeForm?.activeInput); - } - - /** - * @returns {Promise} - */ - async getAlias() { - const { - alias - } = await this.deviceApi.request(new _additionalDeviceApiCalls.GetAlias({ - requiresUserPermission: !this.globalConfig.isApp, - shouldConsumeAliasIfProvided: !this.globalConfig.isApp, - isIncontextSignupAvailable: this.inContextSignup.isAvailable() - })); - return alias ? (0, _autofillUtils.formatDuckAddress)(alias) : alias; - } - addLogoutListener(handler) { - // Only deal with logging out if we're in the email web app - if (!this.globalConfig.isDDGDomain) return; - window.addEventListener('message', e => { - if (this.globalConfig.isDDGDomain && e.data.emailProtectionSignedOut) { - handler(); - } + }; + ZodObject.lazycreate = (shape, params) => { + return new ZodObject({ + shape, + unknownKeys: "strip", + catchall: ZodNever.create(), + typeName: ZodFirstPartyTypeKind.ZodObject, + ...processCreateParams(params) }); - } - async addDeviceListeners() { - this.thirdPartyProvider.init(); - } - - /** @type {any} */ - pollingTimeout = null; - /** - * Poll the native listener until the user has selected a credential. - * Message return types are: - * - 'stop' is returned whenever the message sent doesn't match the native last opened tooltip. - * - This also is triggered when the close event is called and prevents any edge case continued polling. - * - 'ok' is when the user has selected a credential and the value can be injected into the page. - * - 'none' is when the tooltip is open in the native window however hasn't been entered. - * @param {(response: {data:IdentityObject|CreditCardObject|CredentialsObject, configType: string} | {stateChange: boolean} | {stop: boolean} | null) => void} callback - */ - async _listenForSelectedCredential(callback) { - // Prevent two timeouts from happening - const poll = async () => { - clearTimeout(this.pollingTimeout); - const response = await this.getSelectedCredentials(); - switch (response.type) { - case 'none': - // Parent hasn't got a selected credential yet - this.pollingTimeout = setTimeout(() => poll(), 100); - return; - case 'ok': - { - await callback({ - data: response.data, - configType: response.configType - }); - return; + }; + var ZodUnion = class extends ZodType { + _parse(input) { + const { ctx } = this._processInputParams(input); + const options = this._def.options; + function handleResults(results) { + for (const result of results) { + if (result.result.status === "valid") { + return result.result; } - case 'state': - { - // Inform that state has changed, but continue polling - // e.g. in-context signup has been dismissed - await callback({ - stateChange: true - }); - this.pollingTimeout = setTimeout(() => poll(), 100); - return; + } + for (const result of results) { + if (result.result.status === "dirty") { + ctx.common.issues.push(...result.ctx.common.issues); + return result.result; } - case 'stop': - // Parent wants us to stop polling - await callback({ - stop: true + } + const unionErrors = results.map((result) => new ZodError(result.ctx.common.issues)); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_union, + unionErrors + }); + return INVALID; + } + if (ctx.common.async) { + return Promise.all(options.map(async (option) => { + const childCtx = { + ...ctx, + common: { + ...ctx.common, + issues: [] + }, + parent: null + }; + return { + result: await option._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: childCtx + }), + ctx: childCtx + }; + })).then(handleResults); + } else { + let dirty = void 0; + const issues = []; + for (const option of options) { + const childCtx = { + ...ctx, + common: { + ...ctx.common, + issues: [] + }, + parent: null + }; + const result = option._parseSync({ + data: ctx.data, + path: ctx.path, + parent: childCtx }); + if (result.status === "valid") { + return result; + } else if (result.status === "dirty" && !dirty) { + dirty = { result, ctx: childCtx }; + } + if (childCtx.common.issues.length) { + issues.push(childCtx.common.issues); + } + } + if (dirty) { + ctx.common.issues.push(...dirty.ctx.common.issues); + return dirty.result; + } + const unionErrors = issues.map((issues2) => new ZodError(issues2)); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_union, + unionErrors + }); + return INVALID; } - }; - poll(); - } -} -exports.AppleDeviceInterface = AppleDeviceInterface; - -},{"../../packages/device-api/index.js":12,"../Form/matching.js":43,"../InContextSignup.js":44,"../ThirdPartyProvider.js":51,"../UI/HTMLTooltip.js":55,"../UI/controllers/HTMLTooltipUIController.js":56,"../UI/controllers/NativeUIController.js":57,"../UI/controllers/OverlayUIController.js":58,"../autofill-utils.js":62,"../deviceApiCalls/__generated__/deviceApiCalls.js":66,"../deviceApiCalls/additionalDeviceApiCalls.js":68,"./InterfacePrototype.js":27}],25:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.AppleOverlayDeviceInterface = void 0; -var _AppleDeviceInterface = require("./AppleDeviceInterface.js"); -var _HTMLTooltipUIController = require("../UI/controllers/HTMLTooltipUIController.js"); -var _overlayApi = require("./overlayApi.js"); -var _index = require("../../packages/device-api/index.js"); -/** - * This subclass is designed to separate code that *only* runs inside the - * Overlay into a single place. - * - * It will only run inside the macOS overlay, therefor all code here - * can be viewed as *not* executing within a regular page context. - */ -class AppleOverlayDeviceInterface extends _AppleDeviceInterface.AppleDeviceInterface { - /** - * Mark top frame as not stripping credential data - * @type {boolean} - */ - stripCredentials = false; - - /** - * overlay API helpers - */ - overlay = (0, _overlayApi.overlayApi)(this); - previousX = 0; - previousY = 0; - - /** - * Because we're running inside the Overlay, we always create the HTML - * Tooltip controller. - * - * @override - * @returns {import("../UI/controllers/UIController.js").UIController} - */ - createUIController() { - return new _HTMLTooltipUIController.HTMLTooltipUIController({ - tooltipKind: /** @type {const} */'modern', - device: this - }, { - wrapperClass: 'top-autofill', - tooltipPositionClass: () => '.wrapper { transform: none; }', - setSize: details => this.deviceApi.notify((0, _index.createNotification)('setSize', details)), - remove: async () => this._closeAutofillParent(), - testMode: this.isTestMode() + } + get options() { + return this._def.options; + } + }; + ZodUnion.create = (types, params) => { + return new ZodUnion({ + options: types, + typeName: ZodFirstPartyTypeKind.ZodUnion, + ...processCreateParams(params) }); - } - addDeviceListeners() { - /** - * The native side will send a custom event 'mouseMove' to indicate - * that the HTMLTooltip should fake an element being focused. - * - * Note: There's no cleanup required here since the Overlay has a fresh - * page load every time it's opened. - */ - window.addEventListener('mouseMove', event => { - // Don't set focus if the mouse hasn't moved ever - // This is to avoid clickjacking where an attacker puts the pulldown under the cursor - // and tricks the user into clicking - if (!this.previousX && !this.previousY || - // if no previous coords - this.previousX === event.detail.x && this.previousY === event.detail.y // or the mouse hasn't moved - ) { - this.previousX = event.detail.x; - this.previousY = event.detail.y; - return; + }; + var getDiscriminator = (type) => { + if (type instanceof ZodLazy) { + return getDiscriminator(type.schema); + } else if (type instanceof ZodEffects) { + return getDiscriminator(type.innerType()); + } else if (type instanceof ZodLiteral) { + return [type.value]; + } else if (type instanceof ZodEnum) { + return type.options; + } else if (type instanceof ZodNativeEnum) { + return Object.keys(type.enum); + } else if (type instanceof ZodDefault) { + return getDiscriminator(type._def.innerType); + } else if (type instanceof ZodUndefined) { + return [void 0]; + } else if (type instanceof ZodNull) { + return [null]; + } else { + return null; + } + }; + var ZodDiscriminatedUnion = class extends ZodType { + _parse(input) { + const { ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.object) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.object, + received: ctx.parsedType + }); + return INVALID; + } + const discriminator = this.discriminator; + const discriminatorValue = ctx.data[discriminator]; + const option = this.optionsMap.get(discriminatorValue); + if (!option) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_union_discriminator, + options: Array.from(this.optionsMap.keys()), + path: [discriminator] + }); + return INVALID; + } + if (ctx.common.async) { + return option._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: ctx + }); + } else { + return option._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx + }); } - const activeTooltip = this.uiController?.getActiveTooltip?.(); - activeTooltip?.focus(event.detail.x, event.detail.y); - this.previousX = event.detail.x; - this.previousY = event.detail.y; - }); - return super.addDeviceListeners(); - } - - /** - * Since we're running inside the Overlay we can limit what happens here to - * be only things that are needed to power the HTML Tooltip - * - * @override - * @returns {Promise} - */ - async setupAutofill() { - await this._getAutofillInitData(); - await this.inContextSignup.init(); - const signedIn = await this._checkDeviceSignedIn(); - if (signedIn) { - await this.getAddresses(); } - } - async postInit() { - // setup overlay API pieces - this.overlay.showImmediately(); - super.postInit(); - } - - /** - * In the top-frame scenario we override the base 'selectedDetail'. - * - * This - * - * @override - * @param {IdentityObject|CreditCardObject|CredentialsObject|{email:string, id: string}} data - * @param {string} type - */ - async selectedDetail(data, type) { - return this.overlay.selectedDetail(data, type); - } -} -exports.AppleOverlayDeviceInterface = AppleOverlayDeviceInterface; - -},{"../../packages/device-api/index.js":12,"../UI/controllers/HTMLTooltipUIController.js":56,"./AppleDeviceInterface.js":24,"./overlayApi.js":31}],26:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.ExtensionInterface = void 0; -var _InterfacePrototype = _interopRequireDefault(require("./InterfacePrototype.js")); -var _autofillUtils = require("../autofill-utils.js"); -var _HTMLTooltipUIController = require("../UI/controllers/HTMLTooltipUIController.js"); -var _HTMLTooltip = require("../UI/HTMLTooltip.js"); -var _InContextSignup = require("../InContextSignup.js"); -var _matching = require("../Form/matching.js"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const TOOLTIP_TYPES = { - EmailProtection: 'EmailProtection', - EmailSignup: 'EmailSignup' -}; -class ExtensionInterface extends _InterfacePrototype.default { - /** - * Adding this here since only the extension currently supports this - */ - inContextSignup = new _InContextSignup.InContextSignup(this); - - /** - * @override - */ - createUIController() { - /** @type {import('../UI/HTMLTooltip.js').HTMLTooltipOptions} */ - const htmlTooltipOptions = { - ..._HTMLTooltip.defaultOptions, - css: ``, - testMode: this.isTestMode(), - hasCaret: true - }; - const tooltipKinds = { - [TOOLTIP_TYPES.EmailProtection]: 'legacy', - [TOOLTIP_TYPES.EmailSignup]: 'emailsignup' - }; - const tooltipKind = tooltipKinds[this.getActiveTooltipType()] || tooltipKinds[TOOLTIP_TYPES.EmailProtection]; - return new _HTMLTooltipUIController.HTMLTooltipUIController({ - tooltipKind, - device: this - }, htmlTooltipOptions); - } - getActiveTooltipType() { - if (this.hasLocalAddresses) { - return TOOLTIP_TYPES.EmailProtection; + get discriminator() { + return this._def.discriminator; } - const inputType = this.activeForm?.activeInput ? (0, _matching.getInputSubtype)(this.activeForm.activeInput) : undefined; - if (this.inContextSignup?.isAvailable(inputType)) { - return TOOLTIP_TYPES.EmailSignup; + get options() { + return this._def.options; + } + get optionsMap() { + return this._def.optionsMap; } - return null; - } - async resetAutofillUI(callback) { - this.removeAutofillUIFromPage('Resetting autofill.'); - await this.setupAutofill(); - if (callback) await callback(); - this.uiController = this.createUIController(); - await this.postInit(); - } - isDeviceSignedIn() { - return this.hasLocalAddresses; - } - async setupAutofill() { /** - * In the extension, we must resolve `inContextSignup` data as part of setup + * The constructor of the discriminated union schema. Its behaviour is very similar to that of the normal z.union() constructor. + * However, it only allows a union of objects, all of which need to share a discriminator property. This property must + * have a different value for each object in the union. + * @param discriminator the name of the discriminator property + * @param types an array of object schemas + * @param params */ - await this.inContextSignup.init(); - return this.getAddresses(); + static create(discriminator, options, params) { + const optionsMap = /* @__PURE__ */ new Map(); + for (const type of options) { + const discriminatorValues = getDiscriminator(type.shape[discriminator]); + if (!discriminatorValues) { + throw new Error(`A discriminator value for key \`${discriminator}\` could not be extracted from all schema options`); + } + for (const value of discriminatorValues) { + if (optionsMap.has(value)) { + throw new Error(`Discriminator property ${String(discriminator)} has duplicate value ${String(value)}`); + } + optionsMap.set(value, type); + } + } + return new ZodDiscriminatedUnion({ + typeName: ZodFirstPartyTypeKind.ZodDiscriminatedUnion, + discriminator, + options, + optionsMap, + ...processCreateParams(params) + }); + } + }; + function mergeValues(a, b) { + const aType = getParsedType(a); + const bType = getParsedType(b); + if (a === b) { + return { valid: true, data: a }; + } else if (aType === ZodParsedType.object && bType === ZodParsedType.object) { + const bKeys = util.objectKeys(b); + const sharedKeys = util.objectKeys(a).filter((key) => bKeys.indexOf(key) !== -1); + const newObj = { ...a, ...b }; + for (const key of sharedKeys) { + const sharedValue = mergeValues(a[key], b[key]); + if (!sharedValue.valid) { + return { valid: false }; + } + newObj[key] = sharedValue.data; + } + return { valid: true, data: newObj }; + } else if (aType === ZodParsedType.array && bType === ZodParsedType.array) { + if (a.length !== b.length) { + return { valid: false }; + } + const newArray = []; + for (let index = 0; index < a.length; index++) { + const itemA = a[index]; + const itemB = b[index]; + const sharedValue = mergeValues(itemA, itemB); + if (!sharedValue.valid) { + return { valid: false }; + } + newArray.push(sharedValue.data); + } + return { valid: true, data: newArray }; + } else if (aType === ZodParsedType.date && bType === ZodParsedType.date && +a === +b) { + return { valid: true, data: a }; + } else { + return { valid: false }; + } } - postInit() { - switch (this.getActiveTooltipType()) { - case TOOLTIP_TYPES.EmailProtection: - { - this._scannerCleanup = this.scanner.init(); - this.addLogoutListener(() => { - this.resetAutofillUI(); - if (this.globalConfig.isDDGDomain) { - (0, _autofillUtils.notifyWebApp)({ - deviceSignedIn: { - value: false - } - }); - } + var ZodIntersection = class extends ZodType { + _parse(input) { + const { status, ctx } = this._processInputParams(input); + const handleParsed = (parsedLeft, parsedRight) => { + if (isAborted(parsedLeft) || isAborted(parsedRight)) { + return INVALID; + } + const merged = mergeValues(parsedLeft.value, parsedRight.value); + if (!merged.valid) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_intersection_types }); - if (this.activeForm?.activeInput) { - this.attachTooltip({ - form: this.activeForm, - input: this.activeForm?.activeInput, - click: null, - trigger: 'postSignup', - triggerMetaData: { - type: 'transactional' - } - }); + return INVALID; + } + if (isDirty(parsedLeft) || isDirty(parsedRight)) { + status.dirty(); + } + return { status: status.value, value: merged.data }; + }; + if (ctx.common.async) { + return Promise.all([ + this._def.left._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: ctx + }), + this._def.right._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: ctx + }) + ]).then(([left, right]) => handleParsed(left, right)); + } else { + return handleParsed(this._def.left._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx + }), this._def.right._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx + })); + } + } + }; + ZodIntersection.create = (left, right, params) => { + return new ZodIntersection({ + left, + right, + typeName: ZodFirstPartyTypeKind.ZodIntersection, + ...processCreateParams(params) + }); + }; + var ZodTuple = class extends ZodType { + _parse(input) { + const { status, ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.array) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.array, + received: ctx.parsedType + }); + return INVALID; + } + if (ctx.data.length < this._def.items.length) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: this._def.items.length, + inclusive: true, + exact: false, + type: "array" + }); + return INVALID; + } + const rest = this._def.rest; + if (!rest && ctx.data.length > this._def.items.length) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: this._def.items.length, + inclusive: true, + exact: false, + type: "array" + }); + status.dirty(); + } + const items = [...ctx.data].map((item, itemIndex) => { + const schema = this._def.items[itemIndex] || this._def.rest; + if (!schema) + return null; + return schema._parse(new ParseInputLazyPath(ctx, item, ctx.path, itemIndex)); + }).filter((x) => !!x); + if (ctx.common.async) { + return Promise.all(items).then((results) => { + return ParseStatus.mergeArray(status, results); + }); + } else { + return ParseStatus.mergeArray(status, items); + } + } + get items() { + return this._def.items; + } + rest(rest) { + return new ZodTuple({ + ...this._def, + rest + }); + } + }; + ZodTuple.create = (schemas, params) => { + if (!Array.isArray(schemas)) { + throw new Error("You must pass an array of schemas to z.tuple([ ... ])"); + } + return new ZodTuple({ + items: schemas, + typeName: ZodFirstPartyTypeKind.ZodTuple, + rest: null, + ...processCreateParams(params) + }); + }; + var ZodRecord = class extends ZodType { + get keySchema() { + return this._def.keyType; + } + get valueSchema() { + return this._def.valueType; + } + _parse(input) { + const { status, ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.object) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.object, + received: ctx.parsedType + }); + return INVALID; + } + const pairs = []; + const keyType = this._def.keyType; + const valueType = this._def.valueType; + for (const key in ctx.data) { + pairs.push({ + key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, key)), + value: valueType._parse(new ParseInputLazyPath(ctx, ctx.data[key], ctx.path, key)) + }); + } + if (ctx.common.async) { + return ParseStatus.mergeObjectAsync(status, pairs); + } else { + return ParseStatus.mergeObjectSync(status, pairs); + } + } + get element() { + return this._def.valueType; + } + static create(first, second, third) { + if (second instanceof ZodType) { + return new ZodRecord({ + keyType: first, + valueType: second, + typeName: ZodFirstPartyTypeKind.ZodRecord, + ...processCreateParams(third) + }); + } + return new ZodRecord({ + keyType: ZodString.create(), + valueType: first, + typeName: ZodFirstPartyTypeKind.ZodRecord, + ...processCreateParams(second) + }); + } + }; + var ZodMap = class extends ZodType { + get keySchema() { + return this._def.keyType; + } + get valueSchema() { + return this._def.valueType; + } + _parse(input) { + const { status, ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.map) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.map, + received: ctx.parsedType + }); + return INVALID; + } + const keyType = this._def.keyType; + const valueType = this._def.valueType; + const pairs = [...ctx.data.entries()].map(([key, value], index) => { + return { + key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, [index, "key"])), + value: valueType._parse(new ParseInputLazyPath(ctx, value, ctx.path, [index, "value"])) + }; + }); + if (ctx.common.async) { + const finalMap = /* @__PURE__ */ new Map(); + return Promise.resolve().then(async () => { + for (const pair of pairs) { + const key = await pair.key; + const value = await pair.value; + if (key.status === "aborted" || value.status === "aborted") { + return INVALID; + } + if (key.status === "dirty" || value.status === "dirty") { + status.dirty(); + } + finalMap.set(key.value, value.value); } - break; + return { status: status.value, value: finalMap }; + }); + } else { + const finalMap = /* @__PURE__ */ new Map(); + for (const pair of pairs) { + const key = pair.key; + const value = pair.value; + if (key.status === "aborted" || value.status === "aborted") { + return INVALID; + } + if (key.status === "dirty" || value.status === "dirty") { + status.dirty(); + } + finalMap.set(key.value, value.value); } - case TOOLTIP_TYPES.EmailSignup: - { - this._scannerCleanup = this.scanner.init(); - break; + return { status: status.value, value: finalMap }; + } + } + }; + ZodMap.create = (keyType, valueType, params) => { + return new ZodMap({ + valueType, + keyType, + typeName: ZodFirstPartyTypeKind.ZodMap, + ...processCreateParams(params) + }); + }; + var ZodSet = class extends ZodType { + _parse(input) { + const { status, ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.set) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.set, + received: ctx.parsedType + }); + return INVALID; + } + const def = this._def; + if (def.minSize !== null) { + if (ctx.data.size < def.minSize.value) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: def.minSize.value, + type: "set", + inclusive: true, + exact: false, + message: def.minSize.message + }); + status.dirty(); } - default: - { - // Don't do anyhing if we don't have a tooltip to show - break; + } + if (def.maxSize !== null) { + if (ctx.data.size > def.maxSize.value) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: def.maxSize.value, + type: "set", + inclusive: true, + exact: false, + message: def.maxSize.message + }); + status.dirty(); + } + } + const valueType = this._def.valueType; + function finalizeSet(elements2) { + const parsedSet = /* @__PURE__ */ new Set(); + for (const element of elements2) { + if (element.status === "aborted") + return INVALID; + if (element.status === "dirty") + status.dirty(); + parsedSet.add(element.value); } + return { status: status.value, value: parsedSet }; + } + const elements = [...ctx.data.values()].map((item, i) => valueType._parse(new ParseInputLazyPath(ctx, item, ctx.path, i))); + if (ctx.common.async) { + return Promise.all(elements).then((elements2) => finalizeSet(elements2)); + } else { + return finalizeSet(elements); + } + } + min(minSize, message) { + return new ZodSet({ + ...this._def, + minSize: { value: minSize, message: errorUtil.toString(message) } + }); + } + max(maxSize, message) { + return new ZodSet({ + ...this._def, + maxSize: { value: maxSize, message: errorUtil.toString(message) } + }); } - } - getAddresses() { - return new Promise(resolve => chrome.runtime.sendMessage({ - getAddresses: true - }, data => { - this.storeLocalAddresses(data); - return resolve(data); - })); - } - - /** - * Used by the email web app - * Settings page displays data of the logged in user data - */ - getUserData() { - return new Promise(resolve => chrome.runtime.sendMessage({ - getUserData: true - }, data => resolve(data))); - } - - /** - * Used by the email web app - * Device capabilities determine which functionality is available to the user - */ - getEmailProtectionCapabilities() { - return new Promise(resolve => chrome.runtime.sendMessage({ - getEmailProtectionCapabilities: true - }, data => resolve(data))); - } - refreshAlias() { - return chrome.runtime.sendMessage({ - refreshAlias: true - }, addresses => this.storeLocalAddresses(addresses)); - } - async trySigningIn() { - if (this.globalConfig.isDDGDomain) { - const data = await (0, _autofillUtils.sendAndWaitForAnswer)(_autofillUtils.SIGN_IN_MSG, 'addUserData'); - this.storeUserData(data); + size(size, message) { + return this.min(size, message).max(size, message); } - } - - /** - * @param {object} message - * @param {object} message.addUserData - * @param {string} message.addUserData.token - * @param {string} message.addUserData.userName - * @param {string} message.addUserData.cohort - */ - storeUserData(message) { - return chrome.runtime.sendMessage(message); - } - - /** - * Used by the email web app - * Provides functionality to log the user out - */ - removeUserData() { - return chrome.runtime.sendMessage({ - removeUserData: true - }); - } - addDeviceListeners() { - // Add contextual menu listeners - let activeEl = null; - document.addEventListener('contextmenu', e => { - activeEl = e.target; + nonempty(message) { + return this.min(1, message); + } + }; + ZodSet.create = (valueType, params) => { + return new ZodSet({ + valueType, + minSize: null, + maxSize: null, + typeName: ZodFirstPartyTypeKind.ZodSet, + ...processCreateParams(params) }); - chrome.runtime.onMessage.addListener((message, sender) => { - if (sender.id !== chrome.runtime.id) return; - switch (message.type) { - case 'ddgUserReady': - this.resetAutofillUI(() => this.setupSettingsPage({ - shouldLog: true - })); - break; - case 'contextualAutofill': - (0, _autofillUtils.setValue)(activeEl, (0, _autofillUtils.formatDuckAddress)(message.alias), this.globalConfig); - activeEl.classList.add('ddg-autofilled'); - this.refreshAlias(); - - // If the user changes the alias, remove the decoration - activeEl.addEventListener('input', e => e.target.classList.remove('ddg-autofilled'), { - once: true + }; + var ZodFunction = class extends ZodType { + constructor() { + super(...arguments); + this.validate = this.implement; + } + _parse(input) { + const { ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.function) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.function, + received: ctx.parsedType + }); + return INVALID; + } + function makeArgsIssue(args, error) { + return makeIssue({ + data: args, + path: ctx.path, + errorMaps: [ + ctx.common.contextualErrorMap, + ctx.schemaErrorMap, + getErrorMap(), + errorMap + ].filter((x) => !!x), + issueData: { + code: ZodIssueCode.invalid_arguments, + argumentsError: error + } + }); + } + function makeReturnsIssue(returns, error) { + return makeIssue({ + data: returns, + path: ctx.path, + errorMaps: [ + ctx.common.contextualErrorMap, + ctx.schemaErrorMap, + getErrorMap(), + errorMap + ].filter((x) => !!x), + issueData: { + code: ZodIssueCode.invalid_return_type, + returnTypeError: error + } + }); + } + const params = { errorMap: ctx.common.contextualErrorMap }; + const fn = ctx.data; + if (this._def.returns instanceof ZodPromise) { + const me = this; + return OK(async function(...args) { + const error = new ZodError([]); + const parsedArgs = await me._def.args.parseAsync(args, params).catch((e) => { + error.addIssue(makeArgsIssue(args, e)); + throw error; }); - break; - default: - break; + const result = await Reflect.apply(fn, this, parsedArgs); + const parsedReturns = await me._def.returns._def.type.parseAsync(result, params).catch((e) => { + error.addIssue(makeReturnsIssue(result, e)); + throw error; + }); + return parsedReturns; + }); + } else { + const me = this; + return OK(function(...args) { + const parsedArgs = me._def.args.safeParse(args, params); + if (!parsedArgs.success) { + throw new ZodError([makeArgsIssue(args, parsedArgs.error)]); + } + const result = Reflect.apply(fn, this, parsedArgs.data); + const parsedReturns = me._def.returns.safeParse(result, params); + if (!parsedReturns.success) { + throw new ZodError([makeReturnsIssue(result, parsedReturns.error)]); + } + return parsedReturns.data; + }); } - }); - } - addLogoutListener(handler) { - // Make sure there's only one log out listener attached by removing the - // previous logout listener first, if it exists. - if (this._logoutListenerHandler) { - chrome.runtime.onMessage.removeListener(this._logoutListenerHandler); } - - // Cleanup on logout events - this._logoutListenerHandler = (message, sender) => { - if (sender.id === chrome.runtime.id && message.type === 'logout') { - handler(); - } - }; - chrome.runtime.onMessage.addListener(this._logoutListenerHandler); - } -} -exports.ExtensionInterface = ExtensionInterface; - -},{"../Form/matching.js":43,"../InContextSignup.js":44,"../UI/HTMLTooltip.js":55,"../UI/controllers/HTMLTooltipUIController.js":56,"../autofill-utils.js":62,"./InterfacePrototype.js":27}],27:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _autofillUtils = require("../autofill-utils.js"); -var _matching = require("../Form/matching.js"); -var _formatters = require("../Form/formatters.js"); -var _Credentials = require("../InputTypes/Credentials.js"); -var _PasswordGenerator = require("../PasswordGenerator.js"); -var _Scanner = require("../Scanner.js"); -var _config = require("../config.js"); -var _NativeUIController = require("../UI/controllers/NativeUIController.js"); -var _transports = require("../deviceApiCalls/transports/transports.js"); -var _Settings = require("../Settings.js"); -var _index = require("../../packages/device-api/index.js"); -var _deviceApiCalls = require("../deviceApiCalls/__generated__/deviceApiCalls.js"); -var _initFormSubmissionsApi = require("./initFormSubmissionsApi.js"); -var _EmailProtection = require("../EmailProtection.js"); -/** - * @typedef {import('../deviceApiCalls/__generated__/validators-ts').StoreFormData} StoreFormData - */ -/** - * @implements {GlobalConfigImpl} - * @implements {FormExtensionPoints} - * @implements {DeviceExtensionPoints} - */ -class InterfacePrototype { - attempts = 0; - /** @type {import("../Form/Form").Form | null} */ - activeForm = null; - /** @type {import("../UI/HTMLTooltip.js").default | null} */ - currentTooltip = null; - /** @type {number} */ - initialSetupDelayMs = 0; - autopromptFired = false; - - /** @type {PasswordGenerator} */ - passwordGenerator = new _PasswordGenerator.PasswordGenerator(); - emailProtection = new _EmailProtection.EmailProtection(this); - - /** @type {import("../InContextSignup.js").InContextSignup | null} */ - inContextSignup = null; - /** @type {import("../ThirdPartyProvider.js").ThirdPartyProvider | null} */ - thirdPartyProvider = null; - - /** @type {{privateAddress: string, personalAddress: string}} */ - #addresses = { - privateAddress: '', - personalAddress: '' - }; - - /** @type {GlobalConfig} */ - globalConfig; - - /** @type {import('../Scanner').Scanner} */ - scanner; - - /** @type {import("../UI/controllers/UIController.js").UIController | null} */ - uiController; - - /** @type {import("../../packages/device-api").DeviceApi} */ - deviceApi; - - /** @type {boolean} */ - isInitializationStarted; - - /** @type {((reason, ...rest) => void) | null} */ - _scannerCleanup = null; - - /** - * @param {GlobalConfig} config - * @param {import("../../packages/device-api").DeviceApi} deviceApi - * @param {Settings} settings - */ - constructor(config, deviceApi, settings) { - this.globalConfig = config; - this.deviceApi = deviceApi; - this.settings = settings; - this.uiController = null; - this.scanner = (0, _Scanner.createScanner)(this, { - initialDelay: this.initialSetupDelayMs - }); - this.isInitializationStarted = false; - } - - /** - * Implementors should override this with a UI controller that suits - * their platform. - * - * @returns {import("../UI/controllers/UIController.js").UIController} - */ - createUIController() { - return new _NativeUIController.NativeUIController(); - } - - /** - * @param {string} reason - */ - removeAutofillUIFromPage(reason) { - this.uiController?.destroy(); - this._scannerCleanup?.(reason); - } - get hasLocalAddresses() { - return !!(this.#addresses?.privateAddress && this.#addresses?.personalAddress); - } - getLocalAddresses() { - return this.#addresses; - } - storeLocalAddresses(addresses) { - this.#addresses = addresses; - // When we get new duck addresses, add them to the identities list - const identities = this.getLocalIdentities(); - const privateAddressIdentity = identities.find(_ref => { - let { - id - } = _ref; - return id === 'privateAddress'; - }); - // If we had previously stored them, just update the private address - if (privateAddressIdentity) { - privateAddressIdentity.emailAddress = (0, _autofillUtils.formatDuckAddress)(addresses.privateAddress); - } else { - // Otherwise, add both addresses - this.#data.identities = this.addDuckAddressesToIdentities(identities); + parameters() { + return this._def.args; } - } - - /** @type { PMData } */ - #data = { - credentials: [], - creditCards: [], - identities: [], - topContextData: undefined - }; - - /** - * @returns {import('../Form/matching').SupportedTypes} - */ - getCurrentInputType() { - throw new Error('Not implemented'); - } - addDuckAddressesToIdentities(identities) { - if (!this.hasLocalAddresses) return identities; - const newIdentities = []; - let { - privateAddress, - personalAddress - } = this.getLocalAddresses(); - privateAddress = (0, _autofillUtils.formatDuckAddress)(privateAddress); - personalAddress = (0, _autofillUtils.formatDuckAddress)(personalAddress); - - // Get the duck addresses in identities - const duckEmailsInIdentities = identities.reduce((duckEmails, _ref2) => { - let { - emailAddress: email - } = _ref2; - return email?.includes(_autofillUtils.ADDRESS_DOMAIN) ? duckEmails.concat(email) : duckEmails; - }, []); - - // Only add the personal duck address to identities if the user hasn't - // already manually added it - if (!duckEmailsInIdentities.includes(personalAddress)) { - newIdentities.push({ - id: 'personalAddress', - emailAddress: personalAddress, - title: 'Block email trackers' + returnType() { + return this._def.returns; + } + args(...items) { + return new ZodFunction({ + ...this._def, + args: ZodTuple.create(items).rest(ZodUnknown.create()) }); } - newIdentities.push({ - id: 'privateAddress', - emailAddress: privateAddress, - title: 'Block email trackers & hide address' + returns(returnType) { + return new ZodFunction({ + ...this._def, + returns: returnType + }); + } + implement(func) { + const validatedFunc = this.parse(func); + return validatedFunc; + } + strictImplement(func) { + const validatedFunc = this.parse(func); + return validatedFunc; + } + static create(args, returns, params) { + return new ZodFunction({ + args: args ? args : ZodTuple.create([]).rest(ZodUnknown.create()), + returns: returns || ZodUnknown.create(), + typeName: ZodFirstPartyTypeKind.ZodFunction, + ...processCreateParams(params) + }); + } + }; + var ZodLazy = class extends ZodType { + get schema() { + return this._def.getter(); + } + _parse(input) { + const { ctx } = this._processInputParams(input); + const lazySchema = this._def.getter(); + return lazySchema._parse({ data: ctx.data, path: ctx.path, parent: ctx }); + } + }; + ZodLazy.create = (getter, params) => { + return new ZodLazy({ + getter, + typeName: ZodFirstPartyTypeKind.ZodLazy, + ...processCreateParams(params) }); - return [...identities, ...newIdentities]; - } - - /** - * Stores init data coming from the tooltipHandler - * @param { InboundPMData } data - */ - storeLocalData(data) { - this.storeLocalCredentials(data.credentials); - data.creditCards.forEach(cc => delete cc.cardNumber && delete cc.cardSecurityCode); - // Store the full name as a separate field to simplify autocomplete - const updatedIdentities = data.identities.map(identity => ({ - ...identity, - fullName: (0, _formatters.formatFullName)(identity) - })); - // Add addresses - this.#data.identities = this.addDuckAddressesToIdentities(updatedIdentities); - this.#data.creditCards = data.creditCards; - - // Top autofill only - if (data.serializedInputContext) { - try { - this.#data.topContextData = JSON.parse(data.serializedInputContext); - } catch (e) { - console.error(e); - this.removeTooltip(); + }; + var ZodLiteral = class extends ZodType { + _parse(input) { + if (input.data !== this._def.value) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + received: ctx.data, + code: ZodIssueCode.invalid_literal, + expected: this._def.value + }); + return INVALID; } + return { status: "valid", value: input.data }; } - } - - /** - * Stores credentials locally - * @param {CredentialsObject[]} credentials - */ - storeLocalCredentials(credentials) { - credentials.forEach(cred => delete cred.password); - this.#data.credentials = credentials; - } - getTopContextData() { - return this.#data.topContextData; - } - - /** - * @deprecated use `availableInputTypes.credentials` directly instead - * @returns {boolean} - */ - get hasLocalCredentials() { - return this.#data.credentials.length > 0; - } - getLocalCredentials() { - return this.#data.credentials.map(cred => { - const { - password, - ...rest - } = cred; - return rest; + get value() { + return this._def.value; + } + }; + ZodLiteral.create = (value, params) => { + return new ZodLiteral({ + value, + typeName: ZodFirstPartyTypeKind.ZodLiteral, + ...processCreateParams(params) + }); + }; + function createZodEnum(values, params) { + return new ZodEnum({ + values, + typeName: ZodFirstPartyTypeKind.ZodEnum, + ...processCreateParams(params) }); } - /** - * @deprecated use `availableInputTypes.identities` directly instead - * @returns {boolean} - */ - get hasLocalIdentities() { - return this.#data.identities.length > 0; - } - getLocalIdentities() { - return this.#data.identities; - } - - /** - * @deprecated use `availableInputTypes.creditCards` directly instead - * @returns {boolean} - */ - get hasLocalCreditCards() { - return this.#data.creditCards.length > 0; - } - /** @return {CreditCardObject[]} */ - getLocalCreditCards() { - return this.#data.creditCards; - } - async startInit() { - if (this.isInitializationStarted) return; - this.alreadyInitialized = true; - await this.settings.refresh(); - this.addDeviceListeners(); - await this.setupAutofill(); - this.uiController = this.createUIController(); - - // this is the temporary measure to support windows whilst we still have 'setupAutofill' - // eventually all interfaces will use this - if (!this.settings.enabled) { - return; + var ZodEnum = class extends ZodType { + _parse(input) { + if (typeof input.data !== "string") { + const ctx = this._getOrReturnCtx(input); + const expectedValues = this._def.values; + addIssueToContext(ctx, { + expected: util.joinValues(expectedValues), + received: ctx.parsedType, + code: ZodIssueCode.invalid_type + }); + return INVALID; + } + if (this._def.values.indexOf(input.data) === -1) { + const ctx = this._getOrReturnCtx(input); + const expectedValues = this._def.values; + addIssueToContext(ctx, { + received: ctx.data, + code: ZodIssueCode.invalid_enum_value, + options: expectedValues + }); + return INVALID; + } + return OK(input.data); } - await this.setupSettingsPage(); - await this.postInit(); - if (this.settings.featureToggles.credentials_saving) { - (0, _initFormSubmissionsApi.initFormSubmissionsApi)(this.scanner.forms, this.scanner.matching); + get options() { + return this._def.values; } - } - async init() { - // bail very early if we can - const isEnabledInitiallyViaConfig = (0, _autofillUtils.autofillEnabled)(this.globalConfig); - if (!isEnabledInitiallyViaConfig) { - return; + get enum() { + const enumValues = {}; + for (const val of this._def.values) { + enumValues[val] = val; + } + return enumValues; } - const handler = async () => { - if (document.readyState === 'complete') { - window.removeEventListener('load', handler); - document.removeEventListener('readystatechange', handler); - await this.startInit(); + get Values() { + const enumValues = {}; + for (const val of this._def.values) { + enumValues[val] = val; } - }; - if (document.readyState === 'complete') { - await this.startInit(); - } else { - window.addEventListener('load', handler); - document.addEventListener('readystatechange', handler); + return enumValues; } - } - postInit() { - const cleanup = this.scanner.init(); - this.addLogoutListener(() => { - cleanup('Logged out'); - if (this.globalConfig.isDDGDomain) { - (0, _autofillUtils.notifyWebApp)({ - deviceSignedIn: { - value: false - } - }); + get Enum() { + const enumValues = {}; + for (const val of this._def.values) { + enumValues[val] = val; } - }); - } - - /** - * @deprecated This was a port from the macOS implementation so the API may not be suitable for all - * @returns {Promise} - */ - async getSelectedCredentials() { - throw new Error('`getSelectedCredentials` not implemented'); - } - isTestMode() { - return this.globalConfig.isDDGTestMode; - } - - /** - * This indicates an item was selected on Desktop, and we should try to autofill - * - * Note: When we're in a top-frame scenario, like on like macOS & Windows in the webview, - * this method gets overridden {@see WindowsOverlayDeviceInterface} {@see AppleOverlayDeviceInterface} - * - * @param {IdentityObject|CreditCardObject|CredentialsObject|{email:string, id: string}} data - * @param {string} type - */ - async selectedDetail(data, type) { - const form = this.activeForm; - if (!form) { - return; + return enumValues; } - - // are we autofilling email? - if (type === 'email' && 'email' in data) { - form.autofillEmail(data.email); - } else { - form.autofillData(data, type); + extract(values) { + return ZodEnum.create(values); } - const isPrivateAddress = data.id === 'privateAddress'; - - /** - * This is desktop only: was it a private address? if so, save it with - * the trigger 'emailProtection' so that native sides can use it - */ - if (isPrivateAddress) { - this.refreshAlias(); - if ('emailAddress' in data && data.emailAddress) { - this.emailProtection.storeReceived(data.emailAddress); - - /** @type {DataStorageObject} */ - const formValues = { - credentials: { - username: data.emailAddress, - autogenerated: true - } - }; - this.storeFormData(formValues, 'emailProtection'); - } + exclude(values) { + return ZodEnum.create(this.options.filter((opt) => !values.includes(opt))); } - await this.removeTooltip(); - } - - /** - * Before the DataWebTooltip opens, we collect the data based on the config.type - * @param {InputTypeConfigs} config - * @param {import('../Form/matching').SupportedTypes} inputType - * @param {TopContextData} [data] - * @returns {(CredentialsObject|CreditCardObject|IdentityObject)[]} - */ - dataForAutofill(config, inputType, data) { - const subtype = (0, _matching.getSubtypeFromType)(inputType); - if (config.type === 'identities') { - return this.getLocalIdentities().filter(identity => !!identity[subtype]); - } - if (config.type === 'creditCards') { - return this.getLocalCreditCards(); - } - if (config.type === 'credentials') { - if (data) { - if (Array.isArray(data.credentials) && data.credentials.length > 0) { - return data.credentials; - } else { - return this.getLocalCredentials().filter(cred => !!cred[subtype] || subtype === 'password' || cred.id === _Credentials.PROVIDER_LOCKED); - } + }; + ZodEnum.create = createZodEnum; + var ZodNativeEnum = class extends ZodType { + _parse(input) { + const nativeEnumValues = util.getValidEnumValues(this._def.values); + const ctx = this._getOrReturnCtx(input); + if (ctx.parsedType !== ZodParsedType.string && ctx.parsedType !== ZodParsedType.number) { + const expectedValues = util.objectValues(nativeEnumValues); + addIssueToContext(ctx, { + expected: util.joinValues(expectedValues), + received: ctx.parsedType, + code: ZodIssueCode.invalid_type + }); + return INVALID; + } + if (nativeEnumValues.indexOf(input.data) === -1) { + const expectedValues = util.objectValues(nativeEnumValues); + addIssueToContext(ctx, { + received: ctx.data, + code: ZodIssueCode.invalid_enum_value, + options: expectedValues + }); + return INVALID; } + return OK(input.data); } - return []; - } - - /** - * @param {object} params - * @param {import("../Form/Form").Form} params.form - * @param {HTMLInputElement} params.input - * @param {{ x: number; y: number; } | null} params.click - * @param {import('../deviceApiCalls/__generated__/validators-ts').GetAutofillDataRequest['trigger']} params.trigger - * @param {import('../UI/controllers/UIController.js').AttachArgs["triggerMetaData"]} params.triggerMetaData - */ - attachTooltip(params) { - const { - form, - input, - click, - trigger - } = params; - // Avoid flashing tooltip from background tabs on macOS - if (document.visibilityState !== 'visible' && trigger !== 'postSignup') return; - // Only autoprompt on mobile devices - if (trigger === 'autoprompt' && !this.globalConfig.isMobileApp) return; - // Only fire autoprompt once - if (trigger === 'autoprompt' && this.autopromptFired) return; - form.activeInput = input; - this.activeForm = form; - const inputType = (0, _matching.getInputType)(input); - - /** @type {PosFn} */ - const getPosition = () => { - // In extensions, the tooltip is centered on the Dax icon - const alignLeft = this.globalConfig.isApp || this.globalConfig.isWindows; - return alignLeft ? input.getBoundingClientRect() : (0, _autofillUtils.getDaxBoundingBox)(input); - }; - - // todo: this will be migrated to use NativeUIController soon - if (this.globalConfig.isMobileApp && inputType === 'identities.emailAddress') { - this.getAlias().then(alias => { - if (alias) { - form.autofillEmail(alias); - /** - * We're on mobile here, so we just record the email received. - * Then later in the form submission we can compare the values - */ - this.emailProtection.storeReceived(alias); - } else { - form.activeInput?.focus(); - } - - // Update data from native-side in case the `getAlias` call - // has included a successful in-context signup - this.updateForStateChange(); - this.onFinishedAutofill(); - }); - return; + get enum() { + return this._def.values; } - - /** @type {TopContextData} */ - const topContextData = { - inputType - }; - - // Allow features to append/change top context data - // for example, generated passwords may get appended here - const processedTopContext = this.preAttachTooltip(topContextData, input, form); - this.uiController?.attach({ - input, - form, - click, - getPosition, - topContextData: processedTopContext, - device: this, - trigger, - triggerMetaData: params.triggerMetaData + }; + ZodNativeEnum.create = (values, params) => { + return new ZodNativeEnum({ + values, + typeName: ZodFirstPartyTypeKind.ZodNativeEnum, + ...processCreateParams(params) }); - if (trigger === 'autoprompt') { - this.autopromptFired = true; + }; + var ZodPromise = class extends ZodType { + unwrap() { + return this._def.type; + } + _parse(input) { + const { ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.promise && ctx.common.async === false) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.promise, + received: ctx.parsedType + }); + return INVALID; + } + const promisified = ctx.parsedType === ZodParsedType.promise ? ctx.data : Promise.resolve(ctx.data); + return OK(promisified.then((data) => { + return this._def.type.parseAsync(data, { + path: ctx.path, + errorMap: ctx.common.contextualErrorMap + }); + })); } - } - - /** - * When an item was selected, we then call back to the device - * to fetch the full suite of data needed to complete the autofill - * - * @param {import('../Form/matching').SupportedTypes} inputType - * @param {(CreditCardObject|IdentityObject|CredentialsObject)[]} items - * @param {CreditCardObject['id']|IdentityObject['id']|CredentialsObject['id']} id - */ - onSelect(inputType, items, id) { - id = String(id); - const mainType = (0, _matching.getMainTypeFromType)(inputType); - const subtype = (0, _matching.getSubtypeFromType)(inputType); - if (id === _Credentials.PROVIDER_LOCKED) { - return this.thirdPartyProvider?.askToUnlockProvider(); - } - const matchingData = items.find(item => String(item.id) === id); - if (!matchingData) throw new Error('unreachable (fatal)'); - const dataPromise = (() => { - switch (mainType) { - case 'creditCards': - return this.getAutofillCreditCard(id); - case 'identities': - return this.getAutofillIdentity(id); - case 'credentials': - { - if (_Credentials.AUTOGENERATED_KEY in matchingData) { - const autogeneratedPayload = { - ...matchingData, - username: '' - }; - return Promise.resolve({ - success: autogeneratedPayload - }); - } - return this.getAutofillCredentials(id); + }; + ZodPromise.create = (schema, params) => { + return new ZodPromise({ + type: schema, + typeName: ZodFirstPartyTypeKind.ZodPromise, + ...processCreateParams(params) + }); + }; + var ZodEffects = class extends ZodType { + innerType() { + return this._def.schema; + } + sourceType() { + return this._def.schema._def.typeName === ZodFirstPartyTypeKind.ZodEffects ? this._def.schema.sourceType() : this._def.schema; + } + _parse(input) { + const { status, ctx } = this._processInputParams(input); + const effect = this._def.effect || null; + const checkCtx = { + addIssue: (arg) => { + addIssueToContext(ctx, arg); + if (arg.fatal) { + status.abort(); + } else { + status.dirty(); } - default: - throw new Error('unreachable!'); + }, + get path() { + return ctx.path; + } + }; + checkCtx.addIssue = checkCtx.addIssue.bind(checkCtx); + if (effect.type === "preprocess") { + const processed = effect.transform(ctx.data, checkCtx); + if (ctx.common.issues.length) { + return { + status: "dirty", + value: ctx.data + }; + } + if (ctx.common.async) { + return Promise.resolve(processed).then((processed2) => { + return this._def.schema._parseAsync({ + data: processed2, + path: ctx.path, + parent: ctx + }); + }); + } else { + return this._def.schema._parseSync({ + data: processed, + path: ctx.path, + parent: ctx + }); + } + } + if (effect.type === "refinement") { + const executeRefinement = (acc) => { + const result = effect.refinement(acc, checkCtx); + if (ctx.common.async) { + return Promise.resolve(result); + } + if (result instanceof Promise) { + throw new Error("Async refinement encountered during synchronous parse operation. Use .parseAsync instead."); + } + return acc; + }; + if (ctx.common.async === false) { + const inner = this._def.schema._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx + }); + if (inner.status === "aborted") + return INVALID; + if (inner.status === "dirty") + status.dirty(); + executeRefinement(inner.value); + return { status: status.value, value: inner.value }; + } else { + return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((inner) => { + if (inner.status === "aborted") + return INVALID; + if (inner.status === "dirty") + status.dirty(); + return executeRefinement(inner.value).then(() => { + return { status: status.value, value: inner.value }; + }); + }); + } } - })(); - - // wait for the data back from the device - dataPromise.then(response => { - if (response) { - const data = response.success || response; - if (mainType === 'identities') { - this.firePixel({ - pixelName: 'autofill_identity', - params: { - fieldType: subtype - } + if (effect.type === "transform") { + if (ctx.common.async === false) { + const base = this._def.schema._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx }); - switch (id) { - case 'personalAddress': - this.firePixel({ - pixelName: 'autofill_personal_address' - }); - break; - case 'privateAddress': - this.firePixel({ - pixelName: 'autofill_private_address' - }); - break; - default: - // Also fire pixel when filling an identity with the personal duck address from an email field - const checks = [subtype === 'emailAddress', this.hasLocalAddresses, data?.emailAddress === (0, _autofillUtils.formatDuckAddress)(this.#addresses.personalAddress)]; - if (checks.every(Boolean)) { - this.firePixel({ - pixelName: 'autofill_personal_address' - }); - } - break; + if (!isValid(base)) + return base; + const result = effect.transform(base.value, checkCtx); + if (result instanceof Promise) { + throw new Error(`Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.`); } + return { status: status.value, value: result }; + } else { + return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((base) => { + if (!isValid(base)) + return base; + return Promise.resolve(effect.transform(base.value, checkCtx)).then((result) => ({ status: status.value, value: result })); + }); } - // some platforms do not include a `success` object, why? - return this.selectedDetail(data, mainType); - } else { - return Promise.reject(new Error('none-success response')); } - }).catch(e => { - console.error(e); - return this.removeTooltip(); + util.assertNever(effect); + } + }; + ZodEffects.create = (schema, effect, params) => { + return new ZodEffects({ + schema, + typeName: ZodFirstPartyTypeKind.ZodEffects, + effect, + ...processCreateParams(params) }); - } - isTooltipActive() { - return this.uiController?.isActive?.() ?? false; - } - removeTooltip() { - return this.uiController?.removeTooltip?.('interface'); - } - onFinishedAutofill() { - // Let input handlers know we've stopped autofilling - this.activeForm?.activeInput?.dispatchEvent(new Event('mouseleave')); - } - async updateForStateChange() { - // Remove decorations before refreshing data to make sure we - // remove the currently set icons - this.activeForm?.removeAllDecorations(); - - // Update for any state that may have changed - await this.refreshData(); - - // Add correct icons and behaviour - this.activeForm?.recategorizeAllInputs(); - } - async refreshData() { - await this.inContextSignup?.refreshData(); - await this.settings.populateData(); - } - async setupSettingsPage() { - let { - shouldLog - } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { - shouldLog: false - }; - if (!this.globalConfig.isDDGDomain) { - return; + }; + ZodEffects.createWithPreprocess = (preprocess, schema, params) => { + return new ZodEffects({ + schema, + effect: { type: "preprocess", transform: preprocess }, + typeName: ZodFirstPartyTypeKind.ZodEffects, + ...processCreateParams(params) + }); + }; + var ZodOptional = class extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType === ZodParsedType.undefined) { + return OK(void 0); + } + return this._def.innerType._parse(input); } - (0, _autofillUtils.notifyWebApp)({ - isApp: this.globalConfig.isApp + unwrap() { + return this._def.innerType; + } + }; + ZodOptional.create = (type, params) => { + return new ZodOptional({ + innerType: type, + typeName: ZodFirstPartyTypeKind.ZodOptional, + ...processCreateParams(params) }); - if (this.isDeviceSignedIn()) { - let userData; - try { - userData = await this.getUserData(); - } catch (e) {} - let capabilities; - try { - capabilities = await this.getEmailProtectionCapabilities(); - } catch (e) {} - - // Set up listener for web app actions - if (this.globalConfig.isDDGDomain) { - window.addEventListener('message', e => { - if (e.data.removeUserData) { - this.removeUserData(); - } - if (e.data.closeEmailProtection) { - this.closeEmailProtection(); - } - }); + }; + var ZodNullable = class extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType === ZodParsedType.null) { + return OK(null); } - const hasUserData = userData && !userData.error && Object.entries(userData).length > 0; - (0, _autofillUtils.notifyWebApp)({ - deviceSignedIn: { - value: true, - shouldLog, - userData: hasUserData ? userData : undefined, - capabilities - } + return this._def.innerType._parse(input); + } + unwrap() { + return this._def.innerType; + } + }; + ZodNullable.create = (type, params) => { + return new ZodNullable({ + innerType: type, + typeName: ZodFirstPartyTypeKind.ZodNullable, + ...processCreateParams(params) + }); + }; + var ZodDefault = class extends ZodType { + _parse(input) { + const { ctx } = this._processInputParams(input); + let data = ctx.data; + if (ctx.parsedType === ZodParsedType.undefined) { + data = this._def.defaultValue(); + } + return this._def.innerType._parse({ + data, + path: ctx.path, + parent: ctx }); - } else { - this.trySigningIn(); } - } - async setupAutofill() {} - - /** @returns {Promise} */ - async getAddresses() { - throw new Error('unimplemented'); - } - - /** @returns {Promise>} */ - getUserData() { - return Promise.resolve(null); - } - - /** @returns {void} */ - removeUserData() {} - - /** @returns {void} */ - closeEmailProtection() {} - - /** @returns {Promise>} */ - getEmailProtectionCapabilities() { - throw new Error('unimplemented'); - } - refreshAlias() {} - async trySigningIn() { - if (this.globalConfig.isDDGDomain) { - if (this.attempts < 10) { - this.attempts++; - const data = await (0, _autofillUtils.sendAndWaitForAnswer)(_autofillUtils.SIGN_IN_MSG, 'addUserData'); - // This call doesn't send a response, so we can't know if it succeeded - this.storeUserData(data); - await this.setupAutofill(); - await this.settings.refresh(); - await this.setupSettingsPage({ - shouldLog: true + removeDefault() { + return this._def.innerType; + } + }; + ZodDefault.create = (type, params) => { + return new ZodDefault({ + innerType: type, + typeName: ZodFirstPartyTypeKind.ZodDefault, + defaultValue: typeof params.default === "function" ? params.default : () => params.default, + ...processCreateParams(params) + }); + }; + var ZodCatch = class extends ZodType { + _parse(input) { + const { ctx } = this._processInputParams(input); + const newCtx = { + ...ctx, + common: { + ...ctx.common, + issues: [] + } + }; + const result = this._def.innerType._parse({ + data: newCtx.data, + path: newCtx.path, + parent: { + ...newCtx + } + }); + if (isAsync(result)) { + return result.then((result2) => { + return { + status: "valid", + value: result2.status === "valid" ? result2.value : this._def.catchValue({ + get error() { + return new ZodError(newCtx.common.issues); + }, + input: newCtx.data + }) + }; }); - await this.postInit(); } else { - console.warn('max attempts reached, bailing'); + return { + status: "valid", + value: result.status === "valid" ? result.value : this._def.catchValue({ + get error() { + return new ZodError(newCtx.common.issues); + }, + input: newCtx.data + }) + }; } } - } - storeUserData(_data) {} - addDeviceListeners() {} - - /** @param {() => void} _fn */ - addLogoutListener(_fn) {} - isDeviceSignedIn() { - return false; - } - /** - * @returns {Promise} - */ - async getAlias() { - return undefined; - } - // PM endpoints - getAccounts() {} - /** - * Gets credentials ready for autofill - * @param {CredentialsObject['id']} id - the credential id - * @returns {Promise} - */ - async getAutofillCredentials(id) { - return this.deviceApi.request(new _deviceApiCalls.GetAutofillCredentialsCall({ - id: String(id) - })); - } - /** @returns {APIResponse} */ - async getAutofillCreditCard(_id) { - throw new Error('getAutofillCreditCard unimplemented'); - } - /** @returns {Promise<{success: IdentityObject|undefined}>} */ - async getAutofillIdentity(_id) { - throw new Error('getAutofillIdentity unimplemented'); - } - openManagePasswords() {} - openManageCreditCards() {} - openManageIdentities() {} - - /** - * @param {StoreFormData} values - * @param {StoreFormData['trigger']} trigger - */ - storeFormData(values, trigger) { - this.deviceApi.notify(new _deviceApiCalls.StoreFormDataCall({ - ...values, - trigger - })); - } - - /** - * `preAttachTooltip` happens just before a tooltip is show - features may want to append some data - * at this point. - * - * For example, if password generation is enabled, this will generate - * a password and send it to the tooltip as though it were a stored credential. - * - * @param {TopContextData} topContextData - * @param {HTMLInputElement} input - * @param {import("../Form/Form").Form} form - */ - preAttachTooltip(topContextData, input, form) { - // A list of checks to determine if we need to generate a password - const checks = [topContextData.inputType === 'credentials.password.new', this.settings.featureToggles.password_generation]; - - // if all checks pass, generate and save a password - if (checks.every(Boolean)) { - const password = this.passwordGenerator.generate({ - input: input.getAttribute('passwordrules'), - domain: window.location.hostname - }); - const rawValues = form.getRawValues(); - const username = rawValues.credentials?.username || rawValues.identities?.emailAddress || ''; - - // append the new credential to the topContextData so that the top autofill can display it - topContextData.credentials = [(0, _Credentials.fromPassword)(password, username)]; + removeCatch() { + return this._def.innerType; } - return topContextData; - } - - /** - * `postAutofill` gives features an opportunity to perform an action directly - * following an autofill. - * - * For example, if a generated password was used, we want to fire a save event. - * - * @param {IdentityObject|CreditCardObject|CredentialsObject} data - * @param {SupportedMainTypes} dataType - * @param {import("../Form/Form").Form} formObj - */ - postAutofill(data, dataType, formObj) { - // If there's an autogenerated password, prompt to save - if (_Credentials.AUTOGENERATED_KEY in data && 'password' in data && - // Don't send message on Android to avoid potential abuse. Data is saved on native confirmation instead. - !this.globalConfig.isAndroid) { - const formValues = formObj.getValuesReadyForStorage(); - if (formValues.credentials?.password === data.password) { - /** @type {StoreFormData} */ - const formData = (0, _Credentials.appendGeneratedKey)(formValues, { - password: data.password + }; + ZodCatch.create = (type, params) => { + return new ZodCatch({ + innerType: type, + typeName: ZodFirstPartyTypeKind.ZodCatch, + catchValue: typeof params.catch === "function" ? params.catch : () => params.catch, + ...processCreateParams(params) + }); + }; + var ZodNaN = class extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.nan) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.nan, + received: ctx.parsedType }); - this.storeFormData(formData, 'passwordGeneration'); + return INVALID; } + return { status: "valid", value: input.data }; + } + }; + ZodNaN.create = (params) => { + return new ZodNaN({ + typeName: ZodFirstPartyTypeKind.ZodNaN, + ...processCreateParams(params) + }); + }; + var BRAND = Symbol("zod_brand"); + var ZodBranded = class extends ZodType { + _parse(input) { + const { ctx } = this._processInputParams(input); + const data = ctx.data; + return this._def.type._parse({ + data, + path: ctx.path, + parent: ctx + }); } - if (dataType === 'credentials' && formObj.shouldAutoSubmit) { - formObj.attemptSubmissionIfNeeded(); + unwrap() { + return this._def.type; } - } - - /** - * `postSubmit` gives features a one-time-only opportunity to perform an - * action directly after a form submission was observed. - * - * Mostly this is about storing data from the form submission, but it can - * also be used like in the case of Password generation, to append additional - * data before it's sent to be saved. - * - * @param {DataStorageObject} values - * @param {import("../Form/Form").Form} form - */ - postSubmit(values, form) { - if (!form.form) return; - if (!form.hasValues(values)) return; - const checks = [form.shouldPromptToStoreData && !form.submitHandlerExecuted, this.passwordGenerator.generated]; - if (checks.some(Boolean)) { - const formData = (0, _Credentials.appendGeneratedKey)(values, { - password: this.passwordGenerator.password, - username: this.emailProtection.lastGenerated + }; + var ZodPipeline = class extends ZodType { + _parse(input) { + const { status, ctx } = this._processInputParams(input); + if (ctx.common.async) { + const handleAsync = async () => { + const inResult = await this._def.in._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: ctx + }); + if (inResult.status === "aborted") + return INVALID; + if (inResult.status === "dirty") { + status.dirty(); + return DIRTY(inResult.value); + } else { + return this._def.out._parseAsync({ + data: inResult.value, + path: ctx.path, + parent: ctx + }); + } + }; + return handleAsync(); + } else { + const inResult = this._def.in._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx + }); + if (inResult.status === "aborted") + return INVALID; + if (inResult.status === "dirty") { + status.dirty(); + return { + status: "dirty", + value: inResult.value + }; + } else { + return this._def.out._parseSync({ + data: inResult.value, + path: ctx.path, + parent: ctx + }); + } + } + } + static create(a, b) { + return new ZodPipeline({ + in: a, + out: b, + typeName: ZodFirstPartyTypeKind.ZodPipeline }); - this.storeFormData(formData, 'formSubmission'); } - } - - /** - * Sends a pixel to be fired on the client side - * @param {import('../deviceApiCalls/__generated__/validators-ts').SendJSPixelParams} pixelParams - */ - firePixel(pixelParams) { - this.deviceApi.notify(new _deviceApiCalls.SendJSPixelCall(pixelParams)); - } - - /** - * This serves as a single place to create a default instance - * of InterfacePrototype that can be useful in testing scenarios - * @param {Partial} [globalConfigOverrides] - * @returns {InterfacePrototype} - */ - static default(globalConfigOverrides) { - const globalConfig = (0, _config.createGlobalConfig)(globalConfigOverrides); - const transport = (0, _transports.createTransport)(globalConfig); - const deviceApi = new _index.DeviceApi(transport); - const settings = _Settings.Settings.default(globalConfig, deviceApi); - return new InterfacePrototype(globalConfig, deviceApi, settings); - } -} -var _default = exports.default = InterfacePrototype; - -},{"../../packages/device-api/index.js":12,"../EmailProtection.js":32,"../Form/formatters.js":36,"../Form/matching.js":43,"../InputTypes/Credentials.js":45,"../PasswordGenerator.js":48,"../Scanner.js":49,"../Settings.js":50,"../UI/controllers/NativeUIController.js":57,"../autofill-utils.js":62,"../config.js":64,"../deviceApiCalls/__generated__/deviceApiCalls.js":66,"../deviceApiCalls/transports/transports.js":72,"./initFormSubmissionsApi.js":30}],28:[function(require,module,exports){ -"use strict"; + }; + var ZodReadonly = class extends ZodType { + _parse(input) { + const result = this._def.innerType._parse(input); + if (isValid(result)) { + result.value = Object.freeze(result.value); + } + return result; + } + }; + ZodReadonly.create = (type, params) => { + return new ZodReadonly({ + innerType: type, + typeName: ZodFirstPartyTypeKind.ZodReadonly, + ...processCreateParams(params) + }); + }; + var custom = (check, params = {}, fatal) => { + if (check) + return ZodAny.create().superRefine((data, ctx) => { + var _a, _b; + if (!check(data)) { + const p = typeof params === "function" ? params(data) : typeof params === "string" ? { message: params } : params; + const _fatal = (_b = (_a = p.fatal) !== null && _a !== void 0 ? _a : fatal) !== null && _b !== void 0 ? _b : true; + const p2 = typeof p === "string" ? { message: p } : p; + ctx.addIssue({ code: "custom", ...p2, fatal: _fatal }); + } + }); + return ZodAny.create(); + }; + var late = { + object: ZodObject.lazycreate + }; + var ZodFirstPartyTypeKind; + (function(ZodFirstPartyTypeKind2) { + ZodFirstPartyTypeKind2["ZodString"] = "ZodString"; + ZodFirstPartyTypeKind2["ZodNumber"] = "ZodNumber"; + ZodFirstPartyTypeKind2["ZodNaN"] = "ZodNaN"; + ZodFirstPartyTypeKind2["ZodBigInt"] = "ZodBigInt"; + ZodFirstPartyTypeKind2["ZodBoolean"] = "ZodBoolean"; + ZodFirstPartyTypeKind2["ZodDate"] = "ZodDate"; + ZodFirstPartyTypeKind2["ZodSymbol"] = "ZodSymbol"; + ZodFirstPartyTypeKind2["ZodUndefined"] = "ZodUndefined"; + ZodFirstPartyTypeKind2["ZodNull"] = "ZodNull"; + ZodFirstPartyTypeKind2["ZodAny"] = "ZodAny"; + ZodFirstPartyTypeKind2["ZodUnknown"] = "ZodUnknown"; + ZodFirstPartyTypeKind2["ZodNever"] = "ZodNever"; + ZodFirstPartyTypeKind2["ZodVoid"] = "ZodVoid"; + ZodFirstPartyTypeKind2["ZodArray"] = "ZodArray"; + ZodFirstPartyTypeKind2["ZodObject"] = "ZodObject"; + ZodFirstPartyTypeKind2["ZodUnion"] = "ZodUnion"; + ZodFirstPartyTypeKind2["ZodDiscriminatedUnion"] = "ZodDiscriminatedUnion"; + ZodFirstPartyTypeKind2["ZodIntersection"] = "ZodIntersection"; + ZodFirstPartyTypeKind2["ZodTuple"] = "ZodTuple"; + ZodFirstPartyTypeKind2["ZodRecord"] = "ZodRecord"; + ZodFirstPartyTypeKind2["ZodMap"] = "ZodMap"; + ZodFirstPartyTypeKind2["ZodSet"] = "ZodSet"; + ZodFirstPartyTypeKind2["ZodFunction"] = "ZodFunction"; + ZodFirstPartyTypeKind2["ZodLazy"] = "ZodLazy"; + ZodFirstPartyTypeKind2["ZodLiteral"] = "ZodLiteral"; + ZodFirstPartyTypeKind2["ZodEnum"] = "ZodEnum"; + ZodFirstPartyTypeKind2["ZodEffects"] = "ZodEffects"; + ZodFirstPartyTypeKind2["ZodNativeEnum"] = "ZodNativeEnum"; + ZodFirstPartyTypeKind2["ZodOptional"] = "ZodOptional"; + ZodFirstPartyTypeKind2["ZodNullable"] = "ZodNullable"; + ZodFirstPartyTypeKind2["ZodDefault"] = "ZodDefault"; + ZodFirstPartyTypeKind2["ZodCatch"] = "ZodCatch"; + ZodFirstPartyTypeKind2["ZodPromise"] = "ZodPromise"; + ZodFirstPartyTypeKind2["ZodBranded"] = "ZodBranded"; + ZodFirstPartyTypeKind2["ZodPipeline"] = "ZodPipeline"; + ZodFirstPartyTypeKind2["ZodReadonly"] = "ZodReadonly"; + })(ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {})); + var instanceOfType = (cls, params = { + message: `Input not instance of ${cls.name}` + }) => custom((data) => data instanceof cls, params); + var stringType = ZodString.create; + var numberType = ZodNumber.create; + var nanType = ZodNaN.create; + var bigIntType = ZodBigInt.create; + var booleanType = ZodBoolean.create; + var dateType = ZodDate.create; + var symbolType = ZodSymbol.create; + var undefinedType = ZodUndefined.create; + var nullType = ZodNull.create; + var anyType = ZodAny.create; + var unknownType = ZodUnknown.create; + var neverType = ZodNever.create; + var voidType = ZodVoid.create; + var arrayType = ZodArray.create; + var objectType = ZodObject.create; + var strictObjectType = ZodObject.strictCreate; + var unionType = ZodUnion.create; + var discriminatedUnionType = ZodDiscriminatedUnion.create; + var intersectionType = ZodIntersection.create; + var tupleType = ZodTuple.create; + var recordType = ZodRecord.create; + var mapType = ZodMap.create; + var setType = ZodSet.create; + var functionType = ZodFunction.create; + var lazyType = ZodLazy.create; + var literalType = ZodLiteral.create; + var enumType = ZodEnum.create; + var nativeEnumType = ZodNativeEnum.create; + var promiseType = ZodPromise.create; + var effectsType = ZodEffects.create; + var optionalType = ZodOptional.create; + var nullableType = ZodNullable.create; + var preprocessType = ZodEffects.createWithPreprocess; + var pipelineType = ZodPipeline.create; + var ostring = () => stringType().optional(); + var onumber = () => numberType().optional(); + var oboolean = () => booleanType().optional(); + var coerce = { + string: (arg) => ZodString.create({ ...arg, coerce: true }), + number: (arg) => ZodNumber.create({ ...arg, coerce: true }), + boolean: (arg) => ZodBoolean.create({ + ...arg, + coerce: true + }), + bigint: (arg) => ZodBigInt.create({ ...arg, coerce: true }), + date: (arg) => ZodDate.create({ ...arg, coerce: true }) + }; + var NEVER = INVALID; + var z = /* @__PURE__ */ Object.freeze({ + __proto__: null, + defaultErrorMap: errorMap, + setErrorMap, + getErrorMap, + makeIssue, + EMPTY_PATH, + addIssueToContext, + ParseStatus, + INVALID, + DIRTY, + OK, + isAborted, + isDirty, + isValid, + isAsync, + get util() { + return util; + }, + get objectUtil() { + return objectUtil; + }, + ZodParsedType, + getParsedType, + ZodType, + ZodString, + ZodNumber, + ZodBigInt, + ZodBoolean, + ZodDate, + ZodSymbol, + ZodUndefined, + ZodNull, + ZodAny, + ZodUnknown, + ZodNever, + ZodVoid, + ZodArray, + ZodObject, + ZodUnion, + ZodDiscriminatedUnion, + ZodIntersection, + ZodTuple, + ZodRecord, + ZodMap, + ZodSet, + ZodFunction, + ZodLazy, + ZodLiteral, + ZodEnum, + ZodNativeEnum, + ZodPromise, + ZodEffects, + ZodTransformer: ZodEffects, + ZodOptional, + ZodNullable, + ZodDefault, + ZodCatch, + ZodNaN, + BRAND, + ZodBranded, + ZodPipeline, + ZodReadonly, + custom, + Schema: ZodType, + ZodSchema: ZodType, + late, + get ZodFirstPartyTypeKind() { + return ZodFirstPartyTypeKind; + }, + coerce, + any: anyType, + array: arrayType, + bigint: bigIntType, + boolean: booleanType, + date: dateType, + discriminatedUnion: discriminatedUnionType, + effect: effectsType, + "enum": enumType, + "function": functionType, + "instanceof": instanceOfType, + intersection: intersectionType, + lazy: lazyType, + literal: literalType, + map: mapType, + nan: nanType, + nativeEnum: nativeEnumType, + never: neverType, + "null": nullType, + nullable: nullableType, + number: numberType, + object: objectType, + oboolean, + onumber, + optional: optionalType, + ostring, + pipeline: pipelineType, + preprocess: preprocessType, + promise: promiseType, + record: recordType, + set: setType, + strictObject: strictObjectType, + string: stringType, + symbol: symbolType, + transformer: effectsType, + tuple: tupleType, + "undefined": undefinedType, + union: unionType, + unknown: unknownType, + "void": voidType, + NEVER, + ZodIssueCode, + quotelessJson, + ZodError + }); -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.WindowsInterface = void 0; -var _InterfacePrototype = _interopRequireDefault(require("./InterfacePrototype.js")); -var _OverlayUIController = require("../UI/controllers/OverlayUIController.js"); -var _deviceApiCalls = require("../deviceApiCalls/__generated__/deviceApiCalls.js"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -/** - * @typedef {import('../deviceApiCalls/__generated__/validators-ts').GetAutofillDataRequest} GetAutofillDataRequest - */ + // src/deviceApiCalls/__generated__/validators.zod.js + var sendJSPixelParamsSchema = z.union([z.object({ + pixelName: z.literal("autofill_identity"), + params: z.object({ + fieldType: z.string().optional() + }).optional() + }), z.object({ + pixelName: z.literal("autofill_show") + }), z.object({ + pixelName: z.literal("autofill_personal_address") + }), z.object({ + pixelName: z.literal("autofill_private_address") + }), z.object({ + pixelName: z.literal("incontext_show") + }), z.object({ + pixelName: z.literal("incontext_primary_cta") + }), z.object({ + pixelName: z.literal("incontext_dismiss_persisted") + }), z.object({ + pixelName: z.literal("incontext_close_x") + })]); + var addDebugFlagParamsSchema = z.object({ + flag: z.string() + }); + var getAutofillCredentialsParamsSchema = z.object({ + id: z.string() + }); + var setSizeParamsSchema = z.object({ + height: z.number(), + width: z.number() + }); + var selectedDetailParamsSchema = z.object({ + data: z.record(z.unknown()), + configType: z.string() + }); + var setIncontextSignupPermanentlyDismissedAtSchema = z.object({ + value: z.number().optional() + }); + var getIncontextSignupDismissedAtSchema = z.object({ + success: z.object({ + permanentlyDismissedAt: z.number().optional(), + isInstalledRecently: z.boolean().optional() + }) + }); + var getAliasParamsSchema = z.object({ + requiresUserPermission: z.boolean(), + shouldConsumeAliasIfProvided: z.boolean(), + isIncontextSignupAvailable: z.boolean().optional() + }); + var getAliasResultSchema = z.object({ + success: z.object({ + alias: z.string().optional() + }) + }); + var emailProtectionStoreUserDataParamsSchema = z.object({ + token: z.string(), + userName: z.string(), + cohort: z.string() + }); + var showInContextEmailProtectionSignupPromptSchema = z.object({ + success: z.object({ + isSignedIn: z.boolean() + }) + }); + var generatedPasswordSchema = z.object({ + value: z.string(), + username: z.string() + }); + var triggerContextSchema = z.object({ + inputTop: z.number(), + inputLeft: z.number(), + inputHeight: z.number(), + inputWidth: z.number(), + wasFromClick: z.boolean() + }); + var credentialsSchema = z.object({ + id: z.string().optional(), + username: z.string(), + password: z.string(), + origin: z.object({ + url: z.string() + }).optional(), + credentialsProvider: z.union([z.literal("duckduckgo"), z.literal("bitwarden")]).optional(), + providerStatus: z.union([z.literal("locked"), z.literal("unlocked")]).optional() + }); + var genericErrorSchema = z.object({ + message: z.string() + }); + var contentScopeSchema = z.object({ + features: z.record(z.object({ + exceptions: z.array(z.unknown()), + state: z.union([z.literal("enabled"), z.literal("disabled")]), + settings: z.record(z.unknown()).optional() + })), + unprotectedTemporary: z.array(z.unknown()) + }); + var userPreferencesSchema = z.object({ + globalPrivacyControlValue: z.boolean().optional(), + sessionKey: z.string().optional(), + debug: z.boolean(), + platform: z.object({ + name: z.union([z.literal("ios"), z.literal("macos"), z.literal("windows"), z.literal("extension"), z.literal("android"), z.literal("unknown")]) + }), + features: z.record(z.object({ + settings: z.record(z.unknown()) + })) + }); + var outgoingCredentialsSchema = z.object({ + username: z.string().optional(), + password: z.string().optional() + }); + var availableInputTypesSchema = z.object({ + credentials: z.object({ + username: z.boolean().optional(), + password: z.boolean().optional() + }).optional(), + identities: z.object({ + firstName: z.boolean().optional(), + middleName: z.boolean().optional(), + lastName: z.boolean().optional(), + birthdayDay: z.boolean().optional(), + birthdayMonth: z.boolean().optional(), + birthdayYear: z.boolean().optional(), + addressStreet: z.boolean().optional(), + addressStreet2: z.boolean().optional(), + addressCity: z.boolean().optional(), + addressProvince: z.boolean().optional(), + addressPostalCode: z.boolean().optional(), + addressCountryCode: z.boolean().optional(), + phone: z.boolean().optional(), + emailAddress: z.boolean().optional() + }).optional(), + creditCards: z.object({ + cardName: z.boolean().optional(), + cardSecurityCode: z.boolean().optional(), + expirationMonth: z.boolean().optional(), + expirationYear: z.boolean().optional(), + cardNumber: z.boolean().optional() + }).optional(), + email: z.boolean().optional(), + credentialsProviderStatus: z.union([z.literal("locked"), z.literal("unlocked")]).optional() + }); + var availableInputTypes1Schema = z.object({ + credentials: z.object({ + username: z.boolean().optional(), + password: z.boolean().optional() + }).optional(), + identities: z.object({ + firstName: z.boolean().optional(), + middleName: z.boolean().optional(), + lastName: z.boolean().optional(), + birthdayDay: z.boolean().optional(), + birthdayMonth: z.boolean().optional(), + birthdayYear: z.boolean().optional(), + addressStreet: z.boolean().optional(), + addressStreet2: z.boolean().optional(), + addressCity: z.boolean().optional(), + addressProvince: z.boolean().optional(), + addressPostalCode: z.boolean().optional(), + addressCountryCode: z.boolean().optional(), + phone: z.boolean().optional(), + emailAddress: z.boolean().optional() + }).optional(), + creditCards: z.object({ + cardName: z.boolean().optional(), + cardSecurityCode: z.boolean().optional(), + expirationMonth: z.boolean().optional(), + expirationYear: z.boolean().optional(), + cardNumber: z.boolean().optional() + }).optional(), + email: z.boolean().optional(), + credentialsProviderStatus: z.union([z.literal("locked"), z.literal("unlocked")]).optional() + }); + var autofillFeatureTogglesSchema = z.object({ + inputType_credentials: z.boolean().optional(), + inputType_identities: z.boolean().optional(), + inputType_creditCards: z.boolean().optional(), + emailProtection: z.boolean().optional(), + emailProtection_incontext_signup: z.boolean().optional(), + password_generation: z.boolean().optional(), + credentials_saving: z.boolean().optional(), + inlineIcon_credentials: z.boolean().optional(), + third_party_credentials_provider: z.boolean().optional() + }); + var getAutofillDataRequestSchema = z.object({ + generatedPassword: generatedPasswordSchema.optional(), + inputType: z.string(), + mainType: z.union([z.literal("credentials"), z.literal("identities"), z.literal("creditCards")]), + subType: z.string(), + trigger: z.union([z.literal("userInitiated"), z.literal("autoprompt"), z.literal("postSignup")]).optional(), + serializedInputContext: z.string().optional(), + triggerContext: triggerContextSchema.optional() + }); + var getAutofillDataResponseSchema = z.object({ + type: z.literal("getAutofillDataResponse").optional(), + success: z.object({ + credentials: credentialsSchema.optional(), + action: z.union([z.literal("fill"), z.literal("focus"), z.literal("none"), z.literal("acceptGeneratedPassword"), z.literal("rejectGeneratedPassword")]) + }).optional(), + error: genericErrorSchema.optional() + }); + var storeFormDataSchema = z.object({ + credentials: outgoingCredentialsSchema.optional(), + trigger: z.union([z.literal("formSubmission"), z.literal("passwordGeneration"), z.literal("emailProtection")]).optional() + }); + var getAvailableInputTypesResultSchema = z.object({ + type: z.literal("getAvailableInputTypesResponse").optional(), + success: availableInputTypesSchema, + error: genericErrorSchema.optional() + }); + var getAutofillInitDataResponseSchema = z.object({ + type: z.literal("getAutofillInitDataResponse").optional(), + success: z.object({ + credentials: z.array(credentialsSchema), + identities: z.array(z.record(z.unknown())), + creditCards: z.array(z.record(z.unknown())), + serializedInputContext: z.string() + }).optional(), + error: genericErrorSchema.optional() + }); + var getAutofillCredentialsResultSchema = z.object({ + type: z.literal("getAutofillCredentialsResponse").optional(), + success: z.object({ + id: z.string().optional(), + autogenerated: z.boolean().optional(), + username: z.string(), + password: z.string().optional() + }).optional(), + error: genericErrorSchema.optional() + }); + var autofillSettingsSchema = z.object({ + featureToggles: autofillFeatureTogglesSchema + }); + var emailProtectionGetIsLoggedInResultSchema = z.object({ + success: z.boolean().optional(), + error: genericErrorSchema.optional() + }); + var emailProtectionGetUserDataResultSchema = z.object({ + success: z.object({ + userName: z.string(), + nextAlias: z.string(), + token: z.string() + }).optional(), + error: genericErrorSchema.optional() + }); + var emailProtectionGetCapabilitiesResultSchema = z.object({ + success: z.object({ + addUserData: z.boolean().optional(), + getUserData: z.boolean().optional(), + removeUserData: z.boolean().optional() + }).optional(), + error: genericErrorSchema.optional() + }); + var emailProtectionGetAddressesResultSchema = z.object({ + success: z.object({ + personalAddress: z.string(), + privateAddress: z.string() + }).optional(), + error: genericErrorSchema.optional() + }); + var emailProtectionRefreshPrivateAddressResultSchema = z.object({ + success: z.object({ + personalAddress: z.string(), + privateAddress: z.string() + }).optional(), + error: genericErrorSchema.optional() + }); + var runtimeConfigurationSchema = z.object({ + contentScope: contentScopeSchema, + userUnprotectedDomains: z.array(z.string()), + userPreferences: userPreferencesSchema + }); + var providerStatusUpdatedSchema = z.object({ + status: z.union([z.literal("locked"), z.literal("unlocked")]), + credentials: z.array(credentialsSchema), + availableInputTypes: availableInputTypes1Schema + }); + var getRuntimeConfigurationResponseSchema = z.object({ + type: z.literal("getRuntimeConfigurationResponse").optional(), + success: runtimeConfigurationSchema.optional(), + error: genericErrorSchema.optional() + }); + var askToUnlockProviderResultSchema = z.object({ + type: z.literal("askToUnlockProviderResponse").optional(), + success: providerStatusUpdatedSchema, + error: genericErrorSchema.optional() + }); + var checkCredentialsProviderStatusResultSchema = z.object({ + type: z.literal("checkCredentialsProviderStatusResponse").optional(), + success: providerStatusUpdatedSchema, + error: genericErrorSchema.optional() + }); + var apiSchema = z.object({ + addDebugFlag: z.record(z.unknown()).and(z.object({ + paramsValidator: addDebugFlagParamsSchema.optional() + })).optional(), + getAutofillData: z.record(z.unknown()).and(z.object({ + id: z.literal("getAutofillDataResponse").optional(), + paramsValidator: getAutofillDataRequestSchema.optional(), + resultValidator: getAutofillDataResponseSchema.optional() + })).optional(), + getRuntimeConfiguration: z.record(z.unknown()).and(z.object({ + id: z.literal("getRuntimeConfigurationResponse").optional(), + resultValidator: getRuntimeConfigurationResponseSchema.optional() + })).optional(), + storeFormData: z.record(z.unknown()).and(z.object({ + paramsValidator: storeFormDataSchema.optional() + })).optional(), + getAvailableInputTypes: z.record(z.unknown()).and(z.object({ + id: z.literal("getAvailableInputTypesResponse").optional(), + resultValidator: getAvailableInputTypesResultSchema.optional() + })).optional(), + getAutofillInitData: z.record(z.unknown()).and(z.object({ + id: z.literal("getAutofillInitDataResponse").optional(), + resultValidator: getAutofillInitDataResponseSchema.optional() + })).optional(), + getAutofillCredentials: z.record(z.unknown()).and(z.object({ + id: z.literal("getAutofillCredentialsResponse").optional(), + paramsValidator: getAutofillCredentialsParamsSchema.optional(), + resultValidator: getAutofillCredentialsResultSchema.optional() + })).optional(), + setSize: z.record(z.unknown()).and(z.object({ + paramsValidator: setSizeParamsSchema.optional() + })).optional(), + selectedDetail: z.record(z.unknown()).and(z.object({ + paramsValidator: selectedDetailParamsSchema.optional() + })).optional(), + closeAutofillParent: z.record(z.unknown()).optional(), + askToUnlockProvider: z.record(z.unknown()).and(z.object({ + id: z.literal("askToUnlockProviderResponse").optional(), + resultValidator: askToUnlockProviderResultSchema.optional() + })).optional(), + checkCredentialsProviderStatus: z.record(z.unknown()).and(z.object({ + id: z.literal("checkCredentialsProviderStatusResponse").optional(), + resultValidator: checkCredentialsProviderStatusResultSchema.optional() + })).optional(), + sendJSPixel: z.record(z.unknown()).and(z.object({ + paramsValidator: sendJSPixelParamsSchema.optional() + })).optional(), + setIncontextSignupPermanentlyDismissedAt: z.record(z.unknown()).and(z.object({ + paramsValidator: setIncontextSignupPermanentlyDismissedAtSchema.optional() + })).optional(), + getIncontextSignupDismissedAt: z.record(z.unknown()).and(z.object({ + id: z.literal("getIncontextSignupDismissedAt").optional(), + resultValidator: getIncontextSignupDismissedAtSchema.optional() + })).optional(), + autofillSettings: z.record(z.unknown()).and(z.object({ + validatorsOnly: z.literal(true).optional(), + resultValidator: autofillSettingsSchema.optional() + })).optional(), + getAlias: z.record(z.unknown()).and(z.object({ + validatorsOnly: z.literal(true).optional(), + paramValidator: getAliasParamsSchema.optional(), + resultValidator: getAliasResultSchema.optional() + })).optional(), + openManagePasswords: z.record(z.unknown()).optional(), + openManageCreditCards: z.record(z.unknown()).optional(), + openManageIdentities: z.record(z.unknown()).optional(), + emailProtectionStoreUserData: z.record(z.unknown()).and(z.object({ + id: z.literal("emailProtectionStoreUserDataResponse").optional(), + paramsValidator: emailProtectionStoreUserDataParamsSchema.optional() + })).optional(), + emailProtectionRemoveUserData: z.record(z.unknown()).optional(), + emailProtectionGetIsLoggedIn: z.record(z.unknown()).and(z.object({ + id: z.literal("emailProtectionGetIsLoggedInResponse").optional(), + resultValidator: emailProtectionGetIsLoggedInResultSchema.optional() + })).optional(), + emailProtectionGetUserData: z.record(z.unknown()).and(z.object({ + id: z.literal("emailProtectionGetUserDataResponse").optional(), + resultValidator: emailProtectionGetUserDataResultSchema.optional() + })).optional(), + emailProtectionGetCapabilities: z.record(z.unknown()).and(z.object({ + id: z.literal("emailProtectionGetCapabilitiesResponse").optional(), + resultValidator: emailProtectionGetCapabilitiesResultSchema.optional() + })).optional(), + emailProtectionGetAddresses: z.record(z.unknown()).and(z.object({ + id: z.literal("emailProtectionGetAddressesResponse").optional(), + resultValidator: emailProtectionGetAddressesResultSchema.optional() + })).optional(), + emailProtectionRefreshPrivateAddress: z.record(z.unknown()).and(z.object({ + id: z.literal("emailProtectionRefreshPrivateAddressResponse").optional(), + resultValidator: emailProtectionRefreshPrivateAddressResultSchema.optional() + })).optional(), + startEmailProtectionSignup: z.record(z.unknown()).optional(), + closeEmailProtectionTab: z.record(z.unknown()).optional(), + ShowInContextEmailProtectionSignupPrompt: z.record(z.unknown()).and(z.object({ + id: z.literal("ShowInContextEmailProtectionSignupPromptResponse").optional(), + resultValidator: showInContextEmailProtectionSignupPromptSchema.optional() + })).optional() + }); -const EMAIL_PROTECTION_LOGOUT_MESSAGE = 'EMAIL_PROTECTION_LOGOUT'; -class WindowsInterface extends _InterfacePrototype.default { - ready = false; - /** @type {AbortController|null} */ - _abortController = null; - async setupAutofill() { - const loggedIn = await this._getIsLoggedIn(); - if (loggedIn) { - await this.getAddresses(); + // packages/device-api/lib/device-api-call.js + var DeviceApiCall = class { + /** + * @param {import("zod").infer} data + */ + constructor(data) { + /** @type {string} */ + __publicField(this, "method", "unknown"); + /** + * An optional 'id' - used to indicate if a request requires a response. + * @type {string|null} + */ + __publicField(this, "id", null); + /** @type {Params | null | undefined} */ + __publicField(this, "paramsValidator", null); + /** @type {Result | null | undefined} */ + __publicField(this, "resultValidator", null); + /** @type {import("zod").infer} */ + __publicField(this, "params"); + /** + * This is a carve-out for legacy messages that are not typed yet. + * If you set this to 'true', then the response will not be checked to conform + * to any shape + * @deprecated this is here to aid migration, should be removed ASAP + * @type {boolean} + */ + __publicField(this, "throwOnResultKeysMissing", true); + /** + * New messages should be in a particular format, eg: { success: T }, + * but you can set this to false if you want to access the result as-is, + * without any unwrapping logic + * @deprecated this is here to aid migration, should be removed ASAP + * @type {boolean} + */ + __publicField(this, "unwrapResult", true); + this.params = data; + } + /** + * @returns {import("zod").infer|undefined} + */ + validateParams() { + if (this.params === void 0) { + return void 0; + } + this._validate(this.params, this.paramsValidator); + return this.params; + } + /** + * @param {any|null} incoming + * @returns {import("zod").infer} + */ + validateResult(incoming) { + this._validate(incoming, this.resultValidator); + if (!incoming) { + return incoming; + } + if (!this.unwrapResult) { + return incoming; + } + if ("data" in incoming) { + console.warn("response had `data` property. Please migrate to `success`"); + return incoming.data; + } + if ("success" in incoming) { + return incoming.success; + } + if ("error" in incoming) { + if (typeof incoming.error.message === "string") { + throw new DeviceApiCallError(`${this.method}: ${incoming.error.message}`); + } + } + if (this.throwOnResultKeysMissing) { + throw new Error("unreachable. Response did not contain `success` or `data`"); + } + return incoming; } - } - postInit() { - super.postInit(); - this.ready = true; - } - createUIController() { /** - * If we get here, we're just a controller for an overlay + * @param {any} data + * @param {import("zod").ZodType|undefined|null} [validator] + * @private */ - return new _OverlayUIController.OverlayUIController({ - remove: async () => this._closeAutofillParent(), - show: async details => this._show(details) - }); - } - - /** - * @param {GetAutofillDataRequest} details - */ - async _show(details) { - const { - mainType - } = details; - // prevent overlapping listeners - if (this._abortController && !this._abortController.signal.aborted) { - this._abortController.abort(); - } - this._abortController = new AbortController(); - this.deviceApi.request(new _deviceApiCalls.GetAutofillDataCall(details), { - signal: this._abortController.signal - }).then(resp => { - if (!this.activeForm) { - throw new Error('this.currentAttached was absent'); - } - switch (resp.action) { - case 'fill': - { - if (mainType in resp) { - this.activeForm?.autofillData(resp[mainType], mainType); - } else { - throw new Error(`action: "fill" cannot occur because "${mainType}" was missing`); - } - break; + _validate(data, validator) { + if (!validator) + return data; + if (validator) { + const result = validator?.safeParse(data); + if (!result) { + throw new Error("unreachable, data failure", data); + } + if (!result.success) { + if ("error" in result) { + this.throwError(result.error.issues); + } else { + console.error("unknown error from validate"); } - case 'focus': - { - this.activeForm?.activeInput?.focus(); + } + } + } + /** + * @param {import('zod').ZodIssue[]} errors + */ + throwError(errors) { + const error = SchemaValidationError.fromZodErrors(errors, this.constructor.name); + throw error; + } + /** + * Use this helper for creating stand-in response messages that are typed correctly. + * + * @examples + * + * ```js + * const msg = new Message(); + * const response = msg.response({}) // <-- This argument will be typed correctly + * ``` + * + * @param {import("zod").infer} response + * @returns {import("zod").infer} + */ + result(response) { + return response; + } + /** + * @returns {import("zod").infer} + */ + preResultValidation(response) { + return response; + } + }; + var DeviceApiCallError = class extends Error { + }; + var SchemaValidationError = class extends Error { + constructor() { + super(...arguments); + /** @type {import("zod").ZodIssue[]} */ + __publicField(this, "validationErrors", []); + } + /** + * @param {import("zod").ZodIssue[]} errors + * @param {string} name + * @returns {SchemaValidationError} + */ + static fromZodErrors(errors, name) { + const heading = `${errors.length} SchemaValidationError(s) errors for ` + name; + function log(issue) { + switch (issue.code) { + case "invalid_literal": + case "invalid_type": { + console.log(`${name}. Path: '${issue.path.join(".")}', Error: '${issue.message}'`); break; } - case 'none': - { - // do nothing + case "invalid_union": { + for (let unionError of issue.unionErrors) { + for (let issue1 of unionError.issues) { + log(issue1); + } + } break; } - default: - { - if (this.globalConfig.isDDGTestMode) { - console.warn('unhandled response', resp); - } + default: { + console.log(name, "other issue:", issue); } - } - return this._closeAutofillParent(); - }).catch(e => { - if (this.globalConfig.isDDGTestMode) { - if (e.name === 'AbortError') { - console.log('Promise Aborted'); - } else { - console.error('Promise Rejected', e); } } - }); - } - - /** - * @returns {Promise} - */ - async _closeAutofillParent() { - return this.deviceApi.notify(new _deviceApiCalls.CloseAutofillParentCall(null)); - } - - /** - * Email Protection calls - */ - - /** - * @returns {Promise} - */ - getEmailProtectionCapabilities() { - return this.deviceApi.request(new _deviceApiCalls.EmailProtectionGetCapabilitiesCall({})); - } - async _getIsLoggedIn() { - const isLoggedIn = await this.deviceApi.request(new _deviceApiCalls.EmailProtectionGetIsLoggedInCall({})); - this.isDeviceSignedIn = () => isLoggedIn; - return isLoggedIn; - } - addLogoutListener(handler) { - // Only deal with logging out if we're in the email web app - if (!this.globalConfig.isDDGDomain) return; - windowsInteropAddEventListener('message', e => { - if (this.globalConfig.isDDGDomain && e.data === EMAIL_PROTECTION_LOGOUT_MESSAGE) { - handler(); + for (let error2 of errors) { + log(error2); } - }); - } - - /** - * @returns {Promise} - */ - storeUserData(_ref) { - let { - addUserData - } = _ref; - return this.deviceApi.request(new _deviceApiCalls.EmailProtectionStoreUserDataCall(addUserData)); - } - /** - * @returns {Promise} - */ - removeUserData() { - return this.deviceApi.request(new _deviceApiCalls.EmailProtectionRemoveUserDataCall({})); - } - /** - * @returns {Promise} - */ - getUserData() { - return this.deviceApi.request(new _deviceApiCalls.EmailProtectionGetUserDataCall({})); - } - async refreshAlias() { - const addresses = await this.deviceApi.request(new _deviceApiCalls.EmailProtectionRefreshPrivateAddressCall({})); - this.storeLocalAddresses(addresses); - } - async getAddresses() { - const addresses = await this.deviceApi.request(new _deviceApiCalls.EmailProtectionGetAddressesCall({})); - this.storeLocalAddresses(addresses); - return addresses; + const message = [heading, "please see the details above"].join("\n "); + const error = new SchemaValidationError(message); + error.validationErrors = errors; + return error; + } + }; + function createDeviceApiCall(method, params, paramsValidator = null, resultValidator = null) { + const deviceApiCall = new DeviceApiCall(params); + deviceApiCall.paramsValidator = paramsValidator; + deviceApiCall.resultValidator = resultValidator; + deviceApiCall.method = method; + deviceApiCall.throwOnResultKeysMissing = false; + deviceApiCall.unwrapResult = false; + return deviceApiCall; + } + function createRequest(method, params, id = "n/a", paramsValidator = null, resultValidator = null) { + const call = createDeviceApiCall(method, params, paramsValidator, resultValidator); + call.id = id; + return call; + } + var createNotification = createDeviceApiCall; + function validate(data, validator = null) { + if (validator) { + return validator.parse(data); + } + return data; } -} -exports.WindowsInterface = WindowsInterface; - -},{"../UI/controllers/OverlayUIController.js":58,"../deviceApiCalls/__generated__/deviceApiCalls.js":66,"./InterfacePrototype.js":27}],29:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.WindowsOverlayDeviceInterface = void 0; -var _InterfacePrototype = _interopRequireDefault(require("./InterfacePrototype.js")); -var _HTMLTooltipUIController = require("../UI/controllers/HTMLTooltipUIController.js"); -var _deviceApiCalls = require("../deviceApiCalls/__generated__/deviceApiCalls.js"); -var _overlayApi = require("./overlayApi.js"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -/** - * This subclass is designed to separate code that *only* runs inside the - * Windows Overlay into a single place. - * - * It has some subtle differences to the macOS version, which is why - * this is another DeviceInterface - */ -class WindowsOverlayDeviceInterface extends _InterfacePrototype.default { - /** - * Mark top frame as not stripping credential data - * @type {boolean} - */ - stripCredentials = false; + // packages/device-api/lib/device-api.js + var DeviceApiTransport = class { + /** + * @param {import("./device-api-call.js").DeviceApiCall} _deviceApiCall + * @param {CallOptions} [_options] + * @returns {Promise} + */ + async send(_deviceApiCall, _options) { + return void 0; + } + }; + var DeviceApi = class { + /** @param {DeviceApiTransport} transport */ + constructor(transport) { + /** @type {DeviceApiTransport} */ + __publicField(this, "transport"); + this.transport = transport; + } + /** + * @template {import("./device-api-call").DeviceApiCall} D + * @param {D} deviceApiCall + * @param {CallOptions} [options] + * @returns {Promise['success']>>} + */ + async request(deviceApiCall, options) { + deviceApiCall.validateParams(); + let result = await this.transport.send(deviceApiCall, options); + let processed = deviceApiCall.preResultValidation(result); + return deviceApiCall.validateResult(processed); + } + /** + * @template {import("./device-api-call").DeviceApiCall} P + * @param {P} deviceApiCall + * @param {CallOptions} [options] + * @returns {Promise} + */ + async notify(deviceApiCall, options) { + deviceApiCall.validateParams(); + return this.transport.send(deviceApiCall, options); + } + }; - /** - * overlay API helpers - */ - overlay = (0, _overlayApi.overlayApi)(this); - previousScreenX = 0; - previousScreenY = 0; + // src/deviceApiCalls/__generated__/deviceApiCalls.js + var AddDebugFlagCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "addDebugFlag"); + __publicField(this, "paramsValidator", addDebugFlagParamsSchema); + } + }; + var GetAutofillDataCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "getAutofillData"); + __publicField(this, "id", "getAutofillDataResponse"); + __publicField(this, "paramsValidator", getAutofillDataRequestSchema); + __publicField(this, "resultValidator", getAutofillDataResponseSchema); + } + }; + var GetRuntimeConfigurationCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "getRuntimeConfiguration"); + __publicField(this, "id", "getRuntimeConfigurationResponse"); + __publicField(this, "resultValidator", getRuntimeConfigurationResponseSchema); + } + }; + var StoreFormDataCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "storeFormData"); + __publicField(this, "paramsValidator", storeFormDataSchema); + } + }; + var GetAvailableInputTypesCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "getAvailableInputTypes"); + __publicField(this, "id", "getAvailableInputTypesResponse"); + __publicField(this, "resultValidator", getAvailableInputTypesResultSchema); + } + }; + var GetAutofillInitDataCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "getAutofillInitData"); + __publicField(this, "id", "getAutofillInitDataResponse"); + __publicField(this, "resultValidator", getAutofillInitDataResponseSchema); + } + }; + var GetAutofillCredentialsCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "getAutofillCredentials"); + __publicField(this, "id", "getAutofillCredentialsResponse"); + __publicField(this, "paramsValidator", getAutofillCredentialsParamsSchema); + __publicField(this, "resultValidator", getAutofillCredentialsResultSchema); + } + }; + var SetSizeCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "setSize"); + __publicField(this, "paramsValidator", setSizeParamsSchema); + } + }; + var SelectedDetailCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "selectedDetail"); + __publicField(this, "paramsValidator", selectedDetailParamsSchema); + } + }; + var CloseAutofillParentCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "closeAutofillParent"); + } + }; + var AskToUnlockProviderCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "askToUnlockProvider"); + __publicField(this, "id", "askToUnlockProviderResponse"); + __publicField(this, "resultValidator", askToUnlockProviderResultSchema); + } + }; + var CheckCredentialsProviderStatusCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "checkCredentialsProviderStatus"); + __publicField(this, "id", "checkCredentialsProviderStatusResponse"); + __publicField(this, "resultValidator", checkCredentialsProviderStatusResultSchema); + } + }; + var SendJSPixelCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "sendJSPixel"); + __publicField(this, "paramsValidator", sendJSPixelParamsSchema); + } + }; + var SetIncontextSignupPermanentlyDismissedAtCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "setIncontextSignupPermanentlyDismissedAt"); + __publicField(this, "paramsValidator", setIncontextSignupPermanentlyDismissedAtSchema); + } + }; + var GetIncontextSignupDismissedAtCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "getIncontextSignupDismissedAt"); + __publicField(this, "id", "getIncontextSignupDismissedAt"); + __publicField(this, "resultValidator", getIncontextSignupDismissedAtSchema); + } + }; + var OpenManagePasswordsCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "openManagePasswords"); + } + }; + var OpenManageCreditCardsCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "openManageCreditCards"); + } + }; + var OpenManageIdentitiesCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "openManageIdentities"); + } + }; + var EmailProtectionStoreUserDataCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "emailProtectionStoreUserData"); + __publicField(this, "id", "emailProtectionStoreUserDataResponse"); + __publicField(this, "paramsValidator", emailProtectionStoreUserDataParamsSchema); + } + }; + var EmailProtectionRemoveUserDataCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "emailProtectionRemoveUserData"); + } + }; + var EmailProtectionGetIsLoggedInCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "emailProtectionGetIsLoggedIn"); + __publicField(this, "id", "emailProtectionGetIsLoggedInResponse"); + __publicField(this, "resultValidator", emailProtectionGetIsLoggedInResultSchema); + } + }; + var EmailProtectionGetUserDataCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "emailProtectionGetUserData"); + __publicField(this, "id", "emailProtectionGetUserDataResponse"); + __publicField(this, "resultValidator", emailProtectionGetUserDataResultSchema); + } + }; + var EmailProtectionGetCapabilitiesCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "emailProtectionGetCapabilities"); + __publicField(this, "id", "emailProtectionGetCapabilitiesResponse"); + __publicField(this, "resultValidator", emailProtectionGetCapabilitiesResultSchema); + } + }; + var EmailProtectionGetAddressesCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "emailProtectionGetAddresses"); + __publicField(this, "id", "emailProtectionGetAddressesResponse"); + __publicField(this, "resultValidator", emailProtectionGetAddressesResultSchema); + } + }; + var EmailProtectionRefreshPrivateAddressCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "emailProtectionRefreshPrivateAddress"); + __publicField(this, "id", "emailProtectionRefreshPrivateAddressResponse"); + __publicField(this, "resultValidator", emailProtectionRefreshPrivateAddressResultSchema); + } + }; + var StartEmailProtectionSignupCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "startEmailProtectionSignup"); + } + }; + var CloseEmailProtectionTabCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "closeEmailProtectionTab"); + } + }; + var ShowInContextEmailProtectionSignupPromptCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "ShowInContextEmailProtectionSignupPrompt"); + __publicField(this, "id", "ShowInContextEmailProtectionSignupPromptResponse"); + __publicField(this, "resultValidator", showInContextEmailProtectionSignupPromptSchema); + } + }; - /** - * Because we're running inside the Overlay, we always create the HTML - * Tooltip controller. - * - * @override - * @returns {import("../UI/controllers/UIController.js").UIController} - */ - createUIController() { - return new _HTMLTooltipUIController.HTMLTooltipUIController({ - tooltipKind: /** @type {const} */'modern', - device: this - }, { - wrapperClass: 'top-autofill', - tooltipPositionClass: () => '.wrapper { transform: none; }', - setSize: details => this.deviceApi.notify(new _deviceApiCalls.SetSizeCall(details)), - remove: async () => this._closeAutofillParent(), - testMode: this.isTestMode(), + // src/Scanner.js + var { + MAX_INPUTS_PER_PAGE, + MAX_FORMS_PER_PAGE, + MAX_INPUTS_PER_FORM: MAX_INPUTS_PER_FORM2, + ATTR_INPUT_TYPE: ATTR_INPUT_TYPE3 + } = constants; + var defaultScannerOptions = { + // This buffer size is very large because it's an unexpected edge-case that + // a DOM will be continually modified over and over without ever stopping. If we do see 1000 unique + // new elements in the buffer however then this will prevent the algorithm from never ending. + bufferSize: 50, + // wait for a 500ms window of event silence before performing the scan + debounceTimePeriod: 500, + // how long to wait when performing the initial scan + initialDelay: 0, + // How many inputs is too many on the page. If we detect that there's above + // this maximum, then we don't scan the page. This will prevent slowdowns on + // large pages which are unlikely to require autofill anyway. + maxInputsPerPage: MAX_INPUTS_PER_PAGE, + maxFormsPerPage: MAX_FORMS_PER_PAGE, + maxInputsPerForm: MAX_INPUTS_PER_FORM2 + }; + var DefaultScanner = class { + /** + * @param {import("./DeviceInterface/InterfacePrototype").default} device + * @param {ScannerOptions} options + */ + constructor(device, options) { + /** @type Map */ + __publicField(this, "forms", /* @__PURE__ */ new Map()); + /** @type {any|undefined} the timer to reset */ + __publicField(this, "debounceTimer"); + /** @type {Set} stored changed elements until they can be processed */ + __publicField(this, "changedElements", /* @__PURE__ */ new Set()); + /** @type {ScannerOptions} */ + __publicField(this, "options"); + /** @type {HTMLInputElement | null} */ + __publicField(this, "activeInput", null); + /** @type {boolean} A flag to indicate the whole page will be re-scanned */ + __publicField(this, "rescanAll", false); + /** @type {boolean} Indicates whether we called stopScanning */ + __publicField(this, "stopped", false); + /** @type {import("./Form/matching").Matching} matching */ + __publicField(this, "matching"); /** - * Note: This is needed because Mutation observer didn't support visibility checks on Windows + * Watch for changes in the DOM, and enqueue elements to be scanned + * @type {MutationObserver} */ - checkVisibility: false - }); - } - addDeviceListeners() { + __publicField(this, "mutObs", new MutationObserver((mutationList) => { + if (this.rescanAll) { + this.enqueue([]); + return; + } + const outgoing = []; + for (const mutationRecord of mutationList) { + if (mutationRecord.type === "childList") { + for (let addedNode of mutationRecord.addedNodes) { + if (!(addedNode instanceof HTMLElement)) + continue; + if (addedNode.nodeName === "DDG-AUTOFILL") + continue; + outgoing.push(addedNode); + } + } + } + this.enqueue(outgoing); + })); + this.device = device; + this.matching = createMatching(); + this.options = options; + this.initTimeStamp = Date.now(); + } + /** + * Determine whether we should fire the credentials autoprompt. This is needed because some sites are blank + * on page load and load scripts asynchronously, so our initial scan didn't set the autoprompt correctly + * @returns {boolean} + */ + get shouldAutoprompt() { + return Date.now() - this.initTimeStamp <= 1500; + } /** - * On Windows (vs. MacOS) we can use the built-in `mousemove` - * event and screen-relative positioning. + * Call this to scan once and then watch for changes. * - * Note: There's no cleanup required here since the Overlay has a fresh - * page load every time it's opened. + * Call the returned function to remove listeners. + * @returns {(reason: string, ...rest) => void} */ - window.addEventListener('mousemove', event => { - // Don't set focus if the mouse hasn't moved ever - // This is to avoid clickjacking where an attacker puts the pulldown under the cursor - // and tricks the user into clicking - if (!this.previousScreenX && !this.previousScreenY || - // if no previous coords - this.previousScreenX === event.screenX && this.previousScreenY === event.screenY // or the mouse hasn't moved - ) { - this.previousScreenX = event.screenX; - this.previousScreenY = event.screenY; - return; + init() { + if (this.device.globalConfig.isExtension) { + this.device.deviceApi.notify(new AddDebugFlagCall({ flag: "autofill" })); } - const activeTooltip = this.uiController?.getActiveTooltip?.(); - activeTooltip?.focus(event.x, event.y); - this.previousScreenX = event.screenX; - this.previousScreenY = event.screenY; - }); - return super.addDeviceListeners(); - } - - /** - * @returns {Promise} - */ - async _closeAutofillParent() { - return this.deviceApi.notify(new _deviceApiCalls.CloseAutofillParentCall(null)); - } - - /** - * @returns {Promise} - */ - openManagePasswords() { - return this.deviceApi.notify(new _deviceApiCalls.OpenManagePasswordsCall({})); - } - /** - * @returns {Promise} - */ - openManageCreditCards() { - return this.deviceApi.notify(new _deviceApiCalls.OpenManageCreditCardsCall({})); - } - /** - * @returns {Promise} - */ - openManageIdentities() { - return this.deviceApi.notify(new _deviceApiCalls.OpenManageIdentitiesCall({})); - } - - /** - * Since we're running inside the Overlay we can limit what happens here to - * be only things that are needed to power the HTML Tooltip - * - * @override - * @returns {Promise} - */ - async setupAutofill() { - const loggedIn = await this._getIsLoggedIn(); - if (loggedIn) { - await this.getAddresses(); - } - const response = await this.deviceApi.request(new _deviceApiCalls.GetAutofillInitDataCall(null)); - // @ts-ignore - this.storeLocalData(response); - } - async postInit() { - // setup overlay API pieces - this.overlay.showImmediately(); - super.postInit(); - } - - /** - * In the top-frame scenario, we send a message to the native - * side to indicate a selection. Once received, the native side will store that selection so that a - * subsequence call from main webpage can retrieve it - * - * @override - * @param {IdentityObject|CreditCardObject|CredentialsObject|{email:string, id: string}} data - * @param {string} type - */ - async selectedDetail(data, type) { - return this.overlay.selectedDetail(data, type); - } - - /** - * Email Protection calls - */ - - async _getIsLoggedIn() { - const isLoggedIn = await this.deviceApi.request(new _deviceApiCalls.EmailProtectionGetIsLoggedInCall({})); - this.isDeviceSignedIn = () => isLoggedIn; - return isLoggedIn; - } - async getAddresses() { - const addresses = await this.deviceApi.request(new _deviceApiCalls.EmailProtectionGetAddressesCall({})); - this.storeLocalAddresses(addresses); - return addresses; - } - - /** - * Gets a single identity obj once the user requests it - * @param {Number} id - * @returns {Promise<{success: IdentityObject|undefined}>} - */ - getAutofillIdentity(id) { - const identity = this.getLocalIdentities().find(_ref => { - let { - id: identityId - } = _ref; - return `${identityId}` === `${id}`; - }); - return Promise.resolve({ - success: identity - }); - } -} -exports.WindowsOverlayDeviceInterface = WindowsOverlayDeviceInterface; - -},{"../UI/controllers/HTMLTooltipUIController.js":56,"../deviceApiCalls/__generated__/deviceApiCalls.js":66,"./InterfacePrototype.js":27,"./overlayApi.js":31}],30:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.initFormSubmissionsApi = initFormSubmissionsApi; -var _autofillUtils = require("../autofill-utils.js"); -var _labelUtil = require("../Form/label-util.js"); -/** - * This is a single place to contain all functionality relating to form submission detection - * - * @param {Map} forms - * @param {import("../Form/matching").Matching} matching - */ -function initFormSubmissionsApi(forms, matching) { - /** - * Global submit events - */ - window.addEventListener('submit', e => { - // @ts-ignore - return forms.get(e.target)?.submitHandler('global submit event'); - }, true); - - /** - * Global keydown events - */ - window.addEventListener('keydown', e => { - if (e.key === 'Enter') { - const focusedForm = [...forms.values()].find(form => form.hasFocus(e)); - focusedForm?.submitHandler('global keydown + Enter'); - } - }, true); - - /** - * Global pointer down events - * @param {PointerEvent} event - */ - window.addEventListener('pointerdown', event => { - const realTarget = (0, _autofillUtils.pierceShadowTree)(event); - const formsArray = [...forms.values()]; - const matchingForm = formsArray.find(form => { - const btns = [...form.submitButtons]; - // @ts-ignore - if (btns.includes(realTarget)) return true; - - // @ts-ignore - if (btns.find(btn => btn.contains(realTarget))) return true; - }); - matchingForm?.submitHandler('global pointerdown event + matching form'); - if (!matchingForm) { - const selector = matching.cssSelector('submitButtonSelector') + ', a[href="#"], a[href^=javascript], *[onclick], [class*=button i]'; - // check if the click happened on a button - const button = /** @type HTMLElement */realTarget?.closest(selector); - if (!button) return; - - // If the element we've found includes a form it can't be a button, it's a false match - const buttonIsAFalsePositive = formsArray.some(form => button?.contains(form.form)); - if (buttonIsAFalsePositive) return; - const text = (0, _autofillUtils.getTextShallow)(button) || (0, _labelUtil.extractElementStrings)(button).join(' '); - const hasRelevantText = (0, _autofillUtils.safeRegexTest)(matching.getDDGMatcherRegex('submitButtonRegex'), text); - if (hasRelevantText && text.length < 25) { - // check if there's a form with values - const filledForm = formsArray.find(form => form.hasValues()); - if (filledForm && (0, _autofillUtils.buttonMatchesFormType)( /** @type HTMLElement */button, filledForm)) { - filledForm?.submitHandler('global pointerdown event + filled form'); - } + window.addEventListener("pointerdown", this, true); + if (!this.device.globalConfig.isMobileApp) { + window.addEventListener("focus", this, true); } - - // TODO: Temporary hack to support Google signin in different languages - // https://app.asana.com/0/1198964220583541/1201650539303898/f - if ( /** @type HTMLElement */realTarget?.closest('#passwordNext button, #identifierNext button')) { - // check if there's a form with values - const filledForm = formsArray.find(form => form.hasValues()); - filledForm?.submitHandler('global pointerdown event + google escape hatch'); + const delay = this.options.initialDelay; + if (delay === 0) { + window.requestIdleCallback(() => this.scanAndObserve()); + } else { + setTimeout(() => this.scanAndObserve(), delay); } + return (reason, ...rest) => { + this.stopScanner(reason, ...rest); + }; } - }, true); - - /** - * @type {PerformanceObserver} - */ - const observer = new PerformanceObserver(list => { - const formsArray = [...forms.values()]; - const entries = list.getEntries().filter(entry => - // @ts-ignore why does TS not know about `entry.initiatorType`? - ['fetch', 'xmlhttprequest'].includes(entry.initiatorType) && (0, _autofillUtils.safeRegexTest)(/login|sign-in|signin/, entry.name)); - if (!entries.length) return; - const filledForm = formsArray.find(form => form.hasValues()); - const focusedForm = formsArray.find(form => form.hasFocus()); - // If a form is still focused the user is still typing: do nothing - if (focusedForm) return; - filledForm?.submitHandler('performance observer'); - }); - observer.observe({ - entryTypes: ['resource'] - }); -} - -},{"../Form/label-util.js":39,"../autofill-utils.js":62}],31:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.overlayApi = overlayApi; -var _deviceApiCalls = require("../deviceApiCalls/__generated__/deviceApiCalls.js"); -/** - * These are some re-usable parts for handling 'overlays' (like on macOS + Windows) - * - * @param {import("./InterfacePrototype").default} device - */ -function overlayApi(device) { - return { /** - * When we are inside an 'overlay' - the HTML tooltip will be opened immediately + * Scan the page and begin observing changes */ - showImmediately() { - const topContextData = device.getTopContextData(); - if (!topContextData) throw new Error('unreachable, topContextData should be available'); - - // Provide dummy values - const getPosition = () => { - return { - x: 0, - y: 0, - height: 50, - width: 50 - }; - }; - - // Create the tooltip, and set it as active - const tooltip = device.uiController?.createTooltip?.(getPosition, topContextData); - if (tooltip) { - device.uiController?.setActiveTooltip?.(tooltip); + scanAndObserve() { + window.performance?.mark?.("initial_scanner:init:start"); + this.findEligibleInputs(document); + window.performance?.mark?.("initial_scanner:init:end"); + logPerformance("initial_scanner"); + this.mutObs.observe(document.documentElement, { childList: true, subtree: true }); + } + /** + * @param context + */ + findEligibleInputs(context) { + if (this.device.globalConfig.isDDGDomain) { + return this; + } + if ("matches" in context && context.matches?.(this.matching.cssSelector("formInputsSelector"))) { + this.addInput(context); + } else { + const inputs = context.querySelectorAll(this.matching.cssSelector("formInputsSelector")); + if (inputs.length > this.options.maxInputsPerPage) { + this.stopScanner(`Too many input fields in the given context (${inputs.length}), stop scanning`, context); + return this; + } + inputs.forEach((input) => this.addInput(input)); } - }, + return this; + } /** - * @param {IdentityObject|CreditCardObject|CredentialsObject|{email:string, id: string}} data - * @param {string} type - * @returns {Promise} + * Stops scanning, switches off the mutation observer and clears all forms + * @param {string} reason + * @param {any} rest */ - async selectedDetail(data, type) { - let detailsEntries = Object.entries(data).map(_ref => { - let [key, value] = _ref; - return [key, String(value)]; + stopScanner(reason, ...rest) { + this.stopped = true; + if (shouldLog()) { + console.log(reason, ...rest); + } + const activeInput = this.device.activeForm?.activeInput; + clearTimeout(this.debounceTimer); + this.changedElements.clear(); + this.mutObs.disconnect(); + window.removeEventListener("pointerdown", this, true); + window.removeEventListener("focus", this, true); + this.forms.forEach((form) => { + form.destroy(); }); - const entries = Object.fromEntries(detailsEntries); - /** @link {import("../deviceApiCalls/schemas/getAutofillData.result.json")} */ - await device.deviceApi.notify(new _deviceApiCalls.SelectedDetailCall({ - data: entries, - configType: type - })); + this.forms.clear(); + activeInput?.focus(); } - }; -} - -},{"../deviceApiCalls/__generated__/deviceApiCalls.js":66}],32:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.EmailProtection = void 0; -/** - * Use this as place to store any state or functionality related to Email Protection - */ -class EmailProtection { - /** @type {string|null} */ - #previous = null; - - /** @param {import("./DeviceInterface/InterfacePrototype").default} device */ - constructor(device) { - this.device = device; - } - - /** @returns {string|null} */ - get lastGenerated() { - return this.#previous; - } - - /** - * Store the last received email address - * @param {string} emailAddress - */ - storeReceived(emailAddress) { - this.#previous = emailAddress; - return emailAddress; - } -} -exports.EmailProtection = EmailProtection; - -},{}],33:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.Form = void 0; -var _FormAnalyzer = _interopRequireDefault(require("./FormAnalyzer.js")); -var _autofillUtils = require("../autofill-utils.js"); -var _matching = require("./matching.js"); -var _inputStyles = require("./inputStyles.js"); -var _inputTypeConfig = require("./inputTypeConfig.js"); -var _formatters = require("./formatters.js"); -var _constants = require("../constants.js"); -var _Credentials = require("../InputTypes/Credentials.js"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const { - ATTR_AUTOFILL, - ATTR_INPUT_TYPE, - MAX_INPUTS_PER_FORM, - MAX_FORM_RESCANS -} = _constants.constants; -class Form { - /** @type {import("../Form/matching").Matching} */ - matching; - /** @type {HTMLElement} */ - form; - /** @type {HTMLInputElement | null} */ - activeInput; - /** - * @param {HTMLElement} form - * @param {HTMLInputElement|HTMLSelectElement} input - * @param {import("../DeviceInterface/InterfacePrototype").default} deviceInterface - * @param {import("../Form/matching").Matching} [matching] - * @param {Boolean} [shouldAutoprompt] - */ - constructor(form, input, deviceInterface, matching) { - let shouldAutoprompt = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; - this.form = form; - this.matching = matching || (0, _matching.createMatching)(); - this.formAnalyzer = new _FormAnalyzer.default(form, input, matching); - this.device = deviceInterface; - - /** @type Record<'all' | SupportedMainTypes, Set> */ - this.inputs = { - all: new Set(), - credentials: new Set(), - creditCards: new Set(), - identities: new Set(), - unknown: new Set() - }; - this.touched = new Set(); - this.listeners = new Set(); - this.activeInput = null; - // We set this to true to skip event listeners while we're autofilling - this.isAutofilling = false; - this.submitHandlerExecuted = false; - this.shouldPromptToStoreData = deviceInterface.settings.featureToggles.credentials_saving; - this.shouldAutoSubmit = this.device.globalConfig.isMobileApp; - /** - * @type {IntersectionObserver | null} + * @param {HTMLElement|HTMLInputElement|HTMLSelectElement} input + * @returns {HTMLFormElement|HTMLElement} */ - this.intObs = new IntersectionObserver(entries => { - for (const entry of entries) { - if (!entry.isIntersecting) this.removeTooltip(); + getParentForm(input) { + if (input instanceof HTMLInputElement || input instanceof HTMLSelectElement) { + if (input.form) { + if (this.forms.has(input.form) || // If we've added the form we've already checked that it's not a page wrapper + !isFormLikelyToBeUsedAsPageWrapper(input.form)) { + return input.form; + } + } } - }); - this.rescanCount = 0; - this.mutObsConfig = { - childList: true, - subtree: true - }; - this.mutObs = new MutationObserver(records => { - const anythingRemoved = records.some(record => record.removedNodes.length > 0); - if (anythingRemoved) { - // Ensure we destroy the form if it's removed from the DOM - if (!this.form.isConnected) { - this.destroy(); - return; + let traversalLayerCount = 0; + let element = input; + while (traversalLayerCount <= 5 && element.parentElement && element.parentElement !== document.documentElement) { + const siblingForm = element.parentElement?.querySelector("form"); + if (siblingForm && siblingForm !== element) { + return element; } - // Must check for inputs because a parent may be removed and not show up in record.removedNodes - if ([...this.inputs.all].some(input => !input.isConnected)) { - // This is re-connected in recategorizeAllInputs, disconnecting here to avoid risk of re-work - this.mutObs.disconnect(); - // If any known input has been removed from the DOM, reanalyze the whole form - window.requestIdleCallback(() => { - this.formAnalyzer = new _FormAnalyzer.default(this.form, input, this.matching); - this.recategorizeAllInputs(); - }); + element = element.parentElement; + const inputs = element.querySelectorAll(this.matching.cssSelector("formInputsSelector")); + const buttons = element.querySelectorAll(this.matching.cssSelector("submitButtonSelector")); + if (inputs.length > 1 || buttons.length) { + return element; } + traversalLayerCount++; } - }); - this.initFormListeners(); - this.categorizeInputs(); - this.logFormInfo(); - if (shouldAutoprompt) { - this.promptLoginIfNeeded(); + return input; } - } - get isLogin() { - return this.formAnalyzer.isLogin; - } - get isSignup() { - return this.formAnalyzer.isSignup; - } - get isHybrid() { - return this.formAnalyzer.isHybrid; - } - get isCCForm() { - return this.formAnalyzer.isCCForm(); - } - logFormInfo() { - if (!(0, _autofillUtils.shouldLog)()) return; - console.log(`Form type: %c${this.getFormType()}`, 'font-weight: bold'); - console.log('Signals: ', this.formAnalyzer.signals); - console.log('Wrapping element: ', this.form); - console.log('Inputs: ', this.inputs); - console.log('Submit Buttons: ', this.submitButtons); - } - getFormType() { - if (this.isHybrid) return `hybrid (hybrid score: ${this.formAnalyzer.hybridSignal}, score: ${this.formAnalyzer.autofillSignal})`; - if (this.isLogin) return `login (score: ${this.formAnalyzer.autofillSignal}, hybrid score: ${this.formAnalyzer.hybridSignal})`; - if (this.isSignup) return `signup (score: ${this.formAnalyzer.autofillSignal}, hybrid score: ${this.formAnalyzer.hybridSignal})`; - return 'something went wrong'; - } - - /** - * Checks if the form element contains the activeElement or the event target - * @return {boolean} - * @param {KeyboardEvent | null} [e] - */ - hasFocus(e) { - return this.form.contains((0, _autofillUtils.getActiveElement)()) || this.form.contains( /** @type HTMLElement */e?.target); - } - submitHandler() { - let via = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'unknown'; - if (this.device.globalConfig.isDDGTestMode) { - console.log('Form.submitHandler via:', via, this); - } - if (this.submitHandlerExecuted) return; - const values = this.getValuesReadyForStorage(); - this.device.postSubmit?.(values, this); - - // mark this form as being handled - this.submitHandlerExecuted = true; - } - - /** - * Reads the values from the form without preparing to store them - * @return {InternalDataStorageObject} - */ - getRawValues() { - const formValues = [...this.inputs.credentials, ...this.inputs.identities, ...this.inputs.creditCards].reduce((output, inputEl) => { - const mainType = (0, _matching.getInputMainType)(inputEl); - const subtype = (0, _matching.getInputSubtype)(inputEl); - let value = inputEl.value || output[mainType]?.[subtype]; - if (subtype === 'addressCountryCode') { - value = (0, _formatters.inferCountryCodeFromElement)(inputEl); - } - // Discard passwords that are shorter than 4 characters - if (subtype === 'password' && value?.length <= 3) { - value = undefined; + /** + * @param {HTMLInputElement|HTMLSelectElement} input + */ + addInput(input) { + if (this.stopped) + return; + const parentForm = this.getParentForm(input); + if (parentForm instanceof HTMLFormElement && this.forms.has(parentForm)) { + const foundForm = this.forms.get(parentForm); + if (foundForm && foundForm.inputs.all.size < MAX_INPUTS_PER_FORM2) { + foundForm.addInput(input); + } else { + this.stopScanner("The form has too many inputs, destroying."); + } + return; } - if (value) { - output[mainType][subtype] = value; + if (parentForm.role === "search") + return; + let previouslyFoundParent, childForm; + for (const [formEl] of this.forms) { + if (!formEl.isConnected) { + this.forms.delete(formEl); + continue; + } + if (formEl.contains(parentForm)) { + previouslyFoundParent = formEl; + break; + } + if (parentForm.contains(formEl)) { + childForm = formEl; + break; + } } - return output; - }, { - credentials: {}, - creditCards: {}, - identities: {} - }); - if (!formValues.credentials.username && !formValues.identities.emailAddress) { - // If we could find no username, let's search further - const hiddenFields = /** @type [HTMLInputElement] */[...this.form.querySelectorAll('input[type=hidden]')]; - const probableField = hiddenFields.find(field => { - const regex = new RegExp('email|' + this.matching.getDDGMatcherRegex('username')?.source); - const attributeText = field.id + ' ' + field.name; - return (0, _autofillUtils.safeRegexTest)(regex, attributeText); - }); - if (probableField?.value) { - formValues.credentials.username = probableField.value; - } else if ( - // If a form has phone + password(s) fields, save the phone as username - formValues.identities.phone && this.inputs.all.size - this.inputs.unknown.size < 4) { - formValues.credentials.username = formValues.identities.phone; + if (previouslyFoundParent) { + if (parentForm instanceof HTMLFormElement && parentForm !== previouslyFoundParent) { + this.forms.delete(previouslyFoundParent); + } else { + this.forms.get(previouslyFoundParent)?.addInput(input); + } } else { - // If we still don't have a username, try scanning the form's text for an email address - this.form.querySelectorAll(this.matching.cssSelector('safeUniversalSelector')).forEach(el => { - const elText = (0, _autofillUtils.getTextShallow)(el); - // Ignore long texts to avoid false positives - if (elText.length > 70) return; - const emailOrUsername = elText.match( - // https://www.emailregex.com/ - /[a-zA-Z\d.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z\d-]+(?:\.[a-zA-Z\d-]+)*/)?.[0]; - if (emailOrUsername) { - formValues.credentials.username = emailOrUsername; - } - }); + if (childForm) { + this.forms.get(childForm)?.destroy(); + this.forms.delete(childForm); + } + if (this.forms.size < this.options.maxFormsPerPage) { + this.forms.set(parentForm, new Form(parentForm, input, this.device, this.matching, this.shouldAutoprompt)); + } else { + this.stopScanner("The page has too many forms, stop adding them."); + } } } - return formValues; - } - - /** - * Return form values ready for storage - * @returns {DataStorageObject} - */ - getValuesReadyForStorage() { - const formValues = this.getRawValues(); - return (0, _formatters.prepareFormValuesForStorage)(formValues); - } - - /** - * Determine if the form has values we want to store in the device - * @param {DataStorageObject} [values] - * @return {boolean} - */ - hasValues(values) { - const { - credentials, - creditCards, - identities - } = values || this.getValuesReadyForStorage(); - return Boolean(credentials || creditCards || identities); - } - async removeTooltip() { - const tooltip = this.device.isTooltipActive(); - if (this.isAutofilling || !tooltip) { - return; - } - await this.device.removeTooltip(); - this.intObs?.disconnect(); - } - showingTooltip(input) { - this.intObs?.observe(input); - } - removeInputHighlight(input) { - if (!input.classList.contains('ddg-autofilled')) return; - (0, _autofillUtils.removeInlineStyles)(input, (0, _inputStyles.getIconStylesAutofilled)(input, this)); - (0, _autofillUtils.removeInlineStyles)(input, { - 'cursor': 'pointer' - }); - input.classList.remove('ddg-autofilled'); - this.addAutofillStyles(input); - } - resetIconStylesToInitial() { - const input = this.activeInput; - if (input) { - const initialStyles = (0, _inputStyles.getIconStylesBase)(input, this); - (0, _autofillUtils.addInlineStyles)(input, initialStyles); + /** + * enqueue elements to be re-scanned after the given + * amount of time has elapsed. + * + * @param {(HTMLElement|Document)[]} htmlElements + */ + enqueue(htmlElements) { + if (this.changedElements.size >= this.options.bufferSize) { + this.rescanAll = true; + this.changedElements.clear(); + } else if (!this.rescanAll) { + for (let element of htmlElements) { + this.changedElements.add(element); + } + } + clearTimeout(this.debounceTimer); + this.debounceTimer = setTimeout(() => { + window.performance?.mark?.("scanner:init:start"); + this.processChangedElements(); + this.changedElements.clear(); + this.rescanAll = false; + window.performance?.mark?.("scanner:init:end"); + logPerformance("scanner"); + }, this.options.debounceTimePeriod); } - } - removeAllHighlights(e, dataType) { - // This ensures we are not removing the highlight ourselves when autofilling more than once - if (e && !e.isTrusted) return; - - // If the user has changed the value, reset shouldPromptToStoreData to initial value - this.resetShouldPromptToStoreData(); - this.execOnInputs(input => this.removeInputHighlight(input), dataType); - } - removeInputDecoration(input) { - (0, _autofillUtils.removeInlineStyles)(input, (0, _inputStyles.getIconStylesBase)(input, this)); - (0, _autofillUtils.removeInlineStyles)(input, (0, _inputStyles.getIconStylesAlternate)(input, this)); - input.removeAttribute(ATTR_AUTOFILL); - input.removeAttribute(ATTR_INPUT_TYPE); - } - removeAllDecorations() { - this.execOnInputs(input => this.removeInputDecoration(input)); - this.listeners.forEach(_ref => { - let { - el, - type, - fn, - opts - } = _ref; - return el.removeEventListener(type, fn, opts); - }); - } - redecorateAllInputs() { - this.removeAllDecorations(); - this.execOnInputs(input => { - if (input instanceof HTMLInputElement) { - this.decorateInput(input); + /** + * re-scan the changed elements, but only if they + * are still present in the DOM + */ + processChangedElements() { + if (this.rescanAll) { + this.findEligibleInputs(document); + return; } - }); - } - - /** - * Removes all scoring attributes from the inputs and deletes them from memory - */ - forgetAllInputs() { - this.execOnInputs(input => { - input.removeAttribute(ATTR_AUTOFILL); - input.removeAttribute(ATTR_INPUT_TYPE); - }); - Object.values(this.inputs).forEach(inputSet => inputSet.clear()); - } - - /** - * Resets our input scoring and starts from scratch - */ - recategorizeAllInputs() { - // If the form mutates too much, disconnect to avoid performance issues - if (this.rescanCount >= MAX_FORM_RESCANS) { - this.mutObs.disconnect(); - return; - } - this.rescanCount++; - this.initialScanComplete = false; - this.removeAllDecorations(); - this.forgetAllInputs(); - this.initFormListeners(); - this.categorizeInputs(); - } - resetAllInputs() { - this.execOnInputs(input => { - (0, _autofillUtils.setValue)(input, '', this.device.globalConfig); - this.removeInputHighlight(input); - }); - if (this.activeInput) this.activeInput.focus(); - this.matching.clear(); - } - resetShouldPromptToStoreData() { - this.shouldPromptToStoreData = this.device.settings.featureToggles.credentials_saving; - } - dismissTooltip() { - this.removeTooltip(); - } - // This removes all listeners to avoid memory leaks and weird behaviours - destroy() { - this.mutObs.disconnect(); - this.removeAllDecorations(); - this.removeTooltip(); - this.forgetAllInputs(); - this.matching.clear(); - this.intObs = null; - this.device.scanner.forms.delete(this.form); - } - initFormListeners() { - // This ensures we fire the handler again if the form is changed - this.addListener(this.form, 'input', () => { - if (!this.isAutofilling) { - this.submitHandlerExecuted = false; - this.resetShouldPromptToStoreData(); + for (let element of this.changedElements) { + if (element.isConnected) { + this.findEligibleInputs(element); + } } - }); - - // If it's a form within a shadow tree, attach the submit listener, because it doesn't bubble outside - if (this.form instanceof HTMLFormElement && this.form.getRootNode()) { - this.addListener(this.form, 'submit', () => { - this.submitHandler('in-form submit handler'); - }, { - capture: true - }); } - } - categorizeInputs() { - const selector = this.matching.cssSelector('formInputsSelector'); - // If there's no form container and it's just a lonely input field (this.form is an input field) - if (this.form.matches(selector)) { - this.addInput(this.form); - } else { - /** @type {Element[] | NodeList} */ - let foundInputs = []; - if (this.form instanceof HTMLFormElement) { - // For form elements we use .elements to catch fields outside the form itself using the form attribute. - // It also catches all elements when the markup is broken. - // We use .filter to avoid fieldset, button, textarea etc. - foundInputs = [...this.form.elements].filter(el => el.matches(selector)); - } else { - foundInputs = this.form.querySelectorAll(selector); + handleEvent(event) { + switch (event.type) { + case "pointerdown": + case "focus": + this.scanShadow(event); + break; } - if (foundInputs.length < MAX_INPUTS_PER_FORM) { - foundInputs.forEach(input => this.addInput(input)); - } else { - // This is rather extreme, but better safe than sorry - this.device.scanner.stopScanner(`The form has too many inputs (${foundInputs.length}), bailing.`); + } + /** + * Scan clicked input fields, even if they're within a shadow tree + * @param {FocusEvent | PointerEvent} event + */ + scanShadow(event) { + if (this.stopped || !(event.target instanceof Element) || !event.target?.shadowRoot) return; + window.performance?.mark?.("scan_shadow:init:start"); + const realTarget = pierceShadowTree(event, HTMLInputElement); + if (realTarget instanceof HTMLInputElement && !realTarget.hasAttribute(ATTR_INPUT_TYPE3)) { + this.findEligibleInputs(realTarget.getRootNode()); } + window.performance?.mark?.("scan_shadow:init:end"); + logPerformance("scan_shadow"); } - this.initialScanComplete = true; - - // Observe only if the container isn't the body, to avoid performance overloads - if (this.form !== document.body) { - this.mutObs.observe(this.form, this.mutObsConfig); - } - } - get submitButtons() { - const selector = this.matching.cssSelector('submitButtonSelector'); - const allButtons = /** @type {HTMLElement[]} */[...this.form.querySelectorAll(selector)]; - return allButtons.filter(btn => (0, _autofillUtils.isPotentiallyViewable)(btn) && (0, _autofillUtils.isLikelyASubmitButton)(btn, this.matching) && (0, _autofillUtils.buttonMatchesFormType)(btn, this)); - } - attemptSubmissionIfNeeded() { - if (!this.isLogin || - // Only submit login forms - this.submitButtons.length > 1 // Do not submit if we're unsure about the submit button - ) return; - - // check for visible empty fields before attemtping submission - // this is to avoid loops where a captcha keeps failing for the user - let isThereAnEmptyVisibleField = false; - this.execOnInputs(input => { - if (input.value === '' && (0, _autofillUtils.isPotentiallyViewable)(input)) isThereAnEmptyVisibleField = true; - }, 'all', false); - if (isThereAnEmptyVisibleField) return; - - // We're not using .submit() to minimise breakage with client-side forms - this.submitButtons.forEach(button => { - if ((0, _autofillUtils.isPotentiallyViewable)(button)) { - button.click(); - } + }; + function createScanner(device, scannerOptions) { + return new DefaultScanner(device, { + ...defaultScannerOptions, + ...scannerOptions }); } - /** - * Executes a function on input elements. Can be limited to certain element types - * @param {(input: HTMLInputElement|HTMLSelectElement) => void} fn - * @param {'all' | SupportedMainTypes} inputType - * @param {boolean} shouldCheckForDecorate - */ - execOnInputs(fn) { - let inputType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'all'; - let shouldCheckForDecorate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; - const inputs = this.inputs[inputType]; - for (const input of inputs) { - let canExecute = true; - // sometimes we want to execute even if we didn't decorate - if (shouldCheckForDecorate) { - canExecute = (0, _inputTypeConfig.isFieldDecorated)(input); - } - if (canExecute) fn(input); + // src/UI/controllers/UIController.js + var UIController = class { + /** + * Implement this method to control what happen when Autofill + * has enough information to 'attach' a tooltip. + * + * @param {AttachArgs} _args + * @returns {void} + */ + attach(_args) { + throw new Error("must implement attach"); } - } - addInput(input) { - if (this.inputs.all.has(input)) return this; - - // If the form has too many inputs, destroy everything to avoid performance issues - if (this.inputs.all.size > MAX_INPUTS_PER_FORM) { - this.device.scanner.stopScanner('The form has too many inputs, bailing.'); - return this; + /** + * Implement this if your tooltip can be created from positioning + * + topContextData. + * + * For example, in an 'overlay' on macOS/Windows this is needed since + * there's no page information to call 'attach' above. + * + * @param {PosFn} _pos + * @param {TopContextData} _topContextData + * @returns {any | null} + */ + createTooltip(_pos, _topContextData) { } - - // When new inputs are added after the initial scan, reanalyze the whole form - if (this.initialScanComplete && this.rescanCount < MAX_FORM_RESCANS) { - this.formAnalyzer = new _FormAnalyzer.default(this.form, input, this.matching); - this.recategorizeAllInputs(); - return this; + /** + * @param {string} _via + */ + removeTooltip(_via) { } - - // Nothing to do with 1-character fields - if (input.maxLength === 1) return this; - this.inputs.all.add(input); - const opts = { - isLogin: this.isLogin, - isHybrid: this.isHybrid, - isCCForm: this.isCCForm, - hasCredentials: Boolean(this.device.settings.availableInputTypes.credentials?.username), - supportsIdentitiesAutofill: this.device.settings.featureToggles.inputType_identities - }; - this.matching.setInputType(input, this.form, opts); - const mainInputType = (0, _matching.getInputMainType)(input); - this.inputs[mainInputType].add(input); - this.decorateInput(input); - return this; - } - - /** - * Adds event listeners and keeps track of them for subsequent removal - * @param {HTMLElement} el - * @param {Event['type']} type - * @param {(Event) => void} fn - * @param {AddEventListenerOptions} [opts] - */ - addListener(el, type, fn, opts) { - el.addEventListener(type, fn, opts); - this.listeners.add({ - el, - type, - fn, - opts - }); - } - addAutofillStyles(input) { - const initialStyles = (0, _inputStyles.getIconStylesBase)(input, this); - const activeStyles = (0, _inputStyles.getIconStylesAlternate)(input, this); - (0, _autofillUtils.addInlineStyles)(input, initialStyles); - return { - onMouseMove: activeStyles, - onMouseLeave: initialStyles - }; - } - - /** - * Decorate here means adding listeners and an optional icon - * @param {HTMLInputElement} input - * @returns {Promise} - */ - async decorateInput(input) { - const config = (0, _inputTypeConfig.getInputConfig)(input); - const shouldDecorate = await config.shouldDecorate(input, this); - if (!shouldDecorate) return this; - input.setAttribute(ATTR_AUTOFILL, 'true'); - const hasIcon = !!config.getIconBase(input, this); - if (hasIcon) { - const { - onMouseMove, - onMouseLeave - } = this.addAutofillStyles(input); - this.addListener(input, 'mousemove', e => { - if ((0, _autofillUtils.wasAutofilledByChrome)(input)) return; - if ((0, _autofillUtils.isEventWithinDax)(e, e.target)) { - (0, _autofillUtils.addInlineStyles)(e.target, { - 'cursor': 'pointer', - ...onMouseMove - }); - } else { - (0, _autofillUtils.removeInlineStyles)(e.target, { - 'cursor': 'pointer' - }); - // Only overwrite active icon styles if tooltip is closed - if (!this.device.isTooltipActive()) { - (0, _autofillUtils.addInlineStyles)(e.target, { - ...onMouseLeave - }); - } - } - }); - this.addListener(input, 'mouseleave', e => { - (0, _autofillUtils.removeInlineStyles)(e.target, { - 'cursor': 'pointer' - }); - // Only overwrite active icon styles if tooltip is closed - if (!this.device.isTooltipActive()) { - (0, _autofillUtils.addInlineStyles)(e.target, { - ...onMouseLeave - }); - } - }); + /** + * Set the currently open HTMLTooltip instance + * + * @param {import("../HTMLTooltip.js").HTMLTooltip} _tooltip + */ + setActiveTooltip(_tooltip) { } - /** - * @param {PointerEvent} e - * @returns {{ x: number; y: number; } | undefined} + * Get the currently open HTMLTooltip instance, if one exists + * + * @returns {import("../HTMLTooltip.js").HTMLTooltip | null} */ - function getMainClickCoords(e) { - if (!e.isTrusted) return; - const isMainMouseButton = e.button === 0; - if (!isMainMouseButton) return; - return { - x: e.clientX, - y: e.clientY - }; + getActiveTooltip() { + return null; } - /** - * @param {Event} e - * @param {WeakMap} storedClickCoords - * @returns {{ x: number; y: number; } | null} + * Indicate whether the controller deems itself 'active' + * + * @returns {boolean} */ - function getClickCoords(e, storedClickCoords) { - // Get click co-ordinates for pointer events - // We need click coordinates to position the tooltip when the field is in an iframe - if (e.type === 'pointerdown') { - return getMainClickCoords( /** @type {PointerEvent} */e) || null; - } - - // Reuse a previous click co-ordinates if they exist for this element - const click = storedClickCoords.get(input); - storedClickCoords.delete(input); - return click || null; + isActive() { + return false; } - - // Store the click to a label so we can use the click when the field is focused - // Needed to handle label clicks when the form is in an iframe - let storedClickCoords = new WeakMap(); - let timeout = null; - /** - * @param {PointerEvent} e + * Updates the items in the tooltip based on new data. Currently only supporting credentials. + * @param {CredentialsObject[]} _data */ - const handlerLabel = e => { - // Look for e.target OR it's closest parent to be a HTMLLabelElement - const control = /** @type HTMLElement */e.target?.closest('label')?.control; - if (!control) return; - if (e.isTrusted) { - storedClickCoords.set(control, getMainClickCoords(e)); - } - clearTimeout(timeout); - // Remove the stored click if the timer expires - timeout = setTimeout(() => { - storedClickCoords = new WeakMap(); - }, 1000); - }; - const handler = e => { - // Avoid firing multiple times - if (this.isAutofilling || this.device.isTooltipActive()) { - return; - } + updateItems(_data7) { + } + destroy() { + } + }; - // On mobile, we don't trigger on focus, so here we get the target control on label click - const isLabel = e.target instanceof HTMLLabelElement; - const input = isLabel ? e.target.control : e.target; - if (!input || !this.inputs.all.has(input)) return; - if ((0, _autofillUtils.wasAutofilledByChrome)(input)) return; - if (!(0, _inputTypeConfig.canBeInteractedWith)(input)) return; - const clickCoords = getClickCoords(e, storedClickCoords); - if (e.type === 'pointerdown') { - // Only allow real user clicks with co-ordinates through - if (!e.isTrusted || !clickCoords) return; - } - if (this.shouldOpenTooltip(e, input)) { - const iconClicked = (0, _autofillUtils.isEventWithinDax)(e, input); - // On mobile and extensions we don't trigger the focus event to avoid - // keyboard flashing and conflicts with browsers' own tooltips - if ((this.device.globalConfig.isMobileApp || this.device.globalConfig.isExtension) && - // Avoid the icon capturing clicks on small fields making it impossible to focus - input.offsetWidth > 50 && iconClicked) { - e.preventDefault(); - e.stopImmediatePropagation(); - input.blur(); - } - this.touched.add(input); - this.device.attachTooltip({ - form: this, - input: input, - click: clickCoords, - trigger: 'userInitiated', - triggerMetaData: { - // An 'icon' click is very different to a field click or focus. - // It indicates an explicit opt-in to the feature. - type: iconClicked ? 'explicit-opt-in' : 'implicit-opt-in' - } + // src/UI/controllers/NativeUIController.js + var _passwordStatus; + var NativeUIController = class extends UIController { + constructor() { + super(...arguments); + /** + * Keep track of when passwords were suggested/rejected/accepted etc + * State is kept here because it's specific to the interactions on mobile (eg: NativeUIController) + * + * @type {"default" | "rejected"} + */ + __privateAdd(this, _passwordStatus, "default"); + } + /** + * @param {import('./UIController').AttachArgs} args + */ + attach(args) { + const { form, input, device, trigger, triggerMetaData, topContextData } = args; + const inputType = getInputType(input); + const mainType = getMainTypeFromType(inputType); + const subType = getSubtypeFromType(inputType); + if (mainType === "unknown") { + throw new Error('unreachable, should not be here if (mainType === "unknown")'); + } + if (trigger === "autoprompt") { + window.scrollTo({ + behavior: "smooth", + top: form.form.getBoundingClientRect().top - document.body.getBoundingClientRect().top - 50 }); - const activeStyles = (0, _inputStyles.getIconStylesAlternate)(input, this); - (0, _autofillUtils.addInlineStyles)(input, activeStyles); } - }; - if (!(input instanceof HTMLSelectElement)) { - const events = ['pointerdown']; - if (!this.device.globalConfig.isMobileApp) events.push('focus'); - input.labels?.forEach(label => { - if (this.device.globalConfig.isMobileApp) { - // On mobile devices we don't trigger on focus, so we use the click handler here - this.addListener(label, 'pointerdown', handler); - } else { - // Needed to handle label clicks when the form is in an iframe - this.addListener(label, 'pointerdown', handlerLabel); - } - }); - events.forEach(ev => this.addListener(input, ev, handler)); - } - return this; - } - shouldOpenTooltip(e, input) { - if (!(0, _autofillUtils.isPotentiallyViewable)(input)) return false; - - // Always open if the user has clicked on the Dax icon - if ((0, _autofillUtils.isEventWithinDax)(e, input)) return true; - if (this.device.globalConfig.isWindows) return true; - const subtype = (0, _matching.getInputSubtype)(input); - const isIncontextSignupAvailable = this.device.inContextSignup?.isAvailable(subtype); - if (this.device.globalConfig.isApp) { - const mainType = (0, _matching.getInputMainType)(input); - // Check if, without in-context signup (passed as `null` below), - // we'd have any other items to show. This lets us know if we're - // just showing in-context signup, or with other autofill items. - const hasSavedDetails = this.device.settings.canAutofillType({ + let payload = { + inputType, mainType, - subtype - }, null); - - // Don't open the tooltip on input focus whenever it'll only show in-context signup - if (!hasSavedDetails && isIncontextSignupAvailable) return false; - return true; - } - if (this.device.globalConfig.isExtension || this.device.globalConfig.isMobileApp) { - // Don't open the tooltip on input focus whenever it's showing in-context signup - if (isIncontextSignupAvailable) return false; + subType, + trigger + }; + if (device.settings.featureToggles.password_generation) { + payload = this.appendGeneratedPassword(topContextData, payload, triggerMetaData); + } + device.deviceApi.request(new GetAutofillDataCall(payload)).then((resp) => { + switch (resp.action) { + case "fill": { + if (mainType in resp) { + form.autofillData(resp[mainType], mainType); + } else { + throw new Error(`action: "fill" cannot occur because "${mainType}" was missing`); + } + break; + } + case "focus": { + form.activeInput?.focus(); + break; + } + case "acceptGeneratedPassword": { + form.autofillData({ + password: topContextData.credentials?.[0].password, + [AUTOGENERATED_KEY]: true + }, mainType); + break; + } + case "rejectGeneratedPassword": { + __privateSet(this, _passwordStatus, "rejected"); + form.touchAllInputs("credentials"); + form.activeInput?.focus(); + break; + } + default: { + if (args.device.isTestMode()) { + console.warn("response not handled", resp); + } + } + } + }).catch((e) => { + console.error("NativeTooltip::device.getAutofillData(payload)"); + console.error(e); + }); } - return !this.touched.has(input) && !input.classList.contains('ddg-autofilled'); - } - autofillInput(input, string, dataType) { - // Do not autofill if it's invisible (select elements can be hidden because of custom implementations) - if (input instanceof HTMLInputElement && !(0, _autofillUtils.isPotentiallyViewable)(input)) return; - // Do not autofill if it's disabled or readonly to avoid potential breakage - if (!(0, _inputTypeConfig.canBeInteractedWith)(input)) return; - - // Don't override values the user provided, unless it's the focused input or we're autofilling creditCards - if (dataType !== 'creditCards' && - // creditCards always override, the others only when we're focusing the input - input.nodeName !== 'SELECT' && input.value !== '' && - // if the input is not empty - this.activeInput !== input // and this is not the active input - ) return; // do not overwrite the value - - // If the value is already there, just return - if (input.value === string) return; - const successful = (0, _autofillUtils.setValue)(input, string, this.device.globalConfig); - if (!successful) return; - input.classList.add('ddg-autofilled'); - (0, _autofillUtils.addInlineStyles)(input, (0, _inputStyles.getIconStylesAutofilled)(input, this)); - this.touched.add(input); - - // If the user changes the value, remove the decoration - input.addEventListener('input', e => this.removeAllHighlights(e, dataType), { - once: true - }); - } - - /** - * Autofill method for email protection only - * @param {string} alias - * @param {'all' | SupportedMainTypes} dataType - */ - autofillEmail(alias) { - let dataType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'identities'; - this.isAutofilling = true; - this.execOnInputs(input => { - const inputSubtype = (0, _matching.getInputSubtype)(input); - if (inputSubtype === 'emailAddress') { - this.autofillInput(input, alias, dataType); - } - }, dataType); - this.isAutofilling = false; - this.removeTooltip(); - } - autofillData(data, dataType) { - this.isAutofilling = true; - this.execOnInputs(input => { - const inputSubtype = (0, _matching.getInputSubtype)(input); - let autofillData = data[inputSubtype]; - if (inputSubtype === 'expiration' && input instanceof HTMLInputElement) { - autofillData = (0, _formatters.getUnifiedExpiryDate)(input, data.expirationMonth, data.expirationYear, this); + /** + * If a password exists in `topContextData`, we can append it to the outgoing data + * in a way that native platforms can easily understand. + * + * @param {TopContextData} topContextData + * @param {import('../../deviceApiCalls/__generated__/validators-ts.js').GetAutofillDataRequest} outgoingData + * @param {import('../../UI/controllers/UIController.js').AttachArgs['triggerMetaData']} triggerMetaData + * @return {import('../../deviceApiCalls/__generated__/validators-ts.js').GetAutofillDataRequest} + */ + appendGeneratedPassword(topContextData, outgoingData, triggerMetaData) { + const autoGeneratedCredential = topContextData.credentials?.find((credential) => credential.autogenerated); + if (!autoGeneratedCredential?.password) { + return outgoingData; + } + function suggestPassword() { + if (!autoGeneratedCredential?.password) + throw new Error("unreachable"); + return { + ...outgoingData, + generatedPassword: { + value: autoGeneratedCredential.password, + username: autoGeneratedCredential.username + } + }; } - if (inputSubtype === 'expirationYear' && input instanceof HTMLInputElement) { - autofillData = (0, _formatters.formatCCYear)(input, autofillData, this); + if (triggerMetaData.type === "explicit-opt-in") { + return suggestPassword(); } - if (inputSubtype === 'addressCountryCode') { - autofillData = (0, _formatters.getCountryName)(input, data); + if (triggerMetaData.type === "implicit-opt-in" && __privateGet(this, _passwordStatus) !== "rejected") { + return suggestPassword(); } - if (autofillData) { - const variant = (0, _matching.getInputVariant)(input); - if (!variant) { - return this.autofillInput(input, autofillData, dataType); - } + return outgoingData; + } + }; + _passwordStatus = new WeakMap(); - // Fields with a variant should only be filled when fill is initiated from the same variant. - // This ensures we don't overwrite the current password when filling a - // generated password in password update forms. - if (variant === 'new' && _Credentials.AUTOGENERATED_KEY in data) { - return this.autofillInput(input, autofillData, dataType); - } - if (variant === 'current' && !(_Credentials.AUTOGENERATED_KEY in data)) { - return this.autofillInput(input, autofillData, dataType); + // packages/messaging/webkit.js + var WebkitMessagingTransport = class { + /** + * @param {WebkitMessagingConfig} config + */ + constructor(config) { + /** @type {WebkitMessagingConfig} */ + __publicField(this, "config"); + __publicField(this, "globals"); + /** + * @type {{name: string, length: number}} + */ + __publicField(this, "algoObj", { name: "AES-GCM", length: 256 }); + this.config = config; + this.globals = captureGlobals(); + if (!this.config.hasModernWebkitAPI) { + this.captureWebkitHandlers(this.config.webkitMessageHandlerNames); + } + } + /** + * Sends message to the webkit layer (fire and forget) + * @param {String} handler + * @param {*} data + * @internal + */ + wkSend(handler, data = {}) { + if (!(handler in this.globals.window.webkit.messageHandlers)) { + throw new MissingHandler(`Missing webkit handler: '${handler}'`, handler); + } + const outgoing = { + ...data, + messageHandling: { ...data.messageHandling, secret: this.config.secret } + }; + if (!this.config.hasModernWebkitAPI) { + if (!(handler in this.globals.capturedWebkitHandlers)) { + throw new MissingHandler(`cannot continue, method ${handler} not captured on macos < 11`, handler); + } else { + return this.globals.capturedWebkitHandlers[handler](outgoing); } } - }, dataType); - this.isAutofilling = false; - - // After autofill we check if form values match the data provided… - const formValues = this.getValuesReadyForStorage(); - const areAllFormValuesKnown = Object.keys(formValues[dataType] || {}).every(subtype => formValues[dataType][subtype] === data[subtype]); - if (areAllFormValuesKnown) { - // …if we know all the values do not prompt to store data - this.shouldPromptToStoreData = false; - // reset this to its initial value - this.shouldAutoSubmit = this.device.globalConfig.isMobileApp; - } else { - // …otherwise we will prompt and do not want to autosubmit because the experience is jarring - this.shouldAutoSubmit = false; + return this.globals.window.webkit.messageHandlers[handler].postMessage?.(outgoing); } - this.device.postAutofill?.(data, dataType, this); - this.removeTooltip(); - } - - /** - * Set all inputs of the data type to "touched" - * @param {'all' | SupportedMainTypes} dataType - */ - touchAllInputs() { - let dataType = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'all'; - this.execOnInputs(input => this.touched.add(input), dataType); - } - getFirstViableCredentialsInput() { - return [...this.inputs.credentials].find(input => (0, _inputTypeConfig.canBeInteractedWith)(input) && (0, _autofillUtils.isPotentiallyViewable)(input)); - } - async promptLoginIfNeeded() { - if (document.visibilityState !== 'visible' || !this.isLogin) return; - const firstCredentialInput = this.getFirstViableCredentialsInput(); - const input = this.activeInput || firstCredentialInput; - if (!input) return; - const mainType = (0, _matching.getInputMainType)(input); - const subtype = (0, _matching.getInputSubtype)(input); - await this.device.settings.populateDataIfNeeded({ - mainType, - subtype - }); - if (this.device.settings.canAutofillType({ - mainType, - subtype - }, this.device.inContextSignup)) { - // The timeout is needed in case the page shows a cookie prompt with a slight delay - setTimeout(() => { - // safeExecute checks that the element is on screen according to IntersectionObserver - (0, _autofillUtils.safeExecute)(this.form, () => { - const { - x, - y, - width, - height - } = this.form.getBoundingClientRect(); - const elHCenter = x + width / 2; - const elVCenter = y + height / 2; - // This checks that the form is not covered by anything else - const topMostElementFromPoint = document.elementFromPoint(elHCenter, elVCenter); - if (this.form.contains(topMostElementFromPoint)) { - this.execOnInputs(input => { - if ((0, _autofillUtils.isPotentiallyViewable)(input)) { - this.touched.add(input); - } - }, 'credentials'); - this.device.attachTooltip({ - form: this, - input: input, - click: null, - trigger: 'autoprompt', - triggerMetaData: { - type: 'implicit-opt-in' - } - }); - } + /** + * Sends message to the webkit layer and waits for the specified response + * @param {String} handler + * @param {*} data + * @returns {Promise<*>} + * @internal + */ + async wkSendAndWait(handler, data = {}) { + if (this.config.hasModernWebkitAPI) { + const response = await this.wkSend(handler, data); + return this.globals.JSONparse(response || "{}"); + } + try { + const randMethodName = this.createRandMethodName(); + const key = await this.createRandKey(); + const iv = this.createRandIv(); + const { ciphertext, tag } = await new this.globals.Promise((resolve) => { + this.generateRandomMethod(randMethodName, resolve); + data.messageHandling = new SecureMessagingParams({ + methodName: randMethodName, + secret: this.config.secret, + key: this.globals.Arrayfrom(key), + iv: this.globals.Arrayfrom(iv) + }); + this.wkSend(handler, data); }); - }, 200); + const cipher = new this.globals.Uint8Array([...ciphertext, ...tag]); + const decrypted = await this.decrypt(cipher, key, iv); + return this.globals.JSONparse(decrypted || "{}"); + } catch (e) { + if (e instanceof MissingHandler) { + throw e; + } else { + console.error("decryption failed", e); + console.error(e); + return { error: e }; + } + } } - } -} -exports.Form = Form; - -},{"../InputTypes/Credentials.js":45,"../autofill-utils.js":62,"../constants.js":65,"./FormAnalyzer.js":34,"./formatters.js":36,"./inputStyles.js":37,"./inputTypeConfig.js":38,"./matching.js":43}],34:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _matching = require("./matching.js"); -var _constants = require("../constants.js"); -var _compiledMatchingConfig = require("./matching-config/__generated__/compiled-matching-config.js"); -var _autofillUtils = require("../autofill-utils.js"); -class FormAnalyzer { - /** @type HTMLElement */ - form; - /** @type Matching */ - matching; - /** - * @param {HTMLElement} form - * @param {HTMLInputElement|HTMLSelectElement} input - * @param {Matching} [matching] - */ - constructor(form, input, matching) { - this.form = form; - this.matching = matching || new _matching.Matching(_compiledMatchingConfig.matchingConfiguration); - /** - * The signal is a continuum where negative values imply login and positive imply signup - * @type {number} - */ - this.autofillSignal = 0; + * @param {string} name + * @param {Record} [data] + */ + notify(name, data = {}) { + this.wkSend(name, data); + } /** - * A hybrid form can be either a login or a signup, the site uses a single form for both - * @type {number} - */ - this.hybridSignal = 0; - + * @param {string} name + * @param {Record} [data] + */ + request(name, data = {}) { + return this.wkSendAndWait(name, data); + } /** - * Collects the signals for debugging purposes - * @type {string[]} - */ - this.signals = []; - this.evaluateElAttributes(input, 1, true); - form ? this.evaluateForm() : this.evaluatePage(); - return this; - } - - /** - * Hybrid forms can be used for both login and signup - * @returns {boolean} - */ - get isHybrid() { - // When marking for hybrid we also want to ensure other signals are weak - const areOtherSignalsWeak = Math.abs(this.autofillSignal) < 10; - return this.hybridSignal > 0 && areOtherSignalsWeak; - } - get isLogin() { - if (this.isHybrid) return false; - return this.autofillSignal < 0; - } - get isSignup() { - if (this.isHybrid) return false; - return this.autofillSignal >= 0; - } - - /** - * Tilts the scoring towards Signup - * @param {number} strength - * @param {string} signal - * @returns {FormAnalyzer} - */ - increaseSignalBy(strength, signal) { - this.autofillSignal += strength; - this.signals.push(`${signal}: +${strength}`); - return this; - } - - /** - * Tilts the scoring towards Login - * @param {number} strength - * @param {string} signal - * @returns {FormAnalyzer} - */ - decreaseSignalBy(strength, signal) { - this.autofillSignal -= strength; - this.signals.push(`${signal}: -${strength}`); - return this; - } - - /** - * Increases the probability that this is a hybrid form (can be either login or signup) - * @param {number} strength - * @param {string} signal - * @returns {FormAnalyzer} - */ - increaseHybridSignal(strength, signal) { - this.hybridSignal += strength; - this.signals.push(`${signal} (hybrid): +${strength}`); - return this; - } - - /** - * Updates the Login<->Signup signal according to the provided parameters - * @param {object} p - * @param {string} p.string - The string to check - * @param {number} p.strength - Strength of the signal - * @param {string} [p.signalType] - For debugging purposes, we give a name to the signal - * @param {boolean} [p.shouldFlip] - Flips the signals, i.e. when a link points outside. See below - * @param {boolean} [p.shouldCheckUnifiedForm] - Should check for login/signup forms - * @param {boolean} [p.shouldBeConservative] - Should use the conservative signup regex - * @returns {FormAnalyzer} - */ - updateSignal(_ref) { - let { - string, - strength, - signalType = 'generic', - shouldFlip = false, - shouldCheckUnifiedForm = false, - shouldBeConservative = false - } = _ref; - // If the string is empty or too long (noisy) do nothing - if (!string || string.length > _constants.constants.TEXT_LENGTH_CUTOFF) return this; - const matchesLogin = (0, _autofillUtils.safeRegexTest)(/current.?password/i, string) || (0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('loginRegex'), string) || (0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('resetPasswordLink'), string); - - // Check explicitly for unified login/signup forms - if (shouldCheckUnifiedForm && matchesLogin && (0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('conservativeSignupRegex'), string)) { - this.increaseHybridSignal(strength, signalType); - return this; + * Generate a random method name and adds it to the global scope + * The native layer will use this method to send the response + * @param {string | number} randomMethodName + * @param {Function} callback + */ + generateRandomMethod(randomMethodName, callback) { + this.globals.ObjectDefineProperty(this.globals.window, randomMethodName, { + enumerable: false, + // configurable, To allow for deletion later + configurable: true, + writable: false, + /** + * @param {any[]} args + */ + value: (...args) => { + callback(...args); + delete this.globals.window[randomMethodName]; + } + }); } - const signupRegexToUse = this.matching.getDDGMatcherRegex(shouldBeConservative ? 'conservativeSignupRegex' : 'signupRegex'); - const matchesSignup = (0, _autofillUtils.safeRegexTest)(/new.?password/i, string) || (0, _autofillUtils.safeRegexTest)(signupRegexToUse, string); - - // In some cases a login match means the login is somewhere else, i.e. when a link points outside - if (shouldFlip) { - if (matchesLogin) this.increaseSignalBy(strength, signalType); - if (matchesSignup) this.decreaseSignalBy(strength, signalType); - } else { - if (matchesLogin) this.decreaseSignalBy(strength, signalType); - if (matchesSignup) this.increaseSignalBy(strength, signalType); + randomString() { + return "" + this.globals.getRandomValues(new this.globals.Uint32Array(1))[0]; } - return this; - } - evaluateElAttributes(el) { - let signalStrength = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 3; - let isInput = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; - Array.from(el.attributes).forEach(attr => { - if (attr.name === 'style') return; - const attributeString = `${attr.name}=${attr.value}`; - this.updateSignal({ - string: attributeString, - strength: signalStrength, - signalType: `${el.name} attr: ${attributeString}`, - shouldCheckUnifiedForm: isInput - }); - }); - } - evaluateUrl() { - const path = window.location.pathname; - const matchesLogin = (0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('loginRegex'), path); - const matchesSignup = (0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('conservativeSignupRegex'), path); - - // If the url matches both, do nothing: the signal is probably confounding - if (matchesLogin && matchesSignup) return; - if (matchesLogin) { - this.decreaseSignalBy(1, 'url matches login'); + createRandMethodName() { + return "_" + this.randomString(); } - if (matchesSignup) { - this.increaseSignalBy(1, 'url matches signup'); + /** + * @returns {Promise} + */ + async createRandKey() { + const key = await this.globals.generateKey(this.algoObj, true, ["encrypt", "decrypt"]); + const exportedKey = await this.globals.exportKey("raw", key); + return new this.globals.Uint8Array(exportedKey); } - } - evaluatePageTitle() { - const pageTitle = document.title; - this.updateSignal({ - string: pageTitle, - strength: 2, - signalType: `page title: ${pageTitle}`, - shouldCheckUnifiedForm: true - }); - } - evaluatePageHeadings() { - const headings = document.querySelectorAll('h1, h2, h3'); - headings.forEach(heading => { - const textContent = (0, _matching.removeExcessWhitespace)(heading.textContent || ''); - this.updateSignal({ - string: textContent, - strength: 0.5, - signalType: `heading: ${textContent}`, - shouldCheckUnifiedForm: true, - shouldBeConservative: true - }); - }); - } - evaluatePage() { - this.evaluatePageTitle(); - this.evaluatePageHeadings(); - // Check for submit buttons - const buttons = document.querySelectorAll(this.matching.cssSelector('submitButtonSelector')); - buttons.forEach(button => { - // if the button has a form, it's not related to our input, because our input has no form here - if (button instanceof HTMLButtonElement) { - if (!button.form && !button.closest('form')) { - this.evaluateElement(button); - this.evaluateElAttributes(button, 0.5); + /** + * @returns {Uint8Array} + */ + createRandIv() { + return this.globals.getRandomValues(new this.globals.Uint8Array(12)); + } + /** + * @param {BufferSource} ciphertext + * @param {BufferSource} key + * @param {Uint8Array} iv + * @returns {Promise} + */ + async decrypt(ciphertext, key, iv) { + const cryptoKey = await this.globals.importKey("raw", key, "AES-GCM", false, ["decrypt"]); + const algo = { name: "AES-GCM", iv }; + let decrypted = await this.globals.decrypt(algo, cryptoKey, ciphertext); + let dec = new this.globals.TextDecoder(); + return dec.decode(decrypted); + } + /** + * When required (such as on macos 10.x), capture the `postMessage` method on + * each webkit messageHandler + * + * @param {string[]} handlerNames + */ + captureWebkitHandlers(handlerNames) { + const handlers = window.webkit.messageHandlers; + if (!handlers) + throw new MissingHandler("window.webkit.messageHandlers was absent", "all"); + for (let webkitMessageHandlerName of handlerNames) { + if (typeof handlers[webkitMessageHandlerName]?.postMessage === "function") { + const original = handlers[webkitMessageHandlerName]; + const bound = handlers[webkitMessageHandlerName].postMessage?.bind(original); + this.globals.capturedWebkitHandlers[webkitMessageHandlerName] = bound; + delete handlers[webkitMessageHandlerName].postMessage; } } - }); - } - evaluateElement(el) { - const string = (0, _autofillUtils.getTextShallow)(el); - if (el.matches(this.matching.cssSelector('password'))) { - // These are explicit signals by the web author, so we weigh them heavily - this.updateSignal({ - string: el.getAttribute('autocomplete') || el.getAttribute('name') || '', - strength: 5, - signalType: `explicit: ${el.getAttribute('autocomplete')}` - }); - return; } - - // check button contents - if (el.matches(this.matching.cssSelector('submitButtonSelector') + ', *[class*=button]')) { - // If we're confident this is the submit button, it's a stronger signal - let likelyASubmit = (0, _autofillUtils.isLikelyASubmitButton)(el, this.matching); - let shouldFlip = false; - if (likelyASubmit) { - this.form.querySelectorAll('input[type=submit], button[type=submit]').forEach(submit => { - // If there is another element marked as submit and this is not, flip back to false - if (el.type !== 'submit' && el !== submit) { - likelyASubmit = false; - } - }); - } else { - // Here we don't think this is a submit, so if there is another submit in the form, flip the score - const thereIsASubmitButton = Boolean(this.form.querySelector('input[type=submit], button[type=submit]')); - const isSocialButton = /facebook|twitter|google|apple/i.test(string); - shouldFlip = thereIsASubmitButton && !isSocialButton; - } - const strength = likelyASubmit ? 20 : 4; - this.updateSignal({ - string, - strength, - signalType: `button: ${string}`, - shouldFlip - }); - return; + }; + var WebkitMessagingConfig = class { + /** + * @param {object} params + * @param {boolean} params.hasModernWebkitAPI + * @param {string[]} params.webkitMessageHandlerNames + * @param {string} params.secret + */ + constructor(params) { + this.hasModernWebkitAPI = params.hasModernWebkitAPI; + this.webkitMessageHandlerNames = params.webkitMessageHandlerNames; + this.secret = params.secret; } - // if an external link matches one of the regexes, we assume the match is not pertinent to the current form - if (el instanceof HTMLAnchorElement && el.href && el.getAttribute('href') !== '#' || (el.getAttribute('role') || '').toUpperCase() === 'LINK' || el.matches('button[class*=secondary]')) { - let shouldFlip = true; - let strength = 1; - // Don't flip forgotten password links - if ((0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('resetPasswordLink'), string)) { - shouldFlip = false; - strength = 3; - } else if ((0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('loginProvidersRegex'), string)) { - // Don't flip login providers links - shouldFlip = false; - } - this.updateSignal({ - string, - strength, - signalType: `external link: ${string}`, - shouldFlip - }); - } else { - // any other case - this.updateSignal({ - string, - strength: 1, - signalType: `generic: ${string}`, - shouldCheckUnifiedForm: true - }); + }; + var SecureMessagingParams = class { + /** + * @param {object} params + * @param {string} params.methodName + * @param {string} params.secret + * @param {number[]} params.key + * @param {number[]} params.iv + */ + constructor(params) { + this.methodName = params.methodName; + this.secret = params.secret; + this.key = params.key; + this.iv = params.iv; } + }; + function captureGlobals() { + return { + window, + // Methods must be bound to their interface, otherwise they throw Illegal invocation + encrypt: window.crypto.subtle.encrypt.bind(window.crypto.subtle), + decrypt: window.crypto.subtle.decrypt.bind(window.crypto.subtle), + generateKey: window.crypto.subtle.generateKey.bind(window.crypto.subtle), + exportKey: window.crypto.subtle.exportKey.bind(window.crypto.subtle), + importKey: window.crypto.subtle.importKey.bind(window.crypto.subtle), + getRandomValues: window.crypto.getRandomValues.bind(window.crypto), + TextEncoder, + TextDecoder, + Uint8Array, + Uint16Array, + Uint32Array, + JSONstringify: window.JSON.stringify, + JSONparse: window.JSON.parse, + Arrayfrom: window.Array.from, + Promise: window.Promise, + ObjectDefineProperty: window.Object.defineProperty, + addEventListener: window.addEventListener.bind(window), + /** @type {Record} */ + capturedWebkitHandlers: {} + }; } - evaluateForm() { - // Check page url - this.evaluateUrl(); - - // Check page title - this.evaluatePageTitle(); - - // Check form attributes - this.evaluateElAttributes(this.form); - - // Check form contents (noisy elements are skipped with the safeUniversalSelector) - this.form.querySelectorAll(this.matching.cssSelector('safeUniversalSelector')).forEach(el => { - // Check if element is not hidden. Note that we can't use offsetHeight - // nor intersectionObserver, because the element could be outside the - // viewport or its parent hidden - const displayValue = window.getComputedStyle(el, null).getPropertyValue('display'); - if (displayValue !== 'none') this.evaluateElement(el); - }); - - // A form with many fields is unlikely to be a login form - const relevantFields = this.form.querySelectorAll(this.matching.cssSelector('genericTextField')); - if (relevantFields.length >= 4) { - this.increaseSignalBy(relevantFields.length * 1.5, 'many fields: it is probably not a login'); - } - // If we can't decide at this point, try reading page headings - if (this.autofillSignal === 0) { - this.evaluatePageHeadings(); + // packages/messaging/messaging.js + var Messaging = class { + /** + * @param {WebkitMessagingConfig} config + */ + constructor(config) { + this.transport = getTransport(config); } - return this; - } - - /** @type {undefined|boolean} */ - _isCCForm = undefined; - /** - * Tries to infer if it's a credit card form - * @returns {boolean} - */ - isCCForm() { - if (this._isCCForm !== undefined) return this._isCCForm; - const formEl = this.form; - const ccFieldSelector = this.matching.joinCssSelectors('cc'); - if (!ccFieldSelector) { - this._isCCForm = false; - return this._isCCForm; + /** + * Send a 'fire-and-forget' message. + * @throws {Error} + * {@link MissingHandler} + * + * @example + * + * ``` + * const messaging = new Messaging(config) + * messaging.notify("foo", {bar: "baz"}) + * ``` + * @param {string} name + * @param {Record} [data] + */ + notify(name, data = {}) { + this.transport.notify(name, data); } - const hasCCSelectorChild = formEl.matches(ccFieldSelector) || formEl.querySelector(ccFieldSelector); - // If the form contains one of the specific selectors, we have high confidence - if (hasCCSelectorChild) { - this._isCCForm = true; - return this._isCCForm; + /** + * Send a request, and wait for a response + * @throws {Error} + * {@link MissingHandler} + * + * @example + * ``` + * const messaging = new Messaging(config) + * const response = await messaging.request("foo", {bar: "baz"}) + * ``` + * + * @param {string} name + * @param {Record} [data] + * @return {Promise} + */ + request(name, data = {}) { + return this.transport.request(name, data); } - - // Read form attributes to find a signal - const hasCCAttribute = [...formEl.attributes].some(_ref2 => { - let { - name, - value - } = _ref2; - return (0, _autofillUtils.safeRegexTest)(/(credit|payment).?card/i, `${name}=${value}`); - }); - if (hasCCAttribute) { - this._isCCForm = true; - return this._isCCForm; + }; + function getTransport(config) { + if (config instanceof WebkitMessagingConfig) { + return new WebkitMessagingTransport(config); } - - // Match form textContent against common cc fields (includes hidden labels) - const textMatches = formEl.textContent?.match(/(credit|payment).?card(.?number)?|ccv|security.?code|cvv|cvc|csc/ig); - // De-dupe matches to avoid counting the same element more than once - const deDupedMatches = new Set(textMatches?.map(match => match.toLowerCase())); - - // We check for more than one to minimise false positives - this._isCCForm = Boolean(textMatches && deDupedMatches.size > 1); - return this._isCCForm; - } -} -var _default = exports.default = FormAnalyzer; - -},{"../autofill-utils.js":62,"../constants.js":65,"./matching-config/__generated__/compiled-matching-config.js":41,"./matching.js":43}],35:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.COUNTRY_NAMES_TO_CODES = exports.COUNTRY_CODES_TO_NAMES = void 0; -/** - * Country names object using 2-letter country codes to reference country name - * Derived from the Intl.DisplayNames implementation - * @source https://stackoverflow.com/a/70517921/1948947 - */ -const COUNTRY_CODES_TO_NAMES = exports.COUNTRY_CODES_TO_NAMES = { - AC: 'Ascension Island', - AD: 'Andorra', - AE: 'United Arab Emirates', - AF: 'Afghanistan', - AG: 'Antigua & Barbuda', - AI: 'Anguilla', - AL: 'Albania', - AM: 'Armenia', - AN: 'Curaçao', - AO: 'Angola', - AQ: 'Antarctica', - AR: 'Argentina', - AS: 'American Samoa', - AT: 'Austria', - AU: 'Australia', - AW: 'Aruba', - AX: 'Åland Islands', - AZ: 'Azerbaijan', - BA: 'Bosnia & Herzegovina', - BB: 'Barbados', - BD: 'Bangladesh', - BE: 'Belgium', - BF: 'Burkina Faso', - BG: 'Bulgaria', - BH: 'Bahrain', - BI: 'Burundi', - BJ: 'Benin', - BL: 'St. Barthélemy', - BM: 'Bermuda', - BN: 'Brunei', - BO: 'Bolivia', - BQ: 'Caribbean Netherlands', - BR: 'Brazil', - BS: 'Bahamas', - BT: 'Bhutan', - BU: 'Myanmar (Burma)', - BV: 'Bouvet Island', - BW: 'Botswana', - BY: 'Belarus', - BZ: 'Belize', - CA: 'Canada', - CC: 'Cocos (Keeling) Islands', - CD: 'Congo - Kinshasa', - CF: 'Central African Republic', - CG: 'Congo - Brazzaville', - CH: 'Switzerland', - CI: 'Côte d’Ivoire', - CK: 'Cook Islands', - CL: 'Chile', - CM: 'Cameroon', - CN: 'China mainland', - CO: 'Colombia', - CP: 'Clipperton Island', - CR: 'Costa Rica', - CS: 'Serbia', - CU: 'Cuba', - CV: 'Cape Verde', - CW: 'Curaçao', - CX: 'Christmas Island', - CY: 'Cyprus', - CZ: 'Czechia', - DD: 'Germany', - DE: 'Germany', - DG: 'Diego Garcia', - DJ: 'Djibouti', - DK: 'Denmark', - DM: 'Dominica', - DO: 'Dominican Republic', - DY: 'Benin', - DZ: 'Algeria', - EA: 'Ceuta & Melilla', - EC: 'Ecuador', - EE: 'Estonia', - EG: 'Egypt', - EH: 'Western Sahara', - ER: 'Eritrea', - ES: 'Spain', - ET: 'Ethiopia', - EU: 'European Union', - EZ: 'Eurozone', - FI: 'Finland', - FJ: 'Fiji', - FK: 'Falkland Islands', - FM: 'Micronesia', - FO: 'Faroe Islands', - FR: 'France', - FX: 'France', - GA: 'Gabon', - GB: 'United Kingdom', - GD: 'Grenada', - GE: 'Georgia', - GF: 'French Guiana', - GG: 'Guernsey', - GH: 'Ghana', - GI: 'Gibraltar', - GL: 'Greenland', - GM: 'Gambia', - GN: 'Guinea', - GP: 'Guadeloupe', - GQ: 'Equatorial Guinea', - GR: 'Greece', - GS: 'So. Georgia & So. Sandwich Isl.', - GT: 'Guatemala', - GU: 'Guam', - GW: 'Guinea-Bissau', - GY: 'Guyana', - HK: 'Hong Kong', - HM: 'Heard & McDonald Islands', - HN: 'Honduras', - HR: 'Croatia', - HT: 'Haiti', - HU: 'Hungary', - HV: 'Burkina Faso', - IC: 'Canary Islands', - ID: 'Indonesia', - IE: 'Ireland', - IL: 'Israel', - IM: 'Isle of Man', - IN: 'India', - IO: 'Chagos Archipelago', - IQ: 'Iraq', - IR: 'Iran', - IS: 'Iceland', - IT: 'Italy', - JE: 'Jersey', - JM: 'Jamaica', - JO: 'Jordan', - JP: 'Japan', - KE: 'Kenya', - KG: 'Kyrgyzstan', - KH: 'Cambodia', - KI: 'Kiribati', - KM: 'Comoros', - KN: 'St. Kitts & Nevis', - KP: 'North Korea', - KR: 'South Korea', - KW: 'Kuwait', - KY: 'Cayman Islands', - KZ: 'Kazakhstan', - LA: 'Laos', - LB: 'Lebanon', - LC: 'St. Lucia', - LI: 'Liechtenstein', - LK: 'Sri Lanka', - LR: 'Liberia', - LS: 'Lesotho', - LT: 'Lithuania', - LU: 'Luxembourg', - LV: 'Latvia', - LY: 'Libya', - MA: 'Morocco', - MC: 'Monaco', - MD: 'Moldova', - ME: 'Montenegro', - MF: 'St. Martin', - MG: 'Madagascar', - MH: 'Marshall Islands', - MK: 'North Macedonia', - ML: 'Mali', - MM: 'Myanmar (Burma)', - MN: 'Mongolia', - MO: 'Macao', - MP: 'Northern Mariana Islands', - MQ: 'Martinique', - MR: 'Mauritania', - MS: 'Montserrat', - MT: 'Malta', - MU: 'Mauritius', - MV: 'Maldives', - MW: 'Malawi', - MX: 'Mexico', - MY: 'Malaysia', - MZ: 'Mozambique', - NA: 'Namibia', - NC: 'New Caledonia', - NE: 'Niger', - NF: 'Norfolk Island', - NG: 'Nigeria', - NH: 'Vanuatu', - NI: 'Nicaragua', - NL: 'Netherlands', - NO: 'Norway', - NP: 'Nepal', - NR: 'Nauru', - NU: 'Niue', - NZ: 'New Zealand', - OM: 'Oman', - PA: 'Panama', - PE: 'Peru', - PF: 'French Polynesia', - PG: 'Papua New Guinea', - PH: 'Philippines', - PK: 'Pakistan', - PL: 'Poland', - PM: 'St. Pierre & Miquelon', - PN: 'Pitcairn Islands', - PR: 'Puerto Rico', - PS: 'Palestinian Territories', - PT: 'Portugal', - PW: 'Palau', - PY: 'Paraguay', - QA: 'Qatar', - QO: 'Outlying Oceania', - RE: 'Réunion', - RH: 'Zimbabwe', - RO: 'Romania', - RS: 'Serbia', - RU: 'Russia', - RW: 'Rwanda', - SA: 'Saudi Arabia', - SB: 'Solomon Islands', - SC: 'Seychelles', - SD: 'Sudan', - SE: 'Sweden', - SG: 'Singapore', - SH: 'St. Helena', - SI: 'Slovenia', - SJ: 'Svalbard & Jan Mayen', - SK: 'Slovakia', - SL: 'Sierra Leone', - SM: 'San Marino', - SN: 'Senegal', - SO: 'Somalia', - SR: 'Suriname', - SS: 'South Sudan', - ST: 'São Tomé & Príncipe', - SU: 'Russia', - SV: 'El Salvador', - SX: 'Sint Maarten', - SY: 'Syria', - SZ: 'Eswatini', - TA: 'Tristan da Cunha', - TC: 'Turks & Caicos Islands', - TD: 'Chad', - TF: 'French Southern Territories', - TG: 'Togo', - TH: 'Thailand', - TJ: 'Tajikistan', - TK: 'Tokelau', - TL: 'Timor-Leste', - TM: 'Turkmenistan', - TN: 'Tunisia', - TO: 'Tonga', - TP: 'Timor-Leste', - TR: 'Turkey', - TT: 'Trinidad & Tobago', - TV: 'Tuvalu', - TW: 'Taiwan', - TZ: 'Tanzania', - UA: 'Ukraine', - UG: 'Uganda', - UK: 'United Kingdom', - UM: 'U.S. Outlying Islands', - UN: 'United Nations', - US: 'United States', - UY: 'Uruguay', - UZ: 'Uzbekistan', - VA: 'Vatican City', - VC: 'St. Vincent & Grenadines', - VD: 'Vietnam', - VE: 'Venezuela', - VG: 'British Virgin Islands', - VI: 'U.S. Virgin Islands', - VN: 'Vietnam', - VU: 'Vanuatu', - WF: 'Wallis & Futuna', - WS: 'Samoa', - XA: 'Pseudo-Accents', - XB: 'Pseudo-Bidi', - XK: 'Kosovo', - YD: 'Yemen', - YE: 'Yemen', - YT: 'Mayotte', - YU: 'Serbia', - ZA: 'South Africa', - ZM: 'Zambia', - ZR: 'Congo - Kinshasa', - ZW: 'Zimbabwe', - ZZ: 'Unknown Region' -}; - -/** - * Country names object using country name to reference 2-letter country codes - * Derived from the solution above with - * Object.fromEntries(Object.entries(COUNTRY_CODES_TO_NAMES).map(entry => [entry[1], entry[0]])) - */ -const COUNTRY_NAMES_TO_CODES = exports.COUNTRY_NAMES_TO_CODES = { - 'Ascension Island': 'AC', - Andorra: 'AD', - 'United Arab Emirates': 'AE', - Afghanistan: 'AF', - 'Antigua & Barbuda': 'AG', - Anguilla: 'AI', - Albania: 'AL', - Armenia: 'AM', - 'Curaçao': 'CW', - Angola: 'AO', - Antarctica: 'AQ', - Argentina: 'AR', - 'American Samoa': 'AS', - Austria: 'AT', - Australia: 'AU', - Aruba: 'AW', - 'Åland Islands': 'AX', - Azerbaijan: 'AZ', - 'Bosnia & Herzegovina': 'BA', - Barbados: 'BB', - Bangladesh: 'BD', - Belgium: 'BE', - 'Burkina Faso': 'HV', - Bulgaria: 'BG', - Bahrain: 'BH', - Burundi: 'BI', - Benin: 'DY', - 'St. Barthélemy': 'BL', - Bermuda: 'BM', - Brunei: 'BN', - Bolivia: 'BO', - 'Caribbean Netherlands': 'BQ', - Brazil: 'BR', - Bahamas: 'BS', - Bhutan: 'BT', - 'Myanmar (Burma)': 'MM', - 'Bouvet Island': 'BV', - Botswana: 'BW', - Belarus: 'BY', - Belize: 'BZ', - Canada: 'CA', - 'Cocos (Keeling) Islands': 'CC', - 'Congo - Kinshasa': 'ZR', - 'Central African Republic': 'CF', - 'Congo - Brazzaville': 'CG', - Switzerland: 'CH', - 'Côte d’Ivoire': 'CI', - 'Cook Islands': 'CK', - Chile: 'CL', - Cameroon: 'CM', - 'China mainland': 'CN', - Colombia: 'CO', - 'Clipperton Island': 'CP', - 'Costa Rica': 'CR', - Serbia: 'YU', - Cuba: 'CU', - 'Cape Verde': 'CV', - 'Christmas Island': 'CX', - Cyprus: 'CY', - Czechia: 'CZ', - Germany: 'DE', - 'Diego Garcia': 'DG', - Djibouti: 'DJ', - Denmark: 'DK', - Dominica: 'DM', - 'Dominican Republic': 'DO', - Algeria: 'DZ', - 'Ceuta & Melilla': 'EA', - Ecuador: 'EC', - Estonia: 'EE', - Egypt: 'EG', - 'Western Sahara': 'EH', - Eritrea: 'ER', - Spain: 'ES', - Ethiopia: 'ET', - 'European Union': 'EU', - Eurozone: 'EZ', - Finland: 'FI', - Fiji: 'FJ', - 'Falkland Islands': 'FK', - Micronesia: 'FM', - 'Faroe Islands': 'FO', - France: 'FX', - Gabon: 'GA', - 'United Kingdom': 'UK', - Grenada: 'GD', - Georgia: 'GE', - 'French Guiana': 'GF', - Guernsey: 'GG', - Ghana: 'GH', - Gibraltar: 'GI', - Greenland: 'GL', - Gambia: 'GM', - Guinea: 'GN', - Guadeloupe: 'GP', - 'Equatorial Guinea': 'GQ', - Greece: 'GR', - 'So. Georgia & So. Sandwich Isl.': 'GS', - Guatemala: 'GT', - Guam: 'GU', - 'Guinea-Bissau': 'GW', - Guyana: 'GY', - 'Hong Kong': 'HK', - 'Heard & McDonald Islands': 'HM', - Honduras: 'HN', - Croatia: 'HR', - Haiti: 'HT', - Hungary: 'HU', - 'Canary Islands': 'IC', - Indonesia: 'ID', - Ireland: 'IE', - Israel: 'IL', - 'Isle of Man': 'IM', - India: 'IN', - 'Chagos Archipelago': 'IO', - Iraq: 'IQ', - Iran: 'IR', - Iceland: 'IS', - Italy: 'IT', - Jersey: 'JE', - Jamaica: 'JM', - Jordan: 'JO', - Japan: 'JP', - Kenya: 'KE', - Kyrgyzstan: 'KG', - Cambodia: 'KH', - Kiribati: 'KI', - Comoros: 'KM', - 'St. Kitts & Nevis': 'KN', - 'North Korea': 'KP', - 'South Korea': 'KR', - Kuwait: 'KW', - 'Cayman Islands': 'KY', - Kazakhstan: 'KZ', - Laos: 'LA', - Lebanon: 'LB', - 'St. Lucia': 'LC', - Liechtenstein: 'LI', - 'Sri Lanka': 'LK', - Liberia: 'LR', - Lesotho: 'LS', - Lithuania: 'LT', - Luxembourg: 'LU', - Latvia: 'LV', - Libya: 'LY', - Morocco: 'MA', - Monaco: 'MC', - Moldova: 'MD', - Montenegro: 'ME', - 'St. Martin': 'MF', - Madagascar: 'MG', - 'Marshall Islands': 'MH', - 'North Macedonia': 'MK', - Mali: 'ML', - Mongolia: 'MN', - Macao: 'MO', - 'Northern Mariana Islands': 'MP', - Martinique: 'MQ', - Mauritania: 'MR', - Montserrat: 'MS', - Malta: 'MT', - Mauritius: 'MU', - Maldives: 'MV', - Malawi: 'MW', - Mexico: 'MX', - Malaysia: 'MY', - Mozambique: 'MZ', - Namibia: 'NA', - 'New Caledonia': 'NC', - Niger: 'NE', - 'Norfolk Island': 'NF', - Nigeria: 'NG', - Vanuatu: 'VU', - Nicaragua: 'NI', - Netherlands: 'NL', - Norway: 'NO', - Nepal: 'NP', - Nauru: 'NR', - Niue: 'NU', - 'New Zealand': 'NZ', - Oman: 'OM', - Panama: 'PA', - Peru: 'PE', - 'French Polynesia': 'PF', - 'Papua New Guinea': 'PG', - Philippines: 'PH', - Pakistan: 'PK', - Poland: 'PL', - 'St. Pierre & Miquelon': 'PM', - 'Pitcairn Islands': 'PN', - 'Puerto Rico': 'PR', - 'Palestinian Territories': 'PS', - Portugal: 'PT', - Palau: 'PW', - Paraguay: 'PY', - Qatar: 'QA', - 'Outlying Oceania': 'QO', - 'Réunion': 'RE', - Zimbabwe: 'ZW', - Romania: 'RO', - Russia: 'SU', - Rwanda: 'RW', - 'Saudi Arabia': 'SA', - 'Solomon Islands': 'SB', - Seychelles: 'SC', - Sudan: 'SD', - Sweden: 'SE', - Singapore: 'SG', - 'St. Helena': 'SH', - Slovenia: 'SI', - 'Svalbard & Jan Mayen': 'SJ', - Slovakia: 'SK', - 'Sierra Leone': 'SL', - 'San Marino': 'SM', - Senegal: 'SN', - Somalia: 'SO', - Suriname: 'SR', - 'South Sudan': 'SS', - 'São Tomé & Príncipe': 'ST', - 'El Salvador': 'SV', - 'Sint Maarten': 'SX', - Syria: 'SY', - Eswatini: 'SZ', - 'Tristan da Cunha': 'TA', - 'Turks & Caicos Islands': 'TC', - Chad: 'TD', - 'French Southern Territories': 'TF', - Togo: 'TG', - Thailand: 'TH', - Tajikistan: 'TJ', - Tokelau: 'TK', - 'Timor-Leste': 'TP', - Turkmenistan: 'TM', - Tunisia: 'TN', - Tonga: 'TO', - Turkey: 'TR', - 'Trinidad & Tobago': 'TT', - Tuvalu: 'TV', - Taiwan: 'TW', - Tanzania: 'TZ', - Ukraine: 'UA', - Uganda: 'UG', - 'U.S. Outlying Islands': 'UM', - 'United Nations': 'UN', - 'United States': 'US', - Uruguay: 'UY', - Uzbekistan: 'UZ', - 'Vatican City': 'VA', - 'St. Vincent & Grenadines': 'VC', - Vietnam: 'VN', - Venezuela: 'VE', - 'British Virgin Islands': 'VG', - 'U.S. Virgin Islands': 'VI', - 'Wallis & Futuna': 'WF', - Samoa: 'WS', - 'Pseudo-Accents': 'XA', - 'Pseudo-Bidi': 'XB', - Kosovo: 'XK', - Yemen: 'YE', - Mayotte: 'YT', - 'South Africa': 'ZA', - Zambia: 'ZM', - 'Unknown Region': 'ZZ' -}; - -},{}],36:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.prepareFormValuesForStorage = exports.inferCountryCodeFromElement = exports.getUnifiedExpiryDate = exports.getMMAndYYYYFromString = exports.getCountryName = exports.getCountryDisplayName = exports.formatPhoneNumber = exports.formatFullName = exports.formatCCYear = void 0; -var _matching = require("./matching.js"); -var _countryNames = require("./countryNames.js"); -// Matches strings like mm/yy, mm-yyyy, mm-aa, 12 / 2024 -const DATE_SEPARATOR_REGEX = /\b((.)\2{1,3}|\d+)(?\s?[/\s.\-_—–]\s?)((.)\5{1,3}|\d+)\b/i; -// Matches 4 non-digit repeated characters (YYYY or AAAA) or 4 digits (2022) -const FOUR_DIGIT_YEAR_REGEX = /(\D)\1{3}|\d{4}/i; - -/** - * Format the cc year to best adapt to the input requirements (YY vs YYYY) - * @param {HTMLInputElement} input - * @param {string} year - * @param {import("./Form").Form} form - * @returns {string} - */ -const formatCCYear = (input, year, form) => { - const selector = form.matching.cssSelector('formInputsSelector'); - if (input.maxLength === 4 || (0, _matching.checkPlaceholderAndLabels)(input, FOUR_DIGIT_YEAR_REGEX, form.form, selector)) return year; - return `${Number(year) - 2000}`; -}; - -/** - * Get a unified expiry date with separator - * @param {HTMLInputElement} input - * @param {string} month - * @param {string} year - * @param {import("./Form").Form} form - * @returns {string} - */ -exports.formatCCYear = formatCCYear; -const getUnifiedExpiryDate = (input, month, year, form) => { - const formattedYear = formatCCYear(input, year, form); - const paddedMonth = `${month}`.padStart(2, '0'); - const cssSelector = form.matching.cssSelector('formInputsSelector'); - const separator = (0, _matching.matchInPlaceholderAndLabels)(input, DATE_SEPARATOR_REGEX, form.form, cssSelector)?.groups?.separator || '/'; - return `${paddedMonth}${separator}${formattedYear}`; -}; -exports.getUnifiedExpiryDate = getUnifiedExpiryDate; -const formatFullName = _ref => { - let { - firstName = '', - middleName = '', - lastName = '' - } = _ref; - return `${firstName} ${middleName ? middleName + ' ' : ''}${lastName}`.trim(); -}; - -/** - * Tries to look up a human-readable country name from the country code - * @param {string} locale - * @param {string} addressCountryCode - * @return {string} - Returns the country code if we can't find a name - */ -exports.formatFullName = formatFullName; -const getCountryDisplayName = (locale, addressCountryCode) => { - try { - const regionNames = new Intl.DisplayNames([locale], { - type: 'region' - }); - // Adding this ts-ignore to prevent having to change this implementation. - // @ts-ignore - return regionNames.of(addressCountryCode); - } catch (e) { - return _countryNames.COUNTRY_CODES_TO_NAMES[addressCountryCode] || addressCountryCode; + throw new Error("unreachable"); } -}; - -/** - * Tries to infer the element locale or returns 'en' - * @param {HTMLInputElement | HTMLSelectElement} el - * @return {string | 'en'} - */ -exports.getCountryDisplayName = getCountryDisplayName; -const inferElementLocale = el => el.lang || el.form?.lang || document.body.lang || document.documentElement.lang || 'en'; - -/** - * Tries to format the country code into a localised country name - * @param {HTMLInputElement | HTMLSelectElement} el - * @param {{addressCountryCode?: string}} options - */ -const getCountryName = function (el) { - let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - const { - addressCountryCode - } = options; - if (!addressCountryCode) return ''; - - // Try to infer the field language or fallback to en - const elLocale = inferElementLocale(el); - const localisedCountryName = getCountryDisplayName(elLocale, addressCountryCode); + var MissingHandler = class extends Error { + /** + * @param {string} message + * @param {string} handlerName + */ + constructor(message, handlerName) { + super(message); + this.handlerName = handlerName; + } + }; - // If it's a select el we try to find a suitable match to autofill - if (el.nodeName === 'SELECT') { - const englishCountryName = getCountryDisplayName('en', addressCountryCode); - // This regex matches both the localised and English country names - const countryNameRegex = new RegExp(String.raw`${localisedCountryName.replace(/ /g, '.?')}|${englishCountryName.replace(/ /g, '.?')}`, 'i'); - const countryCodeRegex = new RegExp(String.raw`\b${addressCountryCode}\b`, 'i'); + // src/deviceApiCalls/transports/apple.transport.js + var AppleTransport = class extends DeviceApiTransport { + /** @param {GlobalConfig} globalConfig */ + constructor(globalConfig) { + super(); + this.config = globalConfig; + const webkitConfig = new WebkitMessagingConfig({ + hasModernWebkitAPI: this.config.hasModernWebkitAPI, + webkitMessageHandlerNames: this.config.webkitMessageHandlerNames, + secret: this.config.secret + }); + this.messaging = new Messaging(webkitConfig); + } + async send(deviceApiCall) { + try { + if (deviceApiCall.id) { + return await this.messaging.request(deviceApiCall.method, deviceApiCall.params || void 0); + } else { + return this.messaging.notify(deviceApiCall.method, deviceApiCall.params || void 0); + } + } catch (e) { + if (e instanceof MissingHandler) { + if (this.config.isDDGTestMode) { + console.log("MissingWebkitHandler error for:", deviceApiCall.method); + } + throw new Error("unimplemented handler: " + deviceApiCall.method); + } else { + throw e; + } + } + } + }; - // We check the country code first because it's more accurate - if (el instanceof HTMLSelectElement) { - for (const option of el.options) { - if (countryCodeRegex.test(option.value)) { - return option.value; + // src/deviceApiCalls/transports/android.transport.js + var AndroidTransport = class extends DeviceApiTransport { + /** @param {GlobalConfig} globalConfig */ + constructor(globalConfig) { + super(); + /** @type {GlobalConfig} */ + __publicField(this, "config"); + this.config = globalConfig; + if (this.config.isDDGTestMode) { + if (typeof window.BrowserAutofill?.getAutofillData !== "function") { + console.warn("window.BrowserAutofill.getAutofillData missing"); } + if (typeof window.BrowserAutofill?.storeFormData !== "function") { + console.warn("window.BrowserAutofill.storeFormData missing"); + } + } + } + /** + * @param {import("../../../packages/device-api").DeviceApiCall} deviceApiCall + * @returns {Promise} + */ + async send(deviceApiCall) { + if (deviceApiCall instanceof GetRuntimeConfigurationCall) { + return androidSpecificRuntimeConfiguration(this.config); + } + if (deviceApiCall instanceof GetAvailableInputTypesCall) { + return androidSpecificAvailableInputTypes(this.config); + } + if (deviceApiCall instanceof GetIncontextSignupDismissedAtCall) { + window.BrowserAutofill.getIncontextSignupDismissedAt(JSON.stringify(deviceApiCall.params)); + return waitForResponse(deviceApiCall.id, this.config); + } + if (deviceApiCall instanceof SetIncontextSignupPermanentlyDismissedAtCall) { + return window.BrowserAutofill.setIncontextSignupPermanentlyDismissedAt(JSON.stringify(deviceApiCall.params)); + } + if (deviceApiCall instanceof StartEmailProtectionSignupCall) { + return window.BrowserAutofill.startEmailProtectionSignup(JSON.stringify(deviceApiCall.params)); } - for (const option of el.options) { - if (countryNameRegex.test(option.value) || countryNameRegex.test(option.innerText)) return option.value; + if (deviceApiCall instanceof CloseEmailProtectionTabCall) { + return window.BrowserAutofill.closeEmailProtectionTab(JSON.stringify(deviceApiCall.params)); } + if (deviceApiCall instanceof ShowInContextEmailProtectionSignupPromptCall) { + window.BrowserAutofill.showInContextEmailProtectionSignupPrompt(JSON.stringify(deviceApiCall.params)); + return waitForResponse(deviceApiCall.id, this.config); + } + if (deviceApiCall instanceof GetAutofillDataCall) { + window.BrowserAutofill.getAutofillData(JSON.stringify(deviceApiCall.params)); + return waitForResponse(deviceApiCall.id, this.config); + } + if (deviceApiCall instanceof StoreFormDataCall) { + return window.BrowserAutofill.storeFormData(JSON.stringify(deviceApiCall.params)); + } + throw new Error("android: not implemented: " + deviceApiCall.method); } + }; + function waitForResponse(expectedResponse, config) { + return new Promise((resolve) => { + const handler = (e) => { + if (!config.isDDGTestMode) { + if (e.origin !== "") { + return; + } + } + if (!e.data) { + return; + } + if (typeof e.data !== "string") { + if (config.isDDGTestMode) { + console.log("\u274C event.data was not a string. Expected a string so that it can be JSON parsed"); + } + return; + } + try { + let data = JSON.parse(e.data); + if (data.type === expectedResponse) { + window.removeEventListener("message", handler); + return resolve(data); + } + if (config.isDDGTestMode) { + console.log(`\u274C event.data.type was '${data.type}', which didnt match '${expectedResponse}'`, JSON.stringify(data)); + } + } catch (e2) { + window.removeEventListener("message", handler); + if (config.isDDGTestMode) { + console.log("\u274C Could not JSON.parse the response"); + } + } + }; + window.addEventListener("message", handler); + }); } - return localisedCountryName; -}; - -/** - * Try to get a map of localised country names to code, or falls back to the English map - * @param {HTMLInputElement | HTMLSelectElement} el - */ -exports.getCountryName = getCountryName; -const getLocalisedCountryNamesToCodes = el => { - if (typeof Intl.DisplayNames !== 'function') return _countryNames.COUNTRY_NAMES_TO_CODES; - - // Try to infer the field language or fallback to en - const elLocale = inferElementLocale(el); - return Object.fromEntries(Object.entries(_countryNames.COUNTRY_CODES_TO_NAMES).map(_ref2 => { - let [code] = _ref2; - return [getCountryDisplayName(elLocale, code), code]; - })); -}; - -/** - * Try to infer a country code from an element we identified as identities.addressCountryCode - * @param {HTMLInputElement | HTMLSelectElement} el - * @return {string} - */ -const inferCountryCodeFromElement = el => { - if (_countryNames.COUNTRY_CODES_TO_NAMES[el.value]) return el.value; - if (_countryNames.COUNTRY_NAMES_TO_CODES[el.value]) return _countryNames.COUNTRY_NAMES_TO_CODES[el.value]; - const localisedCountryNamesToCodes = getLocalisedCountryNamesToCodes(el); - if (localisedCountryNamesToCodes[el.value]) return localisedCountryNamesToCodes[el.value]; - if (el instanceof HTMLSelectElement) { - const selectedText = el.selectedOptions[0]?.text; - if (_countryNames.COUNTRY_CODES_TO_NAMES[selectedText]) return selectedText; - if (_countryNames.COUNTRY_NAMES_TO_CODES[selectedText]) return localisedCountryNamesToCodes[selectedText]; - if (localisedCountryNamesToCodes[selectedText]) return localisedCountryNamesToCodes[selectedText]; - } - return ''; -}; - -/** - * Gets separate expiration month and year from a single string - * @param {string} expiration - * @return {{expirationYear: string, expirationMonth: string}} - */ -exports.inferCountryCodeFromElement = inferCountryCodeFromElement; -const getMMAndYYYYFromString = expiration => { - /** @type {string[]} */ - const values = expiration.match(/(\d+)/g) || []; - return values?.reduce((output, current) => { - if (Number(current) > 12) { - output.expirationYear = current.padStart(4, '20'); - } else { - output.expirationMonth = current.padStart(2, '0'); + function androidSpecificRuntimeConfiguration(globalConfig) { + if (!globalConfig.userPreferences) { + throw new Error("globalConfig.userPreferences not supported yet on Android"); } - return output; - }, { - expirationYear: '', - expirationMonth: '' - }); -}; - -/** - * @param {InternalDataStorageObject} credentials - * @return {boolean} - */ -exports.getMMAndYYYYFromString = getMMAndYYYYFromString; -const shouldStoreCredentials = _ref3 => { - let { - credentials - } = _ref3; - return Boolean(credentials.password); -}; - -/** - * @param {InternalDataStorageObject} credentials - * @return {boolean} - */ -const shouldStoreIdentities = _ref4 => { - let { - identities - } = _ref4; - return Boolean((identities.firstName || identities.fullName) && identities.addressStreet && identities.addressCity); -}; - -/** - * @param {InternalDataStorageObject} credentials - * @return {boolean} - */ -const shouldStoreCreditCards = _ref5 => { - let { - creditCards - } = _ref5; - if (!creditCards.cardNumber) return false; - if (creditCards.cardSecurityCode) return true; - // Some forms (Amazon) don't have the cvv, so we still save if there's the expiration - if (creditCards.expiration) return true; - // Expiration can also be two separate values - return Boolean(creditCards.expirationYear && creditCards.expirationMonth); -}; - -/** - * Removes formatting characters from phone numbers, only leaves digits and the + sign - * @param {String} phone - * @returns {String} - */ -const formatPhoneNumber = phone => phone.replaceAll(/[^0-9|+]/g, ''); - -/** - * Formats form data into an object to send to the device for storage - * If values are insufficient for a complete entry, they are discarded - * @param {InternalDataStorageObject} formValues - * @return {DataStorageObject} - */ -exports.formatPhoneNumber = formatPhoneNumber; -const prepareFormValuesForStorage = formValues => { - /** @type {Partial} */ - let { - credentials, - identities, - creditCards - } = formValues; - - // If we have an identity name but not a card name, copy it over there - if (!creditCards.cardName && (identities?.fullName || identities?.firstName)) { - creditCards.cardName = identities?.fullName || formatFullName(identities); + return { + success: { + // @ts-ignore + contentScope: globalConfig.contentScope, + // @ts-ignore + userPreferences: globalConfig.userPreferences, + // @ts-ignore + userUnprotectedDomains: globalConfig.userUnprotectedDomains, + // @ts-ignore + availableInputTypes: globalConfig.availableInputTypes + } + }; } - - /** Fixes for credentials **/ - // Don't store if there isn't enough data - if (shouldStoreCredentials(formValues)) { - // If we don't have a username to match a password, let's see if the email is available - if (credentials.password && !credentials.username && identities.emailAddress) { - credentials.username = identities.emailAddress; - } - } else { - credentials = undefined; + function androidSpecificAvailableInputTypes(globalConfig) { + if (!globalConfig.availableInputTypes) { + throw new Error("globalConfig.availableInputTypes not supported yet on Android"); + } + return { + success: globalConfig.availableInputTypes + }; } - /** Fixes for identities **/ - // Don't store if there isn't enough data - if (shouldStoreIdentities(formValues)) { - if (identities.fullName) { - // when forms have both first/last and fullName we keep the individual values and drop the fullName - if (!(identities.firstName && identities.lastName)) { - // If the fullname can be easily split into two, we'll store it as first and last - const nameParts = identities.fullName.trim().split(/\s+/); - if (nameParts.length === 2) { - identities.firstName = nameParts[0]; - identities.lastName = nameParts[1]; - } else { - // If we can't split it, just store it as first name - identities.firstName = identities.fullName; + // src/Settings.js + var _Settings = class { + /** + * @param {GlobalConfig} config + * @param {DeviceApi} deviceApi + */ + constructor(config, deviceApi) { + /** @type {GlobalConfig} */ + __publicField(this, "globalConfig"); + /** @type {DeviceApi} */ + __publicField(this, "deviceApi"); + /** @type {AutofillFeatureToggles | null} */ + __publicField(this, "_featureToggles", null); + /** @type {AvailableInputTypes | null} */ + __publicField(this, "_availableInputTypes", null); + /** @type {RuntimeConfiguration | null | undefined} */ + __publicField(this, "_runtimeConfiguration", null); + /** @type {boolean | null} */ + __publicField(this, "_enabled", null); + this.deviceApi = deviceApi; + this.globalConfig = config; + } + /** + * Feature toggles are delivered as part of the Runtime Configuration - a flexible design that + * allows data per user + remote config to be accessed together. + * + * Once we access the Runtime Configuration, we then extract the autofill-specific settings via + * `runtimeConfig.userPreferences.features.autofill.settings` and validate that separately. + * + * The 2-step validation occurs because RuntimeConfiguration will be coming from a shared library + * and does not know about the shape of Autofill specific settings. + * + * @returns {Promise} + */ + async getFeatureToggles() { + try { + const runtimeConfig = await this._getRuntimeConfiguration(); + const autofillSettings = validate(runtimeConfig.userPreferences?.features?.autofill?.settings, autofillSettingsSchema); + return autofillSettings.featureToggles; + } catch (e) { + if (this.globalConfig.isDDGTestMode) { + console.log("isDDGTestMode: getFeatureToggles: \u274C", e); } + return _Settings.defaults.featureToggles; } - delete identities.fullName; } - if (identities.phone) { - identities.phone = formatPhoneNumber(identities.phone); - } - } else { - identities = undefined; - } - - /** Fixes for credit cards **/ - // Don't store if there isn't enough data - if (shouldStoreCreditCards(formValues)) { - if (creditCards.expiration) { - const { - expirationMonth, - expirationYear - } = getMMAndYYYYFromString(creditCards.expiration); - creditCards.expirationMonth = expirationMonth; - creditCards.expirationYear = expirationYear; - delete creditCards.expiration; - } - creditCards.expirationYear = creditCards.expirationYear?.padStart(4, '20'); - if (creditCards.cardNumber) { - creditCards.cardNumber = creditCards.cardNumber.replace(/\D/g, ''); - } - } else { - creditCards = undefined; - } - return { - credentials, - identities, - creditCards - }; -}; -exports.prepareFormValuesForStorage = prepareFormValuesForStorage; - -},{"./countryNames.js":35,"./matching.js":43}],37:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.getIconStylesBase = exports.getIconStylesAutofilled = exports.getIconStylesAlternate = void 0; -var _inputTypeConfig = require("./inputTypeConfig.js"); -/** - * Returns the css-ready base64 encoding of the icon for the given input - * @param {HTMLInputElement} input - * @param {import("./Form").Form} form - * @param {'base' | 'filled' | 'alternate'} type - * @return {string} - */ -const getIcon = function (input, form) { - let type = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'base'; - const config = (0, _inputTypeConfig.getInputConfig)(input); - if (type === 'base') { - return config.getIconBase(input, form); - } - if (type === 'filled') { - return config.getIconFilled(input, form); - } - if (type === 'alternate') { - return config.getIconAlternate(input, form); - } - return ''; -}; - -/** - * Returns an object with styles to be applied inline - * @param {HTMLInputElement} input - * @param {String} icon - * @return {Object} - */ -const getBasicStyles = (input, icon) => ({ - // Height must be > 0 to account for fields initially hidden - 'background-size': `auto ${input.offsetHeight <= 30 && input.offsetHeight > 0 ? '100%' : '24px'}`, - 'background-position': 'center right', - 'background-repeat': 'no-repeat', - 'background-origin': 'content-box', - 'background-image': `url(${icon})`, - 'transition': 'background 0s' -}); - -/** - * Get inline styles for the injected icon, base state - * @param {HTMLInputElement} input - * @param {import("./Form").Form} form - * @return {Object} - */ -const getIconStylesBase = (input, form) => { - const icon = getIcon(input, form); - if (!icon) return {}; - return getBasicStyles(input, icon); -}; - -/** - * Get inline styles for the injected icon, alternate state - * @param {HTMLInputElement} input - * @param {import("./Form").Form} form - * @return {Object} - */ -exports.getIconStylesBase = getIconStylesBase; -const getIconStylesAlternate = (input, form) => { - const icon = getIcon(input, form, 'alternate'); - if (!icon) return {}; - return { - ...getBasicStyles(input, icon) - }; -}; - -/** - * Get inline styles for the injected icon, autofilled state - * @param {HTMLInputElement} input - * @param {import("./Form").Form} form - * @return {Object} - */ -exports.getIconStylesAlternate = getIconStylesAlternate; -const getIconStylesAutofilled = (input, form) => { - const icon = getIcon(input, form, 'filled'); - const iconStyle = icon ? getBasicStyles(input, icon) : {}; - return { - ...iconStyle, - 'background-color': '#F8F498', - 'color': '#333333' - }; -}; -exports.getIconStylesAutofilled = getIconStylesAutofilled; - -},{"./inputTypeConfig.js":38}],38:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.isFieldDecorated = exports.getInputConfigFromType = exports.getInputConfig = exports.canBeInteractedWith = void 0; -var _logoSvg = require("./logo-svg.js"); -var ddgPasswordIcons = _interopRequireWildcard(require("../UI/img/ddgPasswordIcon.js")); -var _matching = require("./matching.js"); -var _Credentials = require("../InputTypes/Credentials.js"); -var _CreditCard = require("../InputTypes/CreditCard.js"); -var _Identity = require("../InputTypes/Identity.js"); -var _constants = require("../constants.js"); -function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } -function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } -/** - * Get the icon for the identities (currently only Dax for emails) - * @param {HTMLInputElement} input - * @param {import("./Form").Form} form - * @return {string} - */ -const getIdentitiesIcon = (input, _ref) => { - let { - device - } = _ref; - if (!canBeInteractedWith(input)) return ''; - - // In Firefox web_accessible_resources could leak a unique user identifier, so we avoid it here - const { - isDDGApp, - isFirefox, - isExtension - } = device.globalConfig; - const subtype = (0, _matching.getInputSubtype)(input); - if (device.inContextSignup?.isAvailable(subtype)) { - if (isDDGApp || isFirefox) { - return _logoSvg.daxGrayscaleBase64; - } else if (isExtension) { - return chrome.runtime.getURL('img/logo-small-grayscale.svg'); + /** + * If the platform in question is happy to derive it's 'enabled' state from the RuntimeConfiguration, + * then they should use this. Currently only Windows supports this, but we aim to move all platforms to + * support this going forward. + * @returns {Promise} + */ + async getEnabled() { + try { + const runtimeConfig = await this._getRuntimeConfiguration(); + const enabled = autofillEnabled(runtimeConfig); + return enabled; + } catch (e) { + if (this.globalConfig.isDDGTestMode) { + console.log("isDDGTestMode: getEnabled: \u274C", e); + } + return null; + } } - } - if (subtype === 'emailAddress' && device.isDeviceSignedIn()) { - if (isDDGApp || isFirefox) { - return _logoSvg.daxBase64; - } else if (isExtension) { - return chrome.runtime.getURL('img/logo-small.svg'); + /** + * Get runtime configuration, but only once. + * + * Some platforms may be reading this directly from inlined variables, whilst others + * may make a DeviceApiCall. + * + * Currently, it's only read once - but we should be open to the idea that we may need + * this to be called multiple times in the future. + * + * @returns {Promise} + * @throws + * @private + */ + async _getRuntimeConfiguration() { + if (this._runtimeConfiguration) + return this._runtimeConfiguration; + const runtimeConfig = await this.deviceApi.request(new GetRuntimeConfigurationCall(null)); + this._runtimeConfiguration = runtimeConfig; + return this._runtimeConfiguration; } - } - return ''; -}; - -/** - * Get the alternate icon for the identities (currently only Dax for emails) - * @param {HTMLInputElement} input - * @param {import("./Form").Form} form - * @return {string} - */ -const getIdentitiesAlternateIcon = (input, _ref2) => { - let { - device - } = _ref2; - if (!canBeInteractedWith(input)) return ''; - - // In Firefox web_accessible_resources could leak a unique user identifier, so we avoid it here - const { - isDDGApp, - isFirefox, - isExtension - } = device.globalConfig; - const subtype = (0, _matching.getInputSubtype)(input); - const isIncontext = device.inContextSignup?.isAvailable(subtype); - const isEmailProtection = subtype === 'emailAddress' && device.isDeviceSignedIn(); - if (isIncontext || isEmailProtection) { - if (isDDGApp || isFirefox) { - return _logoSvg.daxBase64; - } else if (isExtension) { - return chrome.runtime.getURL('img/logo-small.svg'); + /** + * Available Input Types are boolean indicators to represent which input types the + * current **user** has data available for. + * + * @returns {Promise} + */ + async getAvailableInputTypes() { + try { + if (this.globalConfig.isTopFrame) { + return _Settings.defaults.availableInputTypes; + } + return await this.deviceApi.request(new GetAvailableInputTypesCall(null)); + } catch (e) { + if (this.globalConfig.isDDGTestMode) { + console.log("isDDGTestMode: getAvailableInputTypes: \u274C", e); + } + return _Settings.defaults.availableInputTypes; + } } - } - return ''; -}; - -/** - * Checks whether a field is readonly or disabled - * @param {HTMLInputElement} input - * @return {boolean} - */ -const canBeInteractedWith = input => !input.readOnly && !input.disabled; - -/** - * Checks if the input can be decorated and we have the needed data - * @param {HTMLInputElement} input - * @param {import("../DeviceInterface/InterfacePrototype").default} device - * @returns {Promise} - */ -exports.canBeInteractedWith = canBeInteractedWith; -const canBeAutofilled = async (input, device) => { - if (!canBeInteractedWith(input)) return false; - const mainType = (0, _matching.getInputMainType)(input); - const subtype = (0, _matching.getInputSubtype)(input); - await device.settings.populateDataIfNeeded({ - mainType, - subtype - }); - const canAutofill = device.settings.canAutofillType({ - mainType, - subtype - }, device.inContextSignup); - return Boolean(canAutofill); -}; - -/** - * A map of config objects. These help by centralising here some complexity - * @type {InputTypeConfig} - */ -const inputTypeConfig = { - /** @type {CredentialsInputTypeConfig} */ - credentials: { - type: 'credentials', - displayName: 'passwords', - getIconBase: (input, _ref3) => { - let { - device - } = _ref3; - if (!canBeInteractedWith(input)) return ''; - if (device.settings.featureToggles.inlineIcon_credentials) { - const subtype = (0, _matching.getInputSubtype)(input); - const variant = (0, _matching.getInputVariant)(input); - if (subtype === 'password' && variant === 'new') { - return ddgPasswordIcons.ddgPasswordGenIconBase; - } - return ddgPasswordIcons.ddgPasswordIconBase; - } - return ''; - }, - getIconFilled: (input, _ref4) => { - let { - device - } = _ref4; - if (device.settings.featureToggles.inlineIcon_credentials) { - const subtype = (0, _matching.getInputSubtype)(input); - const variant = (0, _matching.getInputVariant)(input); - if (subtype === 'password' && variant === 'new') { - return ddgPasswordIcons.ddgPasswordGenIconFilled; - } - return ddgPasswordIcons.ddgPasswordIconFilled; - } - return ''; - }, - getIconAlternate: () => '', - shouldDecorate: async (input, _ref5) => { - let { - isLogin, - isHybrid, - device - } = _ref5; - const subtype = (0, _matching.getInputSubtype)(input); - const variant = (0, _matching.getInputVariant)(input); - - // Check first for password generation and the password.new scoring - if (device.settings.featureToggles.password_generation) { - if (subtype === 'password' && variant === 'new') { - return canBeInteractedWith(input); + /** + * To 'refresh' settings means to re-call APIs to determine new state. This may + * only occur once per page, but it must be done before any page scanning/decorating can happen + * + * @returns {Promise<{ + * availableInputTypes: AvailableInputTypes, + * featureToggles: AutofillFeatureToggles, + * enabled: boolean | null + * }>} + */ + async refresh() { + this.setEnabled(await this.getEnabled()); + this.setFeatureToggles(await this.getFeatureToggles()); + this.setAvailableInputTypes(await this.getAvailableInputTypes()); + if (typeof this.enabled === "boolean") { + if (!this.enabled) { + return _Settings.defaults; } } - - // if we are on a 'login' page, check if we have data to autofill the field - if (isLogin || isHybrid || variant === 'current') { - return canBeAutofilled(input, device); + return { + featureToggles: this.featureToggles, + availableInputTypes: this.availableInputTypes, + enabled: this.enabled + }; + } + /** + * Checks if input type is one which we can't autofill + * @param {{ + * mainType: SupportedMainTypes + * subtype: import('./Form/matching.js').SupportedSubTypes | "unknown" + * }} types + * @returns {boolean} + */ + isTypeUnavailable({ mainType, subtype }) { + if (mainType === "unknown") + return true; + if (!this.featureToggles[`inputType_${mainType}`] && subtype !== "emailAddress") { + return true; } return false; - }, - dataType: 'Credentials', - tooltipItem: data => (0, _Credentials.createCredentialsTooltipItem)(data) - }, - /** @type {CreditCardsInputTypeConfig} */ - creditCards: { - type: 'creditCards', - displayName: 'credit cards', - getIconBase: () => '', - getIconFilled: () => '', - getIconAlternate: () => '', - shouldDecorate: async (input, _ref6) => { - let { - device - } = _ref6; - return canBeAutofilled(input, device); - }, - dataType: 'CreditCards', - tooltipItem: data => new _CreditCard.CreditCardTooltipItem(data) - }, - /** @type {IdentitiesInputTypeConfig} */ - identities: { - type: 'identities', - displayName: 'identities', - getIconBase: getIdentitiesIcon, - getIconFilled: getIdentitiesIcon, - getIconAlternate: getIdentitiesAlternateIcon, - shouldDecorate: async (input, _ref7) => { - let { - device - } = _ref7; - return canBeAutofilled(input, device); - }, - dataType: 'Identities', - tooltipItem: data => new _Identity.IdentityTooltipItem(data) - }, - /** @type {UnknownInputTypeConfig} */ - unknown: { - type: 'unknown', - displayName: '', - getIconBase: () => '', - getIconFilled: () => '', - getIconAlternate: () => '', - shouldDecorate: async () => false, - dataType: '', - tooltipItem: _data => { - throw new Error('unreachable - setting tooltip to unknown field type'); } - } -}; - -/** - * Retrieves configs from an input el - * @param {HTMLInputElement} input - * @returns {InputTypeConfigs} - */ -const getInputConfig = input => { - const inputType = (0, _matching.getInputType)(input); - return getInputConfigFromType(inputType); -}; - -/** - * Retrieves configs from an input type - * @param {import('./matching').SupportedTypes} inputType - * @returns {InputTypeConfigs} - */ -exports.getInputConfig = getInputConfig; -const getInputConfigFromType = inputType => { - const inputMainType = (0, _matching.getMainTypeFromType)(inputType); - return inputTypeConfig[inputMainType]; -}; - -/** - * Given an input field checks wheter it was previously decorated - * @param {HTMLInputElement} input - * @returns {Boolean} - */ -exports.getInputConfigFromType = getInputConfigFromType; -const isFieldDecorated = input => { - return input.hasAttribute(_constants.constants.ATTR_INPUT_TYPE); -}; -exports.isFieldDecorated = isFieldDecorated; - -},{"../InputTypes/Credentials.js":45,"../InputTypes/CreditCard.js":46,"../InputTypes/Identity.js":47,"../UI/img/ddgPasswordIcon.js":60,"../constants.js":65,"./logo-svg.js":40,"./matching.js":43}],39:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.extractElementStrings = exports.EXCLUDED_TAGS = void 0; -var _matching = require("./matching.js"); -const EXCLUDED_TAGS = exports.EXCLUDED_TAGS = ['BR', 'SCRIPT', 'NOSCRIPT', 'OPTION', 'STYLE']; - -/** - * Extract all strings of an element's children to an array. - * "element.textContent" is a string which is merged of all children nodes, - * which can cause issues with things like script tags etc. - * - * @param {Element} element - * A DOM element to be extracted. - * @returns {string[]} - * All strings in an element. - */ -const extractElementStrings = element => { - const strings = new Set(); - const _extractElementStrings = el => { - if (EXCLUDED_TAGS.includes(el.tagName)) { - return; + /** + * Requests data from remote + * @returns {Promise<>} + */ + async populateData() { + const availableInputTypesFromRemote = await this.getAvailableInputTypes(); + this.setAvailableInputTypes(availableInputTypesFromRemote); } - - // only take the string when it's an explicit text node - if (el.nodeType === el.TEXT_NODE || !el.childNodes.length) { - let trimmedText = (0, _matching.removeExcessWhitespace)(el.textContent); - if (trimmedText) { - strings.add(trimmedText); + /** + * Requests data from remote if not available + * @param {{ + * mainType: SupportedMainTypes + * subtype: import('./Form/matching.js').SupportedSubTypes | "unknown" + * }} types + * @returns {Promise} + */ + async populateDataIfNeeded({ mainType, subtype }) { + if (this.isTypeUnavailable({ mainType, subtype })) + return false; + if (this.availableInputTypes?.[mainType] === void 0) { + await this.populateData(); + return true; } - return; + return false; } - for (let node of el.childNodes) { - let nodeType = node.nodeType; - if (nodeType !== node.ELEMENT_NODE && nodeType !== node.TEXT_NODE) { - continue; + /** + * Checks if items will show in the autofill menu, including in-context signup. + * Triggers side-effect if input types is not already available. + * @param {{ + * mainType: SupportedMainTypes + * subtype: import('./Form/matching.js').SupportedSubTypes | "unknown" + * }} types + * @param {import("./InContextSignup.js").InContextSignup?} inContextSignup + * @returns {boolean} + */ + canAutofillType({ mainType, subtype }, inContextSignup) { + if (this.isTypeUnavailable({ mainType, subtype })) + return false; + const isEmailProtectionEnabled = this.featureToggles.emailProtection && this.availableInputTypes.email; + if (subtype === "emailAddress" && isEmailProtectionEnabled) { + return true; + } + if (inContextSignup?.isAvailable(subtype)) { + return true; } - _extractElementStrings(node); + if (subtype === "fullName") { + return Boolean(this.availableInputTypes.identities?.firstName || this.availableInputTypes.identities?.lastName); + } + if (subtype === "expiration") { + return Boolean(this.availableInputTypes.creditCards?.expirationMonth || this.availableInputTypes.creditCards?.expirationYear); + } + return Boolean(this.availableInputTypes[mainType]?.[subtype]); + } + /** @returns {AutofillFeatureToggles} */ + get featureToggles() { + if (this._featureToggles === null) + throw new Error("feature toggles accessed before being set"); + return this._featureToggles; + } + /** @param {AutofillFeatureToggles} input */ + setFeatureToggles(input) { + this._featureToggles = input; + } + /** @returns {AvailableInputTypes} */ + get availableInputTypes() { + if (this._availableInputTypes === null) + throw new Error("available input types accessed before being set"); + return this._availableInputTypes; + } + /** @param {AvailableInputTypes} value */ + setAvailableInputTypes(value) { + this._availableInputTypes = { ...this._availableInputTypes, ...value }; + } + static default(globalConfig, deviceApi) { + const settings = new _Settings(globalConfig, deviceApi); + settings.setFeatureToggles(_Settings.defaults.featureToggles); + settings.setAvailableInputTypes(_Settings.defaults.availableInputTypes); + return settings; + } + /** @returns {boolean|null} */ + get enabled() { + return this._enabled; + } + /** + * @param {boolean|null} enabled + */ + setEnabled(enabled) { + this._enabled = enabled; } }; - _extractElementStrings(element); - return [...strings]; -}; -exports.extractElementStrings = extractElementStrings; - -},{"./matching.js":43}],40:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.daxGrayscaleBase64 = exports.daxBase64 = void 0; -const daxSvg = ` - - - - - - - - - - - - - -`.trim(); -const daxBase64 = exports.daxBase64 = `data:image/svg+xml;base64,${window.btoa(daxSvg)}`; -const daxGrayscaleSvg = ` - - - - - - -`.trim(); -const daxGrayscaleBase64 = exports.daxGrayscaleBase64 = `data:image/svg+xml;base64,${window.btoa(daxGrayscaleSvg)}`; - -},{}],41:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.matchingConfiguration = void 0; -/* DO NOT EDIT, this file was generated by scripts/precompile-regexes.js */ - -/** @type {MatchingConfiguration} */ -const matchingConfiguration = exports.matchingConfiguration = { - matchers: { - fields: { - unknown: { - type: 'unknown', - strategies: { - ddgMatcher: 'unknown' - } - }, - emailAddress: { - type: 'emailAddress', - strategies: { - cssSelector: 'emailAddress', - ddgMatcher: 'emailAddress', - vendorRegex: 'email' - } - }, - password: { - type: 'password', - strategies: { - cssSelector: 'password', - ddgMatcher: 'password' - } - }, - username: { - type: 'username', - strategies: { - cssSelector: 'username', - ddgMatcher: 'username' - } - }, - firstName: { - type: 'firstName', - strategies: { - cssSelector: 'firstName', - ddgMatcher: 'firstName', - vendorRegex: 'given-name' - } - }, - middleName: { - type: 'middleName', - strategies: { - cssSelector: 'middleName', - ddgMatcher: 'middleName', - vendorRegex: 'additional-name' - } - }, - lastName: { - type: 'lastName', - strategies: { - cssSelector: 'lastName', - ddgMatcher: 'lastName', - vendorRegex: 'family-name' - } - }, - fullName: { - type: 'fullName', - strategies: { - cssSelector: 'fullName', - ddgMatcher: 'fullName', - vendorRegex: 'name' - } - }, - phone: { - type: 'phone', - strategies: { - cssSelector: 'phone', - ddgMatcher: 'phone', - vendorRegex: 'tel' - } - }, - addressStreet: { - type: 'addressStreet', - strategies: { - cssSelector: 'addressStreet', - ddgMatcher: 'addressStreet', - vendorRegex: 'address-line1' - } - }, - addressStreet2: { - type: 'addressStreet2', - strategies: { - cssSelector: 'addressStreet2', - ddgMatcher: 'addressStreet2', - vendorRegex: 'address-line2' - } - }, - addressCity: { - type: 'addressCity', - strategies: { - cssSelector: 'addressCity', - ddgMatcher: 'addressCity', - vendorRegex: 'address-level2' - } - }, - addressProvince: { - type: 'addressProvince', - strategies: { - cssSelector: 'addressProvince', - ddgMatcher: 'addressProvince', - vendorRegex: 'address-level1' - } - }, - addressPostalCode: { - type: 'addressPostalCode', - strategies: { - cssSelector: 'addressPostalCode', - ddgMatcher: 'addressPostalCode', - vendorRegex: 'postal-code' - } - }, - addressCountryCode: { - type: 'addressCountryCode', - strategies: { - cssSelector: 'addressCountryCode', - ddgMatcher: 'addressCountryCode', - vendorRegex: 'country' - } - }, - birthdayDay: { - type: 'birthdayDay', - strategies: { - cssSelector: 'birthdayDay', - ddgMatcher: 'birthdayDay' - } - }, - birthdayMonth: { - type: 'birthdayMonth', - strategies: { - cssSelector: 'birthdayMonth', - ddgMatcher: 'birthdayMonth' - } - }, - birthdayYear: { - type: 'birthdayYear', - strategies: { - cssSelector: 'birthdayYear', - ddgMatcher: 'birthdayYear' - } - }, - cardName: { - type: 'cardName', - strategies: { - cssSelector: 'cardName', - ddgMatcher: 'cardName', - vendorRegex: 'cc-name' - } - }, - cardNumber: { - type: 'cardNumber', - strategies: { - cssSelector: 'cardNumber', - ddgMatcher: 'cardNumber', - vendorRegex: 'cc-number' - } - }, - cardSecurityCode: { - type: 'cardSecurityCode', - strategies: { - cssSelector: 'cardSecurityCode', - ddgMatcher: 'cardSecurityCode' - } - }, - expirationMonth: { - type: 'expirationMonth', - strategies: { - cssSelector: 'expirationMonth', - ddgMatcher: 'expirationMonth', - vendorRegex: 'cc-exp-month' - } - }, - expirationYear: { - type: 'expirationYear', - strategies: { - cssSelector: 'expirationYear', - ddgMatcher: 'expirationYear', - vendorRegex: 'cc-exp-year' - } - }, - expiration: { - type: 'expiration', - strategies: { - cssSelector: 'expiration', - ddgMatcher: 'expiration', - vendorRegex: 'cc-exp' - } - } - }, - lists: { - unknown: ['unknown'], - emailAddress: ['emailAddress'], - password: ['password'], - username: ['username'], - cc: ['cardName', 'cardNumber', 'cardSecurityCode', 'expirationMonth', 'expirationYear', 'expiration'], - id: ['firstName', 'middleName', 'lastName', 'fullName', 'phone', 'addressStreet', 'addressStreet2', 'addressCity', 'addressProvince', 'addressPostalCode', 'addressCountryCode', 'birthdayDay', 'birthdayMonth', 'birthdayYear'] - } - }, - strategies: { - cssSelector: { - selectors: { - genericTextField: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month])', - submitButtonSelector: 'input[type=submit], input[type=button], input[type=image], button:not([role=switch]):not([role=link]), [role=button], a[href="#"][id*=button i], a[href="#"][id*=btn i]', - formInputsSelector: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month]),[autocomplete=username],select', - safeUniversalSelector: '*:not(select):not(option):not(script):not(noscript):not(style):not(br)', - emailAddress: 'input:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]), input[type=""][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([type=tel]), input[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]):not([name*=code i]), input:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]), input[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=""][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=email], input[type=text][aria-label*=email i]:not([aria-label*=search i]), input:not([type])[aria-label*=email i]:not([aria-label*=search i]), input[name=username][type=email], input[autocomplete=username][type=email], input[autocomplete=username][placeholder*=email i], input[autocomplete=email],input[name="mail_tel" i],input[value=email i]', - username: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month])[autocomplete^=user i],input[name=username i],input[name="loginId" i],input[name="userid" i],input[id="userid" i],input[name="user_id" i],input[name="user-id" i],input[id="login-id" i],input[id="login_id" i],input[id="loginid" i],input[name="login" i],input[name=accountname i],input[autocomplete=username i],input[name*=accountid i],input[name="j_username" i],input[id="j_username" i],input[name="uwinid" i],input[name="livedoor_id" i],input[name="ssousername" i],input[name="j_userlogin_pwd" i],input[name="user[login]" i],input[name="user" i],input[name$="_username" i],input[id="lmSsoinput" i],input[name="account_subdomain" i],input[name="masterid" i],input[name="tridField" i],input[id="signInName" i],input[id="w3c_accountsbundle_accountrequeststep1_login" i],input[id="username" i],input[name="_user" i],input[name="login_username" i],input[name^="login-user-account" i],input[id="loginusuario" i],input[name="usuario" i],input[id="UserLoginFormUsername" i],input[id="nw_username" i],input[can-field="accountName"],input[placeholder^="username" i]', - password: 'input[type=password]:not([autocomplete*=cc]):not([autocomplete=one-time-code]):not([name*=answer i]):not([name*=mfa i]):not([name*=tin i]):not([name*=card i]):not([name*=cvv i]),input.js-cloudsave-phrase', - cardName: 'input[autocomplete="cc-name" i], input[autocomplete="ccname" i], input[name="ccname" i], input[name="cc-name" i], input[name="ppw-accountHolderName" i], input[id*=cardname i], input[id*=card-name i], input[id*=card_name i]', - cardNumber: 'input[autocomplete="cc-number" i], input[autocomplete="ccnumber" i], input[autocomplete="cardnumber" i], input[autocomplete="card-number" i], input[name="ccnumber" i], input[name="cc-number" i], input[name*=card i][name*=number i], input[name*=cardnumber i], input[id*=cardnumber i], input[id*=card-number i], input[id*=card_number i]', - cardSecurityCode: 'input[autocomplete="cc-csc" i], input[autocomplete="csc" i], input[autocomplete="cc-cvc" i], input[autocomplete="cvc" i], input[name="cvc" i], input[name="cc-cvc" i], input[name="cc-csc" i], input[name="csc" i], input[name*=security i][name*=code i]', - expirationMonth: '[autocomplete="cc-exp-month" i], [autocomplete="cc_exp_month" i], [name="ccmonth" i], [name="ppw-expirationDate_month" i], [name=cardExpiryMonth i], [name*=ExpDate_Month i], [name*=expiration i][name*=month i], [id*=expiration i][id*=month i], [name*=cc-exp-month i], [name*="card_exp-month" i], [name*=cc_exp_month i], [id*=cc-month i]', - expirationYear: '[autocomplete="cc-exp-year" i], [autocomplete="cc_exp_year" i], [name="ccyear" i], [name="ppw-expirationDate_year" i], [name=cardExpiryYear i], [name*=ExpDate_Year i], [name*=expiration i][name*=year i], [id*=expiration i][id*=year i], [name*="cc-exp-year" i], [name*="card_exp-year" i], [name*=cc_exp_year i], [id*=cc-year i]', - expiration: '[autocomplete="cc-exp" i], [name="cc-exp" i], [name="exp-date" i], [name="expirationDate" i], input[id*=expiration i]', - firstName: '[name*=fname i], [autocomplete*=given-name i], [name*=firstname i], [autocomplete*=firstname i], [name*=first-name i], [autocomplete*=first-name i], [name*=first_name i], [autocomplete*=first_name i], [name*=givenname i], [autocomplete*=givenname i], [name*=given-name i], [name*=given_name i], [autocomplete*=given_name i], [name*=forename i], [autocomplete*=forename i]', - middleName: '[name*=mname i], [autocomplete*=additional-name i], [name*=middlename i], [autocomplete*=middlename i], [name*=middle-name i], [autocomplete*=middle-name i], [name*=middle_name i], [autocomplete*=middle_name i], [name*=additionalname i], [autocomplete*=additionalname i], [name*=additional-name i], [name*=additional_name i], [autocomplete*=additional_name i]', - lastName: '[name=lname], [autocomplete*=family-name i], [name*=lastname i], [autocomplete*=lastname i], [name*=last-name i], [autocomplete*=last-name i], [name*=last_name i], [autocomplete*=last_name i], [name*=familyname i], [autocomplete*=familyname i], [name*=family-name i], [name*=family_name i], [autocomplete*=family_name i], [name*=surname i], [autocomplete*=surname i]', - fullName: '[autocomplete=name], [name*=fullname i], [autocomplete*=fullname i], [name*=full-name i], [autocomplete*=full-name i], [name*=full_name i], [autocomplete*=full_name i], [name*=your-name i], [autocomplete*=your-name i]', - phone: '[name*=phone i]:not([name*=extension i]):not([name*=type i]):not([name*=country i]), [name*=mobile i]:not([name*=type i]), [autocomplete=tel], [autocomplete="tel-national"], [placeholder*="phone number" i]', - addressStreet: '[name=address i], [autocomplete=street-address i], [autocomplete=address-line1 i], [name=street i], [name=ppw-line1 i], [name*=addressLine1 i]', - addressStreet2: '[name=address2 i], [autocomplete=address-line2 i], [name=ppw-line2 i], [name*=addressLine2 i]', - addressCity: '[name=city i], [autocomplete=address-level2 i], [name=ppw-city i], [name*=addressCity i]', - addressProvince: '[name=province i], [name=state i], [autocomplete=address-level1 i]', - addressPostalCode: '[name=zip i], [name=zip2 i], [name=postal i], [autocomplete=postal-code i], [autocomplete=zip-code i], [name*=postalCode i], [name*=zipcode i]', - addressCountryCode: '[name=country i], [autocomplete=country i], [name*=countryCode i], [name*=country-code i], [name*=countryName i], [name*=country-name i],select.idms-address-country', - birthdayDay: '[name=bday-day i], [name*=birthday_day i], [name*=birthday-day i], [name=date_of_birth_day i], [name=date-of-birth-day i], [name^=birthdate_d i], [name^=birthdate-d i], [aria-label="birthday" i][placeholder="day" i]', - birthdayMonth: '[name=bday-month i], [name*=birthday_month i], [name*=birthday-month i], [name=date_of_birth_month i], [name=date-of-birth-month i], [name^=birthdate_m i], [name^=birthdate-m i], select[name="mm" i]', - birthdayYear: '[name=bday-year i], [name*=birthday_year i], [name*=birthday-year i], [name=date_of_birth_year i], [name=date-of-birth-year i], [name^=birthdate_y i], [name^=birthdate-y i], [aria-label="birthday" i][placeholder="year" i]' - } - }, - ddgMatcher: { - matchers: { - unknown: { - match: /search|filter|subject|title|captcha|mfa|2fa|(two|2).?factor|one-time|otp|cerca|filtr|oggetto|titolo|(due|2|più).?fattori|suche|filtern|betreff|zoeken|filter|onderwerp|titel|chercher|filtrer|objet|titre|authentification multifacteur|double authentification|à usage unique|busca|busqueda|filtra|dos pasos|un solo uso|sök|filter|ämne|multifaktorsautentisering|tvåfaktorsautentisering|två.?faktor|engångs/iu, - skip: /phone|mobile|email|password/iu - }, - emailAddress: { - match: /.mail\b|apple.?id|posta elettronica|e.?mailadres|correo electr|correo-e|^correo$|\be.?post|e.?postadress/iu, - skip: /phone|(first.?|last.?)name|number|code/iu, - forceUnknown: /search|filter|subject|title|\btab\b|otp/iu - }, - password: { - match: /password|passwort|kennwort|wachtwoord|mot de passe|clave|contraseña|lösenord/iu, - skip: /email|one-time|error|hint/iu, - forceUnknown: /captcha|mfa|2fa|two factor|otp|pin/iu - }, - newPassword: { - match: /new|re.?(enter|type)|repeat|update|reset\b/iu - }, - currentPassword: { - match: /current|old|previous|expired|existing/iu - }, - username: { - match: /(user|account|online.?id|log(i|o)n|net)((.)?(name|i.?d.?|log(i|o)n).?)?(.?((or|\/).+|\*|:)( required)?)?$|(nome|id|login).?utente|(nome|id) (dell.)?account|codice cliente|nutzername|anmeldename|gebruikersnaam|nom d.utilisateur|identifiant|pseudo|usuari|cuenta|identificador|apodo|\bdni\b|\bnie\b| del? documento|documento de identidad|användarnamn|kontonamn|användar-id/iu, - skip: /phone/iu, - forceUnknown: /search|policy|choose a user\b/iu - }, - cardName: { - match: /(card.*name|name.*card)|(card.*holder|holder.*card)|(card.*owner|owner.*card)/iu - }, - cardNumber: { - match: /card.*number|number.*card/iu, - skip: /phone/iu, - forceUnknown: /plus/iu - }, - cardSecurityCode: { - match: /security.?code|card.?verif|cvv|csc|cvc|cv2|card id/iu - }, - expirationMonth: { - match: /(card|\bcc\b)?.?(exp(iry|iration)?)?.?(month|\bmm\b(?![.\s/-]yy))/iu, - skip: /mm[/\s.\-_—–]|check/iu - }, - expirationYear: { - match: /(card|\bcc\b)?.?(exp(iry|iration)?)?.?(year|yy)/iu, - skip: /mm[/\s.\-_—–]|check/iu - }, - expiration: { - match: /(\bmm\b|\b\d\d\b)[/\s.\-_—–](\byy|\bjj|\baa|\b\d\d)|\bexp|\bvalid(idity| through| until)/iu, - skip: /invalid|^dd\/|check/iu - }, - firstName: { - match: /(first|given|fore).?name|\bnome/iu, - skip: /last|cognome|completo/iu - }, - middleName: { - match: /(middle|additional).?name/iu - }, - lastName: { - match: /(last|family|sur)[^i]?name|cognome/iu, - skip: /first|\bnome/iu - }, - fullName: { - match: /^(full.?|whole\s|first.*last\s|real\s|contact.?)?name\b|\bnome/iu, - forceUnknown: /company|org|item/iu - }, - phone: { - match: /phone|mobile|telefono|cellulare/iu, - skip: /code|pass|country/iu, - forceUnknown: /ext|type|otp/iu - }, - addressStreet: { - match: /address/iu, - forceUnknown: /\bip\b|duck|web|url/iu, - skip: /address.*(2|two|3|three)|email|log.?in|sign.?in|civico/iu - }, - addressStreet2: { - match: /address.*(2|two)|apartment|\bapt\b|\bflat\b|\bline.*(2|two)/iu, - forceUnknown: /\bip\b|duck/iu, - skip: /email|log.?in|sign.?in/iu - }, - addressCity: { - match: /city|town|città|comune/iu, - skip: /\bzip\b|\bcap\b/iu, - forceUnknown: /vatican/iu - }, - addressProvince: { - match: /state|province|region|county|provincia|regione/iu, - forceUnknown: /united/iu, - skip: /country/iu - }, - addressPostalCode: { - match: /\bzip\b|postal\b|post.?code|\bcap\b|codice postale/iu - }, - addressCountryCode: { - match: /country|\bnation\b|nazione|paese/iu - }, - birthdayDay: { - match: /(birth.*day|day.*birth)/iu, - skip: /month|year/iu - }, - birthdayMonth: { - match: /(birth.*month|month.*birth)/iu, - skip: /year/iu - }, - birthdayYear: { - match: /(birth.*year|year.*birth)/iu - }, - loginRegex: { - match: /sign(ing)?.?[io]n(?!g)|log.?[io]n|log.?out|unsubscri|(forgot(ten)?|reset) (your )?password|password (forgotten|lost)|mfa-submit-form|unlock|logged in as|entra|accedi|accesso|resetta password|password dimenticata|dimenticato la password|recuper[ao] password|(ein|aus)loggen|anmeld(eformular|ung|efeld)|abmelden|passwort (vergessen|verloren)|zugang| zugangsformular|einwahl|inloggen|se (dé)?connecter|(dé)?connexion|récupérer ((mon|ton|votre|le) )?mot de passe|mot de passe (oublié|perdu)|clave(?! su)|olvidó su (clave|contraseña)|.*sesión|conect(arse|ado)|conéctate|acce(de|so)|entrar|logga (in|ut)|avprenumerera|avregistrera|glömt lösenord|återställ lösenord/iu - }, - signupRegex: { - match: /sign(ing)?.?up|join|\bregist(er|ration)|newsletter|\bsubscri(be|ption)|contact|create|start|enroll|settings|preferences|profile|update|checkout|purchase|buy|order|schedule|estimate|request|new.?customer|(confirm|re.?(type|enter)|repeat) password|password confirm|iscri(viti|zione)|registra(ti|zione)|(?:nuovo|crea(?:zione)?) account|contatt(?:ac)i|sottoscriv|sottoscrizione|compra|acquist(a|o)|ordin[aeio]|richie(?:di|sta)|(?:conferma|ripeti) password|inizia|nuovo cliente|impostazioni|preferenze|profilo|aggiorna|paga|registrier(ung|en)|profil (anlegen|erstellen)| nachrichten|verteiler|neukunde|neuer (kunde|benutzer|nutzer)|passwort wiederholen|anmeldeseite|nieuwsbrief|aanmaken|profiel|s.inscrire|inscription|s.abonner|créer|préférences|profil|mise à jour|payer|ach(eter|at)| nouvel utilisateur|(confirmer|réessayer) ((mon|ton|votre|le) )?mot de passe|regis(trarse|tro)|regístrate|inscr(ibirse|ipción|íbete)|solicitar|crea(r cuenta)?|nueva cuenta|nuevo (cliente|usuario)|preferencias|perfil|lista de correo|registrer(a|ing)|(nytt|öppna) konto|nyhetsbrev|prenumer(era|ation)|kontakt|skapa|starta|inställningar|min (sida|kundvagn)|uppdatera|till kassan|gäst|köp|beställ|schemalägg|ny kund|(repetera|bekräfta) lösenord/iu - }, - conservativeSignupRegex: { - match: /sign.?up|join|register|enroll|(create|new).+account|newsletter|subscri(be|ption)|settings|preferences|profile|update|iscri(viti|zione)|registra(ti|zione)|(?:nuovo|crea(?:zione)?) account|contatt(?:ac)?i|sottoscriv|sottoscrizione|impostazioni|preferenze|aggiorna|anmeld(en|ung)|registrier(en|ung)|neukunde|neuer (kunde|benutzer|nutzer)|registreren|eigenschappen|profiel|bijwerken|s.inscrire|inscription|s.abonner|abonnement|préférences|profil|créer un compte|regis(trarse|tro)|regístrate|inscr(ibirse|ipción|íbete)|crea(r cuenta)?|nueva cuenta|nuevo (cliente|usuario)|preferencias|perfil|lista de correo|registrer(a|ing)|(nytt|öppna) konto|nyhetsbrev|prenumer(era|ation)|kontakt|skapa|starta|inställningar|min (sida|kundvagn)|uppdatera/iu + var Settings = _Settings; + __publicField(Settings, "defaults", { + /** @type {AutofillFeatureToggles} */ + featureToggles: { + credentials_saving: false, + password_generation: false, + emailProtection: false, + emailProtection_incontext_signup: false, + inputType_identities: false, + inputType_credentials: false, + inputType_creditCards: false, + inlineIcon_credentials: false + }, + /** @type {AvailableInputTypes} */ + availableInputTypes: { + credentials: { + username: false, + password: false + }, + identities: { + firstName: false, + middleName: false, + lastName: false, + birthdayDay: false, + birthdayMonth: false, + birthdayYear: false, + addressStreet: false, + addressStreet2: false, + addressCity: false, + addressProvince: false, + addressPostalCode: false, + addressCountryCode: false, + phone: false, + emailAddress: false + }, + creditCards: { + cardName: false, + cardSecurityCode: false, + expirationMonth: false, + expirationYear: false, + cardNumber: false + }, + email: false + }, + /** @type {boolean | null} */ + enabled: null + }); + + // src/deviceApiCalls/transports/extension.transport.js + var ExtensionTransport = class extends DeviceApiTransport { + /** @param {GlobalConfig} globalConfig */ + constructor(globalConfig) { + super(); + this.config = globalConfig; + } + async send(deviceApiCall) { + if (deviceApiCall instanceof GetRuntimeConfigurationCall) { + return deviceApiCall.result(await extensionSpecificRuntimeConfiguration(this)); + } + if (deviceApiCall instanceof GetAvailableInputTypesCall) { + return deviceApiCall.result(await extensionSpecificGetAvailableInputTypes()); + } + if (deviceApiCall instanceof SetIncontextSignupPermanentlyDismissedAtCall) { + return deviceApiCall.result(await extensionSpecificSetIncontextSignupPermanentlyDismissedAtCall(deviceApiCall.params)); + } + if (deviceApiCall instanceof GetIncontextSignupDismissedAtCall) { + return deviceApiCall.result(await extensionSpecificGetIncontextSignupDismissedAt()); + } + if (deviceApiCall instanceof SendJSPixelCall) { + return deviceApiCall.result(await extensionSpecificSendPixel(deviceApiCall.params)); + } + if (deviceApiCall instanceof AddDebugFlagCall) { + return deviceApiCall.result(await extensionSpecificAddDebugFlag(deviceApiCall.params)); + } + if (deviceApiCall instanceof CloseAutofillParentCall || deviceApiCall instanceof StartEmailProtectionSignupCall) { + return; + } + console.error("Send not implemented for " + deviceApiCall.method); + } + }; + async function extensionSpecificRuntimeConfiguration(deviceApi) { + const contentScope = await getContentScopeConfig(); + const emailProtectionEnabled = isAutofillEnabledFromProcessedConfig(contentScope); + const incontextSignupEnabled = isIncontextSignupEnabledFromProcessedConfig(contentScope); + return { + success: { + // @ts-ignore + contentScope, + // @ts-ignore + userPreferences: { + features: { + autofill: { + settings: { + featureToggles: { + ...Settings.defaults.featureToggles, + emailProtection: emailProtectionEnabled, + emailProtection_incontext_signup: incontextSignupEnabled + } + } + } + } }, - resetPasswordLink: { - match: /(forgot(ten)?|reset|don't remember) (your )?password|password forgotten|password dimenticata|reset(?:ta) password|recuper[ao] password|(vergessen|verloren|verlegt|wiederherstellen) passwort|wachtwoord (vergeten|reset)|(oublié|récupérer) ((mon|ton|votre|le) )?mot de passe|mot de passe (oublié|perdu)|re(iniciar|cuperar) (contraseña|clave)|olvid(ó su|aste tu|é mi) (contraseña|clave)|recordar( su)? (contraseña|clave)|glömt lösenord|återställ lösenord/iu + // @ts-ignore + userUnprotectedDomains: deviceApi.config?.userUnprotectedDomains || [] + } + }; + } + async function extensionSpecificGetAvailableInputTypes() { + const contentScope = await getContentScopeConfig(); + const emailProtectionEnabled = isAutofillEnabledFromProcessedConfig(contentScope); + return { + success: { + ...Settings.defaults.availableInputTypes, + email: emailProtectionEnabled + } + }; + } + async function getContentScopeConfig() { + return new Promise((resolve) => { + chrome.runtime.sendMessage( + { + registeredTempAutofillContentScript: true, + documentUrl: window.location.href }, - loginProvidersRegex: { - match: / with | con | mit | met | avec /iu + (response) => { + if (response && "site" in response) { + resolve(response); + } + } + ); + }); + } + async function extensionSpecificSendPixel(params) { + return new Promise((resolve) => { + chrome.runtime.sendMessage( + { + messageType: "sendJSPixel", + options: params }, - submitButtonRegex: { - match: /submit|send|confirm|save|continue|next|sign|log.?([io])n|buy|purchase|check.?out|subscribe|donate|update|\bset\b|invia|conferma|salva|continua|entra|acced|accesso|compra|paga|sottoscriv|registra|dona|senden|\bja\b|bestätigen|weiter|nächste|kaufen|bezahlen|spenden|versturen|verzenden|opslaan|volgende|koop|kopen|voeg toe|aanmelden|envoyer|confirmer|sauvegarder|continuer|suivant|signer|connexion|acheter|payer|s.abonner|donner|enviar|confirmar|registrarse|continuar|siguiente|comprar|donar|skicka|bekräfta|spara|fortsätt|nästa|logga in|köp|handla|till kassan|registrera|donera/iu + () => { + resolve(true); + } + ); + }); + } + async function extensionSpecificAddDebugFlag(params) { + return new Promise((resolve) => { + chrome.runtime.sendMessage( + { + messageType: "addDebugFlag", + options: params }, - submitButtonUnlikelyRegex: { - match: /facebook|twitter|google|apple|cancel|show|toggle|reveal|hide|print|back|already|annulla|mostra|nascondi|stampa|indietro|già|abbrechen|passwort|zeigen|verbergen|drucken|zurück|annuleer|wachtwoord|toon|vorige|annuler|mot de passe|montrer|cacher|imprimer|retour|déjà|anular|cancelar|imprimir|cerrar|avbryt|lösenord|visa|dölj|skirv ut|tillbaka|redan/iu - } - } - }, - vendorRegex: { - rules: { - email: /((^e-?mail$)|(^email-?address$))|(e.?mail|courriel|correo.*electr(o|ó)nico|メールアドレス|электронной.?почты|邮件|邮箱|電郵地址|ഇ-മെയില്‍|ഇലക്ട്രോണിക്.?മെയിൽ|ایمیل|پست.*الکترونیک|ईमेल|इलॅक्ट्रॉनिक.?मेल|(\b|_)eposta(\b|_)|(?:이메일|전자.?우편|[ee]-?mail)(.?주소)?)/iu, - tel: /((^phone$)|(^mobile$)|(^mobile-?phone$)|(^tel$)|(^telephone$)|(^phone-?number$))|(phone|mobile|contact.?number|telefonnummer|telefono|teléfono|telfixe|電話|telefone|telemovel|телефон|मोबाइल|(\b|_|\*)telefon(\b|_|\*)|电话|മൊബൈല്‍|(?:전화|핸드폰|휴대폰|휴대전화)(?:.?번호)?)/iu, - organization: /((^company$)|(^company-?name$)|(^organization$)|(^organization-?name$))|(company|business|organization|organisation|empresa|societe|société|ragione.?sociale|会社|название.?компании|单位|公司|شرکت|회사|직장)/iu, - 'street-address': /((^address$)|(^street-?address$)|(^addr$)|(^street$)|(^mailing-?addr(ess)?$)|(^billing-?addr(ess)?$)|(^mail-?addr(ess)?$)|(^bill-?addr(ess)?$))|(streetaddress|street-address)/iu, - 'address-line1': /(addrline1|address_1)|((^address-?1$)|(^address-?line-?1$)|(^addr-?1$)|(^street-?1$))|(^address$|address[_-]?line[_-]?(1|one)|address1|addr1|street|(?:shipping|billing)address$|strasse|straße|hausnummer|housenumber|house.?name|direccion|dirección|adresse|indirizzo|^住所$|住所1|адрес|地址|(\b|_)adres(?! (başlığı(nız)?|tarifi))(\b|_)|^주소.?$|주소.?1)/iu, - 'address-line2': /(addrline2|address_2)|((^address-?2$)|(^address-?line-?2$)|(^addr-?2$)|(^street-?2$))|(address[_-]?line(2|two)|address2|addr2|street|suite|unit(?!e)|adresszusatz|ergänzende.?angaben|direccion2|colonia|adicional|addresssuppl|complementnom|appartement|indirizzo2|住所2|complemento|addrcomplement|улица|地址2|주소.?2)/iu, - 'address-line3': /(addrline3|address_3)|((^address-?3$)|(^address-?line-?3$)|(^addr-?3$)|(^street-?3$))|(address[_-]?line(3|three)|address3|addr3|street|suite|unit(?!e)|adresszusatz|ergänzende.?angaben|direccion3|colonia|adicional|addresssuppl|complementnom|appartement|indirizzo3|住所3|complemento|addrcomplement|улица|地址3|주소.?3)/iu, - 'address-level2': /((^city$)|(^town$)|(^address-?level-?2$)|(^address-?city$)|(^address-?town$))|(city|town|\bort\b|stadt|suburb|ciudad|provincia|localidad|poblacion|ville|commune|localit(a|à)|citt(a|à)|市区町村|cidade|город|市|分區|شهر|शहर|ग्राम|गाँव|നഗരം|ഗ്രാമം|((\b|_|\*)([i̇ii̇]l[cç]e(miz|niz)?)(\b|_|\*))|^시[^도·・]|시[·・]?군[·・]?구)/iu, - 'address-level1': /(land)|((^state$)|(^province$)|(^provence$)|(^address-?level-?1$)|(^address-?state$)|(^address-?province$))|(county|region|province|county|principality|都道府県|estado|provincia|область|省|地區|സംസ്ഥാനം|استان|राज्य|((\b|_|\*)(eyalet|[şs]ehir|[i̇ii̇]limiz|kent)(\b|_|\*))|^시[·・]?도)/iu, - 'postal-code': /((^postal$)|(^zip$)|(^zip2$)|(^zip-?code$)|(^postal-?code$)|(^post-?code$)|(^address-?zip$)|(^address-?postal$)|(^address-?code$)|(^address-?postal-?code$)|(^address-?zip-?code$))|(zip|postal|post.*code|pcode|pin.?code|postleitzahl|\bcp\b|\bcdp\b|\bcap\b|郵便番号|codigo|codpos|\bcep\b|почтовый.?индекс|पिन.?कोड|പിന്‍കോഡ്|邮政编码|邮编|郵遞區號|(\b|_)posta kodu(\b|_)|우편.?번호)/iu, - country: /((^country$)|(^country-?code$)|(^country-?name$)|(^address-?country$)|(^address-?country-?name$)|(^address-?country-?code$))|(country|countries|país|pais|(\b|_)land(\b|_)(?!.*(mark.*))|国家|국가|나라|(\b|_)(ülke|ulce|ulke)(\b|_)|کشور)/iu, - 'cc-name': /(accountholdername|titulaire)|(cc-?name|card-?name|cardholder-?name|cardholder|(^nom$))|(card.?(?:holder|owner)|name.*(\b)?on(\b)?.*card|(?:card|cc).?name|cc.?full.?name|karteninhaber|nombre.*tarjeta|nom.*carte|nome.*cart|名前|имя.*карты|信用卡开户名|开户名|持卡人姓名|持卡人姓名)/iu, - name: /((^name$)|full-?name|your-?name)|(^name|full.?name|your.?name|customer.?name|bill.?name|ship.?name|name.*first.*last|firstandlastname|nombre.*y.*apellidos|^nom(?!bre)\b|お名前|氏名|^nome|نام.*نام.*خانوادگی|姓名|(\b|_|\*)ad[ı]? soyad[ı]?(\b|_|\*)|성명)/iu, - 'given-name': /((^f-?name$)|(^first-?name$)|(^given-?name$)|(^first-?n$))|(first.*name|initials|fname|first$|given.*name|vorname|nombre|forename|prénom|prenom|名|\bnome|имя|نام|이름|പേര്|(\b|_|\*)(isim|ad|ad(i|ı|iniz|ınız)?)(\b|_|\*)|नाम)/iu, - 'additional-name': /(apellido.?materno|lastlastname)|((^m-?name$)|(^middle-?name$)|(^additional-?name$)|(^middle-?initial$)|(^middle-?n$)|(^middle-?i$))|(middle.*name|mname|middle$|middle.*initial|m\.i\.|mi$|\bmi\b)/iu, - 'family-name': /((^l-?name$)|(^last-?name$)|(^s-?name$)|(^surname$)|(^family-?name$)|(^family-?n$)|(^last-?n$))|(last.*name|lname|surname|last$|secondname|family.*name|nachname|apellidos?|famille|^nom(?!bre)|cognome|姓|apelidos|surename|sobrenome|фамилия|نام.*خانوادگی|उपनाम|മറുപേര്|(\b|_|\*)(soyisim|soyad(i|ı|iniz|ınız)?)(\b|_|\*)|\b성(?:[^명]|\b))/iu, - 'cc-number': /((cc|kk)nr)|(cc-?number|cc-?num|card-?number|card-?num|(^number$)|(^cc$)|cc-?no|card-?no|(^credit-?card$)|numero-?carte|(^carte$)|(^carte-?credit$)|num-?carte|cb-?num)|((add)?(?:card|cc|acct).?(?:number|#|no|num|field)|カード番号|номер.*карты|信用卡号|信用卡号码|信用卡卡號|카드|(numero|número|numéro)(?!.*(document|fono|phone|réservation)))/iu, - 'cc-exp-month': /((cc|kk)month)|((^exp-?month$)|(^cc-?exp-?month$)|(^cc-?month$)|(^card-?month$)|(^cc-?mo$)|(^card-?mo$)|(^exp-?mo$)|(^card-?exp-?mo$)|(^cc-?exp-?mo$)|(^card-?expiration-?month$)|(^expiration-?month$)|(^cc-?mm$)|(^cc-?m$)|(^card-?mm$)|(^card-?m$)|(^card-?exp-?mm$)|(^cc-?exp-?mm$)|(^exp-?mm$)|(^exp-?m$)|(^expire-?month$)|(^expire-?mo$)|(^expiry-?month$)|(^expiry-?mo$)|(^card-?expire-?month$)|(^card-?expire-?mo$)|(^card-?expiry-?month$)|(^card-?expiry-?mo$)|(^mois-?validite$)|(^mois-?expiration$)|(^m-?validite$)|(^m-?expiration$)|(^expiry-?date-?field-?month$)|(^expiration-?date-?month$)|(^expiration-?date-?mm$)|(^exp-?mon$)|(^validity-?mo$)|(^exp-?date-?mo$)|(^cb-?date-?mois$)|(^date-?m$))|(gueltig|gültig|monat|fecha|date.*exp|scadenza|有効期限|validade|срок действия карты|月)/iu, - 'cc-exp-year': /((cc|kk)year)|((^exp-?year$)|(^cc-?exp-?year$)|(^cc-?year$)|(^card-?year$)|(^cc-?yr$)|(^card-?yr$)|(^exp-?yr$)|(^card-?exp-?yr$)|(^cc-?exp-?yr$)|(^card-?expiration-?year$)|(^expiration-?year$)|(^cc-?yy$)|(^cc-?y$)|(^card-?yy$)|(^card-?y$)|(^card-?exp-?yy$)|(^cc-?exp-?yy$)|(^exp-?yy$)|(^exp-?y$)|(^cc-?yyyy$)|(^card-?yyyy$)|(^card-?exp-?yyyy$)|(^cc-?exp-?yyyy$)|(^expire-?year$)|(^expire-?yr$)|(^expiry-?year$)|(^expiry-?yr$)|(^card-?expire-?year$)|(^card-?expire-?yr$)|(^card-?expiry-?year$)|(^card-?expiry-?yr$)|(^an-?validite$)|(^an-?expiration$)|(^annee-?validite$)|(^annee-?expiration$)|(^expiry-?date-?field-?year$)|(^expiration-?date-?year$)|(^cb-?date-?ann$)|(^expiration-?date-?yy$)|(^expiration-?date-?yyyy$)|(^validity-?year$)|(^exp-?date-?year$)|(^date-?y$))|(ablaufdatum|gueltig|gültig|jahr|fecha|scadenza|有効期限|validade|срок действия карты|年|有效期)/iu, - 'cc-exp': /((^cc-?exp$)|(^card-?exp$)|(^cc-?expiration$)|(^card-?expiration$)|(^cc-?ex$)|(^card-?ex$)|(^card-?expire$)|(^card-?expiry$)|(^validite$)|(^expiration$)|(^expiry$)|mm-?yy|mm-?yyyy|yy-?mm|yyyy-?mm|expiration-?date|payment-?card-?expiration|(^payment-?cc-?date$))|(expir|exp.*date|^expfield$|gueltig|gültig|fecha|date.*exp|scadenza|有効期限|validade|срок действия карты)/iu, - 'cc-type': /(type|kartenmarke)|((^cc-?type$)|(^card-?type$)|(^card-?brand$)|(^cc-?brand$)|(^cb-?type$))/iu - }, - ruleSets: [{ - 'address-line1': 'addrline1|address_1', - 'address-line2': 'addrline2|address_2', - 'address-line3': 'addrline3|address_3', - 'address-level1': 'land', - 'additional-name': 'apellido.?materno|lastlastname', - 'cc-name': 'accountholdername|titulaire', - 'cc-number': '(cc|kk)nr', - 'cc-exp-month': '(cc|kk)month', - 'cc-exp-year': '(cc|kk)year', - 'cc-type': 'type|kartenmarke' - }, { - email: '(^e-?mail$)|(^email-?address$)', - tel: '(^phone$)|(^mobile$)|(^mobile-?phone$)|(^tel$)|(^telephone$)|(^phone-?number$)', - organization: '(^company$)|(^company-?name$)|(^organization$)|(^organization-?name$)', - 'street-address': '(^address$)|(^street-?address$)|(^addr$)|(^street$)|(^mailing-?addr(ess)?$)|(^billing-?addr(ess)?$)|(^mail-?addr(ess)?$)|(^bill-?addr(ess)?$)', - 'address-line1': '(^address-?1$)|(^address-?line-?1$)|(^addr-?1$)|(^street-?1$)', - 'address-line2': '(^address-?2$)|(^address-?line-?2$)|(^addr-?2$)|(^street-?2$)', - 'address-line3': '(^address-?3$)|(^address-?line-?3$)|(^addr-?3$)|(^street-?3$)', - 'address-level2': '(^city$)|(^town$)|(^address-?level-?2$)|(^address-?city$)|(^address-?town$)', - 'address-level1': '(^state$)|(^province$)|(^provence$)|(^address-?level-?1$)|(^address-?state$)|(^address-?province$)', - 'postal-code': '(^postal$)|(^zip$)|(^zip2$)|(^zip-?code$)|(^postal-?code$)|(^post-?code$)|(^address-?zip$)|(^address-?postal$)|(^address-?code$)|(^address-?postal-?code$)|(^address-?zip-?code$)', - country: '(^country$)|(^country-?code$)|(^country-?name$)|(^address-?country$)|(^address-?country-?name$)|(^address-?country-?code$)', - name: '(^name$)|full-?name|your-?name', - 'given-name': '(^f-?name$)|(^first-?name$)|(^given-?name$)|(^first-?n$)', - 'additional-name': '(^m-?name$)|(^middle-?name$)|(^additional-?name$)|(^middle-?initial$)|(^middle-?n$)|(^middle-?i$)', - 'family-name': '(^l-?name$)|(^last-?name$)|(^s-?name$)|(^surname$)|(^family-?name$)|(^family-?n$)|(^last-?n$)', - 'cc-name': 'cc-?name|card-?name|cardholder-?name|cardholder|(^nom$)', - 'cc-number': 'cc-?number|cc-?num|card-?number|card-?num|(^number$)|(^cc$)|cc-?no|card-?no|(^credit-?card$)|numero-?carte|(^carte$)|(^carte-?credit$)|num-?carte|cb-?num', - 'cc-exp': '(^cc-?exp$)|(^card-?exp$)|(^cc-?expiration$)|(^card-?expiration$)|(^cc-?ex$)|(^card-?ex$)|(^card-?expire$)|(^card-?expiry$)|(^validite$)|(^expiration$)|(^expiry$)|mm-?yy|mm-?yyyy|yy-?mm|yyyy-?mm|expiration-?date|payment-?card-?expiration|(^payment-?cc-?date$)', - 'cc-exp-month': '(^exp-?month$)|(^cc-?exp-?month$)|(^cc-?month$)|(^card-?month$)|(^cc-?mo$)|(^card-?mo$)|(^exp-?mo$)|(^card-?exp-?mo$)|(^cc-?exp-?mo$)|(^card-?expiration-?month$)|(^expiration-?month$)|(^cc-?mm$)|(^cc-?m$)|(^card-?mm$)|(^card-?m$)|(^card-?exp-?mm$)|(^cc-?exp-?mm$)|(^exp-?mm$)|(^exp-?m$)|(^expire-?month$)|(^expire-?mo$)|(^expiry-?month$)|(^expiry-?mo$)|(^card-?expire-?month$)|(^card-?expire-?mo$)|(^card-?expiry-?month$)|(^card-?expiry-?mo$)|(^mois-?validite$)|(^mois-?expiration$)|(^m-?validite$)|(^m-?expiration$)|(^expiry-?date-?field-?month$)|(^expiration-?date-?month$)|(^expiration-?date-?mm$)|(^exp-?mon$)|(^validity-?mo$)|(^exp-?date-?mo$)|(^cb-?date-?mois$)|(^date-?m$)', - 'cc-exp-year': '(^exp-?year$)|(^cc-?exp-?year$)|(^cc-?year$)|(^card-?year$)|(^cc-?yr$)|(^card-?yr$)|(^exp-?yr$)|(^card-?exp-?yr$)|(^cc-?exp-?yr$)|(^card-?expiration-?year$)|(^expiration-?year$)|(^cc-?yy$)|(^cc-?y$)|(^card-?yy$)|(^card-?y$)|(^card-?exp-?yy$)|(^cc-?exp-?yy$)|(^exp-?yy$)|(^exp-?y$)|(^cc-?yyyy$)|(^card-?yyyy$)|(^card-?exp-?yyyy$)|(^cc-?exp-?yyyy$)|(^expire-?year$)|(^expire-?yr$)|(^expiry-?year$)|(^expiry-?yr$)|(^card-?expire-?year$)|(^card-?expire-?yr$)|(^card-?expiry-?year$)|(^card-?expiry-?yr$)|(^an-?validite$)|(^an-?expiration$)|(^annee-?validite$)|(^annee-?expiration$)|(^expiry-?date-?field-?year$)|(^expiration-?date-?year$)|(^cb-?date-?ann$)|(^expiration-?date-?yy$)|(^expiration-?date-?yyyy$)|(^validity-?year$)|(^exp-?date-?year$)|(^date-?y$)', - 'cc-type': '(^cc-?type$)|(^card-?type$)|(^card-?brand$)|(^cc-?brand$)|(^cb-?type$)' - }, { - email: 'e.?mail|courriel|correo.*electr(o|ó)nico|メールアドレス|Электронной.?Почты|邮件|邮箱|電郵地址|ഇ-മെയില്‍|ഇലക്ട്രോണിക്.?മെയിൽ|ایمیل|پست.*الکترونیک|ईमेल|इलॅक्ट्रॉनिक.?मेल|(\\b|_)eposta(\\b|_)|(?:이메일|전자.?우편|[Ee]-?mail)(.?주소)?', - tel: 'phone|mobile|contact.?number|telefonnummer|telefono|teléfono|telfixe|電話|telefone|telemovel|телефон|मोबाइल|(\\b|_|\\*)telefon(\\b|_|\\*)|电话|മൊബൈല്‍|(?:전화|핸드폰|휴대폰|휴대전화)(?:.?번호)?', - organization: 'company|business|organization|organisation|empresa|societe|société|ragione.?sociale|会社|название.?компании|单位|公司|شرکت|회사|직장', - 'street-address': 'streetaddress|street-address', - 'address-line1': '^address$|address[_-]?line[_-]?(1|one)|address1|addr1|street|(?:shipping|billing)address$|strasse|straße|hausnummer|housenumber|house.?name|direccion|dirección|adresse|indirizzo|^住所$|住所1|Адрес|地址|(\\b|_)adres(?! (başlığı(nız)?|tarifi))(\\b|_)|^주소.?$|주소.?1', - 'address-line2': 'address[_-]?line(2|two)|address2|addr2|street|suite|unit(?!e)|adresszusatz|ergänzende.?angaben|direccion2|colonia|adicional|addresssuppl|complementnom|appartement|indirizzo2|住所2|complemento|addrcomplement|Улица|地址2|주소.?2', - 'address-line3': 'address[_-]?line(3|three)|address3|addr3|street|suite|unit(?!e)|adresszusatz|ergänzende.?angaben|direccion3|colonia|adicional|addresssuppl|complementnom|appartement|indirizzo3|住所3|complemento|addrcomplement|Улица|地址3|주소.?3', - 'address-level2': 'city|town|\\bort\\b|stadt|suburb|ciudad|provincia|localidad|poblacion|ville|commune|localit(a|à)|citt(a|à)|市区町村|cidade|Город|市|分區|شهر|शहर|ग्राम|गाँव|നഗരം|ഗ്രാമം|((\\b|_|\\*)([İii̇]l[cç]e(miz|niz)?)(\\b|_|\\*))|^시[^도·・]|시[·・]?군[·・]?구', - 'address-level1': 'county|region|province|county|principality|都道府県|estado|provincia|область|省|地區|സംസ്ഥാനം|استان|राज्य|((\\b|_|\\*)(eyalet|[şs]ehir|[İii̇]limiz|kent)(\\b|_|\\*))|^시[·・]?도', - 'postal-code': 'zip|postal|post.*code|pcode|pin.?code|postleitzahl|\\bcp\\b|\\bcdp\\b|\\bcap\\b|郵便番号|codigo|codpos|\\bcep\\b|Почтовый.?Индекс|पिन.?कोड|പിന്‍കോഡ്|邮政编码|邮编|郵遞區號|(\\b|_)posta kodu(\\b|_)|우편.?번호', - country: 'country|countries|país|pais|(\\b|_)land(\\b|_)(?!.*(mark.*))|国家|국가|나라|(\\b|_)(ülke|ulce|ulke)(\\b|_)|کشور', - 'cc-name': 'card.?(?:holder|owner)|name.*(\\b)?on(\\b)?.*card|(?:card|cc).?name|cc.?full.?name|karteninhaber|nombre.*tarjeta|nom.*carte|nome.*cart|名前|Имя.*карты|信用卡开户名|开户名|持卡人姓名|持卡人姓名', - name: '^name|full.?name|your.?name|customer.?name|bill.?name|ship.?name|name.*first.*last|firstandlastname|nombre.*y.*apellidos|^nom(?!bre)\\b|お名前|氏名|^nome|نام.*نام.*خانوادگی|姓名|(\\b|_|\\*)ad[ı]? soyad[ı]?(\\b|_|\\*)|성명', - 'given-name': 'first.*name|initials|fname|first$|given.*name|vorname|nombre|forename|prénom|prenom|名|\\bnome|Имя|نام|이름|പേര്|(\\b|_|\\*)(isim|ad|ad(i|ı|iniz|ınız)?)(\\b|_|\\*)|नाम', - 'additional-name': 'middle.*name|mname|middle$|middle.*initial|m\\.i\\.|mi$|\\bmi\\b', - 'family-name': 'last.*name|lname|surname|last$|secondname|family.*name|nachname|apellidos?|famille|^nom(?!bre)|cognome|姓|apelidos|surename|sobrenome|Фамилия|نام.*خانوادگی|उपनाम|മറുപേര്|(\\b|_|\\*)(soyisim|soyad(i|ı|iniz|ınız)?)(\\b|_|\\*)|\\b성(?:[^명]|\\b)', - 'cc-number': '(add)?(?:card|cc|acct).?(?:number|#|no|num|field)|カード番号|Номер.*карты|信用卡号|信用卡号码|信用卡卡號|카드|(numero|número|numéro)(?!.*(document|fono|phone|réservation))', - 'cc-exp-month': 'gueltig|gültig|monat|fecha|date.*exp|scadenza|有効期限|validade|Срок действия карты|月', - 'cc-exp-year': 'ablaufdatum|gueltig|gültig|jahr|fecha|scadenza|有効期限|validade|Срок действия карты|年|有效期', - 'cc-exp': 'expir|exp.*date|^expfield$|gueltig|gültig|fecha|date.*exp|scadenza|有効期限|validade|Срок действия карты' - }] - } + () => { + resolve(true); + } + ); + }); } -}; - -},{}],42:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.logMatching = logMatching; -exports.logUnmatched = logUnmatched; -var _autofillUtils = require("../autofill-utils.js"); -var _matching = require("./matching.js"); -/** - * Logs out matching details when debug flag is active - * @param {HTMLInputElement | HTMLSelectElement} el - * @param {MatchingResult} matchingResult - */ -function logMatching(el, matchingResult) { - if (!(0, _autofillUtils.shouldLog)()) return; - const fieldIdentifier = getInputIdentifier(el); - console.group(fieldIdentifier); - console.log(el); - const { - strategyName, - matchedString, - matchedFrom, - matcherType - } = matchingResult; - const verb = getVerb(matchingResult); - let stringToLog = `${verb} for "${matcherType}" with "${strategyName}"`; - if (matchedString && matchedFrom) { - stringToLog += `\nString: "${matchedString}"\nSource: "${matchedFrom}"`; + async function extensionSpecificGetIncontextSignupDismissedAt() { + return new Promise((resolve) => { + chrome.runtime.sendMessage( + { + messageType: "getIncontextSignupDismissedAt" + }, + (response) => { + resolve(response); + } + ); + }); } - console.log(stringToLog); - console.groupEnd(); -} - -/** - * Helper to form the correct string based on matching result type - * @param {MatchingResult} matchingResult - * @return {string} - */ -function getVerb(matchingResult) { - if (matchingResult.matched) return 'Matched'; - if (matchingResult.proceed === false) return 'Matched forceUnknown'; - if (matchingResult.skip) return 'Skipped'; - return ''; -} - -/** - * Returns a human-friendly name to identify a single input field - * @param {HTMLInputElement | HTMLSelectElement} el - * @returns {string} - */ -function getInputIdentifier(el) { - const label = (0, _matching.getExplicitLabelsText)(el); - const placeholder = el instanceof HTMLInputElement && el.placeholder ? `${el.placeholder}` : ''; - const name = el.name ? `${el.name}` : ''; - const id = el.id ? `#${el.id}` : ''; - return 'Field: ' + (label || placeholder || name || id); -} - -/** - * Logs info when a field was not matched by the algo - * @param el - * @param allStrings - */ -function logUnmatched(el, allStrings) { - if (!(0, _autofillUtils.shouldLog)()) return; - const fieldIdentifier = getInputIdentifier(el); - console.group(fieldIdentifier); - console.log(el); - const stringToLog = 'Field not matched.'; - console.log(stringToLog, allStrings); - console.groupEnd(); -} - -},{"../autofill-utils.js":62,"./matching.js":43}],43:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.checkPlaceholderAndLabels = exports.Matching = void 0; -exports.createMatching = createMatching; -exports.getInputMainType = exports.getExplicitLabelsText = void 0; -exports.getInputSubtype = getInputSubtype; -exports.getInputType = getInputType; -exports.getInputVariant = getInputVariant; -exports.getMainTypeFromType = getMainTypeFromType; -exports.getRelatedText = void 0; -exports.getSubtypeFromType = getSubtypeFromType; -exports.getVariantFromType = getVariantFromType; -exports.removeExcessWhitespace = exports.matchInPlaceholderAndLabels = void 0; -var _constants = require("../constants.js"); -var _labelUtil = require("./label-util.js"); -var _compiledMatchingConfig = require("./matching-config/__generated__/compiled-matching-config.js"); -var _matchingUtils = require("./matching-utils.js"); -var _autofillUtils = require("../autofill-utils.js"); -const { - TEXT_LENGTH_CUTOFF, - ATTR_INPUT_TYPE -} = _constants.constants; - -/** @type {{[K in keyof MatcherLists]?: { minWidth: number }} } */ -const dimensionBounds = { - emailAddress: { - minWidth: 35 + async function extensionSpecificSetIncontextSignupPermanentlyDismissedAtCall(params) { + return new Promise((resolve) => { + chrome.runtime.sendMessage( + { + messageType: "setIncontextSignupPermanentlyDismissedAt", + options: params + }, + () => { + resolve(true); + } + ); + }); } -}; - -/** - * An abstraction around the concept of classifying input fields. - * - * The only state this class keeps is derived from the passed-in MatchingConfiguration. - */ -class Matching { - /** @type {MatchingConfiguration} */ - #config; - - /** @type {CssSelectorConfiguration['selectors']} */ - #cssSelectors; - - /** @type {Record} */ - #ddgMatchers; - - /** - * This acts as an internal cache for the larger vendorRegexes - * @type {VendorRegexConfiguration['rules']} - */ - #vendorRegexRules; - - /** @type {MatcherLists} */ - #matcherLists; - /** @type {Array} */ - #defaultStrategyOrder = ['cssSelector', 'ddgMatcher', 'vendorRegex']; - - /** @type {Record} */ - activeElementStrings = { - nameAttr: '', - labelText: '', - placeholderAttr: '', - relatedText: '', - id: '' - }; - - /** - * @param {MatchingConfiguration} config - */ - constructor(config) { - this.#config = config; - this.#vendorRegexRules = this.#config.strategies.vendorRegex.rules; - this.#cssSelectors = this.#config.strategies.cssSelector.selectors; - this.#ddgMatchers = this.#config.strategies.ddgMatcher.matchers; - this.#matcherLists = { - unknown: [], - cc: [], - id: [], - password: [], - username: [], - emailAddress: [] - }; - - /** - * Convert the raw config data into actual references. - * - * For example this takes `email: ["email"]` and creates - * - * `email: [{type: "email", strategies: {cssSelector: "email", ... etc}]` - */ - for (let [listName, matcherNames] of Object.entries(this.#config.matchers.lists)) { - for (let fieldName of matcherNames) { - if (!this.#matcherLists[listName]) { - this.#matcherLists[listName] = []; - } - this.#matcherLists[listName].push(this.#config.matchers.fields[fieldName]); + // src/deviceApiCalls/transports/windows.transport.js + var WindowsTransport = class extends DeviceApiTransport { + async send(deviceApiCall, options) { + if (deviceApiCall.id) { + return windowsTransport(deviceApiCall, options).withResponse(deviceApiCall.id); } + return windowsTransport(deviceApiCall, options); } + }; + function windowsTransport(deviceApiCall, options) { + windowsInteropPostMessage({ + Feature: "Autofill", + Name: deviceApiCall.method, + Data: deviceApiCall.params + }); + return { + /** + * Sends a message and returns a Promise that resolves with the response + * @param responseId + * @returns {Promise<*>} + */ + withResponse(responseId) { + return waitForWindowsResponse(responseId, options); + } + }; } - - /** - * @param {HTMLInputElement|HTMLSelectElement} input - * @param {HTMLElement} formEl - */ - setActiveElementStrings(input, formEl) { - this.activeElementStrings = this.getElementStrings(input, formEl); + function waitForWindowsResponse(responseId, options) { + return new Promise((resolve, reject) => { + if (options?.signal?.aborted) { + return reject(new DOMException("Aborted", "AbortError")); + } + let teardown; + const handler = (event) => { + if (!event.data) { + console.warn("data absent from message"); + return; + } + if (event.data.type === responseId) { + teardown(); + resolve(event.data); + } + }; + const abortHandler = () => { + teardown(); + reject(new DOMException("Aborted", "AbortError")); + }; + windowsInteropAddEventListener("message", handler); + options?.signal?.addEventListener("abort", abortHandler); + teardown = () => { + windowsInteropRemoveEventListener("message", handler); + options?.signal?.removeEventListener("abort", abortHandler); + }; + }); } - /** - * Try to access a 'vendor regex' by name - * @param {string} regexName - * @returns {RegExp | undefined} - */ - vendorRegex(regexName) { - const match = this.#vendorRegexRules[regexName]; - if (!match) { - console.warn('Vendor Regex not found for', regexName); - return undefined; + // src/deviceApiCalls/transports/transports.js + function createTransport(globalConfig) { + if (typeof globalConfig.userPreferences?.platform?.name === "string") { + switch (globalConfig.userPreferences?.platform?.name) { + case "ios": + case "macos": + return new AppleTransport(globalConfig); + case "android": + return new AndroidTransport(globalConfig); + default: + throw new Error("selectSender unimplemented!"); + } } - return match; + if (globalConfig.isWindows) { + return new WindowsTransport(); + } + if (globalConfig.isDDGApp) { + if (globalConfig.isAndroid) { + return new AndroidTransport(globalConfig); + } + throw new Error("unreachable, createTransport"); + } + return new ExtensionTransport(globalConfig); } - /** - * Strategies can have different lookup names. This returns the correct one - * @param {MatcherTypeNames} matcherName - * @param {StrategyNames} vendorRegex - * @returns {MatcherTypeNames} - */ - getStrategyLookupByType(matcherName, vendorRegex) { - return this.#config.matchers.fields[matcherName]?.strategies[vendorRegex]; + // src/DeviceInterface/initFormSubmissionsApi.js + function initFormSubmissionsApi(forms, matching) { + window.addEventListener("submit", (e) => { + return forms.get(e.target)?.submitHandler("global submit event"); + }, true); + window.addEventListener("keydown", (e) => { + if (e.key === "Enter") { + const focusedForm = [...forms.values()].find((form) => form.hasFocus(e)); + focusedForm?.submitHandler("global keydown + Enter"); + } + }, true); + window.addEventListener("pointerdown", (event) => { + const realTarget = pierceShadowTree(event); + const formsArray = [...forms.values()]; + const matchingForm = formsArray.find( + (form) => { + const btns = [...form.submitButtons]; + if (btns.includes(realTarget)) + return true; + if (btns.find((btn) => btn.contains(realTarget))) + return true; + } + ); + matchingForm?.submitHandler("global pointerdown event + matching form"); + if (!matchingForm) { + const selector = matching.cssSelector("submitButtonSelector") + ', a[href="#"], a[href^=javascript], *[onclick], [class*=button i]'; + const button = ( + /** @type HTMLElement */ + realTarget?.closest(selector) + ); + if (!button) + return; + const buttonIsAFalsePositive = formsArray.some((form) => button?.contains(form.form)); + if (buttonIsAFalsePositive) + return; + const text = getTextShallow(button) || extractElementStrings(button).join(" "); + const hasRelevantText = safeRegexTest(matching.getDDGMatcherRegex("submitButtonRegex"), text); + if (hasRelevantText && text.length < 25) { + const filledForm = formsArray.find((form) => form.hasValues()); + if (filledForm && buttonMatchesFormType( + /** @type HTMLElement */ + button, + filledForm + )) { + filledForm?.submitHandler("global pointerdown event + filled form"); + } + } + if ( + /** @type HTMLElement */ + realTarget?.closest("#passwordNext button, #identifierNext button") + ) { + const filledForm = formsArray.find((form) => form.hasValues()); + filledForm?.submitHandler("global pointerdown event + google escape hatch"); + } + } + }, true); + const observer = new PerformanceObserver((list) => { + const formsArray = [...forms.values()]; + const entries = list.getEntries().filter( + (entry) => ( + // @ts-ignore why does TS not know about `entry.initiatorType`? + ["fetch", "xmlhttprequest"].includes(entry.initiatorType) && safeRegexTest(/login|sign-in|signin/, entry.name) + ) + ); + if (!entries.length) + return; + const filledForm = formsArray.find((form) => form.hasValues()); + const focusedForm = formsArray.find((form) => form.hasFocus()); + if (focusedForm) + return; + filledForm?.submitHandler("performance observer"); + }); + observer.observe({ entryTypes: ["resource"] }); } - /** - * Try to access a 'css selector' by name from configuration - * @param {RequiredCssSelectors | string} selectorName - * @returns {string}; - */ - cssSelector(selectorName) { - const match = this.#cssSelectors[selectorName]; - if (!match) { - console.warn('CSS selector not found for %s, using a default value', selectorName); - return ''; + // src/EmailProtection.js + var _previous2; + var EmailProtection = class { + /** @param {import("./DeviceInterface/InterfacePrototype").default} device */ + constructor(device) { + /** @type {string|null} */ + __privateAdd(this, _previous2, null); + this.device = device; } - return match; - } - - /** - * Try to access a 'ddg matcher' by name from configuration - * @param {MatcherTypeNames | string} matcherName - * @returns {DDGMatcher | undefined} - */ - ddgMatcher(matcherName) { - const match = this.#ddgMatchers[matcherName]; - if (!match) { - console.warn('DDG matcher not found for', matcherName); - return undefined; + /** @returns {string|null} */ + get lastGenerated() { + return __privateGet(this, _previous2); } - return match; - } - - /** - * Returns the RegExp for the given matcherName, with proper flags - * @param {AllDDGMatcherNames} matcherName - * @returns {RegExp|undefined} - */ - getDDGMatcherRegex(matcherName) { - const matcher = this.ddgMatcher(matcherName); - if (!matcher || !matcher.match) { - console.warn('DDG matcher has unexpected format'); - return undefined; + /** + * Store the last received email address + * @param {string} emailAddress + */ + storeReceived(emailAddress) { + __privateSet(this, _previous2, emailAddress); + return emailAddress; } - return matcher?.match; - } + }; + _previous2 = new WeakMap(); - /** - * Try to access a list of matchers by name - these are the ones collected in the constructor - * @param {keyof MatcherLists} listName - * @return {Matcher[]} - */ - matcherList(listName) { - const matcherList = this.#matcherLists[listName]; - if (!matcherList) { - console.warn('MatcherList not found for ', listName); - return []; + // src/DeviceInterface/InterfacePrototype.js + var _addresses, _data6; + var _InterfacePrototype = class { + /** + * @param {GlobalConfig} config + * @param {import("../../packages/device-api").DeviceApi} deviceApi + * @param {Settings} settings + */ + constructor(config, deviceApi, settings) { + __publicField(this, "attempts", 0); + /** @type {import("../Form/Form").Form | null} */ + __publicField(this, "activeForm", null); + /** @type {import("../UI/HTMLTooltip.js").default | null} */ + __publicField(this, "currentTooltip", null); + /** @type {number} */ + __publicField(this, "initialSetupDelayMs", 0); + __publicField(this, "autopromptFired", false); + /** @type {PasswordGenerator} */ + __publicField(this, "passwordGenerator", new PasswordGenerator()); + __publicField(this, "emailProtection", new EmailProtection(this)); + /** @type {import("../InContextSignup.js").InContextSignup | null} */ + __publicField(this, "inContextSignup", null); + /** @type {import("../ThirdPartyProvider.js").ThirdPartyProvider | null} */ + __publicField(this, "thirdPartyProvider", null); + /** @type {{privateAddress: string, personalAddress: string}} */ + __privateAdd(this, _addresses, { + privateAddress: "", + personalAddress: "" + }); + /** @type {GlobalConfig} */ + __publicField(this, "globalConfig"); + /** @type {import('../Scanner').Scanner} */ + __publicField(this, "scanner"); + /** @type {import("../UI/controllers/UIController.js").UIController | null} */ + __publicField(this, "uiController"); + /** @type {import("../../packages/device-api").DeviceApi} */ + __publicField(this, "deviceApi"); + /** @type {boolean} */ + __publicField(this, "isInitializationStarted"); + /** @type {((reason, ...rest) => void) | null} */ + __publicField(this, "_scannerCleanup", null); + /** @type { PMData } */ + __privateAdd(this, _data6, { + credentials: [], + creditCards: [], + identities: [], + topContextData: void 0 + }); + this.globalConfig = config; + this.deviceApi = deviceApi; + this.settings = settings; + this.uiController = null; + this.scanner = createScanner(this, { + initialDelay: this.initialSetupDelayMs + }); + this.isInitializationStarted = false; } - return matcherList; - } - - /** - * Convert a list of matchers into a single CSS selector. - * - * This will consider all matchers in the list and if it - * contains a CSS Selector it will be added to the final output - * - * @param {keyof MatcherLists} listName - * @returns {string | undefined} - */ - joinCssSelectors(listName) { - const matcherList = this.matcherList(listName); - if (!matcherList) { - console.warn('Matcher list not found for', listName); - return undefined; + /** + * Implementors should override this with a UI controller that suits + * their platform. + * + * @returns {import("../UI/controllers/UIController.js").UIController} + */ + createUIController() { + return new NativeUIController(); } - /** - * @type {string[]} + * @param {string} reason */ - const selectors = []; - for (let matcher of matcherList) { - if (matcher.strategies.cssSelector) { - const css = this.cssSelector(matcher.strategies.cssSelector); - if (css) { - selectors.push(css); - } + removeAutofillUIFromPage(reason) { + this.uiController?.destroy(); + this._scannerCleanup?.(reason); + } + get hasLocalAddresses() { + return !!(__privateGet(this, _addresses)?.privateAddress && __privateGet(this, _addresses)?.personalAddress); + } + getLocalAddresses() { + return __privateGet(this, _addresses); + } + storeLocalAddresses(addresses) { + __privateSet(this, _addresses, addresses); + const identities = this.getLocalIdentities(); + const privateAddressIdentity = identities.find(({ id }) => id === "privateAddress"); + if (privateAddressIdentity) { + privateAddressIdentity.emailAddress = formatDuckAddress(addresses.privateAddress); + } else { + __privateGet(this, _data6).identities = this.addDuckAddressesToIdentities(identities); } } - return selectors.join(', '); - } - - /** - * Returns true if the field is visible and large enough - * @param {keyof MatcherLists} matchedType - * @param {HTMLInputElement} input - * @returns {boolean} - */ - isInputLargeEnough(matchedType, input) { - const expectedDimensionBounds = dimensionBounds[matchedType]; - if (!expectedDimensionBounds) return true; - const width = input.offsetWidth; - const height = input.offsetHeight; - - // Ignore hidden elements as we can't determine their dimensions - const isHidden = height === 0 && width === 0; - if (isHidden) return true; - return width >= expectedDimensionBounds.minWidth; - } - - /** - * Tries to infer the input type for an input - * - * @param {HTMLInputElement|HTMLSelectElement} input - * @param {HTMLElement} formEl - * @param {SetInputTypeOpts} [opts] - * @returns {SupportedTypes} - */ - inferInputType(input, formEl) { - let opts = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - const presetType = getInputType(input); - if (presetType !== 'unknown') { - return presetType; - } - this.setActiveElementStrings(input, formEl); - if (this.subtypeFromMatchers('unknown', input)) return 'unknown'; - - // For CC forms we run aggressive matches, so we want to make sure we only - // run them on actual CC forms to avoid false positives and expensive loops - if (opts.isCCForm) { - const subtype = this.subtypeFromMatchers('cc', input); - if (subtype && isValidCreditCardSubtype(subtype)) { - return `creditCards.${subtype}`; - } - } - if (input instanceof HTMLInputElement) { - if (this.subtypeFromMatchers('password', input)) { - // Any other input type is likely a false match - // Arguably "text" should be as well, but it can be used for password reveal fields - if (['password', 'text'].includes(input.type) && input.name !== 'email' && - // pcsretirement.com, improper use of the for attribute - input.name !== 'Username') { - return this.inferPasswordVariant(input, opts); - } - } - if (this.subtypeFromMatchers('emailAddress', input)) { - if (!this.isInputLargeEnough('emailAddress', input)) { - if ((0, _autofillUtils.shouldLog)()) { - console.log('Field matched for Email Address, but discarded because too small when scanned'); - } - return 'unknown'; - } - if (opts.isLogin || opts.isHybrid) { - // TODO: Bring this support back in the future - // https://app.asana.com/0/1198964220583541/1204686960531034/f - // Show identities when supported and there are no credentials - // if (opts.supportsIdentitiesAutofill && !opts.hasCredentials) { - // return 'identities.emailAddress' - // } - - return 'credentials.username'; - } - - // TODO: Temporary hack to support Google signin in different languages - // https://app.asana.com/0/1198964220583541/1201650539303898/f - if (window.location.href.includes('https://accounts.google.com/v3/signin/identifier') && input.matches('[type=email][autocomplete=username]')) { - return 'credentials.username'; - } - return 'identities.emailAddress'; - } - if (this.subtypeFromMatchers('username', input)) { - return 'credentials.username'; + /** + * @returns {import('../Form/matching').SupportedTypes} + */ + getCurrentInputType() { + throw new Error("Not implemented"); + } + addDuckAddressesToIdentities(identities) { + if (!this.hasLocalAddresses) + return identities; + const newIdentities = []; + let { privateAddress, personalAddress } = this.getLocalAddresses(); + privateAddress = formatDuckAddress(privateAddress); + personalAddress = formatDuckAddress(personalAddress); + const duckEmailsInIdentities = identities.reduce( + (duckEmails, { emailAddress: email }) => email?.includes(ADDRESS_DOMAIN) ? duckEmails.concat(email) : duckEmails, + [] + ); + if (!duckEmailsInIdentities.includes(personalAddress)) { + newIdentities.push({ + id: "personalAddress", + emailAddress: personalAddress, + title: "Block email trackers" + }); } + newIdentities.push({ + id: "privateAddress", + emailAddress: privateAddress, + title: "Block email trackers & hide address" + }); + return [...identities, ...newIdentities]; } - const idSubtype = this.subtypeFromMatchers('id', input); - if (idSubtype && isValidIdentitiesSubtype(idSubtype)) { - return `identities.${idSubtype}`; - } - (0, _matchingUtils.logUnmatched)(input, this.activeElementStrings); - return 'unknown'; - } - - /** - * @typedef {{ - * isLogin?: boolean, - * isHybrid?: boolean, - * isCCForm?: boolean, - * hasCredentials?: boolean, - * supportsIdentitiesAutofill?: boolean - * }} SetInputTypeOpts - */ - - /** - * Sets the input type as a data attribute to the element and returns it - * @param {HTMLInputElement} input - * @param {HTMLElement} formEl - * @param {SetInputTypeOpts} [opts] - * @returns {SupportedSubTypes | string} - */ - setInputType(input, formEl) { - let opts = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - const type = this.inferInputType(input, formEl, opts); - input.setAttribute(ATTR_INPUT_TYPE, type); - return type; - } - - /** - * Tries to infer input subtype, with checks in decreasing order of reliability - * @param {keyof MatcherLists} listName - * @param {HTMLInputElement|HTMLSelectElement} el - * @return {MatcherTypeNames|undefined} - */ - subtypeFromMatchers(listName, el) { - const matchers = this.matcherList(listName); - /** - * Loop through each strategy in order + * Stores init data coming from the tooltipHandler + * @param { InboundPMData } data */ - for (let strategyName of this.#defaultStrategyOrder) { - let result; - /** - * Now loop through each matcher in the list. - */ - for (let matcher of matchers) { - /** - * for each `strategyName` (such as cssSelector), check - * if the current matcher implements it. - */ - const lookup = matcher.strategies[strategyName]; - /** - * Sometimes a matcher may not implement the current strategy, - * so we skip it - */ - if (!lookup) continue; - - /** - * Now perform the matching - */ - if (strategyName === 'cssSelector') { - result = this.execCssSelector(lookup, el); - } - if (strategyName === 'ddgMatcher') { - result = this.execDDGMatcher(lookup); - } - if (strategyName === 'vendorRegex') { - result = this.execVendorRegex(lookup); - } - - /** - * If there's a match, return the matcher type. - * - * So, for example if 'username' had a `cssSelector` implemented, and - * it matched the current element, then we'd return 'username' - */ - if (result?.matched) { - (0, _matchingUtils.logMatching)(el, result); - return matcher.type; - } - - /** - * If a matcher wants to prevent all future matching on this element, - * it would return { matched: false, proceed: false } - */ - if (!result?.matched && result?.proceed === false) { - (0, _matchingUtils.logMatching)(el, result); - // If we get here, do not allow subsequent strategies to continue - return undefined; + storeLocalData(data) { + this.storeLocalCredentials(data.credentials); + data.creditCards.forEach((cc) => delete cc.cardNumber && delete cc.cardSecurityCode); + const updatedIdentities = data.identities.map((identity) => ({ + ...identity, + fullName: formatFullName(identity) + })); + __privateGet(this, _data6).identities = this.addDuckAddressesToIdentities(updatedIdentities); + __privateGet(this, _data6).creditCards = data.creditCards; + if (data.serializedInputContext) { + try { + __privateGet(this, _data6).topContextData = JSON.parse(data.serializedInputContext); + } catch (e) { + console.error(e); + this.removeTooltip(); } } - if (result?.skip) { - (0, _matchingUtils.logMatching)(el, result); - break; - } } - return undefined; - } - - /** - * Returns the password type string including the variant - * @param {HTMLInputElement} input - * @param opts - * @returns {'credentials.password.new'|'credentials.password.current'} - */ - inferPasswordVariant(input, opts) { - // Check attributes first - // This is done mainly to ensure coverage for all languages, since attributes are usually in English - const attrsToCheck = [input.autocomplete, input.name, input.id]; - if (opts.isSignup && attrsToCheck.some(str => (0, _autofillUtils.safeRegexTest)(/new.?password|password.?new/i, str))) { - return 'credentials.password.new'; + /** + * Stores credentials locally + * @param {CredentialsObject[]} credentials + */ + storeLocalCredentials(credentials) { + credentials.forEach((cred) => delete cred.password); + __privateGet(this, _data6).credentials = credentials; + } + getTopContextData() { + return __privateGet(this, _data6).topContextData; + } + /** + * @deprecated use `availableInputTypes.credentials` directly instead + * @returns {boolean} + */ + get hasLocalCredentials() { + return __privateGet(this, _data6).credentials.length > 0; } - if ((opts.isLogin || opts.isHybrid) && attrsToCheck.some(str => (0, _autofillUtils.safeRegexTest)(/(current|old|previous).?password|password.?(current|old|previous)/i, str))) { - return 'credentials.password.current'; + getLocalCredentials() { + return __privateGet(this, _data6).credentials.map((cred) => { + const { password, ...rest } = cred; + return rest; + }); } - - // Check strings using the usual DDG matcher - const newPasswordMatch = this.execDDGMatcher('newPassword'); - if (newPasswordMatch.matched) { - return 'credentials.password.new'; + /** + * @deprecated use `availableInputTypes.identities` directly instead + * @returns {boolean} + */ + get hasLocalIdentities() { + return __privateGet(this, _data6).identities.length > 0; } - const currentPasswordMatch = this.execDDGMatcher('currentPassword'); - if (currentPasswordMatch.matched) { - return 'credentials.password.current'; + getLocalIdentities() { + return __privateGet(this, _data6).identities; } - - // Otherwise, rely on the passed form type - if (opts.isLogin || opts.isHybrid) { - return 'credentials.password.current'; + /** + * @deprecated use `availableInputTypes.creditCards` directly instead + * @returns {boolean} + */ + get hasLocalCreditCards() { + return __privateGet(this, _data6).creditCards.length > 0; } - return 'credentials.password.new'; - } - - /** - * CSS selector matching just leverages the `.matches` method on elements - * - * @param {MatcherTypeNames} lookup - * @param {HTMLInputElement|HTMLSelectElement} el - * @returns {MatchingResult} - */ - execCssSelector(lookup, el) { - const selector = this.cssSelector(lookup); - return { - matched: el.matches(selector), - strategyName: 'cssSelector', - matcherType: lookup - }; - } - - /** - * A DDG Matcher can have a `match` regex along with a `not` regex. This is done - * to allow it to be driven by configuration as it avoids needing to invoke custom functions. - * - * todo: maxDigits was added as an edge-case when converting this over to be declarative, but I'm - * unsure if it's actually needed. It's not urgent, but we should consider removing it if that's the case - * - * @param {MatcherTypeNames} lookup - * @returns {MatchingResult} - */ - execDDGMatcher(lookup) { - /** @type {MatchingResult} */ - const defaultResult = { - matched: false, - strategyName: 'ddgMatcher', - matcherType: lookup - }; - const ddgMatcher = this.ddgMatcher(lookup); - if (!ddgMatcher || !ddgMatcher.match) { - return defaultResult; + /** @return {CreditCardObject[]} */ + getLocalCreditCards() { + return __privateGet(this, _data6).creditCards; } - let matchRexExp = this.getDDGMatcherRegex(lookup); - if (!matchRexExp) { - return defaultResult; + async startInit() { + if (this.isInitializationStarted) + return; + this.alreadyInitialized = true; + await this.settings.refresh(); + this.addDeviceListeners(); + await this.setupAutofill(); + this.uiController = this.createUIController(); + if (!this.settings.enabled) { + return; + } + await this.setupSettingsPage(); + await this.postInit(); + if (this.settings.featureToggles.credentials_saving) { + initFormSubmissionsApi(this.scanner.forms, this.scanner.matching); + } } - let requiredScore = ['match', 'forceUnknown', 'maxDigits'].filter(ddgMatcherProp => ddgMatcherProp in ddgMatcher).length; - - /** @type {MatchableStrings[]} */ - const matchableStrings = ddgMatcher.matchableStrings || ['labelText', 'placeholderAttr', 'relatedText']; - for (let stringName of matchableStrings) { - let elementString = this.activeElementStrings[stringName]; - if (!elementString) continue; - - // Scoring to ensure all DDG tests are valid - let score = 0; - - /** @type {MatchingResult} */ - const result = { - ...defaultResult, - matchedString: elementString, - matchedFrom: stringName - }; - - // If a negated regex was provided, ensure it does not match - // If it DOES match - then we need to prevent any future strategies from continuing - if (ddgMatcher.forceUnknown) { - let notRegex = ddgMatcher.forceUnknown; - if (!notRegex) { - return { - ...result, - matched: false - }; + async init() { + const isEnabledInitiallyViaConfig = autofillEnabled(this.globalConfig); + if (!isEnabledInitiallyViaConfig) { + return; + } + const handler = async () => { + if (document.readyState === "complete") { + window.removeEventListener("load", handler); + document.removeEventListener("readystatechange", handler); + await this.startInit(); } - if ((0, _autofillUtils.safeRegexTest)(notRegex, elementString)) { - return { - ...result, - matched: false, - proceed: false - }; - } else { - // All good here, increment the score - score++; + }; + if (document.readyState === "complete") { + await this.startInit(); + } else { + window.addEventListener("load", handler); + document.addEventListener("readystatechange", handler); + } + } + postInit() { + const cleanup = this.scanner.init(); + this.addLogoutListener(() => { + cleanup("Logged out"); + if (this.globalConfig.isDDGDomain) { + notifyWebApp({ deviceSignedIn: { value: false } }); } + }); + } + /** + * @deprecated This was a port from the macOS implementation so the API may not be suitable for all + * @returns {Promise} + */ + async getSelectedCredentials() { + throw new Error("`getSelectedCredentials` not implemented"); + } + isTestMode() { + return this.globalConfig.isDDGTestMode; + } + /** + * This indicates an item was selected on Desktop, and we should try to autofill + * + * Note: When we're in a top-frame scenario, like on like macOS & Windows in the webview, + * this method gets overridden {@see WindowsOverlayDeviceInterface} {@see AppleOverlayDeviceInterface} + * + * @param {IdentityObject|CreditCardObject|CredentialsObject|{email:string, id: string}} data + * @param {string} type + */ + async selectedDetail(data, type) { + const form = this.activeForm; + if (!form) { + return; } - if (ddgMatcher.skip) { - let skipRegex = ddgMatcher.skip; - if (!skipRegex) { - return { - ...result, - matched: false + if (type === "email" && "email" in data) { + form.autofillEmail(data.email); + } else { + form.autofillData(data, type); + } + const isPrivateAddress = data.id === "privateAddress"; + if (isPrivateAddress) { + this.refreshAlias(); + if ("emailAddress" in data && data.emailAddress) { + this.emailProtection.storeReceived(data.emailAddress); + const formValues = { + credentials: { + username: data.emailAddress, + autogenerated: true + } }; + this.storeFormData(formValues, "emailProtection"); } - if ((0, _autofillUtils.safeRegexTest)(skipRegex, elementString)) { - return { - ...result, - matched: false, - skip: true - }; + } + await this.removeTooltip(); + } + /** + * Before the DataWebTooltip opens, we collect the data based on the config.type + * @param {InputTypeConfigs} config + * @param {import('../Form/matching').SupportedTypes} inputType + * @param {TopContextData} [data] + * @returns {(CredentialsObject|CreditCardObject|IdentityObject)[]} + */ + dataForAutofill(config, inputType, data) { + const subtype = getSubtypeFromType(inputType); + if (config.type === "identities") { + return this.getLocalIdentities().filter((identity) => !!identity[subtype]); + } + if (config.type === "creditCards") { + return this.getLocalCreditCards(); + } + if (config.type === "credentials") { + if (data) { + if (Array.isArray(data.credentials) && data.credentials.length > 0) { + return data.credentials; + } else { + return this.getLocalCredentials().filter((cred) => !!cred[subtype] || subtype === "password" || cred.id === PROVIDER_LOCKED); + } } } - - // if the `match` regex fails, moves onto the next string - if (!(0, _autofillUtils.safeRegexTest)(matchRexExp, elementString)) { - continue; + return []; + } + /** + * @param {object} params + * @param {import("../Form/Form").Form} params.form + * @param {HTMLInputElement} params.input + * @param {{ x: number; y: number; } | null} params.click + * @param {import('../deviceApiCalls/__generated__/validators-ts').GetAutofillDataRequest['trigger']} params.trigger + * @param {import('../UI/controllers/UIController.js').AttachArgs["triggerMetaData"]} params.triggerMetaData + */ + attachTooltip(params) { + const { + form, + input, + click, + trigger + } = params; + if (document.visibilityState !== "visible" && trigger !== "postSignup") + return; + if (trigger === "autoprompt" && !this.globalConfig.isMobileApp) + return; + if (trigger === "autoprompt" && this.autopromptFired) + return; + form.activeInput = input; + this.activeForm = form; + const inputType = getInputType(input); + const getPosition = () => { + const alignLeft = this.globalConfig.isApp || this.globalConfig.isWindows; + return alignLeft ? input.getBoundingClientRect() : getDaxBoundingBox(input); + }; + if (this.globalConfig.isMobileApp && inputType === "identities.emailAddress") { + this.getAlias().then((alias) => { + if (alias) { + form.autofillEmail(alias); + this.emailProtection.storeReceived(alias); + } else { + form.activeInput?.focus(); + } + this.updateForStateChange(); + this.onFinishedAutofill(); + }); + return; } - - // Otherwise, increment the score - score++; - - // If a 'maxDigits' rule was provided, validate it - if (ddgMatcher.maxDigits) { - const digitLength = elementString.replace(/[^0-9]/g, '').length; - if (digitLength > ddgMatcher.maxDigits) { - return { - ...result, - matched: false - }; + const topContextData = { + inputType + }; + const processedTopContext = this.preAttachTooltip(topContextData, input, form); + this.uiController?.attach({ + input, + form, + click, + getPosition, + topContextData: processedTopContext, + device: this, + trigger, + triggerMetaData: params.triggerMetaData + }); + if (trigger === "autoprompt") { + this.autopromptFired = true; + } + } + /** + * When an item was selected, we then call back to the device + * to fetch the full suite of data needed to complete the autofill + * + * @param {import('../Form/matching').SupportedTypes} inputType + * @param {(CreditCardObject|IdentityObject|CredentialsObject)[]} items + * @param {CreditCardObject['id']|IdentityObject['id']|CredentialsObject['id']} id + */ + onSelect(inputType, items, id) { + id = String(id); + const mainType = getMainTypeFromType(inputType); + const subtype = getSubtypeFromType(inputType); + if (id === PROVIDER_LOCKED) { + return this.thirdPartyProvider?.askToUnlockProvider(); + } + const matchingData = items.find((item) => String(item.id) === id); + if (!matchingData) + throw new Error("unreachable (fatal)"); + const dataPromise = (() => { + switch (mainType) { + case "creditCards": + return this.getAutofillCreditCard(id); + case "identities": + return this.getAutofillIdentity(id); + case "credentials": { + if (AUTOGENERATED_KEY in matchingData) { + const autogeneratedPayload = { ...matchingData, username: "" }; + return Promise.resolve({ success: autogeneratedPayload }); + } + return this.getAutofillCredentials(id); + } + default: + throw new Error("unreachable!"); + } + })(); + dataPromise.then((response) => { + if (response) { + const data = response.success || response; + if (mainType === "identities") { + this.firePixel({ pixelName: "autofill_identity", params: { fieldType: subtype } }); + switch (id) { + case "personalAddress": + this.firePixel({ pixelName: "autofill_personal_address" }); + break; + case "privateAddress": + this.firePixel({ pixelName: "autofill_private_address" }); + break; + default: + const checks = [ + subtype === "emailAddress", + this.hasLocalAddresses, + data?.emailAddress === formatDuckAddress(__privateGet(this, _addresses).personalAddress) + ]; + if (checks.every(Boolean)) { + this.firePixel({ pixelName: "autofill_personal_address" }); + } + break; + } + } + return this.selectedDetail(data, mainType); } else { - score++; + return Promise.reject(new Error("none-success response")); } - } - if (score === requiredScore) { - return { - ...result, - matched: true - }; - } + }).catch((e) => { + console.error(e); + return this.removeTooltip(); + }); } - return defaultResult; - } - - /** - * If we get here, a firefox/vendor regex was given and we can execute it on the element - * strings - * @param {MatcherTypeNames} lookup - * @return {MatchingResult} - */ - execVendorRegex(lookup) { - /** @type {MatchingResult} */ - const defaultResult = { - matched: false, - strategyName: 'vendorRegex', - matcherType: lookup - }; - const regex = this.vendorRegex(lookup); - if (!regex) { - return defaultResult; + isTooltipActive() { + return this.uiController?.isActive?.() ?? false; } - /** @type {MatchableStrings[]} */ - const stringsToMatch = ['placeholderAttr', 'nameAttr', 'labelText', 'id', 'relatedText']; - for (let stringName of stringsToMatch) { - let elementString = this.activeElementStrings[stringName]; - if (!elementString) continue; - if ((0, _autofillUtils.safeRegexTest)(regex, elementString)) { - return { - ...defaultResult, - matched: true, - matchedString: elementString, - matchedFrom: stringName - }; - } + removeTooltip() { + return this.uiController?.removeTooltip?.("interface"); } - return defaultResult; - } - - /** - * Yield strings in the order in which they should be checked against. - * - * Note: some strategies may not want to accept all strings, which is - * where `matchableStrings` helps. It defaults to when you see below but can - * be overridden. - * - * For example, `nameAttr` is first, since this has the highest chance of matching - * and then the rest are in decreasing order of value vs cost - * - * A generator function is used here to prevent any potentially expensive - * lookups occurring if they are rare. For example if 90% of all matching never needs - * to look at the output from `relatedText`, then the cost of computing it will be avoided. - * - * @param {HTMLInputElement|HTMLSelectElement} el - * @param {HTMLElement} form - * @returns {Record} - */ - _elementStringCache = new WeakMap(); - getElementStrings(el, form) { - if (this._elementStringCache.has(el)) { - return this._elementStringCache.get(el); + onFinishedAutofill() { + this.activeForm?.activeInput?.dispatchEvent(new Event("mouseleave")); } - const explicitLabelsText = getExplicitLabelsText(el); - - /** @type {Record} */ - const next = { - nameAttr: el.name, - labelText: explicitLabelsText, - placeholderAttr: el.placeholder || '', - id: el.id, - relatedText: explicitLabelsText ? '' : getRelatedText(el, form, this.cssSelector('formInputsSelector')) - }; - this._elementStringCache.set(el, next); - return next; - } - clear() { - this._elementStringCache = new WeakMap(); - } - - /** - * Only used for testing - * @param {HTMLInputElement|HTMLSelectElement} input - * @param {HTMLElement} form - * @returns {Matching} - */ - forInput(input, form) { - this.setActiveElementStrings(input, form); - return this; - } - - /** - * @type {MatchingConfiguration} - */ - static emptyConfig = { - matchers: { - lists: {}, - fields: {} - }, - strategies: { - 'vendorRegex': { - rules: {}, - ruleSets: [] - }, - 'ddgMatcher': { - matchers: {} - }, - 'cssSelector': { - selectors: {} + async updateForStateChange() { + this.activeForm?.removeAllDecorations(); + await this.refreshData(); + this.activeForm?.recategorizeAllInputs(); + } + async refreshData() { + await this.inContextSignup?.refreshData(); + await this.settings.populateData(); + } + async setupSettingsPage({ shouldLog: shouldLog2 } = { shouldLog: false }) { + if (!this.globalConfig.isDDGDomain) { + return; + } + notifyWebApp({ isApp: this.globalConfig.isApp }); + if (this.isDeviceSignedIn()) { + let userData; + try { + userData = await this.getUserData(); + } catch (e) { + } + let capabilities; + try { + capabilities = await this.getEmailProtectionCapabilities(); + } catch (e) { + } + if (this.globalConfig.isDDGDomain) { + window.addEventListener("message", (e) => { + if (e.data.removeUserData) { + this.removeUserData(); + } + if (e.data.closeEmailProtection) { + this.closeEmailProtection(); + } + }); + } + const hasUserData = userData && !userData.error && Object.entries(userData).length > 0; + notifyWebApp({ + deviceSignedIn: { + value: true, + shouldLog: shouldLog2, + userData: hasUserData ? userData : void 0, + capabilities + } + }); + } else { + this.trySigningIn(); } } - }; -} - -/** - * @returns {SupportedTypes} - */ -exports.Matching = Matching; -function getInputType(input) { - const attr = input?.getAttribute(ATTR_INPUT_TYPE); - if (isValidSupportedType(attr)) { - return attr; - } - return 'unknown'; -} - -/** - * Retrieves the main type - * @param {SupportedTypes | string} type - * @returns {SupportedMainTypes} - */ -function getMainTypeFromType(type) { - const mainType = type.split('.')[0]; - switch (mainType) { - case 'credentials': - case 'creditCards': - case 'identities': - return mainType; - } - return 'unknown'; -} - -/** - * Retrieves the input main type - * @param {HTMLInputElement} input - * @returns {SupportedMainTypes} - */ -const getInputMainType = input => getMainTypeFromType(getInputType(input)); - -/** @typedef {supportedIdentitiesSubtypes[number]} SupportedIdentitiesSubTypes */ -exports.getInputMainType = getInputMainType; -const supportedIdentitiesSubtypes = /** @type {const} */['emailAddress', 'firstName', 'middleName', 'lastName', 'fullName', 'phone', 'addressStreet', 'addressStreet2', 'addressCity', 'addressProvince', 'addressPostalCode', 'addressCountryCode', 'birthdayDay', 'birthdayMonth', 'birthdayYear']; - -/** - * @param {SupportedTypes | any} supportedType - * @returns {supportedType is SupportedIdentitiesSubTypes} - */ -function isValidIdentitiesSubtype(supportedType) { - return supportedIdentitiesSubtypes.includes(supportedType); -} - -/** @typedef {supportedCreditCardSubtypes[number]} SupportedCreditCardSubTypes */ -const supportedCreditCardSubtypes = /** @type {const} */['cardName', 'cardNumber', 'cardSecurityCode', 'expirationMonth', 'expirationYear', 'expiration']; - -/** - * @param {SupportedTypes | any} supportedType - * @returns {supportedType is SupportedCreditCardSubTypes} - */ -function isValidCreditCardSubtype(supportedType) { - return supportedCreditCardSubtypes.includes(supportedType); -} - -/** @typedef {supportedCredentialsSubtypes[number]} SupportedCredentialsSubTypes */ -const supportedCredentialsSubtypes = /** @type {const} */['password', 'password.new', 'password.current', 'username']; - -/** @typedef {supportedVariants[number]} SupportedVariants */ -const supportedVariants = /** @type {const} */['new', 'current']; - -/** - * @param {SupportedTypes | any} supportedType - * @returns {supportedType is SupportedCredentialsSubTypes} - */ -function isValidCredentialsSubtype(supportedType) { - return supportedCredentialsSubtypes.includes(supportedType); -} - -/** @typedef {SupportedIdentitiesSubTypes | SupportedCreditCardSubTypes | SupportedCredentialsSubTypes} SupportedSubTypes */ - -/** @typedef {`identities.${SupportedIdentitiesSubTypes}` | `creditCards.${SupportedCreditCardSubTypes}` | `credentials.${SupportedCredentialsSubTypes}` | 'unknown'} SupportedTypes */ -const supportedTypes = [...supportedIdentitiesSubtypes.map(type => `identities.${type}`), ...supportedCreditCardSubtypes.map(type => `creditCards.${type}`), ...supportedCredentialsSubtypes.map(type => `credentials.${type}`)]; - -/** - * Retrieves the subtype - * @param {SupportedTypes | string} type - * @returns {SupportedSubTypes | 'unknown'} - */ -function getSubtypeFromType(type) { - const subType = type?.split('.')[1]; - const validType = isValidSubtype(subType); - return validType ? subType : 'unknown'; -} - -/** - * Retrieves the variant - * @param {SupportedTypes | string} type - * @returns {SupportedVariants | ''} - */ -function getVariantFromType(type) { - const variant = type?.split('.')[2]; - const validVariant = isValidVariant(variant); - return validVariant ? variant : ''; -} - -/** - * @param {SupportedSubTypes | any} supportedSubType - * @returns {supportedSubType is SupportedSubTypes} - */ -function isValidSubtype(supportedSubType) { - return isValidIdentitiesSubtype(supportedSubType) || isValidCreditCardSubtype(supportedSubType) || isValidCredentialsSubtype(supportedSubType); -} - -/** - * @param {SupportedTypes | any} supportedType - * @returns {supportedType is SupportedTypes} - */ -function isValidSupportedType(supportedType) { - return supportedTypes.includes(supportedType); -} - -/** - * @param {SupportedVariants | any} supportedVariant - * @returns {supportedVariant is SupportedVariants} - */ -function isValidVariant(supportedVariant) { - return supportedVariants.includes(supportedVariant); -} - -/** - * Retrieves the input subtype - * @param {HTMLInputElement|Element} input - * @returns {SupportedSubTypes | 'unknown'} - */ -function getInputSubtype(input) { - const type = getInputType(input); - return getSubtypeFromType(type); -} - -/** - * Retrieves the input variant - * @param {HTMLInputElement|Element} input - * @returns {SupportedVariants | ''} - */ -function getInputVariant(input) { - const type = getInputType(input); - return getVariantFromType(type); -} - -/** - * Remove whitespace of more than 2 in a row and trim the string - * @param {string | null} string - * @return {string} - */ -const removeExcessWhitespace = function () { - let string = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; - string = string?.trim() || ''; - // The length check is extra safety to avoid trimming strings that would be discarded anyway - if (!string || string.length > TEXT_LENGTH_CUTOFF + 50) return ''; - return string.replace(/\n/g, ' ').replace(/\s{2,}/g, ' '); -}; - -/** - * Get text from all explicit labels - * @param {HTMLInputElement|HTMLSelectElement} el - * @return {string} - */ -exports.removeExcessWhitespace = removeExcessWhitespace; -const getExplicitLabelsText = el => { - const labelTextCandidates = []; - for (let label of el.labels || []) { - labelTextCandidates.push(...(0, _labelUtil.extractElementStrings)(label)); - } - if (el.hasAttribute('aria-label')) { - labelTextCandidates.push(removeExcessWhitespace(el.getAttribute('aria-label'))); - } - - // Try to access another element if it was marked as the label for this input/select - const ariaLabelAttr = removeExcessWhitespace(el.getAttribute('aria-labelled') || el.getAttribute('aria-labelledby')); - if (ariaLabelAttr) { - const labelledByElement = document.getElementById(ariaLabelAttr); - if (labelledByElement) { - labelTextCandidates.push(...(0, _labelUtil.extractElementStrings)(labelledByElement)); + async setupAutofill() { } - } - - // Labels with long text are likely to be noisy and lead to false positives - const filteredLabels = labelTextCandidates.filter(string => string.length < 65); - if (filteredLabels.length > 0) { - return filteredLabels.join(' '); - } - return ''; -}; - -/** - * Tries to get a relevant previous Element sibling, excluding certain tags - * @param {Element} el - * @returns {Element|null} - */ -exports.getExplicitLabelsText = getExplicitLabelsText; -const recursiveGetPreviousElSibling = el => { - const previousEl = el.previousElementSibling; - if (!previousEl) return null; - - // Skip elements with no childNodes - if (_labelUtil.EXCLUDED_TAGS.includes(previousEl.tagName)) { - return recursiveGetPreviousElSibling(previousEl); - } - return previousEl; -}; - -/** - * Get all text close to the input (useful when no labels are defined) - * @param {HTMLInputElement|HTMLSelectElement} el - * @param {HTMLElement} form - * @param {string} cssSelector - * @return {string} - */ -const getRelatedText = (el, form, cssSelector) => { - let scope = getLargestMeaningfulContainer(el, form, cssSelector); - - // If we didn't find a container, try looking for an adjacent label - if (scope === el) { - let previousEl = recursiveGetPreviousElSibling(el); - if (previousEl instanceof HTMLElement) { - scope = previousEl; + /** @returns {Promise} */ + async getAddresses() { + throw new Error("unimplemented"); } - // If there is still no meaningful container return empty string - if (scope === el || scope instanceof HTMLSelectElement) { - if (el.previousSibling instanceof Text) { - return removeExcessWhitespace(el.previousSibling.textContent); - } - return ''; + /** @returns {Promise>} */ + getUserData() { + return Promise.resolve(null); } - } - - // If there is still no meaningful container return empty string - if (scope === el || scope instanceof HTMLSelectElement) { - if (el.previousSibling instanceof Text) { - return removeExcessWhitespace(el.previousSibling.textContent); + /** @returns {void} */ + removeUserData() { } - return ''; - } - let trimmedText = ''; - const label = scope.querySelector('label'); - if (label) { - // Try searching for a label first - trimmedText = (0, _autofillUtils.getTextShallow)(label); - } else { - // If the container has a select element, remove its contents to avoid noise - trimmedText = (0, _labelUtil.extractElementStrings)(scope).join(' '); - } - - // If the text is longer than n chars it's too noisy and likely to yield false positives, so return '' - if (trimmedText.length < TEXT_LENGTH_CUTOFF) return trimmedText; - return ''; -}; - -/** - * Find a container for the input field that won't contain other inputs (useful to get elements related to the field) - * @param {HTMLElement} el - * @param {HTMLElement} form - * @param {string} cssSelector - * @return {HTMLElement} - */ -exports.getRelatedText = getRelatedText; -const getLargestMeaningfulContainer = (el, form, cssSelector) => { - /* TODO: there could be more than one select el for the same label, in that case we should - change how we compute the container */ - const parentElement = el.parentElement; - if (!parentElement || el === form || !cssSelector) return el; - const inputsInParentsScope = parentElement.querySelectorAll(cssSelector); - // To avoid noise, ensure that our input is the only in scope - if (inputsInParentsScope.length === 1) { - return getLargestMeaningfulContainer(parentElement, form, cssSelector); - } - return el; -}; - -/** - * Find a regex match for a given input - * @param {HTMLInputElement} input - * @param {RegExp} regex - * @param {HTMLElement} form - * @param {string} cssSelector - * @returns {RegExpMatchArray|null} - */ -const matchInPlaceholderAndLabels = (input, regex, form, cssSelector) => { - return input.placeholder?.match(regex) || getExplicitLabelsText(input).match(regex) || getRelatedText(input, form, cssSelector).match(regex); -}; - -/** - * Check if a given input matches a regex - * @param {HTMLInputElement} input - * @param {RegExp} regex - * @param {HTMLElement} form - * @param {string} cssSelector - * @returns {boolean} - */ -exports.matchInPlaceholderAndLabels = matchInPlaceholderAndLabels; -const checkPlaceholderAndLabels = (input, regex, form, cssSelector) => { - return !!matchInPlaceholderAndLabels(input, regex, form, cssSelector); -}; - -/** - * Factory for instances of Matching - * - * @return {Matching} - */ -exports.checkPlaceholderAndLabels = checkPlaceholderAndLabels; -function createMatching() { - return new Matching(_compiledMatchingConfig.matchingConfiguration); -} - -},{"../autofill-utils.js":62,"../constants.js":65,"./label-util.js":39,"./matching-config/__generated__/compiled-matching-config.js":41,"./matching-utils.js":42}],44:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.InContextSignup = void 0; -var _deviceApiCalls = require("./deviceApiCalls/__generated__/deviceApiCalls.js"); -var _autofillUtils = require("./autofill-utils.js"); -class InContextSignup { - /** - * @param {import("./DeviceInterface/InterfacePrototype").default} device - */ - constructor(device) { - this.device = device; - } - async init() { - await this.refreshData(); - this.addNativeAccessibleGlobalFunctions(); - } - addNativeAccessibleGlobalFunctions() { - if (!this.device.globalConfig.hasModernWebkitAPI) return; - try { - // Set up a function which can be called from the native layer after completed sign-up or sign-in. - Object.defineProperty(window, 'openAutofillAfterClosingEmailProtectionTab', { - enumerable: false, - configurable: false, - writable: false, - value: () => { - this.openAutofillTooltip(); - } - }); - } catch (e) { - // Ignore if function can't be set up, it's a UX enhancement not a critical flow + /** @returns {void} */ + closeEmailProtection() { } - } - async refreshData() { - const incontextSignupDismissedAt = await this.device.deviceApi.request(new _deviceApiCalls.GetIncontextSignupDismissedAtCall(null)); - this.permanentlyDismissedAt = incontextSignupDismissedAt.permanentlyDismissedAt; - this.isInstalledRecently = incontextSignupDismissedAt.isInstalledRecently; - } - async openAutofillTooltip() { - // Make sure we're working with the latest data - await this.device.refreshData(); - - // Make sure the tooltip is closed before we try to open it - await this.device.uiController?.removeTooltip('stateChange'); - - // Make sure the input doesn't have focus so we can focus on it again - const activeInput = this.device.activeForm?.activeInput; - activeInput?.blur(); - - // Select the active input to open the tooltip - const selectActiveInput = () => { - activeInput?.focus(); - }; - if (document.hasFocus()) { - selectActiveInput(); - } else { - document.addEventListener('visibilitychange', () => { - selectActiveInput(); - }, { - once: true - }); + /** @returns {Promise>} */ + getEmailProtectionCapabilities() { + throw new Error("unimplemented"); } - } - isPermanentlyDismissed() { - return Boolean(this.permanentlyDismissedAt); - } - isOnValidDomain() { - // Only show in-context signup if we've high confidence that the page is - // not internally hosted or an intranet - return (0, _autofillUtils.isValidTLD)() && !(0, _autofillUtils.isLocalNetwork)(); - } - isAllowedByDevice() { - if (typeof this.isInstalledRecently === 'boolean') { - return this.isInstalledRecently; - } else { - // Don't restrict in-context signup based on recent installation - // if the device hasn't provided a clear indication - return true; + refreshAlias() { + } + async trySigningIn() { + if (this.globalConfig.isDDGDomain) { + if (this.attempts < 10) { + this.attempts++; + const data = await sendAndWaitForAnswer(SIGN_IN_MSG, "addUserData"); + this.storeUserData(data); + await this.setupAutofill(); + await this.settings.refresh(); + await this.setupSettingsPage({ shouldLog: true }); + await this.postInit(); + } else { + console.warn("max attempts reached, bailing"); + } + } } - } - - /** - * @param {import('./Form/matching.js').SupportedSubTypes | "unknown"} [inputType] - * @returns {boolean} - */ - isAvailable() { - let inputType = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'emailAddress'; - const isEmailInput = inputType === 'emailAddress'; - const isEmailProtectionEnabled = !!this.device.settings?.featureToggles.emailProtection; - const isIncontextSignupEnabled = !!this.device.settings?.featureToggles.emailProtection_incontext_signup; - const isNotAlreadyLoggedIn = !this.device.isDeviceSignedIn(); - const isNotDismissed = !this.isPermanentlyDismissed(); - const isOnExpectedPage = this.device.globalConfig.isTopFrame || this.isOnValidDomain(); - const isAllowedByDevice = this.isAllowedByDevice(); - return isEmailInput && isEmailProtectionEnabled && isIncontextSignupEnabled && isNotAlreadyLoggedIn && isNotDismissed && isOnExpectedPage && isAllowedByDevice; - } - onIncontextSignup() { - this.device.deviceApi.notify(new _deviceApiCalls.StartEmailProtectionSignupCall({})); - this.device.firePixel({ - pixelName: 'incontext_primary_cta' - }); - } - onIncontextSignupDismissed() { - let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { - shouldHideTooltip: true - }; - if (options.shouldHideTooltip) { - this.device.removeAutofillUIFromPage('Email Protection in-context signup dismissed.'); - this.device.deviceApi.notify(new _deviceApiCalls.CloseAutofillParentCall(null)); - } - this.permanentlyDismissedAt = new Date().getTime(); - this.device.deviceApi.notify(new _deviceApiCalls.SetIncontextSignupPermanentlyDismissedAtCall({ - value: this.permanentlyDismissedAt - })); - this.device.firePixel({ - pixelName: 'incontext_dismiss_persisted' - }); - } - - // In-context signup can be closed when displayed as a stand-alone tooltip, e.g. extension - onIncontextSignupClosed() { - this.device.activeForm?.dismissTooltip(); - this.device.firePixel({ - pixelName: 'incontext_close_x' - }); - } -} -exports.InContextSignup = InContextSignup; - -},{"./autofill-utils.js":62,"./deviceApiCalls/__generated__/deviceApiCalls.js":66}],45:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.PROVIDER_LOCKED = exports.AUTOGENERATED_KEY = void 0; -exports.appendGeneratedKey = appendGeneratedKey; -exports.createCredentialsTooltipItem = createCredentialsTooltipItem; -exports.fromPassword = fromPassword; -var _autofillUtils = require("../autofill-utils.js"); -const AUTOGENERATED_KEY = exports.AUTOGENERATED_KEY = 'autogenerated'; -const PROVIDER_LOCKED = exports.PROVIDER_LOCKED = 'provider_locked'; - -/** - * @implements {TooltipItemRenderer} - */ -class CredentialsTooltipItem { - /** @type {CredentialsObject} */ - #data; - /** @param {CredentialsObject} data */ - constructor(data) { - this.#data = data; - } - id = () => String(this.#data.id); - labelMedium = _subtype => { - if (this.#data.username) { - return this.#data.username; + storeUserData(_data7) { } - if (this.#data.origin?.url) { - return `Password for ${(0, _autofillUtils.truncateFromMiddle)(this.#data.origin.url)}`; + addDeviceListeners() { } - return ''; - }; - labelSmall = _subtype => { - if (this.#data.origin?.url) { - return (0, _autofillUtils.truncateFromMiddle)(this.#data.origin.url); + /** @param {() => void} _fn */ + addLogoutListener(_fn) { } - return '•••••••••••••••'; - }; - credentialsProvider = () => this.#data.credentialsProvider; -} - -/** - * @implements {TooltipItemRenderer} - */ -class AutoGeneratedCredential { - /** @type {CredentialsObject} */ - #data; - /** @param {CredentialsObject} data */ - constructor(data) { - this.#data = data; - } - id = () => String(this.#data.id); - label = _subtype => this.#data.password; - labelMedium = _subtype => 'Generated password'; - labelSmall = _subtype => 'Password will be saved for this website'; -} - -/** - * Generate a stand-in 'CredentialsObject' from a - * given (generated) password. - * - * @param {string} password - * @param {string} username - * @returns {CredentialsObject} - */ -function fromPassword(password, username) { - return { - [AUTOGENERATED_KEY]: true, - password, - username - }; -} - -/** - * @implements TooltipItemRenderer - */ -class ProviderLockedItem { - /** @type {CredentialsObject} */ - #data; - /** @param {CredentialsObject} data */ - constructor(data) { - this.#data = data; - } - id = () => String(this.#data.id); - labelMedium = _subtype => 'Bitwarden is locked'; - labelSmall = _subtype => 'Unlock your vault to access credentials or generate passwords'; - credentialsProvider = () => this.#data.credentialsProvider; -} - -/** - * If the locally generated/stored password or username ends up being the same - * as submitted in a subsequent form submission - then we mark the - * credentials as 'autogenerated' so that the native layer can decide - * how to process it - * - * @param {DataStorageObject} data - * @param {object} [autofilledFields] - * @param {string|null|undefined} [autofilledFields.username] - if present, it's the last username generated by something like email Protection - * @param {string|null|undefined} [autofilledFields.password] - if present, it's the last generated password - * - */ -function appendGeneratedKey(data) { - let autofilledFields = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - let autogenerated = false; - - // does the current password match the most recently generated one? - if (autofilledFields.password && data.credentials?.password === autofilledFields.password) { - autogenerated = true; - } - - // does the current username match a recently generated one? (eg: email protection) - if (autofilledFields.username && data.credentials?.username === autofilledFields.username) { - autogenerated = true; - } - - // if neither username nor password were generated, don't alter the outgoing data - if (!autogenerated) return data; - - // if we get here, we're confident that something was generated + filled - // so we mark the credential as 'autogenerated' for the benefit of native implementations - return { - ...data, - credentials: { - ...data.credentials, - [AUTOGENERATED_KEY]: true + isDeviceSignedIn() { + return false; } - }; -} - -/** - * Factory for creating a TooltipItemRenderer - * - * @param {CredentialsObject} data - * @returns {TooltipItemRenderer} - */ -function createCredentialsTooltipItem(data) { - if (data.id === PROVIDER_LOCKED) { - return new ProviderLockedItem(data); - } - if (AUTOGENERATED_KEY in data && data.password) { - return new AutoGeneratedCredential(data); - } - return new CredentialsTooltipItem(data); -} - -},{"../autofill-utils.js":62}],46:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.CreditCardTooltipItem = void 0; -/** - * @implements {TooltipItemRenderer} - */ -class CreditCardTooltipItem { - /** @type {CreditCardObject} */ - #data; - /** @param {CreditCardObject} data */ - constructor(data) { - this.#data = data; - } - id = () => String(this.#data.id); - labelMedium = _ => this.#data.title; - labelSmall = _ => this.#data.displayNumber; -} -exports.CreditCardTooltipItem = CreditCardTooltipItem; - -},{}],47:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.IdentityTooltipItem = void 0; -var _formatters = require("../Form/formatters.js"); -/** - * @implements {TooltipItemRenderer} - */ -class IdentityTooltipItem { - /** @type {IdentityObject} */ - #data; - /** @param {IdentityObject} data */ - constructor(data) { - this.#data = data; - } - id = () => String(this.#data.id); - labelMedium = subtype => { - if (subtype === 'addressCountryCode') { - return (0, _formatters.getCountryDisplayName)('en', this.#data.addressCountryCode || ''); + /** + * @returns {Promise} + */ + async getAlias() { + return void 0; } - if (this.#data.id === 'privateAddress') { - return 'Generate Private Duck Address'; + // PM endpoints + getAccounts() { } - return this.#data[subtype]; - }; - label(subtype) { - if (this.#data.id === 'privateAddress') { - return this.#data[subtype]; + /** + * Gets credentials ready for autofill + * @param {CredentialsObject['id']} id - the credential id + * @returns {Promise} + */ + async getAutofillCredentials(id) { + return this.deviceApi.request(new GetAutofillCredentialsCall({ id: String(id) })); } - return null; - } - labelSmall = _ => { - return this.#data.title; - }; -} -exports.IdentityTooltipItem = IdentityTooltipItem; - -},{"../Form/formatters.js":36}],48:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.PasswordGenerator = void 0; -var _index = require("../packages/password/index.js"); -var _rules = _interopRequireDefault(require("../packages/password/rules.json")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -/** - * Create a password once and reuse it. - */ -class PasswordGenerator { - /** @type {string|null} */ - #previous = null; - - /** @returns {boolean} */ - get generated() { - return this.#previous !== null; - } - - /** @returns {string|null} */ - get password() { - return this.#previous; - } - - /** @param {import('../packages/password').GenerateOptions} [params] */ - generate() { - let params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - if (this.#previous) { - return this.#previous; - } - this.#previous = (0, _index.generate)({ - ...params, - rules: _rules.default - }); - return this.#previous; - } -} -exports.PasswordGenerator = PasswordGenerator; - -},{"../packages/password/index.js":17,"../packages/password/rules.json":21}],49:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.createScanner = createScanner; -var _Form = require("./Form/Form.js"); -var _constants = require("./constants.js"); -var _matching = require("./Form/matching.js"); -var _autofillUtils = require("./autofill-utils.js"); -var _deviceApiCalls = require("./deviceApiCalls/__generated__/deviceApiCalls.js"); -const { - MAX_INPUTS_PER_PAGE, - MAX_FORMS_PER_PAGE, - MAX_INPUTS_PER_FORM, - ATTR_INPUT_TYPE -} = _constants.constants; - -/** - * @typedef {{ - * forms: Map; - * init(): (reason, ...rest)=> void; - * enqueue(elements: (HTMLElement|Document)[]): void; - * findEligibleInputs(context): Scanner; - * matching: import("./Form/matching").Matching; - * options: ScannerOptions; - * stopScanner: (reason: string, ...rest: any) => void; - * }} Scanner - * - * @typedef {{ - * initialDelay: number, - * bufferSize: number, - * debounceTimePeriod: number, - * maxInputsPerPage: number, - * maxFormsPerPage: number, - * maxInputsPerForm: number - * }} ScannerOptions - */ - -/** @type {ScannerOptions} */ -const defaultScannerOptions = { - // This buffer size is very large because it's an unexpected edge-case that - // a DOM will be continually modified over and over without ever stopping. If we do see 1000 unique - // new elements in the buffer however then this will prevent the algorithm from never ending. - bufferSize: 50, - // wait for a 500ms window of event silence before performing the scan - debounceTimePeriod: 500, - // how long to wait when performing the initial scan - initialDelay: 0, - // How many inputs is too many on the page. If we detect that there's above - // this maximum, then we don't scan the page. This will prevent slowdowns on - // large pages which are unlikely to require autofill anyway. - maxInputsPerPage: MAX_INPUTS_PER_PAGE, - maxFormsPerPage: MAX_FORMS_PER_PAGE, - maxInputsPerForm: MAX_INPUTS_PER_FORM -}; - -/** - * This allows: - * 1) synchronous DOM scanning + mutations - via `createScanner(device).findEligibleInputs(document)` - * 2) or, as above + a debounced mutation observer to re-run the scan after the given time - */ -class DefaultScanner { - /** @type Map */ - forms = new Map(); - /** @type {any|undefined} the timer to reset */ - debounceTimer; - /** @type {Set} stored changed elements until they can be processed */ - changedElements = new Set(); - /** @type {ScannerOptions} */ - options; - /** @type {HTMLInputElement | null} */ - activeInput = null; - /** @type {boolean} A flag to indicate the whole page will be re-scanned */ - rescanAll = false; - /** @type {boolean} Indicates whether we called stopScanning */ - stopped = false; - /** @type {import("./Form/matching").Matching} matching */ - matching; - - /** - * @param {import("./DeviceInterface/InterfacePrototype").default} device - * @param {ScannerOptions} options - */ - constructor(device, options) { - this.device = device; - this.matching = (0, _matching.createMatching)(); - this.options = options; - /** @type {number} A timestamp of the */ - this.initTimeStamp = Date.now(); - } - - /** - * Determine whether we should fire the credentials autoprompt. This is needed because some sites are blank - * on page load and load scripts asynchronously, so our initial scan didn't set the autoprompt correctly - * @returns {boolean} - */ - get shouldAutoprompt() { - return Date.now() - this.initTimeStamp <= 1500; - } - - /** - * Call this to scan once and then watch for changes. - * - * Call the returned function to remove listeners. - * @returns {(reason: string, ...rest) => void} - */ - init() { - var _this = this; - if (this.device.globalConfig.isExtension) { - this.device.deviceApi.notify(new _deviceApiCalls.AddDebugFlagCall({ - flag: 'autofill' - })); + /** @returns {APIResponse} */ + async getAutofillCreditCard(_id) { + throw new Error("getAutofillCreditCard unimplemented"); } - - // Add the shadow DOM listener. Handlers in handleEvent - window.addEventListener('pointerdown', this, true); - // We don't listen for focus events on mobile, they can cause keyboard flashing - if (!this.device.globalConfig.isMobileApp) { - window.addEventListener('focus', this, true); - } - const delay = this.options.initialDelay; - // if the delay is zero, (chrome/firefox etc) then use `requestIdleCallback` - if (delay === 0) { - window.requestIdleCallback(() => this.scanAndObserve()); - } else { - // otherwise, use the delay time to defer the initial scan - setTimeout(() => this.scanAndObserve(), delay); + /** @returns {Promise<{success: IdentityObject|undefined}>} */ + async getAutofillIdentity(_id) { + throw new Error("getAutofillIdentity unimplemented"); } - return function (reason) { - for (var _len = arguments.length, rest = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - rest[_key - 1] = arguments[_key]; - } - _this.stopScanner(reason, ...rest); - }; - } - - /** - * Scan the page and begin observing changes - */ - scanAndObserve() { - window.performance?.mark?.('initial_scanner:init:start'); - this.findEligibleInputs(document); - window.performance?.mark?.('initial_scanner:init:end'); - (0, _autofillUtils.logPerformance)('initial_scanner'); - this.mutObs.observe(document.documentElement, { - childList: true, - subtree: true - }); - } - - /** - * @param context - */ - findEligibleInputs(context) { - // Avoid autofill on Email Protection web app - if (this.device.globalConfig.isDDGDomain) { - return this; + openManagePasswords() { } - if ('matches' in context && context.matches?.(this.matching.cssSelector('formInputsSelector'))) { - this.addInput(context); - } else { - const inputs = context.querySelectorAll(this.matching.cssSelector('formInputsSelector')); - if (inputs.length > this.options.maxInputsPerPage) { - this.stopScanner(`Too many input fields in the given context (${inputs.length}), stop scanning`, context); - return this; - } - inputs.forEach(input => this.addInput(input)); + openManageCreditCards() { } - return this; - } - - /** - * Stops scanning, switches off the mutation observer and clears all forms - * @param {string} reason - * @param {any} rest - */ - stopScanner(reason) { - this.stopped = true; - if ((0, _autofillUtils.shouldLog)()) { - for (var _len2 = arguments.length, rest = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { - rest[_key2 - 1] = arguments[_key2]; - } - console.log(reason, ...rest); + openManageIdentities() { } - const activeInput = this.device.activeForm?.activeInput; - - // remove Dax, listeners, timers, and observers - clearTimeout(this.debounceTimer); - this.changedElements.clear(); - this.mutObs.disconnect(); - window.removeEventListener('pointerdown', this, true); - window.removeEventListener('focus', this, true); - this.forms.forEach(form => { - form.destroy(); - }); - this.forms.clear(); - - // Bring the user back to the input they were interacting with - activeInput?.focus(); - } - - /** - * @param {HTMLElement|HTMLInputElement|HTMLSelectElement} input - * @returns {HTMLFormElement|HTMLElement} - */ - getParentForm(input) { - if (input instanceof HTMLInputElement || input instanceof HTMLSelectElement) { - if (input.form) { - // Use input.form unless it encloses most of the DOM - // In that case we proceed to identify more precise wrappers - if (this.forms.has(input.form) || - // If we've added the form we've already checked that it's not a page wrapper - !(0, _autofillUtils.isFormLikelyToBeUsedAsPageWrapper)(input.form)) { - return input.form; - } - } + /** + * @param {StoreFormData} values + * @param {StoreFormData['trigger']} trigger + */ + storeFormData(values, trigger) { + this.deviceApi.notify(new StoreFormDataCall({ ...values, trigger })); } - /** - * Max number of nodes we want to traverse upwards, critical to avoid enclosing large portions of the DOM - * @type {number} + * `preAttachTooltip` happens just before a tooltip is show - features may want to append some data + * at this point. + * + * For example, if password generation is enabled, this will generate + * a password and send it to the tooltip as though it were a stored credential. + * + * @param {TopContextData} topContextData + * @param {HTMLInputElement} input + * @param {import("../Form/Form").Form} form */ - let traversalLayerCount = 0; - let element = input; - // traverse the DOM to search for related inputs - while (traversalLayerCount <= 5 && element.parentElement && element.parentElement !== document.documentElement) { - // Avoid overlapping containers or forms - const siblingForm = element.parentElement?.querySelector('form'); - if (siblingForm && siblingForm !== element) { - return element; - } - element = element.parentElement; - const inputs = element.querySelectorAll(this.matching.cssSelector('formInputsSelector')); - const buttons = element.querySelectorAll(this.matching.cssSelector('submitButtonSelector')); - // If we find a button or another input, we assume that's our form - if (inputs.length > 1 || buttons.length) { - // found related input, return common ancestor - return element; - } - traversalLayerCount++; - } - return input; - } - - /** - * @param {HTMLInputElement|HTMLSelectElement} input - */ - addInput(input) { - if (this.stopped) return; - const parentForm = this.getParentForm(input); - if (parentForm instanceof HTMLFormElement && this.forms.has(parentForm)) { - const foundForm = this.forms.get(parentForm); - // We've met the form, add the input provided it's below the max input limit - if (foundForm && foundForm.inputs.all.size < MAX_INPUTS_PER_FORM) { - foundForm.addInput(input); - } else { - this.stopScanner('The form has too many inputs, destroying.'); + preAttachTooltip(topContextData, input, form) { + const checks = [ + topContextData.inputType === "credentials.password.new", + this.settings.featureToggles.password_generation + ]; + if (checks.every(Boolean)) { + const password = this.passwordGenerator.generate({ + input: input.getAttribute("passwordrules"), + domain: window.location.hostname + }); + const rawValues = form.getRawValues(); + const username = rawValues.credentials?.username || rawValues.identities?.emailAddress || ""; + topContextData.credentials = [fromPassword(password, username)]; } - return; + return topContextData; } - - // Do not add explicitly search forms - if (parentForm.role === 'search') return; - - // Check if the forms we've seen are either disconnected, - // or are parent/child of the currently-found form - let previouslyFoundParent, childForm; - for (const [formEl] of this.forms) { - // Remove disconnected forms to avoid leaks - if (!formEl.isConnected) { - this.forms.delete(formEl); - continue; - } - if (formEl.contains(parentForm)) { - previouslyFoundParent = formEl; - break; + /** + * `postAutofill` gives features an opportunity to perform an action directly + * following an autofill. + * + * For example, if a generated password was used, we want to fire a save event. + * + * @param {IdentityObject|CreditCardObject|CredentialsObject} data + * @param {SupportedMainTypes} dataType + * @param {import("../Form/Form").Form} formObj + */ + postAutofill(data, dataType, formObj) { + if (AUTOGENERATED_KEY in data && "password" in data && // Don't send message on Android to avoid potential abuse. Data is saved on native confirmation instead. + !this.globalConfig.isAndroid) { + const formValues = formObj.getValuesReadyForStorage(); + if (formValues.credentials?.password === data.password) { + const formData = appendGeneratedKey(formValues, { password: data.password }); + this.storeFormData(formData, "passwordGeneration"); + } } - if (parentForm.contains(formEl)) { - childForm = formEl; - break; + if (dataType === "credentials" && formObj.shouldAutoSubmit) { + formObj.attemptSubmissionIfNeeded(); } } - if (previouslyFoundParent) { - if (parentForm instanceof HTMLFormElement && parentForm !== previouslyFoundParent) { - // If we had a prior parent but this is an explicit form, the previous was a false positive - this.forms.delete(previouslyFoundParent); - } else { - // If we've already met the form or a descendant, add the input - this.forms.get(previouslyFoundParent)?.addInput(input); - } - } else { - // if this form is an ancestor of an existing form, remove that before adding this - if (childForm) { - this.forms.get(childForm)?.destroy(); - this.forms.delete(childForm); - } - - // Only add the form if below the limit of forms per page - if (this.forms.size < this.options.maxFormsPerPage) { - this.forms.set(parentForm, new _Form.Form(parentForm, input, this.device, this.matching, this.shouldAutoprompt)); - } else { - this.stopScanner('The page has too many forms, stop adding them.'); + /** + * `postSubmit` gives features a one-time-only opportunity to perform an + * action directly after a form submission was observed. + * + * Mostly this is about storing data from the form submission, but it can + * also be used like in the case of Password generation, to append additional + * data before it's sent to be saved. + * + * @param {DataStorageObject} values + * @param {import("../Form/Form").Form} form + */ + postSubmit(values, form) { + if (!form.form) + return; + if (!form.hasValues(values)) + return; + const checks = [ + form.shouldPromptToStoreData && !form.submitHandlerExecuted, + this.passwordGenerator.generated + ]; + if (checks.some(Boolean)) { + const formData = appendGeneratedKey(values, { + password: this.passwordGenerator.password, + username: this.emailProtection.lastGenerated + }); + this.storeFormData(formData, "formSubmission"); } } - } - - /** - * enqueue elements to be re-scanned after the given - * amount of time has elapsed. - * - * @param {(HTMLElement|Document)[]} htmlElements - */ - enqueue(htmlElements) { - // if the buffer limit is reached, stop trying to track elements and process body instead. - if (this.changedElements.size >= this.options.bufferSize) { - this.rescanAll = true; - this.changedElements.clear(); - } else if (!this.rescanAll) { - // otherwise keep adding each element to the queue - for (let element of htmlElements) { - this.changedElements.add(element); - } + /** + * Sends a pixel to be fired on the client side + * @param {import('../deviceApiCalls/__generated__/validators-ts').SendJSPixelParams} pixelParams + */ + firePixel(pixelParams) { + this.deviceApi.notify(new SendJSPixelCall(pixelParams)); } - clearTimeout(this.debounceTimer); - this.debounceTimer = setTimeout(() => { - window.performance?.mark?.('scanner:init:start'); - this.processChangedElements(); - this.changedElements.clear(); - this.rescanAll = false; - window.performance?.mark?.('scanner:init:end'); - (0, _autofillUtils.logPerformance)('scanner'); - }, this.options.debounceTimePeriod); - } + /** + * This serves as a single place to create a default instance + * of InterfacePrototype that can be useful in testing scenarios + * @param {Partial} [globalConfigOverrides] + * @returns {InterfacePrototype} + */ + static default(globalConfigOverrides) { + const globalConfig = createGlobalConfig(globalConfigOverrides); + const transport = createTransport(globalConfig); + const deviceApi = new DeviceApi(transport); + const settings = Settings.default(globalConfig, deviceApi); + return new _InterfacePrototype(globalConfig, deviceApi, settings); + } + }; + var InterfacePrototype = _InterfacePrototype; + _addresses = new WeakMap(); + _data6 = new WeakMap(); + var InterfacePrototype_default = InterfacePrototype; - /** - * re-scan the changed elements, but only if they - * are still present in the DOM - */ - processChangedElements() { - if (this.rescanAll) { - this.findEligibleInputs(document); - return; + // src/InContextSignup.js + var InContextSignup = class { + /** + * @param {import("./DeviceInterface/InterfacePrototype").default} device + */ + constructor(device) { + this.device = device; + } + async init() { + await this.refreshData(); + this.addNativeAccessibleGlobalFunctions(); } - for (let element of this.changedElements) { - if (element.isConnected) { - this.findEligibleInputs(element); + addNativeAccessibleGlobalFunctions() { + if (!this.device.globalConfig.hasModernWebkitAPI) + return; + try { + Object.defineProperty(window, "openAutofillAfterClosingEmailProtectionTab", { + enumerable: false, + configurable: false, + writable: false, + value: () => { + this.openAutofillTooltip(); + } + }); + } catch (e) { } } - } - - /** - * Watch for changes in the DOM, and enqueue elements to be scanned - * @type {MutationObserver} - */ - mutObs = new MutationObserver(mutationList => { - /** @type {HTMLElement[]} */ - if (this.rescanAll) { - // quick version if buffer full - this.enqueue([]); - return; + async refreshData() { + const incontextSignupDismissedAt = await this.device.deviceApi.request(new GetIncontextSignupDismissedAtCall(null)); + this.permanentlyDismissedAt = incontextSignupDismissedAt.permanentlyDismissedAt; + this.isInstalledRecently = incontextSignupDismissedAt.isInstalledRecently; } - const outgoing = []; - for (const mutationRecord of mutationList) { - if (mutationRecord.type === 'childList') { - for (let addedNode of mutationRecord.addedNodes) { - if (!(addedNode instanceof HTMLElement)) continue; - if (addedNode.nodeName === 'DDG-AUTOFILL') continue; - outgoing.push(addedNode); - } + async openAutofillTooltip() { + await this.device.refreshData(); + await this.device.uiController?.removeTooltip("stateChange"); + const activeInput = this.device.activeForm?.activeInput; + activeInput?.blur(); + const selectActiveInput = () => { + activeInput?.focus(); + }; + if (document.hasFocus()) { + selectActiveInput(); + } else { + document.addEventListener("visibilitychange", () => { + selectActiveInput(); + }, { once: true }); } } - this.enqueue(outgoing); - }); - handleEvent(event) { - switch (event.type) { - case 'pointerdown': - case 'focus': - this.scanShadow(event); - break; + isPermanentlyDismissed() { + return Boolean(this.permanentlyDismissedAt); } - } - - /** - * Scan clicked input fields, even if they're within a shadow tree - * @param {FocusEvent | PointerEvent} event - */ - scanShadow(event) { - // If the scanner is stopped or there's no shadow root, just return - if (this.stopped || !(event.target instanceof Element) || !event.target?.shadowRoot) return; - window.performance?.mark?.('scan_shadow:init:start'); - const realTarget = (0, _autofillUtils.pierceShadowTree)(event, HTMLInputElement); - - // If it's an input we haven't already scanned, scan the whole shadow tree - if (realTarget instanceof HTMLInputElement && !realTarget.hasAttribute(ATTR_INPUT_TYPE)) { - this.findEligibleInputs(realTarget.getRootNode()); + isOnValidDomain() { + return isValidTLD() && !isLocalNetwork(); } - window.performance?.mark?.('scan_shadow:init:end'); - (0, _autofillUtils.logPerformance)('scan_shadow'); - } -} - -/** - * @param {import("./DeviceInterface/InterfacePrototype").default} device - * @param {Partial} [scannerOptions] - * @returns {Scanner} - */ -function createScanner(device, scannerOptions) { - return new DefaultScanner(device, { - ...defaultScannerOptions, - ...scannerOptions - }); -} - -},{"./Form/Form.js":33,"./Form/matching.js":43,"./autofill-utils.js":62,"./constants.js":65,"./deviceApiCalls/__generated__/deviceApiCalls.js":66}],50:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.Settings = void 0; -var _index = require("../packages/device-api/index.js"); -var _deviceApiCalls = require("./deviceApiCalls/__generated__/deviceApiCalls.js"); -var _validatorsZod = require("./deviceApiCalls/__generated__/validators.zod.js"); -var _autofillUtils = require("./autofill-utils.js"); -/** - * Some Type helpers to prevent duplication - * @typedef {import("./deviceApiCalls/__generated__/validators-ts").AutofillFeatureToggles} AutofillFeatureToggles - * @typedef {import("./deviceApiCalls/__generated__/validators-ts").AvailableInputTypes} AvailableInputTypes - * @typedef {import("./deviceApiCalls/__generated__/validators-ts").RuntimeConfiguration} RuntimeConfiguration - * @typedef {import("../packages/device-api").DeviceApi} DeviceApi - */ - -/** - * The Settings class encapsulates the concept of 1) feature toggles + 2) available input types. - * - * 1) Feature toggles are boolean flags that can represent a device's capabilities. That may be user-toggled - * or not, we don't make that distinction. - * - * 2) Available Input Types are indicators to whether the given platform can provide data for a given type. - * For example, a user might have credentials saved for https://example.com, so when the page loads, but **before** - * we can decorate any fields, we determine this first. - */ -class Settings { - /** @type {GlobalConfig} */ - globalConfig; - /** @type {DeviceApi} */ - deviceApi; - /** @type {AutofillFeatureToggles | null} */ - _featureToggles = null; - /** @type {AvailableInputTypes | null} */ - _availableInputTypes = null; - /** @type {RuntimeConfiguration | null | undefined} */ - _runtimeConfiguration = null; - /** @type {boolean | null} */ - _enabled = null; - - /** - * @param {GlobalConfig} config - * @param {DeviceApi} deviceApi - */ - constructor(config, deviceApi) { - this.deviceApi = deviceApi; - this.globalConfig = config; - } - - /** - * Feature toggles are delivered as part of the Runtime Configuration - a flexible design that - * allows data per user + remote config to be accessed together. - * - * Once we access the Runtime Configuration, we then extract the autofill-specific settings via - * `runtimeConfig.userPreferences.features.autofill.settings` and validate that separately. - * - * The 2-step validation occurs because RuntimeConfiguration will be coming from a shared library - * and does not know about the shape of Autofill specific settings. - * - * @returns {Promise} - */ - async getFeatureToggles() { - try { - const runtimeConfig = await this._getRuntimeConfiguration(); - const autofillSettings = (0, _index.validate)(runtimeConfig.userPreferences?.features?.autofill?.settings, _validatorsZod.autofillSettingsSchema); - return autofillSettings.featureToggles; - } catch (e) { - // these are the fallbacks for when a platform hasn't implemented the calls above. - if (this.globalConfig.isDDGTestMode) { - console.log('isDDGTestMode: getFeatureToggles: ❌', e); + isAllowedByDevice() { + if (typeof this.isInstalledRecently === "boolean") { + return this.isInstalledRecently; + } else { + return true; } - return Settings.defaults.featureToggles; } - } - - /** - * If the platform in question is happy to derive it's 'enabled' state from the RuntimeConfiguration, - * then they should use this. Currently only Windows supports this, but we aim to move all platforms to - * support this going forward. - * @returns {Promise} - */ - async getEnabled() { - try { - const runtimeConfig = await this._getRuntimeConfiguration(); - const enabled = (0, _autofillUtils.autofillEnabled)(runtimeConfig); - return enabled; - } catch (e) { - // these are the fallbacks for when a platform hasn't implemented the calls above. (like on android) - if (this.globalConfig.isDDGTestMode) { - console.log('isDDGTestMode: getEnabled: ❌', e); - } - return null; + /** + * @param {import('./Form/matching.js').SupportedSubTypes | "unknown"} [inputType] + * @returns {boolean} + */ + isAvailable(inputType = "emailAddress") { + const isEmailInput = inputType === "emailAddress"; + const isEmailProtectionEnabled = !!this.device.settings?.featureToggles.emailProtection; + const isIncontextSignupEnabled = !!this.device.settings?.featureToggles.emailProtection_incontext_signup; + const isNotAlreadyLoggedIn = !this.device.isDeviceSignedIn(); + const isNotDismissed = !this.isPermanentlyDismissed(); + const isOnExpectedPage = this.device.globalConfig.isTopFrame || this.isOnValidDomain(); + const isAllowedByDevice = this.isAllowedByDevice(); + return isEmailInput && isEmailProtectionEnabled && isIncontextSignupEnabled && isNotAlreadyLoggedIn && isNotDismissed && isOnExpectedPage && isAllowedByDevice; + } + onIncontextSignup() { + this.device.deviceApi.notify(new StartEmailProtectionSignupCall({})); + this.device.firePixel({ pixelName: "incontext_primary_cta" }); + } + onIncontextSignupDismissed(options = { shouldHideTooltip: true }) { + if (options.shouldHideTooltip) { + this.device.removeAutofillUIFromPage("Email Protection in-context signup dismissed."); + this.device.deviceApi.notify(new CloseAutofillParentCall(null)); + } + this.permanentlyDismissedAt = (/* @__PURE__ */ new Date()).getTime(); + this.device.deviceApi.notify(new SetIncontextSignupPermanentlyDismissedAtCall({ value: this.permanentlyDismissedAt })); + this.device.firePixel({ pixelName: "incontext_dismiss_persisted" }); + } + // In-context signup can be closed when displayed as a stand-alone tooltip, e.g. extension + onIncontextSignupClosed() { + this.device.activeForm?.dismissTooltip(); + this.device.firePixel({ pixelName: "incontext_close_x" }); } - } - - /** - * Get runtime configuration, but only once. - * - * Some platforms may be reading this directly from inlined variables, whilst others - * may make a DeviceApiCall. - * - * Currently, it's only read once - but we should be open to the idea that we may need - * this to be called multiple times in the future. - * - * @returns {Promise} - * @throws - * @private - */ - async _getRuntimeConfiguration() { - if (this._runtimeConfiguration) return this._runtimeConfiguration; - const runtimeConfig = await this.deviceApi.request(new _deviceApiCalls.GetRuntimeConfigurationCall(null)); - this._runtimeConfiguration = runtimeConfig; - return this._runtimeConfiguration; - } + }; - /** - * Available Input Types are boolean indicators to represent which input types the - * current **user** has data available for. - * - * @returns {Promise} - */ - async getAvailableInputTypes() { - try { - // This info is not needed in the topFrame, so we avoid calling the native app - if (this.globalConfig.isTopFrame) { - return Settings.defaults.availableInputTypes; + // src/DeviceInterface/AndroidInterface.js + var AndroidInterface = class extends InterfacePrototype_default { + constructor() { + super(...arguments); + __publicField(this, "inContextSignup", new InContextSignup(this)); + } + /** + * @returns {Promise} + */ + async getAlias() { + const { alias } = await sendAndWaitForAnswer(async () => { + if (this.inContextSignup.isAvailable()) { + const { isSignedIn } = await this.deviceApi.request(new ShowInContextEmailProtectionSignupPromptCall(null)); + if (this.globalConfig.availableInputTypes) { + this.globalConfig.availableInputTypes.email = isSignedIn; + } + this.updateForStateChange(); + this.onFinishedAutofill(); + } + return window.EmailInterface.showTooltip(); + }, "getAliasResponse"); + return alias; + } + /** + * @override + */ + createUIController() { + return new NativeUIController(); + } + /** + * @deprecated use `this.settings.availableInputTypes.email` in the future + * @returns {boolean} + */ + isDeviceSignedIn() { + if (this.globalConfig.isDDGDomain) { + return window.EmailInterface.isSignedIn() === "true"; } - return await this.deviceApi.request(new _deviceApiCalls.GetAvailableInputTypesCall(null)); - } catch (e) { - if (this.globalConfig.isDDGTestMode) { - console.log('isDDGTestMode: getAvailableInputTypes: ❌', e); + if (typeof this.globalConfig.availableInputTypes?.email === "boolean") { + return this.globalConfig.availableInputTypes.email; } - return Settings.defaults.availableInputTypes; + return true; } - } - - /** - * To 'refresh' settings means to re-call APIs to determine new state. This may - * only occur once per page, but it must be done before any page scanning/decorating can happen - * - * @returns {Promise<{ - * availableInputTypes: AvailableInputTypes, - * featureToggles: AutofillFeatureToggles, - * enabled: boolean | null - * }>} - */ - async refresh() { - this.setEnabled(await this.getEnabled()); - this.setFeatureToggles(await this.getFeatureToggles()); - this.setAvailableInputTypes(await this.getAvailableInputTypes()); - - // If 'this.enabled' is a boolean it means we were able to set it correctly and therefor respect its value - if (typeof this.enabled === 'boolean') { - if (!this.enabled) { - return Settings.defaults; + async setupAutofill() { + await this.inContextSignup.init(); + } + /** + * Used by the email web app + * Settings page displays data of the logged in user data + */ + getUserData() { + let userData = null; + try { + userData = JSON.parse(window.EmailInterface.getUserData()); + } catch (e) { + if (this.globalConfig.isDDGTestMode) { + console.error(e); + } } + return Promise.resolve(userData); } - return { - featureToggles: this.featureToggles, - availableInputTypes: this.availableInputTypes, - enabled: this.enabled - }; - } - - /** - * Checks if input type is one which we can't autofill - * @param {{ - * mainType: SupportedMainTypes - * subtype: import('./Form/matching.js').SupportedSubTypes | "unknown" - * }} types - * @returns {boolean} - */ - isTypeUnavailable(_ref) { - let { - mainType, - subtype - } = _ref; - if (mainType === 'unknown') return true; - if (!this.featureToggles[`inputType_${mainType}`] && subtype !== 'emailAddress') { - return true; + /** + * Used by the email web app + * Device capabilities determine which functionality is available to the user + */ + getEmailProtectionCapabilities() { + let deviceCapabilities = null; + try { + deviceCapabilities = JSON.parse(window.EmailInterface.getDeviceCapabilities()); + } catch (e) { + if (this.globalConfig.isDDGTestMode) { + console.error(e); + } + } + return Promise.resolve(deviceCapabilities); } - return false; - } - - /** - * Requests data from remote - * @returns {Promise<>} - */ - async populateData() { - const availableInputTypesFromRemote = await this.getAvailableInputTypes(); - this.setAvailableInputTypes(availableInputTypesFromRemote); - } - - /** - * Requests data from remote if not available - * @param {{ - * mainType: SupportedMainTypes - * subtype: import('./Form/matching.js').SupportedSubTypes | "unknown" - * }} types - * @returns {Promise} - */ - async populateDataIfNeeded(_ref2) { - let { - mainType, - subtype - } = _ref2; - if (this.isTypeUnavailable({ - mainType, - subtype - })) return false; - if (this.availableInputTypes?.[mainType] === undefined) { - await this.populateData(); - return true; + storeUserData({ addUserData: { token, userName, cohort } }) { + return window.EmailInterface.storeCredentials(token, userName, cohort); } - return false; - } - - /** - * Checks if items will show in the autofill menu, including in-context signup. - * Triggers side-effect if input types is not already available. - * @param {{ - * mainType: SupportedMainTypes - * subtype: import('./Form/matching.js').SupportedSubTypes | "unknown" - * }} types - * @param {import("./InContextSignup.js").InContextSignup?} inContextSignup - * @returns {boolean} - */ - canAutofillType(_ref3, inContextSignup) { - let { - mainType, - subtype - } = _ref3; - if (this.isTypeUnavailable({ - mainType, - subtype - })) return false; - - // If it's an email field and Email Protection is enabled, return true regardless of other options - const isEmailProtectionEnabled = this.featureToggles.emailProtection && this.availableInputTypes.email; - if (subtype === 'emailAddress' && isEmailProtectionEnabled) { - return true; + /** + * Used by the email web app + * Provides functionality to log the user out + */ + removeUserData() { + try { + return window.EmailInterface.removeCredentials(); + } catch (e) { + if (this.globalConfig.isDDGTestMode) { + console.error(e); + } + } } - if (inContextSignup?.isAvailable(subtype)) { - return true; + /** + * Used by the email web app + * Provides functionality to close the window after in-context sign-up or sign-in + */ + closeEmailProtection() { + this.deviceApi.request(new CloseEmailProtectionTabCall(null)); } - if (subtype === 'fullName') { - return Boolean(this.availableInputTypes.identities?.firstName || this.availableInputTypes.identities?.lastName); + addLogoutListener(handler) { + if (!this.globalConfig.isDDGDomain) + return; + window.addEventListener("message", (e) => { + if (this.globalConfig.isDDGDomain && e.data.emailProtectionSignedOut) { + handler(); + } + }); } - if (subtype === 'expiration') { - return Boolean(this.availableInputTypes.creditCards?.expirationMonth || this.availableInputTypes.creditCards?.expirationYear); + /** Noop */ + firePixel(_pixelParam) { } - return Boolean(this.availableInputTypes[mainType]?.[subtype]); - } - - /** @returns {AutofillFeatureToggles} */ - get featureToggles() { - if (this._featureToggles === null) throw new Error('feature toggles accessed before being set'); - return this._featureToggles; - } - - /** @param {AutofillFeatureToggles} input */ - setFeatureToggles(input) { - this._featureToggles = input; - } + }; - /** @returns {AvailableInputTypes} */ - get availableInputTypes() { - if (this._availableInputTypes === null) throw new Error('available input types accessed before being set'); - return this._availableInputTypes; - } + // src/UI/styles/autofill-tooltip-styles.css + var autofill_tooltip_styles_default = ":root {\n color-scheme: light dark;\n}\n\n.wrapper *, .wrapper *::before, .wrapper *::after {\n box-sizing: border-box;\n}\n.wrapper {\n position: fixed;\n top: 0;\n left: 0;\n padding: 0;\n font-family: 'DDG_ProximaNova', 'Proxima Nova', system-ui, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',\n 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;\n -webkit-font-smoothing: antialiased;\n z-index: 2147483647;\n}\n.wrapper--data {\n font-family: 'SF Pro Text', system-ui, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',\n 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;\n}\n:not(.top-autofill) .tooltip {\n position: absolute;\n width: 300px;\n max-width: calc(100vw - 25px);\n transform: translate(-1000px, -1000px);\n z-index: 2147483647;\n}\n.tooltip--data, #topAutofill {\n background-color: rgba(242, 240, 240, 1);\n -webkit-backdrop-filter: blur(40px);\n backdrop-filter: blur(40px);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip--data, #topAutofill {\n background: rgb(100, 98, 102, .9);\n }\n}\n.tooltip--data {\n padding: 6px;\n font-size: 13px;\n line-height: 14px;\n width: 315px;\n max-height: 290px;\n overflow-y: auto;\n}\n.top-autofill .tooltip--data {\n min-height: 100vh;\n}\n.tooltip--data.tooltip--incontext-signup {\n width: 360px;\n}\n:not(.top-autofill) .tooltip--data {\n top: 100%;\n left: 100%;\n border: 0.5px solid rgba(255, 255, 255, 0.2);\n border-radius: 6px;\n box-shadow: 0 10px 20px rgba(0, 0, 0, 0.32);\n}\n@media (prefers-color-scheme: dark) {\n :not(.top-autofill) .tooltip--data {\n border: 1px solid rgba(255, 255, 255, 0.2);\n }\n}\n:not(.top-autofill) .tooltip--email {\n top: calc(100% + 6px);\n right: calc(100% - 48px);\n padding: 8px;\n border: 1px solid #D0D0D0;\n border-radius: 10px;\n background-color: #FFFFFF;\n font-size: 14px;\n line-height: 1.3;\n color: #333333;\n box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);\n}\n.tooltip--email__caret {\n position: absolute;\n transform: translate(-1000px, -1000px);\n z-index: 2147483647;\n}\n.tooltip--email__caret::before,\n.tooltip--email__caret::after {\n content: \"\";\n width: 0;\n height: 0;\n border-left: 10px solid transparent;\n border-right: 10px solid transparent;\n display: block;\n border-bottom: 8px solid #D0D0D0;\n position: absolute;\n right: -28px;\n}\n.tooltip--email__caret::before {\n border-bottom-color: #D0D0D0;\n top: -1px;\n}\n.tooltip--email__caret::after {\n border-bottom-color: #FFFFFF;\n top: 0px;\n}\n\n/* Buttons */\n.tooltip__button {\n display: flex;\n width: 100%;\n padding: 8px 8px 8px 0px;\n font-family: inherit;\n color: inherit;\n background: transparent;\n border: none;\n border-radius: 6px;\n}\n.tooltip__button.currentFocus,\n.wrapper:not(.top-autofill) .tooltip__button:hover {\n background-color: #3969EF;\n color: #FFFFFF;\n}\n\n/* Data autofill tooltip specific */\n.tooltip__button--data {\n position: relative;\n min-height: 48px;\n flex-direction: row;\n justify-content: flex-start;\n font-size: inherit;\n font-weight: 500;\n line-height: 16px;\n text-align: left;\n border-radius: 3px;\n}\n.tooltip--data__item-container {\n max-height: 220px;\n overflow: auto;\n}\n.tooltip__button--data:first-child {\n margin-top: 0;\n}\n.tooltip__button--data:last-child {\n margin-bottom: 0;\n}\n.tooltip__button--data::before {\n content: '';\n flex-shrink: 0;\n display: block;\n width: 32px;\n height: 32px;\n margin: 0 8px;\n background-size: 20px 20px;\n background-repeat: no-repeat;\n background-position: center center;\n}\n#provider_locked::after {\n position: absolute;\n content: '';\n flex-shrink: 0;\n display: block;\n width: 32px;\n height: 32px;\n margin: 0 8px;\n background-size: 11px 13px;\n background-repeat: no-repeat;\n background-position: right bottom;\n}\n.tooltip__button--data.currentFocus:not(.tooltip__button--data--bitwarden)::before,\n.wrapper:not(.top-autofill) .tooltip__button--data:not(.tooltip__button--data--bitwarden):hover::before {\n filter: invert(100%);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip__button--data:not(.tooltip__button--data--bitwarden)::before,\n .tooltip__button--data:not(.tooltip__button--data--bitwarden)::before {\n filter: invert(100%);\n opacity: .9;\n }\n}\n.tooltip__button__text-container {\n margin: auto 0;\n}\n.label {\n display: block;\n font-weight: 400;\n letter-spacing: -0.25px;\n color: rgba(0,0,0,.8);\n font-size: 13px;\n line-height: 1;\n}\n.label + .label {\n margin-top: 2px;\n}\n.label.label--medium {\n font-weight: 500;\n letter-spacing: -0.25px;\n color: rgba(0,0,0,.9);\n}\n.label.label--small {\n font-size: 11px;\n font-weight: 400;\n letter-spacing: 0.06px;\n color: rgba(0,0,0,0.6);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip--data .label {\n color: #ffffff;\n }\n .tooltip--data .label--medium {\n color: #ffffff;\n }\n .tooltip--data .label--small {\n color: #cdcdcd;\n }\n}\n.tooltip__button.currentFocus .label,\n.wrapper:not(.top-autofill) .tooltip__button:hover .label {\n color: #FFFFFF;\n}\n\n.tooltip__button--manage {\n font-size: 13px;\n padding: 5px 9px;\n border-radius: 3px;\n margin: 0;\n}\n\n/* Icons */\n.tooltip__button--data--credentials::before,\n.tooltip__button--data--credentials__current::before {\n background-size: 28px 28px;\n background-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNS4zMzQgNi42NjdhMiAyIDAgMSAwIDAgNCAyIDIgMCAwIDAgMC00Wm0tLjY2NyAyYS42NjcuNjY3IDAgMSAxIDEuMzMzIDAgLjY2Ny42NjcgMCAwIDEtMS4zMzMgMFoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTQuNjY3IDRhNS4zMzMgNS4zMzMgMCAwIDAtNS4xODggNi41NzhsLTUuMjg0IDUuMjg0YS42NjcuNjY3IDAgMCAwLS4xOTUuNDcxdjNjMCAuMzY5LjI5OC42NjcuNjY3LjY2N2gyLjY2NmMuNzM3IDAgMS4zMzQtLjU5NyAxLjMzNC0xLjMzM1YxOGguNjY2Yy43MzcgMCAxLjMzNC0uNTk3IDEuMzM0LTEuMzMzdi0xLjMzNEgxMmMuMTc3IDAgLjM0Ni0uMDcuNDcxLS4xOTVsLjY4OC0uNjg4QTUuMzMzIDUuMzMzIDAgMSAwIDE0LjY2NyA0Wm0tNCA1LjMzM2E0IDQgMCAxIDEgMi41NTUgMy43MzIuNjY3LjY2NyAwIDAgMC0uNzEzLjE1bC0uNzg1Ljc4NUgxMGEuNjY3LjY2NyAwIDAgMC0uNjY3LjY2N3YySDhhLjY2Ny42NjcgMCAwIDAtLjY2Ny42NjZ2MS4zMzRoLTJ2LTIuMDU4bDUuMzY1LTUuMzY0YS42NjcuNjY3IDAgMCAwIC4xNjMtLjY3NyAzLjk5NiAzLjk5NiAwIDAgMS0uMTk0LTEuMjM1WiIgY2xpcC1ydWxlPSJldmVub2RkIi8+Cjwvc3ZnPgo=');\n}\n.tooltip__button--data--credentials__new::before {\n background-size: 28px 28px;\n background-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjMDAwIiBkPSJNOC4wNDcgNC42MjVDNy45MzcgNC4xMjUgNy44NjIgNCA3LjUgNGMtLjM2MiAwLS40MzguMTI1LS41NDcuNjI1LS4wNjguMzEtLjE3NyAxLjMzOC0uMjUxIDIuMDc3LS43MzguMDc0LTEuNzY3LjE4My0yLjA3Ny4yNTEtLjUuMTEtLjYyNS4xODQtLjYyNS41NDcgMCAuMzYyLjEyNS40MzcuNjI1LjU0Ny4zMS4wNjcgMS4zMzYuMTc3IDIuMDc0LjI1LjA3My43NjcuMTg1IDEuODQyLjI1NCAyLjA3OC4xMS4zNzUuMTg1LjYyNS41NDcuNjI1LjM2MiAwIC40MzgtLjEyNS41NDctLjYyNS4wNjgtLjMxLjE3Ny0xLjMzNi4yNS0yLjA3NC43NjctLjA3MyAxLjg0Mi0uMTg1IDIuMDc4LS4yNTQuMzc1LS4xMS42MjUtLjE4NS42MjUtLjU0NyAwLS4zNjMtLjEyNS0uNDM4LS42MjUtLjU0Ny0uMzEtLjA2OC0xLjMzOS0uMTc3LTIuMDc3LS4yNTEtLjA3NC0uNzM5LS4xODMtMS43NjctLjI1MS0yLjA3N1oiLz4KICAgIDxwYXRoIGZpbGw9IiMwMDAiIGQ9Ik0xNC42ODEgNS4xOTljLS43NjYgMC0xLjQ4Mi4yMS0yLjA5My41NzhhLjYzNi42MzYgMCAwIDEtLjY1NS0xLjA5IDUuMzQgNS4zNCAwIDEgMSAxLjMwMiA5LjcyMmwtLjc3NS43NzZhLjYzNi42MzYgMCAwIDEtLjQ1LjE4NmgtMS4zOTh2MS42NWMwIC40OTMtLjQuODkzLS44OTMuODkzSDguNTc4djEuMTQxYzAgLjQ5NC0uNC44OTMtLjg5NC44OTNINC42MzZBLjYzNi42MzYgMCAwIDEgNCAxOS4zMTNWMTYuMjZjMC0uMTY5LjA2Ny0uMzMuMTg2LS40NWw1LjU2Mi01LjU2MmEuNjM2LjYzNiAwIDEgMSAuOS45bC01LjM3NiA1LjM3NXYyLjE1M2gyLjAzNHYtMS4zOTljMC0uMzUuMjg1LS42MzYuNjM2LS42MzZIOS4zNHYtMS45MDdjMC0uMzUxLjI4NC0uNjM2LjYzNS0uNjM2aDEuNzcxbC44NjQtLjg2M2EuNjM2LjYzNiAwIDAgMSAuNjY4LS4xNDcgNC4wNjkgNC4wNjkgMCAxIDAgMS40MDItNy44OVoiLz4KICAgIDxwYXRoIGZpbGw9IiMwMDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTEzLjYyNSA4LjQ5OWExLjg3NSAxLjg3NSAwIDEgMSAzLjc1IDAgMS44NzUgMS44NzUgMCAwIDEtMy43NSAwWm0xLjg3NS0uNjI1YS42MjUuNjI1IDAgMSAwIDAgMS4yNS42MjUuNjI1IDAgMCAwIDAtMS4yNVoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZmlsbD0iIzAwMCIgZD0iTTQuNjI1IDEyLjEyNWEuNjI1LjYyNSAwIDEgMCAwLTEuMjUuNjI1LjYyNSAwIDAgMCAwIDEuMjVaIi8+Cjwvc3ZnPgo=');\n}\n.tooltip__button--data--creditCards::before {\n background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBkPSJNNSA5Yy0uNTUyIDAtMSAuNDQ4LTEgMXYyYzAgLjU1Mi40NDggMSAxIDFoM2MuNTUyIDAgMS0uNDQ4IDEtMXYtMmMwLS41NTItLjQ0OC0xLTEtMUg1eiIgZmlsbD0iIzAwMCIvPgogICAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xIDZjMC0yLjIxIDEuNzktNCA0LTRoMTRjMi4yMSAwIDQgMS43OSA0IDR2MTJjMCAyLjIxLTEuNzkgNC00IDRINWMtMi4yMSAwLTQtMS43OS00LTRWNnptNC0yYy0xLjEwNSAwLTIgLjg5NS0yIDJ2OWgxOFY2YzAtMS4xMDUtLjg5NS0yLTItMkg1em0wIDE2Yy0xLjEwNSAwLTItLjg5NS0yLTJoMThjMCAxLjEwNS0uODk1IDItMiAySDV6IiBmaWxsPSIjMDAwIi8+Cjwvc3ZnPgo=');\n}\n.tooltip__button--data--identities::before {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTEyIDIxYzIuMTQzIDAgNC4xMTEtLjc1IDUuNjU3LTItLjYyNi0uNTA2LTEuMzE4LS45MjctMi4wNi0xLjI1LTEuMS0uNDgtMi4yODUtLjczNS0zLjQ4Ni0uNzUtMS4yLS4wMTQtMi4zOTIuMjExLTMuNTA0LjY2NC0uODE3LjMzMy0xLjU4Ljc4My0yLjI2NCAxLjMzNiAxLjU0NiAxLjI1IDMuNTE0IDIgNS42NTcgMnptNC4zOTctNS4wODNjLjk2Ny40MjIgMS44NjYuOTggMi42NzIgMS42NTVDMjAuMjc5IDE2LjAzOSAyMSAxNC4xMDQgMjEgMTJjMC00Ljk3LTQuMDMtOS05LTlzLTkgNC4wMy05IDljMCAyLjEwNC43MjIgNC4wNCAxLjkzMiA1LjU3Mi44NzQtLjczNCAxLjg2LTEuMzI4IDIuOTIxLTEuNzYgMS4zNi0uNTU0IDIuODE2LS44MyA0LjI4My0uODExIDEuNDY3LjAxOCAyLjkxNi4zMyA0LjI2LjkxNnpNMTIgMjNjNi4wNzUgMCAxMS00LjkyNSAxMS0xMVMxOC4wNzUgMSAxMiAxIDEgNS45MjUgMSAxMnM0LjkyNSAxMSAxMSAxMXptMy0xM2MwIDEuNjU3LTEuMzQzIDMtMyAzcy0zLTEuMzQzLTMtMyAxLjM0My0zIDMtMyAzIDEuMzQzIDMgM3ptMiAwYzAgMi43NjEtMi4yMzkgNS01IDVzLTUtMi4yMzktNS01IDIuMjM5LTUgNS01IDUgMi4yMzkgNSA1eiIgZmlsbD0iIzAwMCIvPgo8L3N2Zz4=');\n}\n.tooltip__button--data--credentials.tooltip__button--data--bitwarden::before,\n.tooltip__button--data--credentials__current.tooltip__button--data--bitwarden::before {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiByeD0iOCIgZmlsbD0iIzE3NUREQyIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTE4LjU2OTYgNS40MzM1NUMxOC41MDg0IDUuMzc0NDIgMTguNDM0NyA1LjMyNzYzIDE4LjM1MzEgNS4yOTYxMUMxOC4yNzE1IDUuMjY0NiAxOC4xODM3IDUuMjQ5MDQgMTguMDk1MyA1LjI1MDQxSDUuOTIxOTFDNS44MzMyNiA1LjI0NzI5IDUuNzQ0OTMgNS4yNjIwNSA1LjY2MzA0IDUuMjkzNjdDNS41ODExNSA1LjMyNTI5IDUuNTA3NjUgNS4zNzMwMiA1LjQ0NzYyIDUuNDMzNTVDNS4zMjE3IDUuNTUwMTMgNS4yNTA2NSA1LjcwODE1IDUuMjUgNS44NzMxVjEzLjM4MjFDNS4yNTMzNiAxMy45NTM1IDUuMzc0MDggMTQuNTE5MSA1LjYwNTcyIDE1LjA0ODdDNS44MTkzMSAxNS41NzI4IDYuMTEyMDcgMTYuMDY2MSA2LjQ3NTI0IDE2LjUxMzlDNi44NDIgMTYuOTY4MyA3LjI1OTI5IDE3LjM4NTcgNy43MjAyNSAxNy43NTkzQzguMTQwNTMgMTguMTI1NiA4LjU4OTcxIDE4LjQ2MjMgOS4wNjQwNyAxOC43NjY2QzkuNDU5MzEgMTkuMDIzIDkuOTEzODMgMTkuMjc5NCAxMC4zNDg2IDE5LjUxNzVDMTAuNzgzNCAxOS43NTU2IDExLjA5OTYgMTkuOTIwNCAxMS4yNzc0IDE5Ljk5MzdDMTEuNDU1MyAyMC4wNjY5IDExLjYxMzQgMjAuMTQwMiAxMS43MTIyIDIwLjE5NTFDMTEuNzk5MiAyMC4yMzEzIDExLjg5MzUgMjAuMjUgMTEuOTg4OCAyMC4yNUMxMi4wODQyIDIwLjI1IDEyLjE3ODUgMjAuMjMxMyAxMi4yNjU1IDIwLjE5NTFDMTIuNDIxMiAyMC4xMzYzIDEyLjU3MjkgMjAuMDY5IDEyLjcyIDE5Ljk5MzdDMTIuNzcxMSAxOS45Njc0IDEyLjgzMzUgMTkuOTM2NiAxMi45MDY5IDE5LjkwMDRDMTMuMDg5MSAxOS44MTA1IDEzLjMzODggMTkuNjg3MiAxMy42NDg5IDE5LjUxNzVDMTQuMDgzNiAxOS4yNzk0IDE0LjUxODQgMTkuMDIzIDE0LjkzMzQgMTguNzY2NkMxNS40MDQgMTguNDU3NyAxNS44NTI4IDE4LjEyMTIgMTYuMjc3MiAxNy43NTkzQzE2LjczMzEgMTcuMzgwOSAxNy4xNDk5IDE2Ljk2NCAxNy41MjIyIDE2LjUxMzlDMTcuODc4IDE2LjA2MTcgMTguMTcwMiAxNS41NjkzIDE4LjM5MTcgMTUuMDQ4N0MxOC42MjM0IDE0LjUxOTEgMTguNzQ0MSAxMy45NTM1IDE4Ljc0NzQgMTMuMzgyMVY1Ljg3MzFDMTguNzU1NyA1Ljc5MjE0IDE4Ljc0MzkgNS43MTA1IDE4LjcxMzEgNS42MzQzNUMxOC42ODIzIDUuNTU4MiAxOC42MzMyIDUuNDg5NTQgMTguNTY5NiA1LjQzMzU1Wk0xNy4wMDg0IDEzLjQ1NTNDMTcuMDA4NCAxNi4xODQyIDEyLjAwODYgMTguNTI4NSAxMi4wMDg2IDE4LjUyODVWNi44NjIwOUgxNy4wMDg0VjEzLjQ1NTNaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K');\n}\n#provider_locked:after {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTEiIGhlaWdodD0iMTMiIHZpZXdCb3g9IjAgMCAxMSAxMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEgNy42MDA1N1Y3LjYwMjVWOS41MjI1QzEgMTAuMDgwMSAxLjIyMTUxIDEwLjYxNDkgMS42MTU4MSAxMS4wMDkyQzIuMDEwMSAxMS40MDM1IDIuNTQ0ODggMTEuNjI1IDMuMTAyNSAxMS42MjVINy4yNzI1QzcuNTQ4NjEgMTEuNjI1IDcuODIyMDEgMTEuNTcwNiA4LjA3NzA5IDExLjQ2NUM4LjMzMjE4IDExLjM1OTMgOC41NjM5NiAxMS4yMDQ0IDguNzU5MTkgMTEuMDA5MkM4Ljk1NDQzIDEwLjgxNCA5LjEwOTMgMTAuNTgyMiA5LjIxNDk2IDEwLjMyNzFDOS4zMjA2MiAxMC4wNzIgOS4zNzUgOS43OTg2MSA5LjM3NSA5LjUyMjVMOS4zNzUgNy42MDI1TDkuMzc1IDcuNjAwNTdDOS4zNzQxNSA3LjE2MTMxIDkuMjM1NzQgNi43MzMzNSA4Ljk3OTIyIDYuMzc2NzhDOC44NzY4MyA2LjIzNDQ2IDguNzU3NjggNi4xMDYzNyA4LjYyNSA1Ljk5NDg5VjUuMTg3NUM4LjYyNSA0LjI3NTgyIDguMjYyODQgMy40MDE0OCA3LjYxODE4IDIuNzU2ODJDNi45NzM1MiAyLjExMjE2IDYuMDk5MTggMS43NSA1LjE4NzUgMS43NUM0LjI3NTgyIDEuNzUgMy40MDE0OCAyLjExMjE2IDIuNzU2ODIgMi43NTY4MkMyLjExMjE2IDMuNDAxNDggMS43NSA0LjI3NTgyIDEuNzUgNS4xODc1VjUuOTk0ODlDMS42MTczMiA2LjEwNjM3IDEuNDk4MTcgNi4yMzQ0NiAxLjM5NTc4IDYuMzc2NzhDMS4xMzkyNiA2LjczMzM1IDEuMDAwODUgNy4xNjEzMSAxIDcuNjAwNTdaTTQuOTY4NyA0Ljk2ODdDNS4wMjY5NCA0LjkxMDQ3IDUuMTA1MzIgNC44NzY5OSA1LjE4NzUgNC44NzUwN0M1LjI2OTY4IDQuODc2OTkgNS4zNDgwNiA0LjkxMDQ3IDUuNDA2MyA0Ljk2ODdDNS40NjU0MiA1LjAyNzgzIDUuNDk5MDQgNS4xMDc3NCA1LjUgNS4xOTEzVjUuNUg0Ljg3NVY1LjE5MTNDNC44NzU5NiA1LjEwNzc0IDQuOTA5NTggNS4wMjc4MyA0Ljk2ODcgNC45Njg3WiIgZmlsbD0iIzIyMjIyMiIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIyIi8+Cjwvc3ZnPgo=');\n}\n\nhr {\n display: block;\n margin: 5px 9px;\n border: none; /* reset the border */\n border-top: 1px solid rgba(0,0,0,.1);\n}\n\nhr:first-child {\n display: none;\n}\n\n@media (prefers-color-scheme: dark) {\n hr {\n border-top: 1px solid rgba(255,255,255,.2);\n }\n}\n\n#privateAddress {\n align-items: flex-start;\n}\n#personalAddress::before,\n#privateAddress::before,\n#incontextSignup::before,\n#personalAddress.currentFocus::before,\n#personalAddress:hover::before,\n#privateAddress.currentFocus::before,\n#privateAddress:hover::before {\n filter: none;\n /* This is the same icon as `daxBase64` in `src/Form/logo-svg.js` */\n background-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMTI4IDEyOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0ibTY0IDEyOGMzNS4zNDYgMCA2NC0yOC42NTQgNjQtNjRzLTI4LjY1NC02NC02NC02NC02NCAyOC42NTQtNjQgNjQgMjguNjU0IDY0IDY0IDY0eiIgZmlsbD0iI2RlNTgzMyIgZmlsbC1ydWxlPSJldmVub2RkIi8+CiAgICA8cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Im03MyAxMTEuNzVjMC0uNS4xMjMtLjYxNC0xLjQ2Ni0zLjc4Mi00LjIyNC04LjQ1OS04LjQ3LTIwLjM4NC02LjU0LTI4LjA3NS4zNTMtMS4zOTctMy45NzgtNTEuNzQ0LTcuMDQtNTMuMzY1LTMuNDAyLTEuODEzLTcuNTg4LTQuNjktMTEuNDE4LTUuMzMtMS45NDMtLjMxLTQuNDktLjE2NC02LjQ4Mi4xMDUtLjM1My4wNDctLjM2OC42ODMtLjAzLjc5OCAxLjMwOC40NDMgMi44OTUgMS4yMTIgMy44MyAyLjM3NS4xNzguMjItLjA2LjU2Ni0uMzQyLjU3Ny0uODgyLjAzMi0yLjQ4Mi40MDItNC41OTMgMi4xOTUtLjI0NC4yMDctLjA0MS41OTIuMjczLjUzIDQuNTM2LS44OTcgOS4xNy0uNDU1IDExLjkgMi4wMjcuMTc3LjE2LjA4NC40NS0uMTQ3LjUxMi0yMy42OTQgNi40NC0xOS4wMDMgMjcuMDUtMTIuNjk2IDUyLjM0NCA1LjYxOSAyMi41MyA3LjczMyAyOS43OTIgOC40IDMyLjAwNGEuNzE4LjcxOCAwIDAgMCAuNDIzLjQ2N2M4LjE1NiAzLjI0OCAyNS45MjggMy4zOTIgMjUuOTI4LTIuMTMyeiIgZmlsbD0iI2RkZCIgZmlsbC1ydWxlPSJldmVub2RkIi8+CiAgICA8cGF0aCBkPSJtNzYuMjUgMTE2LjVjLTIuODc1IDEuMTI1LTguNSAxLjYyNS0xMS43NSAxLjYyNS00Ljc2NCAwLTExLjYyNS0uNzUtMTQuMTI1LTEuODc1LTEuNTQ0LTQuNzUxLTYuMTY0LTE5LjQ4LTEwLjcyNy0zOC4xODVsLS40NDctMS44MjctLjAwNC0uMDE1Yy01LjQyNC0yMi4xNTctOS44NTUtNDAuMjUzIDE0LjQyNy00NS45MzguMjIyLS4wNTIuMzMtLjMxNy4xODQtLjQ5Mi0yLjc4Ni0zLjMwNS04LjAwNS00LjM4OC0xNC42MDUtMi4xMTEtLjI3LjA5My0uNTA2LS4xOC0uMzM3LS40MTIgMS4yOTQtMS43ODMgMy44MjMtMy4xNTUgNS4wNzEtMy43NTYuMjU4LS4xMjQuMjQyLS41MDItLjAzLS41ODhhMjcuODc3IDI3Ljg3NyAwIDAgMCAtMy43NzItLjljLS4zNy0uMDU5LS40MDMtLjY5My0uMDMyLS43NDMgOS4zNTYtMS4yNTkgMTkuMTI1IDEuNTUgMjQuMDI4IDcuNzI2YS4zMjYuMzI2IDAgMCAwIC4xODYuMTE0YzE3Ljk1MiAzLjg1NiAxOS4yMzggMzIuMjM1IDE3LjE3IDMzLjUyOC0uNDA4LjI1NS0xLjcxNS4xMDgtMy40MzgtLjA4NS02Ljk4Ni0uNzgxLTIwLjgxOC0yLjMyOS05LjQwMiAxOC45NDguMTEzLjIxLS4wMzYuNDg4LS4yNzIuNTI1LTYuNDM4IDEgMS44MTIgMjEuMTczIDcuODc1IDM0LjQ2MXoiIGZpbGw9IiNmZmYiLz4KICAgIDxwYXRoIGQ9Im04NC4yOCA5MC42OThjLTEuMzY3LS42MzMtNi42MjEgMy4xMzUtMTAuMTEgNi4wMjgtLjcyOC0xLjAzMS0yLjEwMy0xLjc4LTUuMjAzLTEuMjQyLTIuNzEzLjQ3Mi00LjIxMSAxLjEyNi00Ljg4IDIuMjU0LTQuMjgzLTEuNjIzLTExLjQ4OC00LjEzLTEzLjIyOS0xLjcxLTEuOTAyIDIuNjQ2LjQ3NiAxNS4xNjEgMy4wMDMgMTYuNzg2IDEuMzIuODQ5IDcuNjMtMy4yMDggMTAuOTI2LTYuMDA1LjUzMi43NDkgMS4zODggMS4xNzggMy4xNDggMS4xMzcgMi42NjItLjA2MiA2Ljk3OS0uNjgxIDcuNjQ5LTEuOTIxLjA0LS4wNzUuMDc1LS4xNjQuMTA1LS4yNjYgMy4zODggMS4yNjYgOS4zNSAyLjYwNiAxMC42ODIgMi40MDYgMy40Ny0uNTIxLS40ODQtMTYuNzIzLTIuMDktMTcuNDY3eiIgZmlsbD0iIzNjYTgyYiIvPgogICAgPHBhdGggZD0ibTc0LjQ5IDk3LjA5N2MuMTQ0LjI1Ni4yNi41MjYuMzU4LjguNDgzIDEuMzUyIDEuMjcgNS42NDguNjc0IDYuNzA5LS41OTUgMS4wNjItNC40NTkgMS41NzQtNi44NDMgMS42MTVzLTIuOTItLjgzMS0zLjQwMy0yLjE4MWMtLjM4Ny0xLjA4MS0uNTc3LTMuNjIxLS41NzItNS4wNzUtLjA5OC0yLjE1OC42OS0yLjkxNiA0LjMzNC0zLjUwNiAyLjY5Ni0uNDM2IDQuMTIxLjA3MSA0Ljk0NC45NCAzLjgyOC0yLjg1NyAxMC4yMTUtNi44ODkgMTAuODM4LTYuMTUyIDMuMTA2IDMuNjc0IDMuNDk5IDEyLjQyIDIuODI2IDE1LjkzOS0uMjIgMS4xNTEtMTAuNTA1LTEuMTM5LTEwLjUwNS0yLjM4IDAtNS4xNTItMS4zMzctNi41NjUtMi42NS02Ljcxem0tMjIuNTMtMS42MDljLjg0My0xLjMzMyA3LjY3NC4zMjUgMTEuNDI0IDEuOTkzIDAgMC0uNzcgMy40OTEuNDU2IDcuNjA0LjM1OSAxLjIwMy04LjYyNyA2LjU1OC05LjggNS42MzctMS4zNTUtMS4wNjUtMy44NS0xMi40MzItMi4wOC0xNS4yMzR6IiBmaWxsPSIjNGNiYTNjIi8+CiAgICA8cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Im01NS4yNjkgNjguNDA2Yy41NTMtMi40MDMgMy4xMjctNi45MzIgMTIuMzIxLTYuODIyIDQuNjQ4LS4wMTkgMTAuNDIyLS4wMDIgMTQuMjUtLjQzNmE1MS4zMTIgNTEuMzEyIDAgMCAwIDEyLjcyNi0zLjA5NWMzLjk4LTEuNTE5IDUuMzkyLTEuMTggNS44ODctLjI3Mi41NDQuOTk5LS4wOTcgMi43MjItMS40ODggNC4zMDktMi42NTYgMy4wMy03LjQzMSA1LjM4LTE1Ljg2NSA2LjA3Ni04LjQzMy42OTgtMTQuMDItMS41NjUtMTYuNDI1IDIuMTE4LTEuMDM4IDEuNTg5LS4yMzYgNS4zMzMgNy45MiA2LjUxMiAxMS4wMiAxLjU5IDIwLjA3Mi0xLjkxNyAyMS4xOS4yMDEgMS4xMTkgMi4xMTgtNS4zMjMgNi40MjgtMTYuMzYyIDYuNTE4cy0xNy45MzQtMy44NjUtMjAuMzc5LTUuODNjLTMuMTAyLTIuNDk1LTQuNDktNi4xMzMtMy43NzUtOS4yNzl6IiBmaWxsPSIjZmMzIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz4KICAgIDxnIGZpbGw9IiMxNDMwN2UiIG9wYWNpdHk9Ii44Ij4KICAgICAgPHBhdGggZD0ibTY5LjMyNyA0Mi4xMjdjLjYxNi0xLjAwOCAxLjk4MS0xLjc4NiA0LjIxNi0xLjc4NiAyLjIzNCAwIDMuMjg1Ljg4OSA0LjAxMyAxLjg4LjE0OC4yMDItLjA3Ni40NC0uMzA2LjM0YTU5Ljg2OSA1OS44NjkgMCAwIDEgLS4xNjgtLjA3M2MtLjgxNy0uMzU3LTEuODItLjc5NS0zLjU0LS44Mi0xLjgzOC0uMDI2LTIuOTk3LjQzNS0zLjcyNy44MzEtLjI0Ni4xMzQtLjYzNC0uMTMzLS40ODgtLjM3MnptLTI1LjE1NyAxLjI5YzIuMTctLjkwNyAzLjg3Ni0uNzkgNS4wODEtLjUwNC4yNTQuMDYuNDMtLjIxMy4yMjctLjM3Ny0uOTM1LS43NTUtMy4wMy0xLjY5Mi01Ljc2LS42NzQtMi40MzcuOTA5LTMuNTg1IDIuNzk2LTMuNTkyIDQuMDM4LS4wMDIuMjkyLjYuMzE3Ljc1Ni4wNy40Mi0uNjcgMS4xMi0xLjY0NiAzLjI4OS0yLjU1M3oiLz4KICAgICAgPHBhdGggY2xpcC1ydWxlPSJldmVub2RkIiBkPSJtNzUuNDQgNTUuOTJhMy40NyAzLjQ3IDAgMCAxIC0zLjQ3NC0zLjQ2MiAzLjQ3IDMuNDcgMCAwIDEgMy40NzUtMy40NiAzLjQ3IDMuNDcgMCAwIDEgMy40NzQgMy40NiAzLjQ3IDMuNDcgMCAwIDEgLTMuNDc1IDMuNDYyem0yLjQ0Ny00LjYwOGEuODk5Ljg5OSAwIDAgMCAtMS43OTkgMGMwIC40OTQuNDA1Ljg5NS45Ljg5NS40OTkgMCAuOS0uNC45LS44OTV6bS0yNS40NjQgMy41NDJhNC4wNDIgNC4wNDIgMCAwIDEgLTQuMDQ5IDQuMDM3IDQuMDQ1IDQuMDQ1IDAgMCAxIC00LjA1LTQuMDM3IDQuMDQ1IDQuMDQ1IDAgMCAxIDQuMDUtNC4wMzcgNC4wNDUgNC4wNDUgMCAwIDEgNC4wNSA0LjAzN3ptLTEuMTkzLTEuMzM4YTEuMDUgMS4wNSAwIDAgMCAtMi4wOTcgMCAxLjA0OCAxLjA0OCAwIDAgMCAyLjA5NyAweiIgZmlsbC1ydWxlPSJldmVub2RkIi8+CiAgICA8L2c+CiAgICA8cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Im02NCAxMTcuNzVjMjkuNjg1IDAgNTMuNzUtMjQuMDY1IDUzLjc1LTUzLjc1cy0yNC4wNjUtNTMuNzUtNTMuNzUtNTMuNzUtNTMuNzUgMjQuMDY1LTUzLjc1IDUzLjc1IDI0LjA2NSA1My43NSA1My43NSA1My43NXptMCA1YzMyLjQ0NyAwIDU4Ljc1LTI2LjMwMyA1OC43NS01OC43NXMtMjYuMzAzLTU4Ljc1LTU4Ljc1LTU4Ljc1LTU4Ljc1IDI2LjMwMy01OC43NSA1OC43NSAyNi4zMDMgNTguNzUgNTguNzUgNTguNzV6IiBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz4KPC9zdmc+');\n}\n\n/* Email tooltip specific */\n.tooltip__button--email {\n flex-direction: column;\n justify-content: center;\n align-items: flex-start;\n font-size: 14px;\n padding: 4px 8px;\n}\n.tooltip__button--email__primary-text {\n font-weight: bold;\n}\n.tooltip__button--email__secondary-text {\n font-size: 12px;\n}\n\n/* Email Protection signup notice */\n:not(.top-autofill) .tooltip--email-signup {\n text-align: left;\n color: #222222;\n padding: 16px 20px;\n width: 380px;\n}\n\n.tooltip--email-signup h1 {\n font-weight: 700;\n font-size: 16px;\n line-height: 1.5;\n margin: 0;\n}\n\n.tooltip--email-signup p {\n font-weight: 400;\n font-size: 14px;\n line-height: 1.4;\n}\n\n.notice-controls {\n display: flex;\n}\n\n.tooltip--email-signup .notice-controls > * {\n border-radius: 8px;\n border: 0;\n cursor: pointer;\n display: inline-block;\n font-family: inherit;\n font-style: normal;\n font-weight: bold;\n padding: 8px 12px;\n text-decoration: none;\n}\n\n.notice-controls .ghost {\n margin-left: 1rem;\n}\n\n.tooltip--email-signup a.primary {\n background: #3969EF;\n color: #fff;\n}\n\n.tooltip--email-signup a.primary:hover,\n.tooltip--email-signup a.primary:focus {\n background: #2b55ca;\n}\n\n.tooltip--email-signup a.primary:active {\n background: #1e42a4;\n}\n\n.tooltip--email-signup button.ghost {\n background: transparent;\n color: #3969EF;\n}\n\n.tooltip--email-signup button.ghost:hover,\n.tooltip--email-signup button.ghost:focus {\n background-color: rgba(0, 0, 0, 0.06);\n color: #2b55ca;\n}\n\n.tooltip--email-signup button.ghost:active {\n background-color: rgba(0, 0, 0, 0.12);\n color: #1e42a4;\n}\n\n.tooltip--email-signup button.close-tooltip {\n background-color: transparent;\n background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iMTMiIHZpZXdCb3g9IjAgMCAxMiAxMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0wLjI5Mjg5NCAwLjY1NjkwN0MwLjY4MzQxOCAwLjI2NjM4MyAxLjMxNjU4IDAuMjY2MzgzIDEuNzA3MTEgMC42NTY5MDdMNiA0Ljk0OThMMTAuMjkyOSAwLjY1NjkwN0MxMC42ODM0IDAuMjY2MzgzIDExLjMxNjYgMC4yNjYzODMgMTEuNzA3MSAwLjY1NjkwN0MxMi4wOTc2IDEuMDQ3NDMgMTIuMDk3NiAxLjY4MDYgMTEuNzA3MSAyLjA3MTEyTDcuNDE0MjEgNi4zNjQwMUwxMS43MDcxIDEwLjY1NjlDMTIuMDk3NiAxMS4wNDc0IDEyLjA5NzYgMTEuNjgwNiAxMS43MDcxIDEyLjA3MTFDMTEuMzE2NiAxMi40NjE2IDEwLjY4MzQgMTIuNDYxNiAxMC4yOTI5IDEyLjA3MTFMNiA3Ljc3ODIzTDEuNzA3MTEgMTIuMDcxMUMxLjMxNjU4IDEyLjQ2MTYgMC42ODM0MTcgMTIuNDYxNiAwLjI5Mjg5MyAxMi4wNzExQy0wLjA5NzYzMTEgMTEuNjgwNiAtMC4wOTc2MzExIDExLjA0NzQgMC4yOTI4OTMgMTAuNjU2OUw0LjU4NTc5IDYuMzY0MDFMMC4yOTI4OTQgMi4wNzExMkMtMC4wOTc2MzA2IDEuNjgwNiAtMC4wOTc2MzA2IDEuMDQ3NDMgMC4yOTI4OTQgMC42NTY5MDdaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjg0Ii8+Cjwvc3ZnPgo=);\n background-position: center center;\n background-repeat: no-repeat;\n border: 0;\n cursor: pointer;\n padding: 16px;\n position: absolute;\n right: 12px;\n top: 12px;\n}\n"; - /** @param {AvailableInputTypes} value */ - setAvailableInputTypes(value) { - this._availableInputTypes = { - ...this._availableInputTypes, - ...value - }; - } - static defaults = { - /** @type {AutofillFeatureToggles} */ - featureToggles: { - credentials_saving: false, - password_generation: false, - emailProtection: false, - emailProtection_incontext_signup: false, - inputType_identities: false, - inputType_credentials: false, - inputType_creditCards: false, - inlineIcon_credentials: false - }, - /** @type {AvailableInputTypes} */ - availableInputTypes: { - credentials: { - username: false, - password: false - }, - identities: { - firstName: false, - middleName: false, - lastName: false, - birthdayDay: false, - birthdayMonth: false, - birthdayYear: false, - addressStreet: false, - addressStreet2: false, - addressCity: false, - addressProvince: false, - addressPostalCode: false, - addressCountryCode: false, - phone: false, - emailAddress: false - }, - creditCards: { - cardName: false, - cardSecurityCode: false, - expirationMonth: false, - expirationYear: false, - cardNumber: false - }, - email: false + // src/UI/HTMLTooltip.js + var defaultOptions = { + wrapperClass: "", + tooltipPositionClass: (top, left) => ` + .tooltip { + transform: translate(${Math.floor(left)}px, ${Math.floor(top)}px) !important; + } + `, + caretPositionClass: (top, left, isAboveInput) => ` + .tooltip--email__caret { + ${isAboveInput ? `transform: translate(${Math.floor(left)}px, ${Math.floor(top)}px) rotate(180deg); transform-origin: 18px !important;` : `transform: translate(${Math.floor(left)}px, ${Math.floor(top)}px) !important;`} + }`, + css: ``, + setSize: void 0, + remove: () => { }, - /** @type {boolean | null} */ - enabled: null + testMode: false, + checkVisibility: true, + hasCaret: false, + isIncontextSignupAvailable: () => false }; - static default(globalConfig, deviceApi) { - const settings = new Settings(globalConfig, deviceApi); - settings.setFeatureToggles(Settings.defaults.featureToggles); - settings.setAvailableInputTypes(Settings.defaults.availableInputTypes); - return settings; - } - - /** @returns {boolean|null} */ - get enabled() { - return this._enabled; - } - - /** - * @param {boolean|null} enabled - */ - setEnabled(enabled) { - this._enabled = enabled; - } -} -exports.Settings = Settings; - -},{"../packages/device-api/index.js":12,"./autofill-utils.js":62,"./deviceApiCalls/__generated__/deviceApiCalls.js":66,"./deviceApiCalls/__generated__/validators.zod.js":67}],51:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.ThirdPartyProvider = void 0; -var _deviceApiCalls = require("./deviceApiCalls/__generated__/deviceApiCalls.js"); -var _validatorsZod = require("./deviceApiCalls/__generated__/validators.zod.js"); -var _matching = require("./Form/matching.js"); -var _index = require("../packages/device-api/index.js"); -class ThirdPartyProvider { - /** - * @param {import("./DeviceInterface/InterfacePrototype").default} device - */ - constructor(device) { - this.device = device; - } - init() { - if (this.device.settings.featureToggles.third_party_credentials_provider) { - if (this.device.globalConfig.hasModernWebkitAPI) { - Object.defineProperty(window, 'providerStatusUpdated', { - enumerable: false, - configurable: false, - writable: false, - value: data => { - this.providerStatusUpdated(data); + var HTMLTooltip = class { + /** + * @param config + * @param inputType + * @param getPosition + * @param {HTMLTooltipOptions} options + */ + constructor(config, inputType, getPosition, options) { + __publicField(this, "isAboveInput", false); + /** @type {HTMLTooltipOptions} */ + __publicField(this, "options"); + __publicField(this, "resObs", new ResizeObserver((entries) => entries.forEach(() => this.checkPosition()))); + __publicField(this, "mutObsCheckPositionWhenIdle", whenIdle.call(this, this.checkPosition)); + __publicField(this, "mutObs", new MutationObserver((mutationList) => { + for (const mutationRecord of mutationList) { + if (mutationRecord.type === "childList") { + mutationRecord.addedNodes.forEach((el) => { + if (el.nodeName === "DDG-AUTOFILL") + return; + this.ensureIsLastInDOM(); + }); } - }); - } else { - // On Catalina we poll the native layer - setTimeout(() => this._pollForUpdatesToCredentialsProvider(), 2000); + } + this.mutObsCheckPositionWhenIdle(); + })); + __publicField(this, "clickableButtons", /* @__PURE__ */ new Map()); + this.options = options; + this.shadow = document.createElement("ddg-autofill").attachShadow({ + mode: options.testMode ? "open" : "closed" + }); + this.host = this.shadow.host; + this.config = config; + this.subtype = getSubtypeFromType(inputType); + this.variant = getVariantFromType(inputType); + this.tooltip = null; + this.getPosition = getPosition; + const forcedVisibilityStyles = { + "display": "block", + "visibility": "visible", + "opacity": "1" + }; + addInlineStyles(this.host, forcedVisibilityStyles); + this.count = 0; + this.device = null; + this.transformRules = { + caret: { + getRuleString: this.options.caretPositionClass, + index: null + }, + tooltip: { + getRuleString: this.options.tooltipPositionClass, + index: null + } + }; + } + get isHidden() { + return this.tooltip.parentNode.hidden; + } + append() { + document.body.appendChild(this.host); + } + remove() { + this.device?.activeForm.resetIconStylesToInitial(); + window.removeEventListener("scroll", this, { capture: true }); + this.resObs.disconnect(); + this.mutObs.disconnect(); + this.lift(); + } + lift() { + this.left = null; + this.top = null; + document.body.removeChild(this.host); + } + handleEvent(event) { + switch (event.type) { + case "scroll": + this.checkPosition(); + break; } } - } - async askToUnlockProvider() { - const response = await this.device.deviceApi.request(new _deviceApiCalls.AskToUnlockProviderCall(null)); - this.providerStatusUpdated(response); - } - - /** - * Called by the native layer on all tabs when the provider status is updated - * @param {import("./deviceApiCalls/__generated__/validators-ts").ProviderStatusUpdated} data - */ - providerStatusUpdated(data) { - try { - const { - credentials, - availableInputTypes - } = (0, _index.validate)(data, _validatorsZod.providerStatusUpdatedSchema); - - // Update local settings and data - this.device.settings.setAvailableInputTypes(availableInputTypes); - this.device.storeLocalCredentials(credentials); - - // rerender the tooltip - this.device.uiController?.updateItems(credentials); - if (!this.device.globalConfig.isTopFrame) { - // If the tooltip is open on an autofill type that's not available, close it - const currentInputSubtype = (0, _matching.getSubtypeFromType)(this.device.getCurrentInputType()); - if (!availableInputTypes.credentials?.[currentInputSubtype]) { - this.device.removeTooltip(); + focus(x, y) { + const focusableElements = "button"; + const currentFocusClassName = "currentFocus"; + const currentFocused = this.shadow.querySelectorAll(`.${currentFocusClassName}`); + [...currentFocused].forEach((el) => { + el.classList.remove(currentFocusClassName); + }); + this.shadow.elementFromPoint(x, y)?.closest(focusableElements)?.classList.add(currentFocusClassName); + } + checkPosition() { + if (this.animationFrame) { + window.cancelAnimationFrame(this.animationFrame); + } + this.animationFrame = window.requestAnimationFrame(() => { + if (this.isHidden) + return; + const { left, bottom, height, top } = this.getPosition(); + if (left !== this.left || bottom !== this.top) { + const coords = { left, top: bottom }; + this.updatePosition("tooltip", coords); + if (this.options.hasCaret) { + const { top: tooltipTop } = this.tooltip.getBoundingClientRect(); + this.isAboveInput = top > tooltipTop; + const borderWidth = 2; + const caretTop = this.isAboveInput ? coords.top - height - borderWidth : coords.top; + this.updatePosition("caret", { ...coords, top: caretTop }); + } } - // Redecorate fields according to the new types - this.device.scanner.forms.forEach(form => form.recategorizeAllInputs()); + this.animationFrame = null; + }); + } + getOverridePosition({ left, top }) { + const tooltipBoundingBox = this.tooltip.getBoundingClientRect(); + const smallScreenWidth = tooltipBoundingBox.width * 2; + const spacing = 5; + if (tooltipBoundingBox.bottom > window.innerHeight) { + const inputPosition = this.getPosition(); + const caretHeight = 14; + const overriddenTopPosition = top - tooltipBoundingBox.height - inputPosition.height - caretHeight; + if (overriddenTopPosition >= 0) + return { left, top: overriddenTopPosition }; + } + if (tooltipBoundingBox.left < 0 && window.innerWidth <= smallScreenWidth) { + const leftOverflow = Math.abs(tooltipBoundingBox.left); + const leftPosWhenCentered = (window.innerWidth - tooltipBoundingBox.width) / 2; + const overriddenLeftPosition = left + leftOverflow + leftPosWhenCentered; + return { left: overriddenLeftPosition, top }; + } + if (tooltipBoundingBox.left < 0 && window.innerWidth > smallScreenWidth) { + const leftOverflow = Math.abs(tooltipBoundingBox.left); + const overriddenLeftPosition = left + leftOverflow + spacing; + return { left: overriddenLeftPosition, top }; + } + if (tooltipBoundingBox.right > window.innerWidth) { + const rightOverflow = tooltipBoundingBox.right - window.innerWidth; + const overriddenLeftPosition = left - rightOverflow - spacing; + return { left: overriddenLeftPosition, top }; } - } catch (e) { - if (this.device.globalConfig.isDDGTestMode) { - console.log('isDDGTestMode: providerStatusUpdated error: ❌', e); + } + /** + * @param {'tooltip' | 'caret'} element + * @param {{ + * left: number, + * top: number + * }} coords + */ + applyPositionalStyles(element, { left, top }) { + const shadow = this.shadow; + const ruleObj = this.transformRules[element]; + if (ruleObj.index) { + if (shadow.styleSheets[0].rules[ruleObj.index]) { + shadow.styleSheets[0].deleteRule(ruleObj.index); + } + } else { + ruleObj.index = shadow.styleSheets[0].rules.length; + } + const cssRule = ruleObj.getRuleString?.(top, left, this.isAboveInput); + if (typeof cssRule === "string") { + shadow.styleSheets[0].insertRule(cssRule, ruleObj.index); } } - } - - // Only used on Catalina - async _pollForUpdatesToCredentialsProvider() { - try { - const response = await this.device.deviceApi.request(new _deviceApiCalls.CheckCredentialsProviderStatusCall(null)); - if (response.availableInputTypes.credentialsProviderStatus !== this.device.settings.availableInputTypes.credentialsProviderStatus) { - this.providerStatusUpdated(response); + /** + * @param {'tooltip' | 'caret'} element + * @param {{ + * left: number, + * top: number + * }} coords + */ + updatePosition(element, { left, top }) { + if (!this.shadow.styleSheets.length) { + this.stylesheet?.addEventListener("load", () => this.checkPosition()); + return; } - setTimeout(() => this._pollForUpdatesToCredentialsProvider(), 2000); - } catch (e) { - if (this.device.globalConfig.isDDGTestMode) { - console.log('isDDGTestMode: _pollForUpdatesToCredentialsProvider: ❌', e); + this.left = left; + this.top = top; + this.applyPositionalStyles(element, { left, top }); + if (this.options.hasCaret) { + const overridePosition = this.getOverridePosition({ left, top }); + if (overridePosition) + this.updatePosition(element, overridePosition); + } + } + ensureIsLastInDOM() { + this.count = this.count || 0; + if (document.body.lastElementChild !== this.host) { + if (this.count < 15) { + this.lift(); + this.append(); + this.checkPosition(); + this.count++; + } else { + this.options.remove(); + console.info(`DDG autofill bailing out`); + } } } - } -} -exports.ThirdPartyProvider = ThirdPartyProvider; - -},{"../packages/device-api/index.js":12,"./Form/matching.js":43,"./deviceApiCalls/__generated__/deviceApiCalls.js":66,"./deviceApiCalls/__generated__/validators.zod.js":67}],52:[function(require,module,exports){ -"use strict"; + setActiveButton(e) { + this.activeButton = e.target; + } + unsetActiveButton() { + this.activeButton = null; + } + registerClickableButton(btn, handler) { + this.clickableButtons.set(btn, handler); + btn.addEventListener("mouseenter", (e) => this.setActiveButton(e)); + btn.addEventListener("mouseleave", () => this.unsetActiveButton()); + } + dispatchClick() { + const handler = this.clickableButtons.get(this.activeButton); + if (handler) { + if (this.activeButton.matches(".wrapper:not(.top-autofill) button:hover, .wrapper:not(.top-autofill) a:hover, .currentFocus")) { + safeExecute(this.activeButton, handler, { + checkVisibility: this.options.checkVisibility + }); + } else { + console.warn("The button doesn't seem to be hovered. Please check."); + } + } + } + setupSizeListener() { + const observer = new PerformanceObserver(() => { + this.setSize(); + }); + observer.observe({ entryTypes: ["layout-shift", "paint"] }); + } + setSize() { + const innerNode = this.shadow.querySelector(".wrapper--data"); + if (!innerNode) + return; + const details = { height: innerNode.clientHeight, width: innerNode.clientWidth }; + this.options.setSize?.(details); + } + init() { + this.animationFrame = null; + this.top = 0; + this.left = 0; + this.transformRuleIndex = null; + this.stylesheet = this.shadow.querySelector("link, style"); + this.stylesheet?.addEventListener("load", () => { + Promise.allSettled([ + document.fonts.load("normal 13px 'DDG_ProximaNova'"), + document.fonts.load("bold 13px 'DDG_ProximaNova'") + ]).then(() => { + this.tooltip.parentNode.removeAttribute("hidden"); + this.checkPosition(); + }); + }); + this.append(); + this.resObs.observe(document.body); + this.mutObs.observe(document.body, { childList: true, subtree: true, attributes: true }); + window.addEventListener("scroll", this, { capture: true }); + this.setSize(); + if (typeof this.options.setSize === "function") { + this.setupSizeListener(); + } + } + }; + var HTMLTooltip_default = HTMLTooltip; -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _autofillUtils = require("../autofill-utils.js"); -var _HTMLTooltip = _interopRequireDefault(require("./HTMLTooltip.js")); -var _Credentials = require("../InputTypes/Credentials.js"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -class DataHTMLTooltip extends _HTMLTooltip.default { - renderEmailProtectionIncontextSignup(isOtherItems) { - const dataTypeClass = `tooltip__button--data--identities`; - const providerIconClass = 'tooltip__button--data--duckduckgo'; - return ` - ${isOtherItems ? '
' : ''} + // src/UI/DataHTMLTooltip.js + var DataHTMLTooltip = class extends HTMLTooltip_default { + renderEmailProtectionIncontextSignup(isOtherItems) { + const dataTypeClass = `tooltip__button--data--identities`; + const providerIconClass = "tooltip__button--data--duckduckgo"; + return ` + ${isOtherItems ? "
" : ""} `; - } - - /** - * @param {InputTypeConfigs} config - * @param {TooltipItemRenderer[]} items - * @param {{ - * onSelect(id:string): void - * onManage(type:InputTypeConfigs['type']): void - * onIncontextSignupDismissed?(data: { - * hasOtherOptions: Boolean - * }): void - * onIncontextSignup?(): void - * }} callbacks - */ - render(config, items, callbacks) { - const { - wrapperClass, - css - } = this.options; - const isTopAutofill = wrapperClass?.includes('top-autofill'); - let hasAddedSeparator = false; - // Only show an hr above the first duck address button, but it can be either personal or private - const shouldShowSeparator = (dataId, index) => { - const shouldShow = ['personalAddress', 'privateAddress'].includes(dataId) && !hasAddedSeparator; - if (shouldShow) hasAddedSeparator = true; - - // Don't show the separator if we want to show it, but it's unnecessary as the first item in the menu - const isFirst = index === 0; - return shouldShow && !isFirst; - }; - - // Only show manage Manage… when it's topAutofill, the provider is unlocked, and it's not just EmailProtection - const shouldShowManageButton = isTopAutofill && items.some(item => !['personalAddress', 'privateAddress', _Credentials.PROVIDER_LOCKED].includes(item.id())); - const topClass = wrapperClass || ''; - const dataTypeClass = `tooltip__button--data--${config.type}${this.variant ? '__' + this.variant : ''}`; - this.shadow.innerHTML = ` + } + /** + * @param {InputTypeConfigs} config + * @param {TooltipItemRenderer[]} items + * @param {{ + * onSelect(id:string): void + * onManage(type:InputTypeConfigs['type']): void + * onIncontextSignupDismissed?(data: { + * hasOtherOptions: Boolean + * }): void + * onIncontextSignup?(): void + * }} callbacks + */ + render(config, items, callbacks) { + const { wrapperClass, css } = this.options; + const isTopAutofill = wrapperClass?.includes("top-autofill"); + let hasAddedSeparator = false; + const shouldShowSeparator = (dataId, index) => { + const shouldShow = ["personalAddress", "privateAddress"].includes(dataId) && !hasAddedSeparator; + if (shouldShow) + hasAddedSeparator = true; + const isFirst = index === 0; + return shouldShow && !isFirst; + }; + const shouldShowManageButton = isTopAutofill && items.some((item) => !["personalAddress", "privateAddress", PROVIDER_LOCKED].includes(item.id())); + const topClass = wrapperClass || ""; + const dataTypeClass = `tooltip__button--data--${config.type}${this.variant ? "__" + this.variant : ""}`; + this.shadow.innerHTML = ` ${css} + +`; + this.wrapper = this.shadow.querySelector(".wrapper"); + this.tooltip = this.shadow.querySelector(".tooltip"); + this.usePersonalButton = this.shadow.querySelector(".js-use-personal"); + this.usePrivateButton = this.shadow.querySelector(".js-use-private"); + this.addressEl = this.shadow.querySelector(".js-address"); + this.updateAddresses = (addresses) => { + if (addresses && this.addressEl) { + this.addresses = addresses; + this.addressEl.textContent = formatDuckAddress(addresses.personalAddress); + } + }; + const firePixel = this.device.firePixel.bind(this.device); + this.registerClickableButton(this.usePersonalButton, () => { + this.fillForm("personalAddress"); + firePixel({ pixelName: "autofill_personal_address" }); + }); + this.registerClickableButton(this.usePrivateButton, () => { + this.fillForm("privateAddress"); + firePixel({ pixelName: "autofill_private_address" }); + }); + this.device.getAddresses().then(this.updateAddresses); + this.init(); + return this; + } + /** + * @param {'personalAddress' | 'privateAddress'} id + */ + async fillForm(id) { + const address = this.addresses[id]; + const formattedAddress = formatDuckAddress(address); + this.device?.selectedDetail({ email: formattedAddress, id }, "email"); + } + }; + var EmailHTMLTooltip_default = EmailHTMLTooltip; -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _HTMLTooltip = _interopRequireDefault(require("./HTMLTooltip.js")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -class EmailSignupHTMLTooltip extends _HTMLTooltip.default { - /** - * @param {import("../DeviceInterface/InterfacePrototype").default} device - */ - render(device) { - this.device = device; - this.shadow.innerHTML = ` + // src/UI/EmailSignupHTMLTooltip.js + var EmailSignupHTMLTooltip = class extends HTMLTooltip_default { + /** + * @param {import("../DeviceInterface/InterfacePrototype").default} device + */ + render(device) { + this.device = device; + this.shadow.innerHTML = ` ${this.options.css} `; - this.tooltip = this.shadow.querySelector('.tooltip'); - this.closeEmailSignup = this.shadow.querySelector('.js-close-email-signup'); - this.registerClickableButton(this.closeEmailSignup, () => { - device.inContextSignup?.onIncontextSignupClosed(); - }); - this.dismissEmailSignup = this.shadow.querySelector('.js-dismiss-email-signup'); - this.registerClickableButton(this.dismissEmailSignup, () => { - device.inContextSignup?.onIncontextSignupDismissed(); - }); - this.getEmailSignup = this.shadow.querySelector('.js-get-email-signup'); - this.registerClickableButton(this.getEmailSignup, () => { - device.inContextSignup?.onIncontextSignup(); - }); - this.init(); - return this; - } -} -var _default = exports.default = EmailSignupHTMLTooltip; - -},{"./HTMLTooltip.js":55}],55:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.defaultOptions = exports.default = exports.HTMLTooltip = void 0; -var _autofillUtils = require("../autofill-utils.js"); -var _matching = require("../Form/matching.js"); -var _styles = require("./styles/styles.js"); -/** - * @typedef {object} HTMLTooltipOptions - * @property {boolean} testMode - * @property {string | null} [wrapperClass] - * @property {(top: number, left: number) => string} [tooltipPositionClass] - * @property {(top: number, left: number, isAboveInput: boolean) => string} [caretPositionClass] - * @property {(details: {height: number, width: number}) => void} [setSize] - if this is set, it will be called initially once + every times the size changes - * @property {() => void} remove - * @property {string} css - * @property {boolean} checkVisibility - * @property {boolean} hasCaret - * @property {() => boolean} isIncontextSignupAvailable - */ - -/** - * @typedef {object} TransformRuleObj - * @property {HTMLTooltipOptions['caretPositionClass']} getRuleString - * @property {number | null} index - */ + this.tooltip = this.shadow.querySelector(".tooltip"); + this.closeEmailSignup = this.shadow.querySelector(".js-close-email-signup"); + this.registerClickableButton(this.closeEmailSignup, () => { + device.inContextSignup?.onIncontextSignupClosed(); + }); + this.dismissEmailSignup = this.shadow.querySelector(".js-dismiss-email-signup"); + this.registerClickableButton(this.dismissEmailSignup, () => { + device.inContextSignup?.onIncontextSignupDismissed(); + }); + this.getEmailSignup = this.shadow.querySelector(".js-get-email-signup"); + this.registerClickableButton(this.getEmailSignup, () => { + device.inContextSignup?.onIncontextSignup(); + }); + this.init(); + return this; + } + }; + var EmailSignupHTMLTooltip_default = EmailSignupHTMLTooltip; -/** @type {HTMLTooltipOptions} */ -const defaultOptions = exports.defaultOptions = { - wrapperClass: '', - tooltipPositionClass: (top, left) => ` - .tooltip { - transform: translate(${Math.floor(left)}px, ${Math.floor(top)}px) !important; - } - `, - caretPositionClass: (top, left, isAboveInput) => ` - .tooltip--email__caret { - ${isAboveInput ? `transform: translate(${Math.floor(left)}px, ${Math.floor(top)}px) rotate(180deg); transform-origin: 18px !important;` : `transform: translate(${Math.floor(left)}px, ${Math.floor(top)}px) !important;`} - }`, - css: ``, - setSize: undefined, - remove: () => {/** noop */}, - testMode: false, - checkVisibility: true, - hasCaret: false, - isIncontextSignupAvailable: () => false -}; -class HTMLTooltip { - isAboveInput = false; - /** @type {HTMLTooltipOptions} */ - options; - /** - * @param config - * @param inputType - * @param getPosition - * @param {HTMLTooltipOptions} options - */ - constructor(config, inputType, getPosition, options) { - this.options = options; - this.shadow = document.createElement('ddg-autofill').attachShadow({ - mode: options.testMode ? 'open' : 'closed' - }); - this.host = this.shadow.host; - this.config = config; - this.subtype = (0, _matching.getSubtypeFromType)(inputType); - this.variant = (0, _matching.getVariantFromType)(inputType); - this.tooltip = null; - this.getPosition = getPosition; - const forcedVisibilityStyles = { - 'display': 'block', - 'visibility': 'visible', - 'opacity': '1' - }; - // @ts-ignore how to narrow this.host to HTMLElement? - (0, _autofillUtils.addInlineStyles)(this.host, forcedVisibilityStyles); - this.count = 0; - this.device = null; + // src/UI/controllers/HTMLTooltipUIController.js + var HTMLTooltipUIController = class extends UIController { /** - * @type {{ - * 'tooltip': TransformRuleObj, - * 'caret': TransformRuleObj - * }} + * @param {HTMLTooltipControllerOptions} options + * @param {Partial} htmlTooltipOptions */ - this.transformRules = { - caret: { - getRuleString: this.options.caretPositionClass, - index: null - }, - tooltip: { - getRuleString: this.options.tooltipPositionClass, - index: null + constructor(options, htmlTooltipOptions = defaultOptions) { + super(); + /** @type {import("../HTMLTooltip.js").HTMLTooltip | null} */ + __publicField(this, "_activeTooltip", null); + /** @type {HTMLTooltipControllerOptions} */ + __publicField(this, "_options"); + /** @type {import('../HTMLTooltip.js').HTMLTooltipOptions} */ + __publicField(this, "_htmlTooltipOptions"); + /** + * Overwritten when calling createTooltip + * @type {import('../../Form/matching').SupportedTypes} + */ + __publicField(this, "_activeInputType", "unknown"); + __publicField(this, "_activeInput"); + __publicField(this, "_activeInputOriginalAutocomplete"); + this._options = options; + this._htmlTooltipOptions = Object.assign({}, defaultOptions, htmlTooltipOptions); + if (options.device.globalConfig.isTopFrame) { + window.addEventListener("pointerup", this, true); + } else { + window.addEventListener("pointerdown", this, true); } - }; - } - get isHidden() { - return this.tooltip.parentNode.hidden; - } - append() { - document.body.appendChild(this.host); - } - remove() { - this.device?.activeForm.resetIconStylesToInitial(); - window.removeEventListener('scroll', this, { - capture: true - }); - this.resObs.disconnect(); - this.mutObs.disconnect(); - this.lift(); - } - lift() { - this.left = null; - this.top = null; - document.body.removeChild(this.host); - } - handleEvent(event) { - switch (event.type) { - case 'scroll': - this.checkPosition(); - break; } - } - focus(x, y) { - const focusableElements = 'button'; - const currentFocusClassName = 'currentFocus'; - const currentFocused = this.shadow.querySelectorAll(`.${currentFocusClassName}`); - [...currentFocused].forEach(el => { - el.classList.remove(currentFocusClassName); - }); - this.shadow.elementFromPoint(x, y)?.closest(focusableElements)?.classList.add(currentFocusClassName); - } - checkPosition() { - if (this.animationFrame) { - window.cancelAnimationFrame(this.animationFrame); + /** + * Cleans up after this UI controller by removing the tooltip and all + * listeners. + */ + destroy() { + this.removeTooltip(); + window.removeEventListener("pointerdown", this, true); + window.removeEventListener("pointerup", this, true); } - this.animationFrame = window.requestAnimationFrame(() => { - if (this.isHidden) return; - const { - left, - bottom, - height, - top - } = this.getPosition(); - if (left !== this.left || bottom !== this.top) { - const coords = { - left, - top: bottom - }; - this.updatePosition('tooltip', coords); - if (this.options.hasCaret) { - // Recalculate tooltip top as it may have changed after update potition above - const { - top: tooltipTop - } = this.tooltip.getBoundingClientRect(); - this.isAboveInput = top > tooltipTop; - const borderWidth = 2; - const caretTop = this.isAboveInput ? coords.top - height - borderWidth : coords.top; - this.updatePosition('caret', { - ...coords, - top: caretTop - }); - } + /** + * @param {import('./UIController').AttachArgs} args + */ + attach(args) { + if (this.getActiveTooltip()) { + return; } - this.animationFrame = null; - }); - } - getOverridePosition(_ref) { - let { - left, - top - } = _ref; - const tooltipBoundingBox = this.tooltip.getBoundingClientRect(); - const smallScreenWidth = tooltipBoundingBox.width * 2; - const spacing = 5; - - // If overflowing from the bottom, move to above the input - if (tooltipBoundingBox.bottom > window.innerHeight) { - const inputPosition = this.getPosition(); - const caretHeight = 14; - const overriddenTopPosition = top - tooltipBoundingBox.height - inputPosition.height - caretHeight; - if (overriddenTopPosition >= 0) return { - left, - top: overriddenTopPosition - }; + const { topContextData, getPosition, input, form } = args; + const tooltip = this.createTooltip(getPosition, topContextData); + this.setActiveTooltip(tooltip); + form.showingTooltip(input); + this._activeInput = input; + this._activeInputOriginalAutocomplete = input.getAttribute("autocomplete"); + input.setAttribute("autocomplete", "off"); } - - // If overflowing from the left on smaller screen, center in the window - if (tooltipBoundingBox.left < 0 && window.innerWidth <= smallScreenWidth) { - const leftOverflow = Math.abs(tooltipBoundingBox.left); - const leftPosWhenCentered = (window.innerWidth - tooltipBoundingBox.width) / 2; - const overriddenLeftPosition = left + leftOverflow + leftPosWhenCentered; - return { - left: overriddenLeftPosition, - top + /** + * Actually create the HTML Tooltip + * @param {PosFn} getPosition + * @param {TopContextData} topContextData + * @return {import("../HTMLTooltip").HTMLTooltip} + */ + createTooltip(getPosition, topContextData) { + this._attachListeners(); + const config = getInputConfigFromType(topContextData.inputType); + this._activeInputType = topContextData.inputType; + const tooltipOptions = { + ...this._htmlTooltipOptions, + remove: () => this.removeTooltip(), + isIncontextSignupAvailable: () => { + const subtype = getSubtypeFromType(topContextData.inputType); + return !!this._options.device.inContextSignup?.isAvailable(subtype); + } }; + if (this._options.tooltipKind === "legacy") { + this._options.device.firePixel({ pixelName: "autofill_show" }); + return new EmailHTMLTooltip_default(config, topContextData.inputType, getPosition, tooltipOptions).render(this._options.device); + } + if (this._options.tooltipKind === "emailsignup") { + this._options.device.firePixel({ pixelName: "incontext_show" }); + return new EmailSignupHTMLTooltip_default(config, topContextData.inputType, getPosition, tooltipOptions).render(this._options.device); + } + const data = this._dataForAutofill(config, topContextData.inputType, topContextData); + const asRenderers = data.map((d) => config.tooltipItem(d)); + return new DataHTMLTooltip_default(config, topContextData.inputType, getPosition, tooltipOptions).render(config, asRenderers, { + onSelect: (id) => { + this._onSelect(topContextData.inputType, data, id); + }, + onManage: (type) => { + this._onManage(type); + }, + onIncontextSignupDismissed: (flags) => { + this._onIncontextSignupDismissed(flags); + }, + onIncontextSignup: () => { + this._onIncontextSignup(); + } + }); } - - // If overflowing from the left on larger screen, move so it's just on screen on the left - if (tooltipBoundingBox.left < 0 && window.innerWidth > smallScreenWidth) { - const leftOverflow = Math.abs(tooltipBoundingBox.left); - const overriddenLeftPosition = left + leftOverflow + spacing; - return { - left: overriddenLeftPosition, - top - }; + updateItems(data) { + if (this._activeInputType === "unknown") + return; + const config = getInputConfigFromType(this._activeInputType); + const asRenderers = data.map((d) => config.tooltipItem(d)); + const activeTooltip = this.getActiveTooltip(); + if (activeTooltip instanceof DataHTMLTooltip_default) { + activeTooltip?.render(config, asRenderers, { + onSelect: (id) => { + this._onSelect(this._activeInputType, data, id); + }, + onManage: (type) => { + this._onManage(type); + }, + onIncontextSignupDismissed: (flags) => { + this._onIncontextSignupDismissed(flags); + }, + onIncontextSignup: () => { + this._onIncontextSignup(); + } + }); + } + setTimeout(() => { + this.getActiveTooltip()?.setSize(); + }, 10); + } + _attachListeners() { + window.addEventListener("input", this); + window.addEventListener("keydown", this, true); + } + _removeListeners() { + window.removeEventListener("input", this); + window.removeEventListener("keydown", this, true); + } + handleEvent(event) { + switch (event.type) { + case "keydown": + if (["Escape", "Tab", "Enter"].includes(event.code)) { + if (event.code === "Escape") { + event.preventDefault(); + event.stopImmediatePropagation(); + } + this.removeTooltip(); + } + break; + case "input": + this.removeTooltip(); + break; + case "pointerdown": { + this._pointerDownListener(event); + break; + } + case "pointerup": { + this._pointerUpListener(event); + break; + } + } } - - // If overflowing from the right, move so it's just on screen on the right - if (tooltipBoundingBox.right > window.innerWidth) { - const rightOverflow = tooltipBoundingBox.right - window.innerWidth; - const overriddenLeftPosition = left - rightOverflow - spacing; - return { - left: overriddenLeftPosition, - top - }; + // Global listener for event delegation + _pointerDownListener(e) { + if (!e.isTrusted) + return; + if (isEventWithinDax(e, e.target)) + return; + if (e.target.nodeName === "DDG-AUTOFILL") { + this._handleClickInTooltip(e); + } else { + this.removeTooltip().catch((e2) => { + console.error("error removing tooltip", e2); + }); + } } - } - - /** - * @param {'tooltip' | 'caret'} element - * @param {{ - * left: number, - * top: number - * }} coords - */ - applyPositionalStyles(element, _ref2) { - let { - left, - top - } = _ref2; - const shadow = this.shadow; - const ruleObj = this.transformRules[element]; - if (ruleObj.index) { - if (shadow.styleSheets[0].rules[ruleObj.index]) { - // If we have already set the rule, remove it… - shadow.styleSheets[0].deleteRule(ruleObj.index); + // Global listener for event delegation + _pointerUpListener(e) { + if (!e.isTrusted) + return; + if (isEventWithinDax(e, e.target)) + return; + if (e.target.nodeName === "DDG-AUTOFILL") { + this._handleClickInTooltip(e); } - } else { - // …otherwise, set the index as the very last rule - ruleObj.index = shadow.styleSheets[0].rules.length; } - const cssRule = ruleObj.getRuleString?.(top, left, this.isAboveInput); - if (typeof cssRule === 'string') { - shadow.styleSheets[0].insertRule(cssRule, ruleObj.index); + _handleClickInTooltip(e) { + e.preventDefault(); + e.stopImmediatePropagation(); + const isMainMouseButton = e.button === 0; + if (!isMainMouseButton) + return; + const activeTooltip = this.getActiveTooltip(); + activeTooltip?.dispatchClick(); + } + async removeTooltip(_via) { + this._htmlTooltipOptions.remove(); + if (this._activeTooltip) { + this._removeListeners(); + this._activeTooltip.remove(); + this._activeTooltip = null; + } + if (this._activeInput) { + if (this._activeInputOriginalAutocomplete) { + this._activeInput.setAttribute("autocomplete", this._activeInputOriginalAutocomplete); + } else { + this._activeInput.removeAttribute("autocomplete"); + } + this._activeInput = null; + this._activeInputOriginalAutocomplete = null; + } + } + /** + * @returns {import("../HTMLTooltip.js").HTMLTooltip|null} + */ + getActiveTooltip() { + return this._activeTooltip; + } + /** + * @param {import("../HTMLTooltip.js").HTMLTooltip} value + */ + setActiveTooltip(value) { + this._activeTooltip = value; } - } + /** + * Collect the data that's needed to populate the Autofill UI. + * + * Note: ideally we'd pass this data instead, so that we didn't have a circular dependency + * + * @param {InputTypeConfigs} config - This is the selected `InputTypeConfig` based on the type of field + * @param {import('../../Form/matching').SupportedTypes} inputType - The input type for the current field + * @param {TopContextData} topContextData + */ + _dataForAutofill(config, inputType, topContextData) { + return this._options.device.dataForAutofill(config, inputType, topContextData); + } + /** + * When a field is selected, call the `onSelect` method from the device. + * + * Note: ideally we'd pass this data instead, so that we didn't have a circular dependency + * + * @param {import('../../Form/matching').SupportedTypes} inputType + * @param {(CreditCardObject | IdentityObject | CredentialsObject)[]} data + * @param {CreditCardObject['id']|IdentityObject['id']|CredentialsObject['id']} id + */ + _onSelect(inputType, data, id) { + return this._options.device.onSelect(inputType, data, id); + } + /** + * Called when clicking on the Manage… button in the html tooltip + * + * @param {SupportedMainTypes} type + * @returns {*} + * @private + */ + _onManage(type) { + this.removeTooltip(); + switch (type) { + case "credentials": + return this._options.device.openManagePasswords(); + case "creditCards": + return this._options.device.openManageCreditCards(); + case "identities": + return this._options.device.openManageIdentities(); + default: + } + } + _onIncontextSignupDismissed({ hasOtherOptions }) { + this._options.device.inContextSignup?.onIncontextSignupDismissed({ shouldHideTooltip: !hasOtherOptions }); + if (hasOtherOptions) { + const topContextData = this._options.device.getTopContextData(); + if (!topContextData) + return; + const config = getInputConfigFromType(topContextData.inputType); + const data = this._dataForAutofill(config, topContextData.inputType, topContextData); + this.updateItems(data); + } + } + _onIncontextSignup() { + this._options.device.inContextSignup?.onIncontextSignup(); + } + isActive() { + return Boolean(this.getActiveTooltip()); + } + }; - /** - * @param {'tooltip' | 'caret'} element - * @param {{ - * left: number, - * top: number - * }} coords - */ - updatePosition(element, _ref3) { - let { - left, - top - } = _ref3; - // If the stylesheet is not loaded wait for load (Chrome bug) - if (!this.shadow.styleSheets.length) { - this.stylesheet?.addEventListener('load', () => this.checkPosition()); - return; + // src/DeviceInterface/ExtensionInterface.js + var TOOLTIP_TYPES = { + EmailProtection: "EmailProtection", + EmailSignup: "EmailSignup" + }; + var ExtensionInterface = class extends InterfacePrototype_default { + constructor() { + super(...arguments); + /** + * Adding this here since only the extension currently supports this + */ + __publicField(this, "inContextSignup", new InContextSignup(this)); } - this.left = left; - this.top = top; - this.applyPositionalStyles(element, { - left, - top - }); - if (this.options.hasCaret) { - const overridePosition = this.getOverridePosition({ - left, - top - }); - if (overridePosition) this.updatePosition(element, overridePosition); + /** + * @override + */ + createUIController() { + const htmlTooltipOptions = { + ...defaultOptions, + css: ``, + testMode: this.isTestMode(), + hasCaret: true + }; + const tooltipKinds = { + [TOOLTIP_TYPES.EmailProtection]: "legacy", + [TOOLTIP_TYPES.EmailSignup]: "emailsignup" + }; + const tooltipKind = tooltipKinds[this.getActiveTooltipType()] || tooltipKinds[TOOLTIP_TYPES.EmailProtection]; + return new HTMLTooltipUIController({ tooltipKind, device: this }, htmlTooltipOptions); } - } - ensureIsLastInDOM() { - this.count = this.count || 0; - // If DDG el is not the last in the doc, move it there - if (document.body.lastElementChild !== this.host) { - // Try up to 15 times to avoid infinite loop in case someone is doing the same - if (this.count < 15) { - this.lift(); - this.append(); - this.checkPosition(); - this.count++; - } else { - // Remove the tooltip from the form to cleanup listeners and observers - this.options.remove(); - console.info(`DDG autofill bailing out`); + getActiveTooltipType() { + if (this.hasLocalAddresses) { + return TOOLTIP_TYPES.EmailProtection; } + const inputType = this.activeForm?.activeInput ? getInputSubtype(this.activeForm.activeInput) : void 0; + if (this.inContextSignup?.isAvailable(inputType)) { + return TOOLTIP_TYPES.EmailSignup; + } + return null; } - } - resObs = new ResizeObserver(entries => entries.forEach(() => this.checkPosition())); - mutObsCheckPositionWhenIdle = _autofillUtils.whenIdle.call(this, this.checkPosition); - mutObs = new MutationObserver(mutationList => { - for (const mutationRecord of mutationList) { - if (mutationRecord.type === 'childList') { - // Only check added nodes - mutationRecord.addedNodes.forEach(el => { - if (el.nodeName === 'DDG-AUTOFILL') return; - this.ensureIsLastInDOM(); - }); + async resetAutofillUI(callback) { + this.removeAutofillUIFromPage("Resetting autofill."); + await this.setupAutofill(); + if (callback) + await callback(); + this.uiController = this.createUIController(); + await this.postInit(); + } + isDeviceSignedIn() { + return this.hasLocalAddresses; + } + async setupAutofill() { + await this.inContextSignup.init(); + return this.getAddresses(); + } + postInit() { + switch (this.getActiveTooltipType()) { + case TOOLTIP_TYPES.EmailProtection: { + this._scannerCleanup = this.scanner.init(); + this.addLogoutListener(() => { + this.resetAutofillUI(); + if (this.globalConfig.isDDGDomain) { + notifyWebApp({ deviceSignedIn: { value: false } }); + } + }); + if (this.activeForm?.activeInput) { + this.attachTooltip({ + form: this.activeForm, + input: this.activeForm?.activeInput, + click: null, + trigger: "postSignup", + triggerMetaData: { + type: "transactional" + } + }); + } + break; + } + case TOOLTIP_TYPES.EmailSignup: { + this._scannerCleanup = this.scanner.init(); + break; + } + default: { + break; + } } } - this.mutObsCheckPositionWhenIdle(); - }); - setActiveButton(e) { - this.activeButton = e.target; - } - unsetActiveButton() { - this.activeButton = null; - } - clickableButtons = new Map(); - registerClickableButton(btn, handler) { - this.clickableButtons.set(btn, handler); - // Needed because clicks within the shadow dom don't provide this info to the outside - btn.addEventListener('mouseenter', e => this.setActiveButton(e)); - btn.addEventListener('mouseleave', () => this.unsetActiveButton()); - } - dispatchClick() { - const handler = this.clickableButtons.get(this.activeButton); - if (handler) { - if (this.activeButton.matches('.wrapper:not(.top-autofill) button:hover, .wrapper:not(.top-autofill) a:hover, .currentFocus')) { - (0, _autofillUtils.safeExecute)(this.activeButton, handler, { - checkVisibility: this.options.checkVisibility - }); - } else { - console.warn('The button doesn\'t seem to be hovered. Please check.'); + getAddresses() { + return new Promise((resolve) => chrome.runtime.sendMessage( + { getAddresses: true }, + (data) => { + this.storeLocalAddresses(data); + return resolve(data); + } + )); + } + /** + * Used by the email web app + * Settings page displays data of the logged in user data + */ + getUserData() { + return new Promise((resolve) => chrome.runtime.sendMessage( + { getUserData: true }, + (data) => resolve(data) + )); + } + /** + * Used by the email web app + * Device capabilities determine which functionality is available to the user + */ + getEmailProtectionCapabilities() { + return new Promise((resolve) => chrome.runtime.sendMessage( + { getEmailProtectionCapabilities: true }, + (data) => resolve(data) + )); + } + refreshAlias() { + return chrome.runtime.sendMessage( + { refreshAlias: true }, + (addresses) => this.storeLocalAddresses(addresses) + ); + } + async trySigningIn() { + if (this.globalConfig.isDDGDomain) { + const data = await sendAndWaitForAnswer(SIGN_IN_MSG, "addUserData"); + this.storeUserData(data); } } - } - setupSizeListener() { - // Listen to layout and paint changes to register the size - const observer = new PerformanceObserver(() => { - this.setSize(); - }); - observer.observe({ - entryTypes: ['layout-shift', 'paint'] - }); - } - setSize() { - const innerNode = this.shadow.querySelector('.wrapper--data'); - // Shouldn't be possible - if (!innerNode) return; - const details = { - height: innerNode.clientHeight, - width: innerNode.clientWidth - }; - this.options.setSize?.(details); - } - init() { - this.animationFrame = null; - this.top = 0; - this.left = 0; - this.transformRuleIndex = null; - this.stylesheet = this.shadow.querySelector('link, style'); - // Un-hide once the style and web fonts have loaded, to avoid flashing - // unstyled content and layout shifts - this.stylesheet?.addEventListener('load', () => { - Promise.allSettled([document.fonts.load("normal 13px 'DDG_ProximaNova'"), document.fonts.load("bold 13px 'DDG_ProximaNova'")]).then(() => { - this.tooltip.parentNode.removeAttribute('hidden'); - this.checkPosition(); - }); - }); - this.append(); - this.resObs.observe(document.body); - this.mutObs.observe(document.body, { - childList: true, - subtree: true, - attributes: true - }); - window.addEventListener('scroll', this, { - capture: true - }); - this.setSize(); - if (typeof this.options.setSize === 'function') { - this.setupSizeListener(); + /** + * @param {object} message + * @param {object} message.addUserData + * @param {string} message.addUserData.token + * @param {string} message.addUserData.userName + * @param {string} message.addUserData.cohort + */ + storeUserData(message) { + return chrome.runtime.sendMessage(message); } - } -} -exports.HTMLTooltip = HTMLTooltip; -var _default = exports.default = HTMLTooltip; - -},{"../Form/matching.js":43,"../autofill-utils.js":62,"./styles/styles.js":61}],56:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.HTMLTooltipUIController = void 0; -var _autofillUtils = require("../../autofill-utils.js"); -var _inputTypeConfig = require("../../Form/inputTypeConfig.js"); -var _matching = require("../../Form/matching.js"); -var _DataHTMLTooltip = _interopRequireDefault(require("../DataHTMLTooltip.js")); -var _EmailHTMLTooltip = _interopRequireDefault(require("../EmailHTMLTooltip.js")); -var _EmailSignupHTMLTooltip = _interopRequireDefault(require("../EmailSignupHTMLTooltip.js")); -var _HTMLTooltip = require("../HTMLTooltip.js"); -var _UIController = require("./UIController.js"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -/** - * @typedef HTMLTooltipControllerOptions - * @property {"modern" | "legacy" | "emailsignup"} tooltipKind - A choice between the newer Autofill UI vs the older ones used in the extension - * @property {import("../../DeviceInterface/InterfacePrototype").default} device - The device interface that's currently running - * regardless of whether this Controller has an open tooltip, or not - */ - -/** - * This encapsulates all the logic relating to showing/hiding the HTML Tooltip - * - * Note: This could be displayed in the current webpage (for example, in the extension) - * or within a webview overlay (like on macOS & upcoming in windows) - */ -class HTMLTooltipUIController extends _UIController.UIController { - /** @type {import("../HTMLTooltip.js").HTMLTooltip | null} */ - _activeTooltip = null; - - /** @type {HTMLTooltipControllerOptions} */ - _options; - - /** @type {import('../HTMLTooltip.js').HTMLTooltipOptions} */ - _htmlTooltipOptions; - - /** - * Overwritten when calling createTooltip - * @type {import('../../Form/matching').SupportedTypes} - */ - _activeInputType = 'unknown'; - - /** - * @param {HTMLTooltipControllerOptions} options - * @param {Partial} htmlTooltipOptions - */ - constructor(options) { - let htmlTooltipOptions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _HTMLTooltip.defaultOptions; - super(); - this._options = options; - this._htmlTooltipOptions = Object.assign({}, _HTMLTooltip.defaultOptions, htmlTooltipOptions); - // Use pointerup to mimic native click behaviour when we're in the top-frame webview - if (options.device.globalConfig.isTopFrame) { - window.addEventListener('pointerup', this, true); - } else { - // Pointerdown is needed here to avoid self-closing modals disappearing because this even happens in the page - window.addEventListener('pointerdown', this, true); + /** + * Used by the email web app + * Provides functionality to log the user out + */ + removeUserData() { + return chrome.runtime.sendMessage({ removeUserData: true }); } - } - _activeInput; - _activeInputOriginalAutocomplete; - - /** - * Cleans up after this UI controller by removing the tooltip and all - * listeners. - */ - destroy() { - this.removeTooltip(); - window.removeEventListener('pointerdown', this, true); - window.removeEventListener('pointerup', this, true); - } - - /** - * @param {import('./UIController').AttachArgs} args - */ - attach(args) { - if (this.getActiveTooltip()) { - return; + addDeviceListeners() { + let activeEl = null; + document.addEventListener("contextmenu", (e) => { + activeEl = e.target; + }); + chrome.runtime.onMessage.addListener((message, sender) => { + if (sender.id !== chrome.runtime.id) + return; + switch (message.type) { + case "ddgUserReady": + this.resetAutofillUI(() => this.setupSettingsPage({ shouldLog: true })); + break; + case "contextualAutofill": + setValue(activeEl, formatDuckAddress(message.alias), this.globalConfig); + activeEl.classList.add("ddg-autofilled"); + this.refreshAlias(); + activeEl.addEventListener( + "input", + (e) => e.target.classList.remove("ddg-autofilled"), + { once: true } + ); + break; + default: + break; + } + }); } - const { - topContextData, - getPosition, - input, - form - } = args; - const tooltip = this.createTooltip(getPosition, topContextData); - this.setActiveTooltip(tooltip); - form.showingTooltip(input); - this._activeInput = input; - this._activeInputOriginalAutocomplete = input.getAttribute('autocomplete'); - input.setAttribute('autocomplete', 'off'); - } - - /** - * Actually create the HTML Tooltip - * @param {PosFn} getPosition - * @param {TopContextData} topContextData - * @return {import("../HTMLTooltip").HTMLTooltip} - */ - createTooltip(getPosition, topContextData) { - this._attachListeners(); - const config = (0, _inputTypeConfig.getInputConfigFromType)(topContextData.inputType); - this._activeInputType = topContextData.inputType; + addLogoutListener(handler) { + if (this._logoutListenerHandler) { + chrome.runtime.onMessage.removeListener(this._logoutListenerHandler); + } + this._logoutListenerHandler = (message, sender) => { + if (sender.id === chrome.runtime.id && message.type === "logout") { + handler(); + } + }; + chrome.runtime.onMessage.addListener(this._logoutListenerHandler); + } + }; + // src/UI/controllers/OverlayUIController.js + var _state; + var OverlayUIController = class extends UIController { + /** + * @param {OverlayControllerOptions} options + */ + constructor(options) { + super(); + /** @type {"idle" | "parentShown"} */ + __privateAdd(this, _state, "idle"); + /** @type {import('../HTMLTooltip.js').HTMLTooltip | null} */ + __publicField(this, "_activeTooltip", null); + /** + * @type {OverlayControllerOptions} + */ + __publicField(this, "_options"); + this._options = options; + window.addEventListener("pointerdown", this, true); + } /** - * @type {import('../HTMLTooltip').HTMLTooltipOptions} + * @param {import('./UIController').AttachArgs} args */ - const tooltipOptions = { - ...this._htmlTooltipOptions, - remove: () => this.removeTooltip(), - isIncontextSignupAvailable: () => { - const subtype = (0, _matching.getSubtypeFromType)(topContextData.inputType); - return !!this._options.device.inContextSignup?.isAvailable(subtype); + attach(args) { + const { getPosition, topContextData, click, input } = args; + if (!input.parentNode) + return; + this._mutObs = new MutationObserver((mutationList) => { + for (const mutationRecord of mutationList) { + mutationRecord.removedNodes.forEach((el) => { + if (el.contains(input)) { + this.removeTooltip("mutation observer"); + } + }); + } + }); + this._mutObs.observe(document.body, { childList: true, subtree: true }); + const position = getPosition(); + if (!click && !this.elementIsInViewport(position)) { + input.scrollIntoView(true); + this._mutObs?.disconnect(); + setTimeout(() => { + this.attach(args); + }, 50); + return; } - }; - if (this._options.tooltipKind === 'legacy') { - this._options.device.firePixel({ - pixelName: 'autofill_show' + __privateSet(this, _state, "parentShown"); + this.showTopTooltip(click, position, topContextData).catch((e) => { + console.error("error from showTopTooltip", e); + __privateSet(this, _state, "idle"); }); - return new _EmailHTMLTooltip.default(config, topContextData.inputType, getPosition, tooltipOptions).render(this._options.device); } - if (this._options.tooltipKind === 'emailsignup') { - this._options.device.firePixel({ - pixelName: 'incontext_show' - }); - return new _EmailSignupHTMLTooltip.default(config, topContextData.inputType, getPosition, tooltipOptions).render(this._options.device); + /** + * @param {{ x: number; y: number; height: number; width: number; }} inputDimensions + * @returns {boolean} + */ + elementIsInViewport(inputDimensions) { + if (inputDimensions.x < 0 || inputDimensions.y < 0 || inputDimensions.x + inputDimensions.width > document.documentElement.clientWidth || inputDimensions.y + inputDimensions.height > document.documentElement.clientHeight) { + return false; + } + const viewport = document.documentElement; + if (inputDimensions.x + inputDimensions.width > viewport.clientWidth || inputDimensions.y + inputDimensions.height > viewport.clientHeight) { + return false; + } + return true; } - - // collect the data for each item to display - const data = this._dataForAutofill(config, topContextData.inputType, topContextData); - - // convert the data into tool tip item renderers - const asRenderers = data.map(d => config.tooltipItem(d)); - - // construct the autofill - return new _DataHTMLTooltip.default(config, topContextData.inputType, getPosition, tooltipOptions).render(config, asRenderers, { - onSelect: id => { - this._onSelect(topContextData.inputType, data, id); - }, - onManage: type => { - this._onManage(type); - }, - onIncontextSignupDismissed: flags => { - this._onIncontextSignupDismissed(flags); - }, - onIncontextSignup: () => { - this._onIncontextSignup(); + /** + * @param {{ x: number; y: number; } | null} click + * @param {{ x: number; y: number; height: number; width: number; }} inputDimensions + * @param {TopContextData} data + */ + async showTopTooltip(click, inputDimensions, data) { + let diffX = inputDimensions.x; + let diffY = inputDimensions.y; + if (click) { + diffX -= click.x; + diffY -= click.y; + } else if (!this.elementIsInViewport(inputDimensions)) { + return; } - }); - } - updateItems(data) { - if (this._activeInputType === 'unknown') return; - const config = (0, _inputTypeConfig.getInputConfigFromType)(this._activeInputType); - - // convert the data into tool tip item renderers - const asRenderers = data.map(d => config.tooltipItem(d)); - const activeTooltip = this.getActiveTooltip(); - if (activeTooltip instanceof _DataHTMLTooltip.default) { - activeTooltip?.render(config, asRenderers, { - onSelect: id => { - this._onSelect(this._activeInputType, data, id); - }, - onManage: type => { - this._onManage(type); - }, - onIncontextSignupDismissed: flags => { - this._onIncontextSignupDismissed(flags); - }, - onIncontextSignup: () => { - this._onIncontextSignup(); + if (!data.inputType) { + throw new Error("No input type found"); + } + const mainType = getMainTypeFromType(data.inputType); + const subType = getSubtypeFromType(data.inputType); + if (mainType === "unknown") { + throw new Error('unreachable, should not be here if (mainType === "unknown")'); + } + const details = { + inputType: data.inputType, + mainType, + subType, + serializedInputContext: JSON.stringify(data), + triggerContext: { + wasFromClick: Boolean(click), + inputTop: Math.floor(diffY), + inputLeft: Math.floor(diffX), + inputHeight: Math.floor(inputDimensions.height), + inputWidth: Math.floor(inputDimensions.width) } - }); + }; + try { + __privateSet(this, _state, "parentShown"); + this._attachListeners(); + await this._options.show(details); + } catch (e) { + console.error("could not show parent", e); + __privateSet(this, _state, "idle"); + } } - // TODO: can we remove this timeout once implemented with real APIs? - // The timeout is needed because clientHeight and clientWidth were returning 0 - setTimeout(() => { - this.getActiveTooltip()?.setSize(); - }, 10); - } - _attachListeners() { - window.addEventListener('input', this); - window.addEventListener('keydown', this, true); - } - _removeListeners() { - window.removeEventListener('input', this); - window.removeEventListener('keydown', this, true); - } - handleEvent(event) { - switch (event.type) { - case 'keydown': - if (['Escape', 'Tab', 'Enter'].includes(event.code)) { - if (event.code === 'Escape') { - event.preventDefault(); - event.stopImmediatePropagation(); + _attachListeners() { + window.addEventListener("scroll", this); + window.addEventListener("keydown", this, true); + window.addEventListener("input", this); + } + _removeListeners() { + window.removeEventListener("scroll", this); + window.removeEventListener("keydown", this, true); + window.removeEventListener("input", this); + } + handleEvent(event) { + switch (event.type) { + case "scroll": { + this.removeTooltip(event.type); + break; + } + case "keydown": { + if (["Escape", "Tab", "Enter"].includes(event.code)) { + if (event.code === "Escape") { + event.preventDefault(); + event.stopImmediatePropagation(); + } + this.removeTooltip(event.type); } - this.removeTooltip(); + break; } - break; - case 'input': - this.removeTooltip(); - break; - case 'pointerdown': - { - this._pointerDownListener(event); + case "input": { + this.removeTooltip(event.type); break; } - case 'pointerup': - { - this._pointerUpListener(event); + case "pointerdown": { + this.removeTooltip(event.type); break; } + } } - } - - // Global listener for event delegation - _pointerDownListener(e) { - if (!e.isTrusted) return; - // Ignore events on the Dax icon, we handle those elsewhere - if ((0, _autofillUtils.isEventWithinDax)(e, e.target)) return; - - // @ts-ignore - if (e.target.nodeName === 'DDG-AUTOFILL') { - this._handleClickInTooltip(e); - } else { - this.removeTooltip().catch(e => { - console.error('error removing tooltip', e); - }); - } - } - - // Global listener for event delegation - _pointerUpListener(e) { - if (!e.isTrusted) return; - // Ignore events on the Dax icon, we handle those elsewhere - if ((0, _autofillUtils.isEventWithinDax)(e, e.target)) return; - - // @ts-ignore - if (e.target.nodeName === 'DDG-AUTOFILL') { - this._handleClickInTooltip(e); - } - } - _handleClickInTooltip(e) { - e.preventDefault(); - e.stopImmediatePropagation(); - const isMainMouseButton = e.button === 0; - if (!isMainMouseButton) return; - const activeTooltip = this.getActiveTooltip(); - activeTooltip?.dispatchClick(); - } - async removeTooltip(_via) { - this._htmlTooltipOptions.remove(); - if (this._activeTooltip) { + /** + * @param {string} trigger + * @returns {Promise} + */ + async removeTooltip(trigger) { + if (trigger !== "pointerdown") { + if (__privateGet(this, _state) !== "parentShown") { + return; + } + } + try { + await this._options.remove(); + } catch (e) { + console.error("Could not close parent", e); + } + __privateSet(this, _state, "idle"); this._removeListeners(); - this._activeTooltip.remove(); - this._activeTooltip = null; + this._mutObs?.disconnect(); } - if (this._activeInput) { - if (this._activeInputOriginalAutocomplete) { - this._activeInput.setAttribute('autocomplete', this._activeInputOriginalAutocomplete); - } else { - this._activeInput.removeAttribute('autocomplete'); - } - this._activeInput = null; - this._activeInputOriginalAutocomplete = null; + isActive() { + return __privateGet(this, _state) === "parentShown"; } - } - - /** - * @returns {import("../HTMLTooltip.js").HTMLTooltip|null} - */ - getActiveTooltip() { - return this._activeTooltip; - } - - /** - * @param {import("../HTMLTooltip.js").HTMLTooltip} value - */ - setActiveTooltip(value) { - this._activeTooltip = value; - } - - /** - * Collect the data that's needed to populate the Autofill UI. - * - * Note: ideally we'd pass this data instead, so that we didn't have a circular dependency - * - * @param {InputTypeConfigs} config - This is the selected `InputTypeConfig` based on the type of field - * @param {import('../../Form/matching').SupportedTypes} inputType - The input type for the current field - * @param {TopContextData} topContextData - */ - _dataForAutofill(config, inputType, topContextData) { - return this._options.device.dataForAutofill(config, inputType, topContextData); - } - - /** - * When a field is selected, call the `onSelect` method from the device. - * - * Note: ideally we'd pass this data instead, so that we didn't have a circular dependency - * - * @param {import('../../Form/matching').SupportedTypes} inputType - * @param {(CreditCardObject | IdentityObject | CredentialsObject)[]} data - * @param {CreditCardObject['id']|IdentityObject['id']|CredentialsObject['id']} id - */ - _onSelect(inputType, data, id) { - return this._options.device.onSelect(inputType, data, id); - } + }; + _state = new WeakMap(); - /** - * Called when clicking on the Manage… button in the html tooltip - * - * @param {SupportedMainTypes} type - * @returns {*} - * @private - */ - _onManage(type) { - this.removeTooltip(); - switch (type) { - case 'credentials': - return this._options.device.openManagePasswords(); - case 'creditCards': - return this._options.device.openManageCreditCards(); - case 'identities': - return this._options.device.openManageIdentities(); - default: - // noop + // src/deviceApiCalls/additionalDeviceApiCalls.js + var GetAlias = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "emailHandlerGetAlias"); + __publicField(this, "id", "n/a"); + __publicField(this, "paramsValidator", getAliasParamsSchema); + __publicField(this, "resultValidator", getAliasResultSchema); } - } - - _onIncontextSignupDismissed(_ref) { - let { - hasOtherOptions - } = _ref; - this._options.device.inContextSignup?.onIncontextSignupDismissed({ - shouldHideTooltip: !hasOtherOptions - }); - - // If there are other options available, just force a re-render - if (hasOtherOptions) { - const topContextData = this._options.device.getTopContextData(); - if (!topContextData) return; - const config = (0, _inputTypeConfig.getInputConfigFromType)(topContextData.inputType); - const data = this._dataForAutofill(config, topContextData.inputType, topContextData); - this.updateItems(data); + preResultValidation(response) { + return { success: response }; } - } - _onIncontextSignup() { - this._options.device.inContextSignup?.onIncontextSignup(); - } - isActive() { - return Boolean(this.getActiveTooltip()); - } -} -exports.HTMLTooltipUIController = HTMLTooltipUIController; - -},{"../../Form/inputTypeConfig.js":38,"../../Form/matching.js":43,"../../autofill-utils.js":62,"../DataHTMLTooltip.js":52,"../EmailHTMLTooltip.js":53,"../EmailSignupHTMLTooltip.js":54,"../HTMLTooltip.js":55,"./UIController.js":59}],57:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.NativeUIController = void 0; -var _UIController = require("./UIController.js"); -var _matching = require("../../Form/matching.js"); -var _deviceApiCalls = require("../../deviceApiCalls/__generated__/deviceApiCalls.js"); -var _Credentials = require("../../InputTypes/Credentials.js"); -/** - * `NativeController` should be used in situations where you DO NOT - * want any Autofill-controlled user interface. - * - * Examples are with iOS/Android, where 'attaching' only means - * messaging a native layer to show a native tooltip. - * - * @example - * - * ```javascript - * const controller = new NativeController(); - * controller.attach(...); - * ``` - */ -class NativeUIController extends _UIController.UIController { - /** - * Keep track of when passwords were suggested/rejected/accepted etc - * State is kept here because it's specific to the interactions on mobile (eg: NativeUIController) - * - * @type {"default" | "rejected"} - */ - #passwordStatus = 'default'; + }; - /** - * @param {import('./UIController').AttachArgs} args - */ - attach(args) { - const { - form, - input, - device, - trigger, - triggerMetaData, - topContextData - } = args; - const inputType = (0, _matching.getInputType)(input); - const mainType = (0, _matching.getMainTypeFromType)(inputType); - const subType = (0, _matching.getSubtypeFromType)(inputType); - if (mainType === 'unknown') { - throw new Error('unreachable, should not be here if (mainType === "unknown")'); - } - if (trigger === 'autoprompt') { - window.scrollTo({ - behavior: 'smooth', - top: form.form.getBoundingClientRect().top - document.body.getBoundingClientRect().top - 50 - }); + // src/ThirdPartyProvider.js + var ThirdPartyProvider = class { + /** + * @param {import("./DeviceInterface/InterfacePrototype").default} device + */ + constructor(device) { + this.device = device; + } + init() { + if (this.device.settings.featureToggles.third_party_credentials_provider) { + if (this.device.globalConfig.hasModernWebkitAPI) { + Object.defineProperty(window, "providerStatusUpdated", { + enumerable: false, + configurable: false, + writable: false, + value: (data) => { + this.providerStatusUpdated(data); + } + }); + } else { + setTimeout(() => this._pollForUpdatesToCredentialsProvider(), 2e3); + } + } } - - /** @type {import('../../deviceApiCalls/__generated__/validators-ts').GetAutofillDataRequest} */ - let payload = { - inputType, - mainType, - subType, - trigger - }; - - // append generated password if enabled - if (device.settings.featureToggles.password_generation) { - payload = this.appendGeneratedPassword(topContextData, payload, triggerMetaData); + async askToUnlockProvider() { + const response = await this.device.deviceApi.request(new AskToUnlockProviderCall(null)); + this.providerStatusUpdated(response); } - device.deviceApi.request(new _deviceApiCalls.GetAutofillDataCall(payload)).then(resp => { - switch (resp.action) { - case 'fill': - { - if (mainType in resp) { - form.autofillData(resp[mainType], mainType); - } else { - throw new Error(`action: "fill" cannot occur because "${mainType}" was missing`); - } - break; - } - case 'focus': - { - form.activeInput?.focus(); - break; - } - case 'acceptGeneratedPassword': - { - form.autofillData({ - password: topContextData.credentials?.[0].password, - [_Credentials.AUTOGENERATED_KEY]: true - }, mainType); - break; - } - case 'rejectGeneratedPassword': - { - this.#passwordStatus = 'rejected'; - form.touchAllInputs('credentials'); - form.activeInput?.focus(); - break; - } - default: - { - if (args.device.isTestMode()) { - console.warn('response not handled', resp); - } + /** + * Called by the native layer on all tabs when the provider status is updated + * @param {import("./deviceApiCalls/__generated__/validators-ts").ProviderStatusUpdated} data + */ + providerStatusUpdated(data) { + try { + const { credentials, availableInputTypes } = validate(data, providerStatusUpdatedSchema); + this.device.settings.setAvailableInputTypes(availableInputTypes); + this.device.storeLocalCredentials(credentials); + this.device.uiController?.updateItems(credentials); + if (!this.device.globalConfig.isTopFrame) { + const currentInputSubtype = getSubtypeFromType(this.device.getCurrentInputType()); + if (!availableInputTypes.credentials?.[currentInputSubtype]) { + this.device.removeTooltip(); } + this.device.scanner.forms.forEach((form) => form.recategorizeAllInputs()); + } + } catch (e) { + if (this.device.globalConfig.isDDGTestMode) { + console.log("isDDGTestMode: providerStatusUpdated error: \u274C", e); + } } - }).catch(e => { - console.error('NativeTooltip::device.getAutofillData(payload)'); - console.error(e); - }); - } - - /** - * If a password exists in `topContextData`, we can append it to the outgoing data - * in a way that native platforms can easily understand. - * - * @param {TopContextData} topContextData - * @param {import('../../deviceApiCalls/__generated__/validators-ts.js').GetAutofillDataRequest} outgoingData - * @param {import('../../UI/controllers/UIController.js').AttachArgs['triggerMetaData']} triggerMetaData - * @return {import('../../deviceApiCalls/__generated__/validators-ts.js').GetAutofillDataRequest} - */ - appendGeneratedPassword(topContextData, outgoingData, triggerMetaData) { - const autoGeneratedCredential = topContextData.credentials?.find(credential => credential.autogenerated); - - // if there's no generated password, we don't need to do anything - if (!autoGeneratedCredential?.password) { - return outgoingData; } - function suggestPassword() { - if (!autoGeneratedCredential?.password) throw new Error('unreachable'); - return { - ...outgoingData, - generatedPassword: { - value: autoGeneratedCredential.password, - username: autoGeneratedCredential.username + // Only used on Catalina + async _pollForUpdatesToCredentialsProvider() { + try { + const response = await this.device.deviceApi.request(new CheckCredentialsProviderStatusCall(null)); + if (response.availableInputTypes.credentialsProviderStatus !== this.device.settings.availableInputTypes.credentialsProviderStatus) { + this.providerStatusUpdated(response); + } + setTimeout(() => this._pollForUpdatesToCredentialsProvider(), 2e3); + } catch (e) { + if (this.device.globalConfig.isDDGTestMode) { + console.log("isDDGTestMode: _pollForUpdatesToCredentialsProvider: \u274C", e); } - }; - } - - // for explicit opt-in, we should *always* append the password - // this can occur when the user clicks icon directly - in that instance we ignore - // any internal state and just append the password to the outgoing data - if (triggerMetaData.type === 'explicit-opt-in') { - return suggestPassword(); - } - - // When the opt-in is 'implicit' though we only append the password if the user has not previously rejected it. - // This helps the situation where the user has rejected a password for the username field, but then - // taps into the confirm password field - if (triggerMetaData.type === 'implicit-opt-in' && this.#passwordStatus !== 'rejected') { - return suggestPassword(); - } - - // if we get here there's nothing to do - return outgoingData; - } -} -exports.NativeUIController = NativeUIController; - -},{"../../Form/matching.js":43,"../../InputTypes/Credentials.js":45,"../../deviceApiCalls/__generated__/deviceApiCalls.js":66,"./UIController.js":59}],58:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.OverlayUIController = void 0; -var _UIController = require("./UIController.js"); -var _matching = require("../../Form/matching.js"); -/** - * @typedef {import('../../deviceApiCalls/__generated__/validators-ts').GetAutofillDataRequest} GetAutofillDataRequest - * @typedef {import('../../deviceApiCalls/__generated__/validators-ts').TriggerContext} TriggerContext - * - * @typedef OverlayControllerOptions - * @property {() => Promise} remove - A callback that will be fired when the tooltip should be removed - * @property {(details: GetAutofillDataRequest) => Promise} show - A callback that will be fired when the tooltip should be shown - */ - -/** - * Use this `OverlayController` when you want to control an overlay, but don't have - * your own UI to display. - * - * For example, on macOS this `OverlayController` would run in the main webpage - * and would then signal to its native side when the overlay should show/close - * - * @example `show` and `remove` can be implemented to match your native side's messaging needs - * - * ```javascript - * const controller = new OverlayController({ - * remove: async () => this.closeAutofillParent(), - * show: async (details) => this.show(details), - * onPointerDown: (e) => this.onPointerDown(e) - * }) - * - * controller.attach(...) - * ``` - */ -class OverlayUIController extends _UIController.UIController { - /** @type {"idle" | "parentShown"} */ - #state = 'idle'; - - /** @type {import('../HTMLTooltip.js').HTMLTooltip | null} */ - _activeTooltip = null; - - /** - * @type {OverlayControllerOptions} - */ - _options; - - /** - * @param {OverlayControllerOptions} options - */ - constructor(options) { - super(); - this._options = options; - - // We always register this 'pointerdown' event, regardless of - // whether we have a tooltip currently open or not. This is to ensure - // we can clear out any existing state before opening a new one. - window.addEventListener('pointerdown', this, true); - } - - /** - * @param {import('./UIController').AttachArgs} args - */ - attach(args) { - const { - getPosition, - topContextData, - click, - input - } = args; - - // Do not attach the tooltip if the input is not in the DOM - if (!input.parentNode) return; - - // If the input is removed from the DOM while the tooltip is attached, remove it - this._mutObs = new MutationObserver(mutationList => { - for (const mutationRecord of mutationList) { - mutationRecord.removedNodes.forEach(el => { - if (el.contains(input)) { - this.removeTooltip('mutation observer'); - } - }); } - }); - this._mutObs.observe(document.body, { - childList: true, - subtree: true - }); - const position = getPosition(); - - // If the element is not in viewport, scroll there and recurse. 50ms is arbitrary - if (!click && !this.elementIsInViewport(position)) { - input.scrollIntoView(true); - this._mutObs?.disconnect(); - setTimeout(() => { - this.attach(args); - }, 50); - return; } - this.#state = 'parentShown'; - this.showTopTooltip(click, position, topContextData).catch(e => { - console.error('error from showTopTooltip', e); - this.#state = 'idle'; - }); - } + }; - /** - * @param {{ x: number; y: number; height: number; width: number; }} inputDimensions - * @returns {boolean} - */ - elementIsInViewport(inputDimensions) { - if (inputDimensions.x < 0 || inputDimensions.y < 0 || inputDimensions.x + inputDimensions.width > document.documentElement.clientWidth || inputDimensions.y + inputDimensions.height > document.documentElement.clientHeight) { - return false; + // src/DeviceInterface/AppleDeviceInterface.js + var AppleDeviceInterface = class extends InterfacePrototype_default { + constructor() { + super(...arguments); + __publicField(this, "inContextSignup", new InContextSignup(this)); + /** @override */ + __publicField(this, "initialSetupDelayMs", 300); + __publicField(this, "thirdPartyProvider", new ThirdPartyProvider(this)); + /** @type {any} */ + __publicField(this, "pollingTimeout", null); } - const viewport = document.documentElement; - if (inputDimensions.x + inputDimensions.width > viewport.clientWidth || inputDimensions.y + inputDimensions.height > viewport.clientHeight) { - return false; + /** + * The default functionality of this class is to operate as an 'overlay controller' - + * which means it's purpose is to message the native layer about when to open/close the overlay. + * + * There is an additional use-case though, when running on older macOS versions, we just display the + * HTMLTooltip in-page (like the extension does). This is why the `!this.globalConfig.supportsTopFrame` + * check exists below - if we know we don't support the overlay, we fall back to in-page. + * + * @override + * @returns {import("../UI/controllers/UIController.js").UIController} + */ + createUIController() { + if (this.globalConfig.userPreferences?.platform?.name === "ios") { + return new NativeUIController(); + } + if (!this.globalConfig.supportsTopFrame) { + const options = { + ...defaultOptions, + testMode: this.isTestMode() + }; + return new HTMLTooltipUIController({ + device: this, + tooltipKind: "modern" + }, options); + } + return new OverlayUIController({ + remove: async () => this._closeAutofillParent(), + show: async (details) => this._show(details) + }); } - return true; - } - - /** - * @param {{ x: number; y: number; } | null} click - * @param {{ x: number; y: number; height: number; width: number; }} inputDimensions - * @param {TopContextData} data - */ - async showTopTooltip(click, inputDimensions, data) { - let diffX = inputDimensions.x; - let diffY = inputDimensions.y; - if (click) { - diffX -= click.x; - diffY -= click.y; - } else if (!this.elementIsInViewport(inputDimensions)) { - // If the focus event is outside the viewport ignore, we've already tried to scroll to it - return; + /** + * For now, this could be running + * 1) on iOS + * 2) on macOS + Overlay + * 3) on macOS + in-page HTMLTooltip + * + * @override + * @returns {Promise} + */ + async setupAutofill() { + if (!this.globalConfig.supportsTopFrame) { + await this._getAutofillInitData(); + } + await this.inContextSignup.init(); + const signedIn = await this._checkDeviceSignedIn(); + if (signedIn) { + if (this.globalConfig.isApp) { + await this.getAddresses(); + } + } } - if (!data.inputType) { - throw new Error('No input type found'); + /** + * Used by the email web app + * Settings page displays data of the logged in user data + */ + getUserData() { + return this.deviceApi.request(createRequest("emailHandlerGetUserData")); } - const mainType = (0, _matching.getMainTypeFromType)(data.inputType); - const subType = (0, _matching.getSubtypeFromType)(data.inputType); - if (mainType === 'unknown') { - throw new Error('unreachable, should not be here if (mainType === "unknown")'); + /** + * Used by the email web app + * Device capabilities determine which functionality is available to the user + */ + getEmailProtectionCapabilities() { + return this.deviceApi.request(createRequest("emailHandlerGetCapabilities")); } - - /** @type {GetAutofillDataRequest} */ - const details = { - inputType: data.inputType, - mainType, - subType, - serializedInputContext: JSON.stringify(data), - triggerContext: { - wasFromClick: Boolean(click), - inputTop: Math.floor(diffY), - inputLeft: Math.floor(diffX), - inputHeight: Math.floor(inputDimensions.height), - inputWidth: Math.floor(inputDimensions.width) - } - }; - try { - this.#state = 'parentShown'; - this._attachListeners(); - await this._options.show(details); - } catch (e) { - console.error('could not show parent', e); - this.#state = 'idle'; + /** + */ + async getSelectedCredentials() { + return this.deviceApi.request(createRequest("getSelectedCredentials")); } - } - _attachListeners() { - window.addEventListener('scroll', this); - window.addEventListener('keydown', this, true); - window.addEventListener('input', this); - } - _removeListeners() { - window.removeEventListener('scroll', this); - window.removeEventListener('keydown', this, true); - window.removeEventListener('input', this); - } - handleEvent(event) { - switch (event.type) { - case 'scroll': - { - this.removeTooltip(event.type); - break; - } - case 'keydown': - { - if (['Escape', 'Tab', 'Enter'].includes(event.code)) { - if (event.code === 'Escape') { - event.preventDefault(); - event.stopImmediatePropagation(); - } - this.removeTooltip(event.type); - } - break; - } - case 'input': - { - this.removeTooltip(event.type); - break; - } - case 'pointerdown': - { - this.removeTooltip(event.type); - break; + /** + * The data format provided here for `parentArgs` matches Window now. + * @param {GetAutofillDataRequest} parentArgs + */ + async _showAutofillParent(parentArgs) { + const applePayload = { + ...parentArgs.triggerContext, + serializedInputContext: parentArgs.serializedInputContext + }; + return this.deviceApi.notify(createNotification("showAutofillParent", applePayload)); + } + /** + * @returns {Promise} + */ + async _closeAutofillParent() { + return this.deviceApi.notify(createNotification("closeAutofillParent", {})); + } + /** + * @param {GetAutofillDataRequest} details + */ + async _show(details) { + await this._showAutofillParent(details); + this._listenForSelectedCredential(async (response) => { + if (!response) + return; + if ("configType" in response) { + this.selectedDetail(response.data, response.configType); + } else if ("stop" in response) { + await this.onFinishedAutofill(); + } else if ("stateChange" in response) { + await this.updateForStateChange(); } + }); } - } - - /** - * @param {string} trigger - * @returns {Promise} - */ - async removeTooltip(trigger) { - // for none pointer events, check to see if the tooltip is open before trying to close it - if (trigger !== 'pointerdown') { - if (this.#state !== 'parentShown') { - return; - } + async refreshData() { + await super.refreshData(); + await this._checkDeviceSignedIn(); } - try { - await this._options.remove(); - } catch (e) { - console.error('Could not close parent', e); + async getAddresses() { + if (!this.globalConfig.isApp) + return this.getAlias(); + const { addresses } = await this.deviceApi.request(createRequest("emailHandlerGetAddresses")); + this.storeLocalAddresses(addresses); + return addresses; } - this.#state = 'idle'; - this._removeListeners(); - this._mutObs?.disconnect(); - } - isActive() { - return this.#state === 'parentShown'; - } -} -exports.OverlayUIController = OverlayUIController; - -},{"../../Form/matching.js":43,"./UIController.js":59}],59:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.UIController = void 0; -/** - * @typedef AttachArgs The argument required to 'attach' a tooltip - * @property {import("../../Form/Form").Form} form the Form that triggered this 'attach' call - * @property {HTMLInputElement} input the input field that triggered this 'attach' call - * @property {() => { x: number; y: number; height: number; width: number; }} getPosition A function that provides positioning information - * @property {{x: number, y: number}|null} click The click positioning - * @property {TopContextData} topContextData - * @property {import("../../DeviceInterface/InterfacePrototype").default} device - * @property {import('../../deviceApiCalls/__generated__/validators-ts').GetAutofillDataRequest['trigger']} trigger - * @property {{type: 'explicit-opt-in' | 'implicit-opt-in' | 'transactional'}} triggerMetaData - metadata about the trigger, used to make client-side decisions - */ - -/** - * This is the base interface that `UIControllers` should extend/implement - */ -class UIController { - /** - * Implement this method to control what happen when Autofill - * has enough information to 'attach' a tooltip. - * - * @param {AttachArgs} _args - * @returns {void} - */ - attach(_args) { - throw new Error('must implement attach'); - } - /** - * Implement this if your tooltip can be created from positioning - * + topContextData. - * - * For example, in an 'overlay' on macOS/Windows this is needed since - * there's no page information to call 'attach' above. - * - * @param {PosFn} _pos - * @param {TopContextData} _topContextData - * @returns {any | null} - */ - createTooltip(_pos, _topContextData) {} - /** - * @param {string} _via - */ - removeTooltip(_via) {} - - /** - * Set the currently open HTMLTooltip instance - * - * @param {import("../HTMLTooltip.js").HTMLTooltip} _tooltip - */ - setActiveTooltip(_tooltip) {} - - /** - * Get the currently open HTMLTooltip instance, if one exists - * - * @returns {import("../HTMLTooltip.js").HTMLTooltip | null} - */ - getActiveTooltip() { - return null; - } - - /** - * Indicate whether the controller deems itself 'active' - * - * @returns {boolean} - */ - isActive() { - return false; - } - - /** - * Updates the items in the tooltip based on new data. Currently only supporting credentials. - * @param {CredentialsObject[]} _data - */ - updateItems(_data) {} - destroy() {} -} -exports.UIController = UIController; - -},{}],60:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.ddgPasswordIconFocused = exports.ddgPasswordIconFilled = exports.ddgPasswordIconBaseWhite = exports.ddgPasswordIconBase = exports.ddgPasswordGenIconFilled = exports.ddgPasswordGenIconBase = exports.ddgIdentityIconBase = exports.ddgCcIconFilled = exports.ddgCcIconBase = void 0; -const ddgPasswordIconBase = exports.ddgPasswordIconBase = 'data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNS4zMzQgNi42NjdhMiAyIDAgMSAwIDAgNCAyIDIgMCAwIDAgMC00Wm0tLjY2NyAyYS42NjcuNjY3IDAgMSAxIDEuMzMzIDAgLjY2Ny42NjcgMCAwIDEtMS4zMzMgMFoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTQuNjY3IDRhNS4zMzMgNS4zMzMgMCAwIDAtNS4xODggNi41NzhsLTUuMjg0IDUuMjg0YS42NjcuNjY3IDAgMCAwLS4xOTUuNDcxdjNjMCAuMzY5LjI5OC42NjcuNjY3LjY2N2gyLjY2NmMuNzM3IDAgMS4zMzQtLjU5NyAxLjMzNC0xLjMzM1YxOGguNjY2Yy43MzcgMCAxLjMzNC0uNTk3IDEuMzM0LTEuMzMzdi0xLjMzNEgxMmMuMTc3IDAgLjM0Ni0uMDcuNDcxLS4xOTVsLjY4OC0uNjg4QTUuMzMzIDUuMzMzIDAgMSAwIDE0LjY2NyA0Wm0tNCA1LjMzM2E0IDQgMCAxIDEgMi41NTUgMy43MzIuNjY3LjY2NyAwIDAgMC0uNzEzLjE1bC0uNzg1Ljc4NUgxMGEuNjY3LjY2NyAwIDAgMC0uNjY3LjY2N3YySDhhLjY2Ny42NjcgMCAwIDAtLjY2Ny42NjZ2MS4zMzRoLTJ2LTIuMDU4bDUuMzY1LTUuMzY0YS42NjcuNjY3IDAgMCAwIC4xNjMtLjY3NyAzLjk5NiAzLjk5NiAwIDAgMS0uMTk0LTEuMjM1WiIgY2xpcC1ydWxlPSJldmVub2RkIi8+Cjwvc3ZnPgo='; -const ddgPasswordIconBaseWhite = exports.ddgPasswordIconBaseWhite = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMjRweCIgaGVpZ2h0PSIyNHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8dGl0bGU+ZGRnLXBhc3N3b3JkLWljb24tYmFzZS13aGl0ZTwvdGl0bGU+CiAgICA8ZyBpZD0iZGRnLXBhc3N3b3JkLWljb24tYmFzZS13aGl0ZSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgaWQ9IlVuaW9uIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0LjAwMDAwMCwgNC4wMDAwMDApIiBmaWxsPSIjRkZGRkZGIj4KICAgICAgICAgICAgPHBhdGggZD0iTTExLjMzMzMsMi42NjY2NyBDMTAuMjI4OCwyLjY2NjY3IDkuMzMzMzMsMy41NjIxIDkuMzMzMzMsNC42NjY2NyBDOS4zMzMzMyw1Ljc3MTI0IDEwLjIyODgsNi42NjY2NyAxMS4zMzMzLDYuNjY2NjcgQzEyLjQzNzksNi42NjY2NyAxMy4zMzMzLDUuNzcxMjQgMTMuMzMzMyw0LjY2NjY3IEMxMy4zMzMzLDMuNTYyMSAxMi40Mzc5LDIuNjY2NjcgMTEuMzMzMywyLjY2NjY3IFogTTEwLjY2NjcsNC42NjY2NyBDMTAuNjY2Nyw0LjI5ODQ4IDEwLjk2NTEsNCAxMS4zMzMzLDQgQzExLjcwMTUsNCAxMiw0LjI5ODQ4IDEyLDQuNjY2NjcgQzEyLDUuMDM0ODYgMTEuNzAxNSw1LjMzMzMzIDExLjMzMzMsNS4zMzMzMyBDMTAuOTY1MSw1LjMzMzMzIDEwLjY2NjcsNS4wMzQ4NiAxMC42NjY3LDQuNjY2NjcgWiIgaWQ9IlNoYXBlIj48L3BhdGg+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0xMC42NjY3LDAgQzcuNzIxMTUsMCA1LjMzMzMzLDIuMzg3ODEgNS4zMzMzMyw1LjMzMzMzIEM1LjMzMzMzLDUuNzYxMTkgNS4zODM4NSw2LjE3Nzk4IDUuNDc5NDUsNi41Nzc3NSBMMC4xOTUyNjIsMTEuODYxOSBDMC4wNzAyMzc5LDExLjk4NyAwLDEyLjE1NjUgMCwxMi4zMzMzIEwwLDE1LjMzMzMgQzAsMTUuNzAxNSAwLjI5ODQ3NywxNiAwLjY2NjY2NywxNiBMMy4zMzMzMywxNiBDNC4wNjk3MSwxNiA0LjY2NjY3LDE1LjQwMyA0LjY2NjY3LDE0LjY2NjcgTDQuNjY2NjcsMTQgTDUuMzMzMzMsMTQgQzYuMDY5NzEsMTQgNi42NjY2NywxMy40MDMgNi42NjY2NywxMi42NjY3IEw2LjY2NjY3LDExLjMzMzMgTDgsMTEuMzMzMyBDOC4xNzY4MSwxMS4zMzMzIDguMzQ2MzgsMTEuMjYzMSA4LjQ3MTQxLDExLjEzODEgTDkuMTU5MDYsMTAuNDUwNCBDOS42Mzc3MiwxMC41OTEyIDEwLjE0MzksMTAuNjY2NyAxMC42NjY3LDEwLjY2NjcgQzEzLjYxMjIsMTAuNjY2NyAxNiw4LjI3ODg1IDE2LDUuMzMzMzMgQzE2LDIuMzg3ODEgMTMuNjEyMiwwIDEwLjY2NjcsMCBaIE02LjY2NjY3LDUuMzMzMzMgQzYuNjY2NjcsMy4xMjQxOSA4LjQ1NzUzLDEuMzMzMzMgMTAuNjY2NywxLjMzMzMzIEMxMi44NzU4LDEuMzMzMzMgMTQuNjY2NywzLjEyNDE5IDE0LjY2NjcsNS4zMzMzMyBDMTQuNjY2Nyw3LjU0MjQ3IDEyLjg3NTgsOS4zMzMzMyAxMC42NjY3LDkuMzMzMzMgQzEwLjE1NTgsOS4zMzMzMyA5LjY2ODg2LDkuMjM3OSA5LjIyMTUyLDkuMDY0NSBDOC45NzUyOCw4Ljk2OTA1IDguNjk1OTEsOS4wMjc5NSA4LjUwOTE2LDkuMjE0NjkgTDcuNzIzODYsMTAgTDYsMTAgQzUuNjMxODEsMTAgNS4zMzMzMywxMC4yOTg1IDUuMzMzMzMsMTAuNjY2NyBMNS4zMzMzMywxMi42NjY3IEw0LDEyLjY2NjcgQzMuNjMxODEsMTIuNjY2NyAzLjMzMzMzLDEyLjk2NTEgMy4zMzMzMywxMy4zMzMzIEwzLjMzMzMzLDE0LjY2NjcgTDEuMzMzMzMsMTQuNjY2NyBMMS4zMzMzMywxMi42MDk1IEw2LjY5Nzg3LDcuMjQ0OTQgQzYuODc1MDIsNy4wNjc3OSA2LjkzNzksNi44MDYyOSA2Ljg2MDY1LDYuNTY3OTggQzYuNzM0ODksNi4xNzk5NyA2LjY2NjY3LDUuNzY1MjcgNi42NjY2Nyw1LjMzMzMzIFoiIGlkPSJTaGFwZSI+PC9wYXRoPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+'; -const ddgPasswordIconFilled = exports.ddgPasswordIconFilled = 'data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjNzY0MzEwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNS4yNSA2Ljc1YTIgMiAwIDEgMCAwIDQgMiAyIDAgMCAwIDAtNFptLS41IDJhLjUuNSAwIDEgMSAxIDAgLjUuNSAwIDAgMS0xIDBaIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz4KICAgIDxwYXRoIGZpbGw9IiM3NjQzMTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTE0LjYyNSA0YTUuMzc1IDUuMzc1IDAgMCAwLTUuMjQ0IDYuNTU5bC01LjE2MSA1LjE2YS43NS43NSAwIDAgMC0uMjIuNTMxdjNjMCAuNDE0LjMzNi43NS43NS43NWgyLjk5N2MuNTU0IDAgMS4wMDMtLjQ1IDEuMDAzLTEuMDAzVjE4aC45OTdjLjU1NCAwIDEuMDAzLS40NSAxLjAwMy0xLjAwM1YxNS41SDEyYS43NS43NSAwIDAgMCAuNTMtLjIybC43MS0uNzFBNS4zOCA1LjM4IDAgMCAwIDIwIDkuMzc1IDUuMzc1IDUuMzc1IDAgMCAwIDE0LjYyNSA0Wk0xMC43NSA5LjM3NWEzLjg3NSAzLjg3NSAwIDEgMSAyLjU0IDMuNjQuNzUuNzUgMCAwIDAtLjc4OS4xNzNMMTEuNjkgMTRIMTBhLjc1Ljc1IDAgMCAwLS43NS43NXYxLjc1SDhhLjc1Ljc1IDAgMCAwLS43NS43NXYxLjI1SDUuNXYtMS45NGw1LjI0OC01LjI0OGEuNzUuNzUgMCAwIDAgLjE4NC0uNzU4IDMuODcyIDMuODcyIDAgMCAxLS4xODItMS4xNzlaIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz4KPC9zdmc+Cg=='; -const ddgPasswordIconFocused = exports.ddgPasswordIconFocused = 'data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8ZyBmaWxsPSIjMDAwIiBjbGlwLXBhdGg9InVybCgjZGRnLXBhc3N3b3JkLWljb24tZm9jdXNlZF9zdmdfX2EpIj4KICAgICAgICA8cGF0aCBmaWxsLW9wYWNpdHk9Ii4xIiBkPSJNMjQgMTJjMC02LjYyNy01LjM3My0xMi0xMi0xMlMwIDUuMzczIDAgMTJzNS4zNzMgMTIgMTIgMTIgMTItNS4zNzMgMTItMTJaIi8+CiAgICAgICAgPHBhdGggZmlsbC1vcGFjaXR5PSIuOSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTUuMjUgNi43NWEyIDIgMCAxIDAgMCA0IDIgMiAwIDAgMCAwLTRabS0uNSAyYS41LjUgMCAxIDEgMSAwIC41LjUgMCAwIDEtMSAwWiIgY2xpcC1ydWxlPSJldmVub2RkIi8+CiAgICAgICAgPHBhdGggZmlsbC1vcGFjaXR5PSIuOSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTQuNjI1IDRhNS4zNzUgNS4zNzUgMCAwIDAtNS4yNDQgNi41NTlsLTUuMTYxIDUuMTZhLjc1Ljc1IDAgMCAwLS4yMi41MzF2M2MwIC40MTQuMzM2Ljc1Ljc1Ljc1aDIuOTk3Yy41NTQgMCAxLjAwMy0uNDUgMS4wMDMtMS4wMDNWMThoLjk5N2MuNTU0IDAgMS4wMDMtLjQ1IDEuMDAzLTEuMDAzVjE1LjVIMTJhLjc1Ljc1IDAgMCAwIC41My0uMjJsLjcxLS43MUE1LjM4IDUuMzggMCAwIDAgMjAgOS4zNzUgNS4zNzUgNS4zNzUgMCAwIDAgMTQuNjI1IDRaTTEwLjc1IDkuMzc1YTMuODc1IDMuODc1IDAgMSAxIDIuNTQgMy42NC43NS43NSAwIDAgMC0uNzg5LjE3M0wxMS42OSAxNEgxMGEuNzUuNzUgMCAwIDAtLjc1Ljc1djEuNzVIOGEuNzUuNzUgMCAwIDAtLjc1Ljc1djEuMjVINS41di0xLjk0bDUuMjQ4LTUuMjQ4YS43NS43NSAwIDAgMCAuMTg0LS43NTggMy44NjkgMy44NjkgMCAwIDEtLjE4Mi0xLjE3OVoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPC9nPgo8L3N2Zz4K'; -const ddgPasswordGenIconBase = exports.ddgPasswordGenIconBase = 'data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjMDAwIiBkPSJNOC4wNDcgNC42MjVDNy45MzcgNC4xMjUgNy44NjIgNCA3LjUgNGMtLjM2MiAwLS40MzguMTI1LS41NDcuNjI1LS4wNjguMzEtLjE3NyAxLjMzOC0uMjUxIDIuMDc3LS43MzguMDc0LTEuNzY3LjE4My0yLjA3Ny4yNTEtLjUuMTEtLjYyNS4xODQtLjYyNS41NDcgMCAuMzYyLjEyNS40MzcuNjI1LjU0Ny4zMS4wNjcgMS4zMzYuMTc3IDIuMDc0LjI1LjA3My43NjcuMTg1IDEuODQyLjI1NCAyLjA3OC4xMS4zNzUuMTg1LjYyNS41NDcuNjI1LjM2MiAwIC40MzgtLjEyNS41NDctLjYyNS4wNjgtLjMxLjE3Ny0xLjMzNi4yNS0yLjA3NC43NjctLjA3MyAxLjg0Mi0uMTg1IDIuMDc4LS4yNTQuMzc1LS4xMS42MjUtLjE4NS42MjUtLjU0NyAwLS4zNjMtLjEyNS0uNDM4LS42MjUtLjU0Ny0uMzEtLjA2OC0xLjMzOS0uMTc3LTIuMDc3LS4yNTEtLjA3NC0uNzM5LS4xODMtMS43NjctLjI1MS0yLjA3N1oiLz4KICAgIDxwYXRoIGZpbGw9IiMwMDAiIGQ9Ik0xNC42ODEgNS4xOTljLS43NjYgMC0xLjQ4Mi4yMS0yLjA5My41NzhhLjYzNi42MzYgMCAwIDEtLjY1NS0xLjA5IDUuMzQgNS4zNCAwIDEgMSAxLjMwMiA5LjcyMmwtLjc3NS43NzZhLjYzNi42MzYgMCAwIDEtLjQ1LjE4NmgtMS4zOTh2MS42NWMwIC40OTMtLjQuODkzLS44OTMuODkzSDguNTc4djEuMTQxYzAgLjQ5NC0uNC44OTMtLjg5NC44OTNINC42MzZBLjYzNi42MzYgMCAwIDEgNCAxOS4zMTNWMTYuMjZjMC0uMTY5LjA2Ny0uMzMuMTg2LS40NWw1LjU2Mi01LjU2MmEuNjM2LjYzNiAwIDEgMSAuOS45bC01LjM3NiA1LjM3NXYyLjE1M2gyLjAzNHYtMS4zOTljMC0uMzUuMjg1LS42MzYuNjM2LS42MzZIOS4zNHYtMS45MDdjMC0uMzUxLjI4NC0uNjM2LjYzNS0uNjM2aDEuNzcxbC44NjQtLjg2M2EuNjM2LjYzNiAwIDAgMSAuNjY4LS4xNDcgNC4wNjkgNC4wNjkgMCAxIDAgMS40MDItNy44OVoiLz4KICAgIDxwYXRoIGZpbGw9IiMwMDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTEzLjYyNSA4LjQ5OWExLjg3NSAxLjg3NSAwIDEgMSAzLjc1IDAgMS44NzUgMS44NzUgMCAwIDEtMy43NSAwWm0xLjg3NS0uNjI1YS42MjUuNjI1IDAgMSAwIDAgMS4yNS42MjUuNjI1IDAgMCAwIDAtMS4yNVoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZmlsbD0iIzAwMCIgZD0iTTQuNjI1IDEyLjEyNWEuNjI1LjYyNSAwIDEgMCAwLTEuMjUuNjI1LjYyNSAwIDAgMCAwIDEuMjVaIi8+Cjwvc3ZnPgo='; -const ddgPasswordGenIconFilled = exports.ddgPasswordGenIconFilled = 'data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjNzY0MzEwIiBkPSJNOC4wNDcgNC42MjVDNy45MzcgNC4xMjUgNy44NjIgNCA3LjUgNGMtLjM2MiAwLS40MzguMTI1LS41NDcuNjI1LS4wNjguMzEtLjE3NyAxLjMzOS0uMjUxIDIuMDc3LS43MzguMDc0LTEuNzY3LjE4My0yLjA3Ny4yNTEtLjUuMTEtLjYyNS4xODUtLjYyNS41NDcgMCAuMzYyLjEyNS40MzcuNjI1LjU0Ny4zMS4wNjcgMS4zMzYuMTc3IDIuMDc0LjI1LjA3My43NjcuMTg1IDEuODQyLjI1NCAyLjA3OC4xMS4zNzUuMTg1LjYyNS41NDcuNjI1LjM2MiAwIC40MzgtLjEyNS41NDctLjYyNS4wNjgtLjMxLjE3Ny0xLjMzNi4yNS0yLjA3NC43NjctLjA3MyAxLjg0Mi0uMTg1IDIuMDc4LS4yNTQuMzc1LS4xMS42MjUtLjE4NS42MjUtLjU0NyAwLS4zNjItLjEyNS0uNDM4LS42MjUtLjU0Ny0uMzEtLjA2OC0xLjMzOS0uMTc3LTIuMDc3LS4yNTEtLjA3NC0uNzM4LS4xODMtMS43NjctLjI1MS0yLjA3N1oiLz4KICAgIDxwYXRoIGZpbGw9IiM3NjQzMTAiIGQ9Ik0xNC42ODEgNS4xOTljLS43NjYgMC0xLjQ4Mi4yMTEtMi4wOTMuNTc4YS42MzYuNjM2IDAgMCAxLS42NTUtMS4wOSA1LjM0IDUuMzQgMCAxIDEgMS4zMDIgOS43MjNsLS43NzUuNzc1YS42MzYuNjM2IDAgMCAxLS40NS4xODZoLTEuMzk4djEuNjVjMCAuNDkzLS40Ljg5My0uODkzLjg5M0g4LjU3OHYxLjE0MWMwIC40OTQtLjQuODk0LS44OTQuODk0SDQuNjM2QS42MzYuNjM2IDAgMCAxIDQgMTkuMzEzVjE2LjI2YS42NC42NCAwIDAgMSAuMTg2LS40NWw1LjU2Mi01LjU2MmEuNjM2LjYzNiAwIDEgMSAuOS45bC01LjM3NiA1LjM3NXYyLjE1M2gyLjAzNHYtMS4zOTljMC0uMzUuMjg1LS42MzYuNjM2LS42MzZIOS4zNHYtMS45MDdjMC0uMzUuMjg0LS42MzYuNjM1LS42MzZoMS43NzFsLjg2NC0uODYzYS42MzYuNjM2IDAgMCAxIC42NjgtLjE0NyA0LjA2OSA0LjA2OSAwIDEgMCAxLjQwMi03Ljg5WiIvPgogICAgPHBhdGggZmlsbD0iIzc2NDMxMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTMuNjI1IDguNWExLjg3NSAxLjg3NSAwIDEgMSAzLjc1IDAgMS44NzUgMS44NzUgMCAwIDEtMy43NSAwWm0xLjg3NS0uNjI2YS42MjUuNjI1IDAgMSAwIDAgMS4yNS42MjUuNjI1IDAgMCAwIDAtMS4yNVoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZmlsbD0iIzc2NDMxMCIgZD0iTTQuNjI1IDEyLjEyNWEuNjI1LjYyNSAwIDEgMCAwLTEuMjUuNjI1LjYyNSAwIDAgMCAwIDEuMjVaIi8+Cjwvc3ZnPgo='; -const ddgCcIconBase = exports.ddgCcIconBase = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBkPSJNNSA5Yy0uNTUyIDAtMSAuNDQ4LTEgMXYyYzAgLjU1Mi40NDggMSAxIDFoM2MuNTUyIDAgMS0uNDQ4IDEtMXYtMmMwLS41NTItLjQ0OC0xLTEtMUg1eiIgZmlsbD0iIzAwMCIvPgogICAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xIDZjMC0yLjIxIDEuNzktNCA0LTRoMTRjMi4yMSAwIDQgMS43OSA0IDR2MTJjMCAyLjIxLTEuNzkgNC00IDRINWMtMi4yMSAwLTQtMS43OS00LTRWNnptNC0yYy0xLjEwNSAwLTIgLjg5NS0yIDJ2OWgxOFY2YzAtMS4xMDUtLjg5NS0yLTItMkg1em0wIDE2Yy0xLjEwNSAwLTItLjg5NS0yLTJoMThjMCAxLjEwNS0uODk1IDItMiAySDV6IiBmaWxsPSIjMDAwIi8+Cjwvc3ZnPgo='; -const ddgCcIconFilled = exports.ddgCcIconFilled = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBkPSJNNSA5Yy0uNTUyIDAtMSAuNDQ4LTEgMXYyYzAgLjU1Mi40NDggMSAxIDFoM2MuNTUyIDAgMS0uNDQ4IDEtMXYtMmMwLS41NTItLjQ0OC0xLTEtMUg1eiIgZmlsbD0iIzc2NDMxMCIvPgogICAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xIDZjMC0yLjIxIDEuNzktNCA0LTRoMTRjMi4yMSAwIDQgMS43OSA0IDR2MTJjMCAyLjIxLTEuNzkgNC00IDRINWMtMi4yMSAwLTQtMS43OS00LTRWNnptNC0yYy0xLjEwNSAwLTIgLjg5NS0yIDJ2OWgxOFY2YzAtMS4xMDUtLjg5NS0yLTItMkg1em0wIDE2Yy0xLjEwNSAwLTItLjg5NS0yLTJoMThjMCAxLjEwNS0uODk1IDItMiAySDV6IiBmaWxsPSIjNzY0MzEwIi8+Cjwvc3ZnPgo='; -const ddgIdentityIconBase = exports.ddgIdentityIconBase = `data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTEyIDIxYzIuMTQzIDAgNC4xMTEtLjc1IDUuNjU3LTItLjYyNi0uNTA2LTEuMzE4LS45MjctMi4wNi0xLjI1LTEuMS0uNDgtMi4yODUtLjczNS0zLjQ4Ni0uNzUtMS4yLS4wMTQtMi4zOTIuMjExLTMuNTA0LjY2NC0uODE3LjMzMy0xLjU4Ljc4My0yLjI2NCAxLjMzNiAxLjU0NiAxLjI1IDMuNTE0IDIgNS42NTcgMnptNC4zOTctNS4wODNjLjk2Ny40MjIgMS44NjYuOTggMi42NzIgMS42NTVDMjAuMjc5IDE2LjAzOSAyMSAxNC4xMDQgMjEgMTJjMC00Ljk3LTQuMDMtOS05LTlzLTkgNC4wMy05IDljMCAyLjEwNC43MjIgNC4wNCAxLjkzMiA1LjU3Mi44NzQtLjczNCAxLjg2LTEuMzI4IDIuOTIxLTEuNzYgMS4zNi0uNTU0IDIuODE2LS44MyA0LjI4My0uODExIDEuNDY3LjAxOCAyLjkxNi4zMyA0LjI2LjkxNnpNMTIgMjNjNi4wNzUgMCAxMS00LjkyNSAxMS0xMVMxOC4wNzUgMSAxMiAxIDEgNS45MjUgMSAxMnM0LjkyNSAxMSAxMSAxMXptMy0xM2MwIDEuNjU3LTEuMzQzIDMtMyAzcy0zLTEuMzQzLTMtMyAxLjM0My0zIDMtMyAzIDEuMzQzIDMgM3ptMiAwYzAgMi43NjEtMi4yMzkgNS01IDVzLTUtMi4yMzktNS01IDIuMjM5LTUgNS01IDUgMi4yMzkgNSA1eiIgZmlsbD0iIzAwMCIvPgo8L3N2Zz4KPHBhdGggeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTEyIDIxYzIuMTQzIDAgNC4xMTEtLjc1IDUuNjU3LTItLjYyNi0uNTA2LTEuMzE4LS45MjctMi4wNi0xLjI1LTEuMS0uNDgtMi4yODUtLjczNS0zLjQ4Ni0uNzUtMS4yLS4wMTQtMi4zOTIuMjExLTMuNTA0LjY2NC0uODE3LjMzMy0xLjU4Ljc4My0yLjI2NCAxLjMzNiAxLjU0NiAxLjI1IDMuNTE0IDIgNS42NTcgMnptNC4zOTctNS4wODNjLjk2Ny40MjIgMS44NjYuOTggMi42NzIgMS42NTVDMjAuMjc5IDE2LjAzOSAyMSAxNC4xMDQgMjEgMTJjMC00Ljk3LTQuMDMtOS05LTlzLTkgNC4wMy05IDljMCAyLjEwNC43MjIgNC4wNCAxLjkzMiA1LjU3Mi44NzQtLjczNCAxLjg2LTEuMzI4IDIuOTIxLTEuNzYgMS4zNi0uNTU0IDIuODE2LS44MyA0LjI4My0uODExIDEuNDY3LjAxOCAyLjkxNi4zMyA0LjI2LjkxNnpNMTIgMjNjNi4wNzUgMCAxMS00LjkyNSAxMS0xMVMxOC4wNzUgMSAxMiAxIDEgNS45MjUgMSAxMnM0LjkyNSAxMSAxMSAxMXptMy0xM2MwIDEuNjU3LTEuMzQzIDMtMyAzcy0zLTEuMzQzLTMtMyAxLjM0My0zIDMtMyAzIDEuMzQzIDMgM3ptMiAwYzAgMi43NjEtMi4yMzkgNS01IDVzLTUtMi4yMzktNS01IDIuMjM5LTUgNS01IDUgMi4yMzkgNSA1eiIgZmlsbD0iIzAwMCIvPgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiBmaWxsPSJub25lIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xMiAyMWMyLjE0MyAwIDQuMTExLS43NSA1LjY1Ny0yLS42MjYtLjUwNi0xLjMxOC0uOTI3LTIuMDYtMS4yNS0xLjEtLjQ4LTIuMjg1LS43MzUtMy40ODYtLjc1LTEuMi0uMDE0LTIuMzkyLjIxMS0zLjUwNC42NjQtLjgxNy4zMzMtMS41OC43ODMtMi4yNjQgMS4zMzYgMS41NDYgMS4yNSAzLjUxNCAyIDUuNjU3IDJ6bTQuMzk3LTUuMDgzYy45NjcuNDIyIDEuODY2Ljk4IDIuNjcyIDEuNjU1QzIwLjI3OSAxNi4wMzkgMjEgMTQuMTA0IDIxIDEyYzAtNC45Ny00LjAzLTktOS05cy05IDQuMDMtOSA5YzAgMi4xMDQuNzIyIDQuMDQgMS45MzIgNS41NzIuODc0LS43MzQgMS44Ni0xLjMyOCAyLjkyMS0xLjc2IDEuMzYtLjU1NCAyLjgxNi0uODMgNC4yODMtLjgxMSAxLjQ2Ny4wMTggMi45MTYuMzMgNC4yNi45MTZ6TTEyIDIzYzYuMDc1IDAgMTEtNC45MjUgMTEtMTFTMTguMDc1IDEgMTIgMSAxIDUuOTI1IDEgMTJzNC45MjUgMTEgMTEgMTF6bTMtMTNjMCAxLjY1Ny0xLjM0MyAzLTMgM3MtMy0xLjM0My0zLTMgMS4zNDMtMyAzLTMgMyAxLjM0MyAzIDN6bTIgMGMwIDIuNzYxLTIuMjM5IDUtNSA1cy01LTIuMjM5LTUtNSAyLjIzOS01IDUtNSA1IDIuMjM5IDUgNXoiIGZpbGw9IiMwMDAiLz4KPC9zdmc+Cg==`; - -},{}],61:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.CSS_STYLES = void 0; -const CSS_STYLES = exports.CSS_STYLES = ":root {\n color-scheme: light dark;\n}\n\n.wrapper *, .wrapper *::before, .wrapper *::after {\n box-sizing: border-box;\n}\n.wrapper {\n position: fixed;\n top: 0;\n left: 0;\n padding: 0;\n font-family: 'DDG_ProximaNova', 'Proxima Nova', system-ui, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',\n 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;\n -webkit-font-smoothing: antialiased;\n z-index: 2147483647;\n}\n.wrapper--data {\n font-family: 'SF Pro Text', system-ui, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',\n 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;\n}\n:not(.top-autofill) .tooltip {\n position: absolute;\n width: 300px;\n max-width: calc(100vw - 25px);\n transform: translate(-1000px, -1000px);\n z-index: 2147483647;\n}\n.tooltip--data, #topAutofill {\n background-color: rgba(242, 240, 240, 1);\n -webkit-backdrop-filter: blur(40px);\n backdrop-filter: blur(40px);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip--data, #topAutofill {\n background: rgb(100, 98, 102, .9);\n }\n}\n.tooltip--data {\n padding: 6px;\n font-size: 13px;\n line-height: 14px;\n width: 315px;\n max-height: 290px;\n overflow-y: auto;\n}\n.top-autofill .tooltip--data {\n min-height: 100vh;\n}\n.tooltip--data.tooltip--incontext-signup {\n width: 360px;\n}\n:not(.top-autofill) .tooltip--data {\n top: 100%;\n left: 100%;\n border: 0.5px solid rgba(255, 255, 255, 0.2);\n border-radius: 6px;\n box-shadow: 0 10px 20px rgba(0, 0, 0, 0.32);\n}\n@media (prefers-color-scheme: dark) {\n :not(.top-autofill) .tooltip--data {\n border: 1px solid rgba(255, 255, 255, 0.2);\n }\n}\n:not(.top-autofill) .tooltip--email {\n top: calc(100% + 6px);\n right: calc(100% - 48px);\n padding: 8px;\n border: 1px solid #D0D0D0;\n border-radius: 10px;\n background-color: #FFFFFF;\n font-size: 14px;\n line-height: 1.3;\n color: #333333;\n box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);\n}\n.tooltip--email__caret {\n position: absolute;\n transform: translate(-1000px, -1000px);\n z-index: 2147483647;\n}\n.tooltip--email__caret::before,\n.tooltip--email__caret::after {\n content: \"\";\n width: 0;\n height: 0;\n border-left: 10px solid transparent;\n border-right: 10px solid transparent;\n display: block;\n border-bottom: 8px solid #D0D0D0;\n position: absolute;\n right: -28px;\n}\n.tooltip--email__caret::before {\n border-bottom-color: #D0D0D0;\n top: -1px;\n}\n.tooltip--email__caret::after {\n border-bottom-color: #FFFFFF;\n top: 0px;\n}\n\n/* Buttons */\n.tooltip__button {\n display: flex;\n width: 100%;\n padding: 8px 8px 8px 0px;\n font-family: inherit;\n color: inherit;\n background: transparent;\n border: none;\n border-radius: 6px;\n}\n.tooltip__button.currentFocus,\n.wrapper:not(.top-autofill) .tooltip__button:hover {\n background-color: #3969EF;\n color: #FFFFFF;\n}\n\n/* Data autofill tooltip specific */\n.tooltip__button--data {\n position: relative;\n min-height: 48px;\n flex-direction: row;\n justify-content: flex-start;\n font-size: inherit;\n font-weight: 500;\n line-height: 16px;\n text-align: left;\n border-radius: 3px;\n}\n.tooltip--data__item-container {\n max-height: 220px;\n overflow: auto;\n}\n.tooltip__button--data:first-child {\n margin-top: 0;\n}\n.tooltip__button--data:last-child {\n margin-bottom: 0;\n}\n.tooltip__button--data::before {\n content: '';\n flex-shrink: 0;\n display: block;\n width: 32px;\n height: 32px;\n margin: 0 8px;\n background-size: 20px 20px;\n background-repeat: no-repeat;\n background-position: center center;\n}\n#provider_locked::after {\n position: absolute;\n content: '';\n flex-shrink: 0;\n display: block;\n width: 32px;\n height: 32px;\n margin: 0 8px;\n background-size: 11px 13px;\n background-repeat: no-repeat;\n background-position: right bottom;\n}\n.tooltip__button--data.currentFocus:not(.tooltip__button--data--bitwarden)::before,\n.wrapper:not(.top-autofill) .tooltip__button--data:not(.tooltip__button--data--bitwarden):hover::before {\n filter: invert(100%);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip__button--data:not(.tooltip__button--data--bitwarden)::before,\n .tooltip__button--data:not(.tooltip__button--data--bitwarden)::before {\n filter: invert(100%);\n opacity: .9;\n }\n}\n.tooltip__button__text-container {\n margin: auto 0;\n}\n.label {\n display: block;\n font-weight: 400;\n letter-spacing: -0.25px;\n color: rgba(0,0,0,.8);\n font-size: 13px;\n line-height: 1;\n}\n.label + .label {\n margin-top: 2px;\n}\n.label.label--medium {\n font-weight: 500;\n letter-spacing: -0.25px;\n color: rgba(0,0,0,.9);\n}\n.label.label--small {\n font-size: 11px;\n font-weight: 400;\n letter-spacing: 0.06px;\n color: rgba(0,0,0,0.6);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip--data .label {\n color: #ffffff;\n }\n .tooltip--data .label--medium {\n color: #ffffff;\n }\n .tooltip--data .label--small {\n color: #cdcdcd;\n }\n}\n.tooltip__button.currentFocus .label,\n.wrapper:not(.top-autofill) .tooltip__button:hover .label {\n color: #FFFFFF;\n}\n\n.tooltip__button--manage {\n font-size: 13px;\n padding: 5px 9px;\n border-radius: 3px;\n margin: 0;\n}\n\n/* Icons */\n.tooltip__button--data--credentials::before,\n.tooltip__button--data--credentials__current::before {\n background-size: 28px 28px;\n background-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNS4zMzQgNi42NjdhMiAyIDAgMSAwIDAgNCAyIDIgMCAwIDAgMC00Wm0tLjY2NyAyYS42NjcuNjY3IDAgMSAxIDEuMzMzIDAgLjY2Ny42NjcgMCAwIDEtMS4zMzMgMFoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTQuNjY3IDRhNS4zMzMgNS4zMzMgMCAwIDAtNS4xODggNi41NzhsLTUuMjg0IDUuMjg0YS42NjcuNjY3IDAgMCAwLS4xOTUuNDcxdjNjMCAuMzY5LjI5OC42NjcuNjY3LjY2N2gyLjY2NmMuNzM3IDAgMS4zMzQtLjU5NyAxLjMzNC0xLjMzM1YxOGguNjY2Yy43MzcgMCAxLjMzNC0uNTk3IDEuMzM0LTEuMzMzdi0xLjMzNEgxMmMuMTc3IDAgLjM0Ni0uMDcuNDcxLS4xOTVsLjY4OC0uNjg4QTUuMzMzIDUuMzMzIDAgMSAwIDE0LjY2NyA0Wm0tNCA1LjMzM2E0IDQgMCAxIDEgMi41NTUgMy43MzIuNjY3LjY2NyAwIDAgMC0uNzEzLjE1bC0uNzg1Ljc4NUgxMGEuNjY3LjY2NyAwIDAgMC0uNjY3LjY2N3YySDhhLjY2Ny42NjcgMCAwIDAtLjY2Ny42NjZ2MS4zMzRoLTJ2LTIuMDU4bDUuMzY1LTUuMzY0YS42NjcuNjY3IDAgMCAwIC4xNjMtLjY3NyAzLjk5NiAzLjk5NiAwIDAgMS0uMTk0LTEuMjM1WiIgY2xpcC1ydWxlPSJldmVub2RkIi8+Cjwvc3ZnPgo=');\n}\n.tooltip__button--data--credentials__new::before {\n background-size: 28px 28px;\n background-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjMDAwIiBkPSJNOC4wNDcgNC42MjVDNy45MzcgNC4xMjUgNy44NjIgNCA3LjUgNGMtLjM2MiAwLS40MzguMTI1LS41NDcuNjI1LS4wNjguMzEtLjE3NyAxLjMzOC0uMjUxIDIuMDc3LS43MzguMDc0LTEuNzY3LjE4My0yLjA3Ny4yNTEtLjUuMTEtLjYyNS4xODQtLjYyNS41NDcgMCAuMzYyLjEyNS40MzcuNjI1LjU0Ny4zMS4wNjcgMS4zMzYuMTc3IDIuMDc0LjI1LjA3My43NjcuMTg1IDEuODQyLjI1NCAyLjA3OC4xMS4zNzUuMTg1LjYyNS41NDcuNjI1LjM2MiAwIC40MzgtLjEyNS41NDctLjYyNS4wNjgtLjMxLjE3Ny0xLjMzNi4yNS0yLjA3NC43NjctLjA3MyAxLjg0Mi0uMTg1IDIuMDc4LS4yNTQuMzc1LS4xMS42MjUtLjE4NS42MjUtLjU0NyAwLS4zNjMtLjEyNS0uNDM4LS42MjUtLjU0Ny0uMzEtLjA2OC0xLjMzOS0uMTc3LTIuMDc3LS4yNTEtLjA3NC0uNzM5LS4xODMtMS43NjctLjI1MS0yLjA3N1oiLz4KICAgIDxwYXRoIGZpbGw9IiMwMDAiIGQ9Ik0xNC42ODEgNS4xOTljLS43NjYgMC0xLjQ4Mi4yMS0yLjA5My41NzhhLjYzNi42MzYgMCAwIDEtLjY1NS0xLjA5IDUuMzQgNS4zNCAwIDEgMSAxLjMwMiA5LjcyMmwtLjc3NS43NzZhLjYzNi42MzYgMCAwIDEtLjQ1LjE4NmgtMS4zOTh2MS42NWMwIC40OTMtLjQuODkzLS44OTMuODkzSDguNTc4djEuMTQxYzAgLjQ5NC0uNC44OTMtLjg5NC44OTNINC42MzZBLjYzNi42MzYgMCAwIDEgNCAxOS4zMTNWMTYuMjZjMC0uMTY5LjA2Ny0uMzMuMTg2LS40NWw1LjU2Mi01LjU2MmEuNjM2LjYzNiAwIDEgMSAuOS45bC01LjM3NiA1LjM3NXYyLjE1M2gyLjAzNHYtMS4zOTljMC0uMzUuMjg1LS42MzYuNjM2LS42MzZIOS4zNHYtMS45MDdjMC0uMzUxLjI4NC0uNjM2LjYzNS0uNjM2aDEuNzcxbC44NjQtLjg2M2EuNjM2LjYzNiAwIDAgMSAuNjY4LS4xNDcgNC4wNjkgNC4wNjkgMCAxIDAgMS40MDItNy44OVoiLz4KICAgIDxwYXRoIGZpbGw9IiMwMDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTEzLjYyNSA4LjQ5OWExLjg3NSAxLjg3NSAwIDEgMSAzLjc1IDAgMS44NzUgMS44NzUgMCAwIDEtMy43NSAwWm0xLjg3NS0uNjI1YS42MjUuNjI1IDAgMSAwIDAgMS4yNS42MjUuNjI1IDAgMCAwIDAtMS4yNVoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZmlsbD0iIzAwMCIgZD0iTTQuNjI1IDEyLjEyNWEuNjI1LjYyNSAwIDEgMCAwLTEuMjUuNjI1LjYyNSAwIDAgMCAwIDEuMjVaIi8+Cjwvc3ZnPgo=');\n}\n.tooltip__button--data--creditCards::before {\n background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBkPSJNNSA5Yy0uNTUyIDAtMSAuNDQ4LTEgMXYyYzAgLjU1Mi40NDggMSAxIDFoM2MuNTUyIDAgMS0uNDQ4IDEtMXYtMmMwLS41NTItLjQ0OC0xLTEtMUg1eiIgZmlsbD0iIzAwMCIvPgogICAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xIDZjMC0yLjIxIDEuNzktNCA0LTRoMTRjMi4yMSAwIDQgMS43OSA0IDR2MTJjMCAyLjIxLTEuNzkgNC00IDRINWMtMi4yMSAwLTQtMS43OS00LTRWNnptNC0yYy0xLjEwNSAwLTIgLjg5NS0yIDJ2OWgxOFY2YzAtMS4xMDUtLjg5NS0yLTItMkg1em0wIDE2Yy0xLjEwNSAwLTItLjg5NS0yLTJoMThjMCAxLjEwNS0uODk1IDItMiAySDV6IiBmaWxsPSIjMDAwIi8+Cjwvc3ZnPgo=');\n}\n.tooltip__button--data--identities::before {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTEyIDIxYzIuMTQzIDAgNC4xMTEtLjc1IDUuNjU3LTItLjYyNi0uNTA2LTEuMzE4LS45MjctMi4wNi0xLjI1LTEuMS0uNDgtMi4yODUtLjczNS0zLjQ4Ni0uNzUtMS4yLS4wMTQtMi4zOTIuMjExLTMuNTA0LjY2NC0uODE3LjMzMy0xLjU4Ljc4My0yLjI2NCAxLjMzNiAxLjU0NiAxLjI1IDMuNTE0IDIgNS42NTcgMnptNC4zOTctNS4wODNjLjk2Ny40MjIgMS44NjYuOTggMi42NzIgMS42NTVDMjAuMjc5IDE2LjAzOSAyMSAxNC4xMDQgMjEgMTJjMC00Ljk3LTQuMDMtOS05LTlzLTkgNC4wMy05IDljMCAyLjEwNC43MjIgNC4wNCAxLjkzMiA1LjU3Mi44NzQtLjczNCAxLjg2LTEuMzI4IDIuOTIxLTEuNzYgMS4zNi0uNTU0IDIuODE2LS44MyA0LjI4My0uODExIDEuNDY3LjAxOCAyLjkxNi4zMyA0LjI2LjkxNnpNMTIgMjNjNi4wNzUgMCAxMS00LjkyNSAxMS0xMVMxOC4wNzUgMSAxMiAxIDEgNS45MjUgMSAxMnM0LjkyNSAxMSAxMSAxMXptMy0xM2MwIDEuNjU3LTEuMzQzIDMtMyAzcy0zLTEuMzQzLTMtMyAxLjM0My0zIDMtMyAzIDEuMzQzIDMgM3ptMiAwYzAgMi43NjEtMi4yMzkgNS01IDVzLTUtMi4yMzktNS01IDIuMjM5LTUgNS01IDUgMi4yMzkgNSA1eiIgZmlsbD0iIzAwMCIvPgo8L3N2Zz4=');\n}\n.tooltip__button--data--credentials.tooltip__button--data--bitwarden::before,\n.tooltip__button--data--credentials__current.tooltip__button--data--bitwarden::before {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiByeD0iOCIgZmlsbD0iIzE3NUREQyIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTE4LjU2OTYgNS40MzM1NUMxOC41MDg0IDUuMzc0NDIgMTguNDM0NyA1LjMyNzYzIDE4LjM1MzEgNS4yOTYxMUMxOC4yNzE1IDUuMjY0NiAxOC4xODM3IDUuMjQ5MDQgMTguMDk1MyA1LjI1MDQxSDUuOTIxOTFDNS44MzMyNiA1LjI0NzI5IDUuNzQ0OTMgNS4yNjIwNSA1LjY2MzA0IDUuMjkzNjdDNS41ODExNSA1LjMyNTI5IDUuNTA3NjUgNS4zNzMwMiA1LjQ0NzYyIDUuNDMzNTVDNS4zMjE3IDUuNTUwMTMgNS4yNTA2NSA1LjcwODE1IDUuMjUgNS44NzMxVjEzLjM4MjFDNS4yNTMzNiAxMy45NTM1IDUuMzc0MDggMTQuNTE5MSA1LjYwNTcyIDE1LjA0ODdDNS44MTkzMSAxNS41NzI4IDYuMTEyMDcgMTYuMDY2MSA2LjQ3NTI0IDE2LjUxMzlDNi44NDIgMTYuOTY4MyA3LjI1OTI5IDE3LjM4NTcgNy43MjAyNSAxNy43NTkzQzguMTQwNTMgMTguMTI1NiA4LjU4OTcxIDE4LjQ2MjMgOS4wNjQwNyAxOC43NjY2QzkuNDU5MzEgMTkuMDIzIDkuOTEzODMgMTkuMjc5NCAxMC4zNDg2IDE5LjUxNzVDMTAuNzgzNCAxOS43NTU2IDExLjA5OTYgMTkuOTIwNCAxMS4yNzc0IDE5Ljk5MzdDMTEuNDU1MyAyMC4wNjY5IDExLjYxMzQgMjAuMTQwMiAxMS43MTIyIDIwLjE5NTFDMTEuNzk5MiAyMC4yMzEzIDExLjg5MzUgMjAuMjUgMTEuOTg4OCAyMC4yNUMxMi4wODQyIDIwLjI1IDEyLjE3ODUgMjAuMjMxMyAxMi4yNjU1IDIwLjE5NTFDMTIuNDIxMiAyMC4xMzYzIDEyLjU3MjkgMjAuMDY5IDEyLjcyIDE5Ljk5MzdDMTIuNzcxMSAxOS45Njc0IDEyLjgzMzUgMTkuOTM2NiAxMi45MDY5IDE5LjkwMDRDMTMuMDg5MSAxOS44MTA1IDEzLjMzODggMTkuNjg3MiAxMy42NDg5IDE5LjUxNzVDMTQuMDgzNiAxOS4yNzk0IDE0LjUxODQgMTkuMDIzIDE0LjkzMzQgMTguNzY2NkMxNS40MDQgMTguNDU3NyAxNS44NTI4IDE4LjEyMTIgMTYuMjc3MiAxNy43NTkzQzE2LjczMzEgMTcuMzgwOSAxNy4xNDk5IDE2Ljk2NCAxNy41MjIyIDE2LjUxMzlDMTcuODc4IDE2LjA2MTcgMTguMTcwMiAxNS41NjkzIDE4LjM5MTcgMTUuMDQ4N0MxOC42MjM0IDE0LjUxOTEgMTguNzQ0MSAxMy45NTM1IDE4Ljc0NzQgMTMuMzgyMVY1Ljg3MzFDMTguNzU1NyA1Ljc5MjE0IDE4Ljc0MzkgNS43MTA1IDE4LjcxMzEgNS42MzQzNUMxOC42ODIzIDUuNTU4MiAxOC42MzMyIDUuNDg5NTQgMTguNTY5NiA1LjQzMzU1Wk0xNy4wMDg0IDEzLjQ1NTNDMTcuMDA4NCAxNi4xODQyIDEyLjAwODYgMTguNTI4NSAxMi4wMDg2IDE4LjUyODVWNi44NjIwOUgxNy4wMDg0VjEzLjQ1NTNaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K');\n}\n#provider_locked:after {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTEiIGhlaWdodD0iMTMiIHZpZXdCb3g9IjAgMCAxMSAxMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEgNy42MDA1N1Y3LjYwMjVWOS41MjI1QzEgMTAuMDgwMSAxLjIyMTUxIDEwLjYxNDkgMS42MTU4MSAxMS4wMDkyQzIuMDEwMSAxMS40MDM1IDIuNTQ0ODggMTEuNjI1IDMuMTAyNSAxMS42MjVINy4yNzI1QzcuNTQ4NjEgMTEuNjI1IDcuODIyMDEgMTEuNTcwNiA4LjA3NzA5IDExLjQ2NUM4LjMzMjE4IDExLjM1OTMgOC41NjM5NiAxMS4yMDQ0IDguNzU5MTkgMTEuMDA5MkM4Ljk1NDQzIDEwLjgxNCA5LjEwOTMgMTAuNTgyMiA5LjIxNDk2IDEwLjMyNzFDOS4zMjA2MiAxMC4wNzIgOS4zNzUgOS43OTg2MSA5LjM3NSA5LjUyMjVMOS4zNzUgNy42MDI1TDkuMzc1IDcuNjAwNTdDOS4zNzQxNSA3LjE2MTMxIDkuMjM1NzQgNi43MzMzNSA4Ljk3OTIyIDYuMzc2NzhDOC44NzY4MyA2LjIzNDQ2IDguNzU3NjggNi4xMDYzNyA4LjYyNSA1Ljk5NDg5VjUuMTg3NUM4LjYyNSA0LjI3NTgyIDguMjYyODQgMy40MDE0OCA3LjYxODE4IDIuNzU2ODJDNi45NzM1MiAyLjExMjE2IDYuMDk5MTggMS43NSA1LjE4NzUgMS43NUM0LjI3NTgyIDEuNzUgMy40MDE0OCAyLjExMjE2IDIuNzU2ODIgMi43NTY4MkMyLjExMjE2IDMuNDAxNDggMS43NSA0LjI3NTgyIDEuNzUgNS4xODc1VjUuOTk0ODlDMS42MTczMiA2LjEwNjM3IDEuNDk4MTcgNi4yMzQ0NiAxLjM5NTc4IDYuMzc2NzhDMS4xMzkyNiA2LjczMzM1IDEuMDAwODUgNy4xNjEzMSAxIDcuNjAwNTdaTTQuOTY4NyA0Ljk2ODdDNS4wMjY5NCA0LjkxMDQ3IDUuMTA1MzIgNC44NzY5OSA1LjE4NzUgNC44NzUwN0M1LjI2OTY4IDQuODc2OTkgNS4zNDgwNiA0LjkxMDQ3IDUuNDA2MyA0Ljk2ODdDNS40NjU0MiA1LjAyNzgzIDUuNDk5MDQgNS4xMDc3NCA1LjUgNS4xOTEzVjUuNUg0Ljg3NVY1LjE5MTNDNC44NzU5NiA1LjEwNzc0IDQuOTA5NTggNS4wMjc4MyA0Ljk2ODcgNC45Njg3WiIgZmlsbD0iIzIyMjIyMiIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIyIi8+Cjwvc3ZnPgo=');\n}\n\nhr {\n display: block;\n margin: 5px 9px;\n border: none; /* reset the border */\n border-top: 1px solid rgba(0,0,0,.1);\n}\n\nhr:first-child {\n display: none;\n}\n\n@media (prefers-color-scheme: dark) {\n hr {\n border-top: 1px solid rgba(255,255,255,.2);\n }\n}\n\n#privateAddress {\n align-items: flex-start;\n}\n#personalAddress::before,\n#privateAddress::before,\n#incontextSignup::before,\n#personalAddress.currentFocus::before,\n#personalAddress:hover::before,\n#privateAddress.currentFocus::before,\n#privateAddress:hover::before {\n filter: none;\n /* This is the same icon as `daxBase64` in `src/Form/logo-svg.js` */\n background-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMTI4IDEyOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0ibTY0IDEyOGMzNS4zNDYgMCA2NC0yOC42NTQgNjQtNjRzLTI4LjY1NC02NC02NC02NC02NCAyOC42NTQtNjQgNjQgMjguNjU0IDY0IDY0IDY0eiIgZmlsbD0iI2RlNTgzMyIgZmlsbC1ydWxlPSJldmVub2RkIi8+CiAgICA8cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Im03MyAxMTEuNzVjMC0uNS4xMjMtLjYxNC0xLjQ2Ni0zLjc4Mi00LjIyNC04LjQ1OS04LjQ3LTIwLjM4NC02LjU0LTI4LjA3NS4zNTMtMS4zOTctMy45NzgtNTEuNzQ0LTcuMDQtNTMuMzY1LTMuNDAyLTEuODEzLTcuNTg4LTQuNjktMTEuNDE4LTUuMzMtMS45NDMtLjMxLTQuNDktLjE2NC02LjQ4Mi4xMDUtLjM1My4wNDctLjM2OC42ODMtLjAzLjc5OCAxLjMwOC40NDMgMi44OTUgMS4yMTIgMy44MyAyLjM3NS4xNzguMjItLjA2LjU2Ni0uMzQyLjU3Ny0uODgyLjAzMi0yLjQ4Mi40MDItNC41OTMgMi4xOTUtLjI0NC4yMDctLjA0MS41OTIuMjczLjUzIDQuNTM2LS44OTcgOS4xNy0uNDU1IDExLjkgMi4wMjcuMTc3LjE2LjA4NC40NS0uMTQ3LjUxMi0yMy42OTQgNi40NC0xOS4wMDMgMjcuMDUtMTIuNjk2IDUyLjM0NCA1LjYxOSAyMi41MyA3LjczMyAyOS43OTIgOC40IDMyLjAwNGEuNzE4LjcxOCAwIDAgMCAuNDIzLjQ2N2M4LjE1NiAzLjI0OCAyNS45MjggMy4zOTIgMjUuOTI4LTIuMTMyeiIgZmlsbD0iI2RkZCIgZmlsbC1ydWxlPSJldmVub2RkIi8+CiAgICA8cGF0aCBkPSJtNzYuMjUgMTE2LjVjLTIuODc1IDEuMTI1LTguNSAxLjYyNS0xMS43NSAxLjYyNS00Ljc2NCAwLTExLjYyNS0uNzUtMTQuMTI1LTEuODc1LTEuNTQ0LTQuNzUxLTYuMTY0LTE5LjQ4LTEwLjcyNy0zOC4xODVsLS40NDctMS44MjctLjAwNC0uMDE1Yy01LjQyNC0yMi4xNTctOS44NTUtNDAuMjUzIDE0LjQyNy00NS45MzguMjIyLS4wNTIuMzMtLjMxNy4xODQtLjQ5Mi0yLjc4Ni0zLjMwNS04LjAwNS00LjM4OC0xNC42MDUtMi4xMTEtLjI3LjA5My0uNTA2LS4xOC0uMzM3LS40MTIgMS4yOTQtMS43ODMgMy44MjMtMy4xNTUgNS4wNzEtMy43NTYuMjU4LS4xMjQuMjQyLS41MDItLjAzLS41ODhhMjcuODc3IDI3Ljg3NyAwIDAgMCAtMy43NzItLjljLS4zNy0uMDU5LS40MDMtLjY5My0uMDMyLS43NDMgOS4zNTYtMS4yNTkgMTkuMTI1IDEuNTUgMjQuMDI4IDcuNzI2YS4zMjYuMzI2IDAgMCAwIC4xODYuMTE0YzE3Ljk1MiAzLjg1NiAxOS4yMzggMzIuMjM1IDE3LjE3IDMzLjUyOC0uNDA4LjI1NS0xLjcxNS4xMDgtMy40MzgtLjA4NS02Ljk4Ni0uNzgxLTIwLjgxOC0yLjMyOS05LjQwMiAxOC45NDguMTEzLjIxLS4wMzYuNDg4LS4yNzIuNTI1LTYuNDM4IDEgMS44MTIgMjEuMTczIDcuODc1IDM0LjQ2MXoiIGZpbGw9IiNmZmYiLz4KICAgIDxwYXRoIGQ9Im04NC4yOCA5MC42OThjLTEuMzY3LS42MzMtNi42MjEgMy4xMzUtMTAuMTEgNi4wMjgtLjcyOC0xLjAzMS0yLjEwMy0xLjc4LTUuMjAzLTEuMjQyLTIuNzEzLjQ3Mi00LjIxMSAxLjEyNi00Ljg4IDIuMjU0LTQuMjgzLTEuNjIzLTExLjQ4OC00LjEzLTEzLjIyOS0xLjcxLTEuOTAyIDIuNjQ2LjQ3NiAxNS4xNjEgMy4wMDMgMTYuNzg2IDEuMzIuODQ5IDcuNjMtMy4yMDggMTAuOTI2LTYuMDA1LjUzMi43NDkgMS4zODggMS4xNzggMy4xNDggMS4xMzcgMi42NjItLjA2MiA2Ljk3OS0uNjgxIDcuNjQ5LTEuOTIxLjA0LS4wNzUuMDc1LS4xNjQuMTA1LS4yNjYgMy4zODggMS4yNjYgOS4zNSAyLjYwNiAxMC42ODIgMi40MDYgMy40Ny0uNTIxLS40ODQtMTYuNzIzLTIuMDktMTcuNDY3eiIgZmlsbD0iIzNjYTgyYiIvPgogICAgPHBhdGggZD0ibTc0LjQ5IDk3LjA5N2MuMTQ0LjI1Ni4yNi41MjYuMzU4LjguNDgzIDEuMzUyIDEuMjcgNS42NDguNjc0IDYuNzA5LS41OTUgMS4wNjItNC40NTkgMS41NzQtNi44NDMgMS42MTVzLTIuOTItLjgzMS0zLjQwMy0yLjE4MWMtLjM4Ny0xLjA4MS0uNTc3LTMuNjIxLS41NzItNS4wNzUtLjA5OC0yLjE1OC42OS0yLjkxNiA0LjMzNC0zLjUwNiAyLjY5Ni0uNDM2IDQuMTIxLjA3MSA0Ljk0NC45NCAzLjgyOC0yLjg1NyAxMC4yMTUtNi44ODkgMTAuODM4LTYuMTUyIDMuMTA2IDMuNjc0IDMuNDk5IDEyLjQyIDIuODI2IDE1LjkzOS0uMjIgMS4xNTEtMTAuNTA1LTEuMTM5LTEwLjUwNS0yLjM4IDAtNS4xNTItMS4zMzctNi41NjUtMi42NS02Ljcxem0tMjIuNTMtMS42MDljLjg0My0xLjMzMyA3LjY3NC4zMjUgMTEuNDI0IDEuOTkzIDAgMC0uNzcgMy40OTEuNDU2IDcuNjA0LjM1OSAxLjIwMy04LjYyNyA2LjU1OC05LjggNS42MzctMS4zNTUtMS4wNjUtMy44NS0xMi40MzItMi4wOC0xNS4yMzR6IiBmaWxsPSIjNGNiYTNjIi8+CiAgICA8cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Im01NS4yNjkgNjguNDA2Yy41NTMtMi40MDMgMy4xMjctNi45MzIgMTIuMzIxLTYuODIyIDQuNjQ4LS4wMTkgMTAuNDIyLS4wMDIgMTQuMjUtLjQzNmE1MS4zMTIgNTEuMzEyIDAgMCAwIDEyLjcyNi0zLjA5NWMzLjk4LTEuNTE5IDUuMzkyLTEuMTggNS44ODctLjI3Mi41NDQuOTk5LS4wOTcgMi43MjItMS40ODggNC4zMDktMi42NTYgMy4wMy03LjQzMSA1LjM4LTE1Ljg2NSA2LjA3Ni04LjQzMy42OTgtMTQuMDItMS41NjUtMTYuNDI1IDIuMTE4LTEuMDM4IDEuNTg5LS4yMzYgNS4zMzMgNy45MiA2LjUxMiAxMS4wMiAxLjU5IDIwLjA3Mi0xLjkxNyAyMS4xOS4yMDEgMS4xMTkgMi4xMTgtNS4zMjMgNi40MjgtMTYuMzYyIDYuNTE4cy0xNy45MzQtMy44NjUtMjAuMzc5LTUuODNjLTMuMTAyLTIuNDk1LTQuNDktNi4xMzMtMy43NzUtOS4yNzl6IiBmaWxsPSIjZmMzIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz4KICAgIDxnIGZpbGw9IiMxNDMwN2UiIG9wYWNpdHk9Ii44Ij4KICAgICAgPHBhdGggZD0ibTY5LjMyNyA0Mi4xMjdjLjYxNi0xLjAwOCAxLjk4MS0xLjc4NiA0LjIxNi0xLjc4NiAyLjIzNCAwIDMuMjg1Ljg4OSA0LjAxMyAxLjg4LjE0OC4yMDItLjA3Ni40NC0uMzA2LjM0YTU5Ljg2OSA1OS44NjkgMCAwIDEgLS4xNjgtLjA3M2MtLjgxNy0uMzU3LTEuODItLjc5NS0zLjU0LS44Mi0xLjgzOC0uMDI2LTIuOTk3LjQzNS0zLjcyNy44MzEtLjI0Ni4xMzQtLjYzNC0uMTMzLS40ODgtLjM3MnptLTI1LjE1NyAxLjI5YzIuMTctLjkwNyAzLjg3Ni0uNzkgNS4wODEtLjUwNC4yNTQuMDYuNDMtLjIxMy4yMjctLjM3Ny0uOTM1LS43NTUtMy4wMy0xLjY5Mi01Ljc2LS42NzQtMi40MzcuOTA5LTMuNTg1IDIuNzk2LTMuNTkyIDQuMDM4LS4wMDIuMjkyLjYuMzE3Ljc1Ni4wNy40Mi0uNjcgMS4xMi0xLjY0NiAzLjI4OS0yLjU1M3oiLz4KICAgICAgPHBhdGggY2xpcC1ydWxlPSJldmVub2RkIiBkPSJtNzUuNDQgNTUuOTJhMy40NyAzLjQ3IDAgMCAxIC0zLjQ3NC0zLjQ2MiAzLjQ3IDMuNDcgMCAwIDEgMy40NzUtMy40NiAzLjQ3IDMuNDcgMCAwIDEgMy40NzQgMy40NiAzLjQ3IDMuNDcgMCAwIDEgLTMuNDc1IDMuNDYyem0yLjQ0Ny00LjYwOGEuODk5Ljg5OSAwIDAgMCAtMS43OTkgMGMwIC40OTQuNDA1Ljg5NS45Ljg5NS40OTkgMCAuOS0uNC45LS44OTV6bS0yNS40NjQgMy41NDJhNC4wNDIgNC4wNDIgMCAwIDEgLTQuMDQ5IDQuMDM3IDQuMDQ1IDQuMDQ1IDAgMCAxIC00LjA1LTQuMDM3IDQuMDQ1IDQuMDQ1IDAgMCAxIDQuMDUtNC4wMzcgNC4wNDUgNC4wNDUgMCAwIDEgNC4wNSA0LjAzN3ptLTEuMTkzLTEuMzM4YTEuMDUgMS4wNSAwIDAgMCAtMi4wOTcgMCAxLjA0OCAxLjA0OCAwIDAgMCAyLjA5NyAweiIgZmlsbC1ydWxlPSJldmVub2RkIi8+CiAgICA8L2c+CiAgICA8cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Im02NCAxMTcuNzVjMjkuNjg1IDAgNTMuNzUtMjQuMDY1IDUzLjc1LTUzLjc1cy0yNC4wNjUtNTMuNzUtNTMuNzUtNTMuNzUtNTMuNzUgMjQuMDY1LTUzLjc1IDUzLjc1IDI0LjA2NSA1My43NSA1My43NSA1My43NXptMCA1YzMyLjQ0NyAwIDU4Ljc1LTI2LjMwMyA1OC43NS01OC43NXMtMjYuMzAzLTU4Ljc1LTU4Ljc1LTU4Ljc1LTU4Ljc1IDI2LjMwMy01OC43NSA1OC43NSAyNi4zMDMgNTguNzUgNTguNzUgNTguNzV6IiBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz4KPC9zdmc+');\n}\n\n/* Email tooltip specific */\n.tooltip__button--email {\n flex-direction: column;\n justify-content: center;\n align-items: flex-start;\n font-size: 14px;\n padding: 4px 8px;\n}\n.tooltip__button--email__primary-text {\n font-weight: bold;\n}\n.tooltip__button--email__secondary-text {\n font-size: 12px;\n}\n\n/* Email Protection signup notice */\n:not(.top-autofill) .tooltip--email-signup {\n text-align: left;\n color: #222222;\n padding: 16px 20px;\n width: 380px;\n}\n\n.tooltip--email-signup h1 {\n font-weight: 700;\n font-size: 16px;\n line-height: 1.5;\n margin: 0;\n}\n\n.tooltip--email-signup p {\n font-weight: 400;\n font-size: 14px;\n line-height: 1.4;\n}\n\n.notice-controls {\n display: flex;\n}\n\n.tooltip--email-signup .notice-controls > * {\n border-radius: 8px;\n border: 0;\n cursor: pointer;\n display: inline-block;\n font-family: inherit;\n font-style: normal;\n font-weight: bold;\n padding: 8px 12px;\n text-decoration: none;\n}\n\n.notice-controls .ghost {\n margin-left: 1rem;\n}\n\n.tooltip--email-signup a.primary {\n background: #3969EF;\n color: #fff;\n}\n\n.tooltip--email-signup a.primary:hover,\n.tooltip--email-signup a.primary:focus {\n background: #2b55ca;\n}\n\n.tooltip--email-signup a.primary:active {\n background: #1e42a4;\n}\n\n.tooltip--email-signup button.ghost {\n background: transparent;\n color: #3969EF;\n}\n\n.tooltip--email-signup button.ghost:hover,\n.tooltip--email-signup button.ghost:focus {\n background-color: rgba(0, 0, 0, 0.06);\n color: #2b55ca;\n}\n\n.tooltip--email-signup button.ghost:active {\n background-color: rgba(0, 0, 0, 0.12);\n color: #1e42a4;\n}\n\n.tooltip--email-signup button.close-tooltip {\n background-color: transparent;\n background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iMTMiIHZpZXdCb3g9IjAgMCAxMiAxMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0wLjI5Mjg5NCAwLjY1NjkwN0MwLjY4MzQxOCAwLjI2NjM4MyAxLjMxNjU4IDAuMjY2MzgzIDEuNzA3MTEgMC42NTY5MDdMNiA0Ljk0OThMMTAuMjkyOSAwLjY1NjkwN0MxMC42ODM0IDAuMjY2MzgzIDExLjMxNjYgMC4yNjYzODMgMTEuNzA3MSAwLjY1NjkwN0MxMi4wOTc2IDEuMDQ3NDMgMTIuMDk3NiAxLjY4MDYgMTEuNzA3MSAyLjA3MTEyTDcuNDE0MjEgNi4zNjQwMUwxMS43MDcxIDEwLjY1NjlDMTIuMDk3NiAxMS4wNDc0IDEyLjA5NzYgMTEuNjgwNiAxMS43MDcxIDEyLjA3MTFDMTEuMzE2NiAxMi40NjE2IDEwLjY4MzQgMTIuNDYxNiAxMC4yOTI5IDEyLjA3MTFMNiA3Ljc3ODIzTDEuNzA3MTEgMTIuMDcxMUMxLjMxNjU4IDEyLjQ2MTYgMC42ODM0MTcgMTIuNDYxNiAwLjI5Mjg5MyAxMi4wNzExQy0wLjA5NzYzMTEgMTEuNjgwNiAtMC4wOTc2MzExIDExLjA0NzQgMC4yOTI4OTMgMTAuNjU2OUw0LjU4NTc5IDYuMzY0MDFMMC4yOTI4OTQgMi4wNzExMkMtMC4wOTc2MzA2IDEuNjgwNiAtMC4wOTc2MzA2IDEuMDQ3NDMgMC4yOTI4OTQgMC42NTY5MDdaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjg0Ii8+Cjwvc3ZnPgo=);\n background-position: center center;\n background-repeat: no-repeat;\n border: 0;\n cursor: pointer;\n padding: 16px;\n position: absolute;\n right: 12px;\n top: 12px;\n}\n"; - -},{}],62:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.buttonMatchesFormType = exports.autofillEnabled = exports.addInlineStyles = exports.SIGN_IN_MSG = exports.ADDRESS_DOMAIN = void 0; -exports.escapeXML = escapeXML; -exports.formatDuckAddress = void 0; -exports.getActiveElement = getActiveElement; -exports.isEventWithinDax = exports.isAutofillEnabledFromProcessedConfig = exports.getTextShallow = exports.getDaxBoundingBox = void 0; -exports.isFormLikelyToBeUsedAsPageWrapper = isFormLikelyToBeUsedAsPageWrapper; -exports.isLikelyASubmitButton = exports.isIncontextSignupEnabledFromProcessedConfig = void 0; -exports.isLocalNetwork = isLocalNetwork; -exports.isPotentiallyViewable = void 0; -exports.isValidTLD = isValidTLD; -exports.logPerformance = logPerformance; -exports.notifyWebApp = void 0; -exports.pierceShadowTree = pierceShadowTree; -exports.safeExecute = exports.removeInlineStyles = void 0; -exports.safeRegexTest = safeRegexTest; -exports.setValue = exports.sendAndWaitForAnswer = void 0; -exports.shouldLog = shouldLog; -exports.shouldLogPerformance = shouldLogPerformance; -exports.truncateFromMiddle = truncateFromMiddle; -exports.wasAutofilledByChrome = void 0; -exports.whenIdle = whenIdle; -var _matching = require("./Form/matching.js"); -var _constants = require("./constants.js"); -var _appleUtils = require("@duckduckgo/content-scope-scripts/src/apple-utils"); -const SIGN_IN_MSG = exports.SIGN_IN_MSG = { - signMeIn: true -}; - -// Send a message to the web app (only on DDG domains) -const notifyWebApp = message => { - window.postMessage(message, window.origin); -}; -/** - * Sends a message and returns a Promise that resolves with the response - * @param {{} | Function} msgOrFn - a fn to call or an object to send via postMessage - * @param {String} expectedResponse - the name of the response - * @returns {Promise<*>} - */ -exports.notifyWebApp = notifyWebApp; -const sendAndWaitForAnswer = (msgOrFn, expectedResponse) => { - if (typeof msgOrFn === 'function') { - msgOrFn(); - } else { - window.postMessage(msgOrFn, window.origin); - } - return new Promise(resolve => { - const handler = e => { - if (e.origin !== window.origin) return; - if (!e.data || e.data && !(e.data[expectedResponse] || e.data.type === expectedResponse)) return; - resolve(e.data); - window.removeEventListener('message', handler); - }; - window.addEventListener('message', handler); - }); -}; - -/** - * @param {Pick} globalConfig - * @return {boolean} - */ -exports.sendAndWaitForAnswer = sendAndWaitForAnswer; -const autofillEnabled = globalConfig => { - if (!globalConfig.contentScope) { - // Return enabled for platforms that haven't implemented the config yet - return true; - } - // already processed? this handles an edgecase in the extension where the config is already processed - if ('site' in globalConfig.contentScope) { - const enabled = isAutofillEnabledFromProcessedConfig(globalConfig.contentScope); - return enabled; - } - const { - contentScope, - userUnprotectedDomains, - userPreferences - } = globalConfig; - - // Check config on Apple platforms - const processedConfig = (0, _appleUtils.processConfig)(contentScope, userUnprotectedDomains, userPreferences); - return isAutofillEnabledFromProcessedConfig(processedConfig); -}; -exports.autofillEnabled = autofillEnabled; -const isAutofillEnabledFromProcessedConfig = processedConfig => { - const site = processedConfig.site; - if (site.isBroken || !site.enabledFeatures.includes('autofill')) { - if (shouldLog()) { - console.log('⚠️ Autofill disabled by remote config'); + async refreshAlias() { + await this.deviceApi.notify(createNotification("emailHandlerRefreshAlias")); + if (this.globalConfig.isApp) + this.getAddresses(); } - return false; - } - return true; -}; -exports.isAutofillEnabledFromProcessedConfig = isAutofillEnabledFromProcessedConfig; -const isIncontextSignupEnabledFromProcessedConfig = processedConfig => { - const site = processedConfig.site; - if (site.isBroken || !site.enabledFeatures.includes('incontextSignup')) { - if (shouldLog()) { - console.log('⚠️ In-context signup disabled by remote config'); + async _checkDeviceSignedIn() { + const { isAppSignedIn } = await this.deviceApi.request(createRequest("emailHandlerCheckAppSignedInStatus")); + this.isDeviceSignedIn = () => !!isAppSignedIn; + return !!isAppSignedIn; } - return false; - } - return true; -}; - -// Access the original setter (needed to bypass React's implementation on mobile) -// @ts-ignore -exports.isIncontextSignupEnabledFromProcessedConfig = isIncontextSignupEnabledFromProcessedConfig; -const originalSet = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set; - -/** - * Ensures the value is set properly and dispatches events to simulate real user action - * @param {HTMLInputElement} el - * @param {string} val - * @param {GlobalConfig} [config] - * @return {boolean} - */ -const setValueForInput = (el, val, config) => { - // Avoid keyboard flashing on Android - if (!config?.isAndroid) { - el.focus(); - } - - // todo(Shane): Not sending a 'key' property on these events can cause exceptions on 3rd party listeners that expect it - el.dispatchEvent(new Event('keydown', { - bubbles: true - })); - originalSet?.call(el, val); - const events = [new Event('input', { - bubbles: true - }), - // todo(Shane): Not sending a 'key' property on these events can cause exceptions on 3rd party listeners that expect it - new Event('keyup', { - bubbles: true - }), new Event('change', { - bubbles: true - })]; - events.forEach(ev => el.dispatchEvent(ev)); - // We call this again to make sure all forms are happy - originalSet?.call(el, val); - events.forEach(ev => el.dispatchEvent(ev)); - el.blur(); - return true; -}; - -/** - * Fires events on a select element to simulate user interaction - * @param {HTMLSelectElement} el - */ -const fireEventsOnSelect = el => { - /** @type {Event[]} */ - const events = [new Event('mousedown', { - bubbles: true - }), new Event('mouseup', { - bubbles: true - }), new Event('click', { - bubbles: true - }), new Event('change', { - bubbles: true - })]; - - // Events fire on the select el, not option - events.forEach(ev => el.dispatchEvent(ev)); - events.forEach(ev => el.dispatchEvent(ev)); - el.blur(); -}; - -/** - * Selects an option of a select element - * We assume Select is only used for dates, i.e. in the credit card - * @param {HTMLSelectElement} el - * @param {string} val - * @return {boolean} - */ -const setValueForSelect = (el, val) => { - const subtype = (0, _matching.getInputSubtype)(el); - const isMonth = subtype.includes('Month'); - const isZeroBasedNumber = isMonth && el.options[0].value === '0' && el.options.length === 12; - const stringVal = String(val); - const numberVal = Number(val); - - // Loop first through all values because they tend to be more precise - for (const option of el.options) { - // If values for months are zero-based (Jan === 0), add one to match our data type - let value = option.value; - if (isZeroBasedNumber) { - value = `${Number(value) + 1}`; - } - // TODO: try to match localised month names - // TODO: implement alternative versions of values (abbreviations for States/Provinces or variations like USA, US, United States, etc.) - if (value === stringVal || Number(value) === numberVal) { - if (option.selected) return false; - option.selected = true; - fireEventsOnSelect(el); - return true; + storeUserData({ addUserData: { token, userName, cohort } }) { + return this.deviceApi.notify(createNotification("emailHandlerStoreToken", { token, username: userName, cohort })); } - } - for (const option of el.options) { - if (option.innerText === stringVal || Number(option.innerText) === numberVal) { - if (option.selected) return false; - option.selected = true; - fireEventsOnSelect(el); - return true; + /** + * Used by the email web app + * Provides functionality to log the user out + */ + removeUserData() { + this.deviceApi.notify(createNotification("emailHandlerRemoveToken")); } - } - // If we didn't find a matching option return false - return false; -}; - -/** - * Sets or selects a value to a form element - * @param {HTMLInputElement | HTMLSelectElement} el - * @param {string} val - * @param {GlobalConfig} [config] - * @return {boolean} - */ -const setValue = (el, val, config) => { - if (el instanceof HTMLInputElement) return setValueForInput(el, val, config); - if (el instanceof HTMLSelectElement) return setValueForSelect(el, val); - return false; -}; - -/** - * Use IntersectionObserver v2 to make sure the element is visible when clicked - * https://developers.google.com/web/updates/2019/02/intersectionobserver-v2 - */ -exports.setValue = setValue; -const safeExecute = function (el, fn) { - let _opts = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - // TODO: temporary fix to misterious bug in Chrome - // const {checkVisibility = true} = opts - const intObs = new IntersectionObserver(changes => { - for (const change of changes) { - // Feature detection - if (typeof change.isVisible === 'undefined') { - // The browser doesn't support Intersection Observer v2, falling back to v1 behavior. - change.isVisible = true; - } - if (change.isIntersecting) { - /** - * If 'checkVisibility' is 'false' (like on Windows), then we always execute the function - * During testing it was found that windows does not `change.isVisible` properly. - */ - // TODO: temporary fix to misterious bug in Chrome - // if (!checkVisibility || change.isVisible) { - // fn() - // } - fn(); - } + /** + * Used by the email web app + * Provides functionality to close the window after in-context sign-up or sign-in + */ + closeEmailProtection() { + this.deviceApi.request(new CloseEmailProtectionTabCall(null)); } - intObs.disconnect(); - }, { - trackVisibility: true, - delay: 100 - }); - intObs.observe(el); -}; - -/** - * Checks that an element is potentially viewable (even if off-screen) - * @param {HTMLElement} el - * @return {boolean} - */ -exports.safeExecute = safeExecute; -const isPotentiallyViewable = el => { - const computedStyle = window.getComputedStyle(el); - const opacity = parseFloat(computedStyle.getPropertyValue('opacity') || '1'); - const visibility = computedStyle.getPropertyValue('visibility'); - const opacityThreshold = 0.6; - return el.clientWidth !== 0 && el.clientHeight !== 0 && opacity > opacityThreshold && visibility !== 'hidden'; -}; - -/** - * Gets the bounding box of the icon - * @param {HTMLInputElement} input - * @returns {{top: number, left: number, bottom: number, width: number, x: number, y: number, right: number, height: number}} - */ -exports.isPotentiallyViewable = isPotentiallyViewable; -const getDaxBoundingBox = input => { - const { - right: inputRight, - top: inputTop, - height: inputHeight - } = input.getBoundingClientRect(); - const inputRightPadding = parseInt(getComputedStyle(input).paddingRight); - const width = 30; - const height = 30; - const top = inputTop + (inputHeight - height) / 2; - const right = inputRight - inputRightPadding; - const left = right - width; - const bottom = top + height; - return { - bottom, - height, - left, - right, - top, - width, - x: left, - y: top - }; -}; - -/** - * Check if a mouse event is within the icon - * @param {MouseEvent} e - * @param {HTMLInputElement} input - * @returns {boolean} - */ -exports.getDaxBoundingBox = getDaxBoundingBox; -const isEventWithinDax = (e, input) => { - const { - left, - right, - top, - bottom - } = getDaxBoundingBox(input); - const withinX = e.clientX >= left && e.clientX <= right; - const withinY = e.clientY >= top && e.clientY <= bottom; - return withinX && withinY; -}; - -/** - * Adds inline styles from a prop:value object - * @param {HTMLElement} el - * @param {Object} styles - */ -exports.isEventWithinDax = isEventWithinDax; -const addInlineStyles = (el, styles) => Object.entries(styles).forEach(_ref => { - let [property, val] = _ref; - return el.style.setProperty(property, val, 'important'); -}); - -/** - * Removes inline styles from a prop:value object - * @param {HTMLElement} el - * @param {Object} styles - */ -exports.addInlineStyles = addInlineStyles; -const removeInlineStyles = (el, styles) => Object.keys(styles).forEach(property => el.style.removeProperty(property)); -exports.removeInlineStyles = removeInlineStyles; -const ADDRESS_DOMAIN = exports.ADDRESS_DOMAIN = '@duck.com'; -/** - * Given a username, returns the full email address - * @param {string} address - * @returns {string} - */ -const formatDuckAddress = address => address + ADDRESS_DOMAIN; - -/** - * Escapes any occurrences of &, ", <, > or / with XML entities. - * @param {string} str The string to escape. - * @return {string} The escaped string. - */ -exports.formatDuckAddress = formatDuckAddress; -function escapeXML(str) { - const replacements = { - '&': '&', - '"': '"', - "'": ''', - '<': '<', - '>': '>', - '/': '/' - }; - return String(str).replace(/[&"'<>/]/g, m => replacements[m]); -} - -/** - * Determines if an element is likely to be a submit button - * @param {HTMLElement} el A button, input, anchor or other element with role=button - * @param {import("./Form/matching").Matching} matching - * @return {boolean} - */ -const isLikelyASubmitButton = (el, matching) => { - const text = getTextShallow(el); - const ariaLabel = el.getAttribute('aria-label') || ''; - const dataTestId = el.getAttribute('data-test-id') || ''; - if ((el.getAttribute('type') === 'submit' || - // is explicitly set as "submit" - el.getAttribute('name') === 'submit') && - // is called "submit" - !safeRegexTest(matching.getDDGMatcherRegex('submitButtonUnlikelyRegex'), text + ' ' + ariaLabel)) return true; - return (safeRegexTest(/primary|submit/i, el.className) || - // has high-signal submit classes - safeRegexTest(/submit/i, dataTestId) || safeRegexTest(matching.getDDGMatcherRegex('submitButtonRegex'), text) || - // has high-signal text - el.offsetHeight * el.offsetWidth >= 10000 && !safeRegexTest(/secondary/i, el.className) // it's a large element 250x40px - ) && el.offsetHeight * el.offsetWidth >= 2000 && - // it's not a very small button like inline links and such - !safeRegexTest(matching.getDDGMatcherRegex('submitButtonUnlikelyRegex'), text + ' ' + ariaLabel); -}; - -/** - * Check that a button matches the form type - login buttons on a login form, signup buttons on a signup form - * @param {HTMLElement} el - * @param {import('./Form/Form').Form} formObj - */ -exports.isLikelyASubmitButton = isLikelyASubmitButton; -const buttonMatchesFormType = (el, formObj) => { - if (formObj.isLogin) { - return !safeRegexTest(/sign.?up|register|join/i, el.textContent || ''); - } else if (formObj.isSignup) { - return !safeRegexTest(/(log|sign).?([io])n/i, el.textContent || ''); - } else { - return true; - } -}; -exports.buttonMatchesFormType = buttonMatchesFormType; -const buttonInputTypes = ['submit', 'button']; -/** - * Get the text of an element, one level deep max - * @param {Node} el - * @returns {string} - */ -const getTextShallow = el => { - // for buttons, we don't care about descendants, just get the whole text as is - // this is important in order to give proper attribution of the text to the button - if (el instanceof HTMLButtonElement) return (0, _matching.removeExcessWhitespace)(el.textContent); - if (el instanceof HTMLInputElement) { - if (buttonInputTypes.includes(el.type)) { - return el.value; + /** + * PM endpoints + */ + /** + * Gets the init data from the device + * @returns {APIResponse} + */ + async _getAutofillInitData() { + const response = await this.deviceApi.request(createRequest("pmHandlerGetAutofillInitData")); + this.storeLocalData(response.success); + return response; } - if (el.type === 'image') { - return (0, _matching.removeExcessWhitespace)(el.alt || el.value || el.title || el.name); + /** + * Gets credentials ready for autofill + * @param {CredentialsObject['id']} id - the credential id + * @returns {APIResponseSingle} + */ + getAutofillCredentials(id) { + return this.deviceApi.request(createRequest("pmHandlerGetAutofillCredentials", { id })); } - } - let text = ''; - for (const childNode of el.childNodes) { - if (childNode instanceof Text) { - text += ' ' + childNode.textContent; + /** + * Opens the native UI for managing passwords + */ + openManagePasswords() { + return this.deviceApi.notify(createNotification("pmHandlerOpenManagePasswords")); + } + /** + * Gets a single identity obj once the user requests it + * @param {IdentityObject['id']} id + * @returns {Promise<{success: IdentityObject|undefined}>} + */ + getAutofillIdentity(id) { + const identity = this.getLocalIdentities().find(({ id: identityId }) => `${identityId}` === `${id}`); + return Promise.resolve({ success: identity }); + } + /** + * Gets a single complete credit card obj once the user requests it + * @param {CreditCardObject['id']} id + * @returns {APIResponse} + */ + getAutofillCreditCard(id) { + return this.deviceApi.request(createRequest("pmHandlerGetCreditCard", { id })); + } + getCurrentInputType() { + const topContextData = this.getTopContextData(); + return topContextData?.inputType ? topContextData.inputType : getInputType(this.activeForm?.activeInput); + } + /** + * @returns {Promise} + */ + async getAlias() { + const { alias } = await this.deviceApi.request(new GetAlias({ + requiresUserPermission: !this.globalConfig.isApp, + shouldConsumeAliasIfProvided: !this.globalConfig.isApp, + isIncontextSignupAvailable: this.inContextSignup.isAvailable() + })); + return alias ? formatDuckAddress(alias) : alias; + } + addLogoutListener(handler) { + if (!this.globalConfig.isDDGDomain) + return; + window.addEventListener("message", (e) => { + if (this.globalConfig.isDDGDomain && e.data.emailProtectionSignedOut) { + handler(); + } + }); + } + async addDeviceListeners() { + this.thirdPartyProvider.init(); + } + /** + * Poll the native listener until the user has selected a credential. + * Message return types are: + * - 'stop' is returned whenever the message sent doesn't match the native last opened tooltip. + * - This also is triggered when the close event is called and prevents any edge case continued polling. + * - 'ok' is when the user has selected a credential and the value can be injected into the page. + * - 'none' is when the tooltip is open in the native window however hasn't been entered. + * @param {(response: {data:IdentityObject|CreditCardObject|CredentialsObject, configType: string} | {stateChange: boolean} | {stop: boolean} | null) => void} callback + */ + async _listenForSelectedCredential(callback) { + const poll = async () => { + clearTimeout(this.pollingTimeout); + const response = await this.getSelectedCredentials(); + switch (response.type) { + case "none": + this.pollingTimeout = setTimeout(() => poll(), 100); + return; + case "ok": { + await callback({ data: response.data, configType: response.configType }); + return; + } + case "state": { + await callback({ stateChange: true }); + this.pollingTimeout = setTimeout(() => poll(), 100); + return; + } + case "stop": + await callback({ stop: true }); + } + }; + poll(); } - } - return (0, _matching.removeExcessWhitespace)(text); -}; - -/** - * Check if hostname is a local address - * @param {string} [hostname] - * @returns {boolean} - */ -exports.getTextShallow = getTextShallow; -function isLocalNetwork() { - let hostname = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : window.location.hostname; - return ['localhost', '', '::1'].includes(hostname) || hostname.includes('127.0.0.1') || hostname.includes('192.168.') || hostname.startsWith('10.0.') || hostname.endsWith('.local') || hostname.endsWith('.internal'); -} - -// Extracted from lib/DDG/Util/Constants.pm -const tldrs = /\.(?:c(?:o(?:m|op)?|at?|[iykgdmnxruhcfzvl])|o(?:rg|m)|n(?:et?|a(?:me)?|[ucgozrfpil])|e(?:d?u|[gechstr])|i(?:n(?:t|fo)?|[stqldroem])|m(?:o(?:bi)?|u(?:seum)?|i?l|[mcyvtsqhaerngxzfpwkd])|g(?:ov|[glqeriabtshdfmuywnp])|b(?:iz?|[drovfhtaywmzjsgbenl])|t(?:r(?:avel)?|[ncmfzdvkopthjwg]|e?l)|k[iemygznhwrp]|s[jtvberindlucygkhaozm]|u[gymszka]|h[nmutkr]|r[owesu]|d[kmzoej]|a(?:e(?:ro)?|r(?:pa)?|[qofiumsgzlwcnxdt])|p(?:ro?|[sgnthfymakwle])|v[aegiucn]|l[sayuvikcbrt]|j(?:o(?:bs)?|[mep])|w[fs]|z[amw]|f[rijkom]|y[eut]|qa)$/i; -/** - * Check if hostname is a valid top-level domain - * @param {string} [hostname] - * @returns {boolean} - */ -function isValidTLD() { - let hostname = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : window.location.hostname; - return tldrs.test(hostname) || hostname === 'fill.dev'; -} - -/** - * Chrome's UA adds styles using this selector when using the built-in autofill - * @param {HTMLInputElement} input - * @returns {boolean} - */ -const wasAutofilledByChrome = input => { - try { - // Other browsers throw because the selector is invalid - return input.matches('input:-internal-autofill-selected'); - } catch (e) { - return false; - } -}; - -/** - * Checks if we should log form analysis debug info to the console - * @returns {boolean} - */ -exports.wasAutofilledByChrome = wasAutofilledByChrome; -function shouldLog() { - return readDebugSetting('ddg-autofill-debug'); -} - -/** - * Checks if we should log performance info to the console - * @returns {boolean} - */ -function shouldLogPerformance() { - return readDebugSetting('ddg-autofill-perf'); -} - -/** - * Check if a sessionStorage item is set to 'true' - * @param setting - * @returns {boolean} - */ -function readDebugSetting(setting) { - // sessionStorage throws in invalid schemes like data: and file: - try { - return window.sessionStorage?.getItem(setting) === 'true'; - } catch (e) { - return false; - } -} -function logPerformance(markName) { - if (shouldLogPerformance()) { - const measurement = window.performance?.measure(`${markName}:init`, `${markName}:init:start`, `${markName}:init:end`); - console.log(`${markName} took ${Math.round(measurement?.duration)}ms`); - window.performance?.clearMarks(); - } -} - -/** - * - * @param {Function} callback - * @returns {Function} - */ -function whenIdle(callback) { - var _this = this; - let timer; - return function () { - for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { - args[_key] = arguments[_key]; - } - cancelIdleCallback(timer); - timer = requestIdleCallback(() => callback.apply(_this, args)); - }; -} - -/** - * Truncate string from the middle if exceeds the totalLength (default: 30) - * @param {string} string - * @param {number} totalLength - * @returns {string} - */ -function truncateFromMiddle(string) { - let totalLength = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 30; - if (totalLength < 4) { - throw new Error('Do not use with strings shorter than 4'); - } - if (string.length <= totalLength) return string; - const truncated = string.slice(0, totalLength / 2).concat('…', string.slice(totalLength / -2)); - return truncated; -} - -/** - * Determines if the form is likely to be enclosing most of the DOM - * @param {HTMLFormElement} form - * @returns {boolean} - */ -function isFormLikelyToBeUsedAsPageWrapper(form) { - if (form.parentElement !== document.body) return false; - const formChildren = form.querySelectorAll('*').length; - // If the form has few content elements, it's unlikely to cause issues anyway - if (formChildren < 100) return false; - const bodyChildren = document.body.querySelectorAll('*').length; - - /** - * Percentage of the formChildren on the total body elements - * form * 100 / body = x - */ - const formChildrenPercentage = formChildren * 100 / bodyChildren; - return formChildrenPercentage > 50; -} - -/** - * Wrapper around RegExp.test that safeguard against checking huge strings - * @param {RegExp | undefined} regex - * @param {String} string - * @returns {boolean} - */ -function safeRegexTest(regex, string) { - if (!string || !regex || string.length > _constants.constants.TEXT_LENGTH_CUTOFF) return false; - return regex.test(string); -} - -/** - * Returns the event target, or an element that matches wantedTargetType, piercing the shadow tree - * @param {PointerEvent | FocusEvent} event - * @param {typeof Element} [wantedTargetType] - * @returns {EventTarget | null} - */ -function pierceShadowTree(event, wantedTargetType) { - const { - target - } = event; - - // Sanity checks - if (!(target instanceof Element) || !target?.shadowRoot || !event.composedPath) return target; - const clickStack = event.composedPath(); - - // If we're not looking for a specific element, get the top of the stack - if (!wantedTargetType) { - return clickStack[0]; - } - - // Otherwise, search the wanted target, or return the original target - return clickStack.find(el => el instanceof wantedTargetType) || target; -} - -/** - * Return the active element, piercing through shadow DOMs, or null - * @param {Document | DocumentOrShadowRoot} root - * @returns {Element | null} - */ -function getActiveElement() { - let root = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document; - const activeElement = root.activeElement; - if (!(activeElement instanceof Element) || !activeElement.shadowRoot) return activeElement; - const innerActiveElement = activeElement.shadowRoot.activeElement; - if (innerActiveElement?.shadowRoot) { - return getActiveElement(innerActiveElement.shadowRoot); - } - return innerActiveElement; -} - -},{"./Form/matching.js":43,"./constants.js":65,"@duckduckgo/content-scope-scripts/src/apple-utils":1}],63:[function(require,module,exports){ -"use strict"; - -require("./requestIdleCallback.js"); -var _DeviceInterface = require("./DeviceInterface.js"); -var _autofillUtils = require("./autofill-utils.js"); -// Polyfills/shims - -(() => { - if ((0, _autofillUtils.shouldLog)()) { - console.log('DuckDuckGo Autofill Active'); - } - if (!window.isSecureContext) return false; - try { - const startupAutofill = () => { - if (document.visibilityState === 'visible') { - const deviceInterface = (0, _DeviceInterface.createDevice)(); - deviceInterface.init(); - } else { - document.addEventListener('visibilitychange', startupAutofill, { - once: true - }); - } - }; - startupAutofill(); - } catch (e) { - console.error(e); - // Noop, we errored - } -})(); - -},{"./DeviceInterface.js":22,"./autofill-utils.js":62,"./requestIdleCallback.js":74}],64:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.DDG_DOMAIN_REGEX = void 0; -exports.createGlobalConfig = createGlobalConfig; -const DDG_DOMAIN_REGEX = exports.DDG_DOMAIN_REGEX = new RegExp(/^https:\/\/(([a-z0-9-_]+?)\.)?duckduckgo\.com\/email/); - -/** - * This is a centralised place to contain all string/variable replacements - * - * @param {Partial} [overrides] - * @returns {GlobalConfig} - */ -function createGlobalConfig(overrides) { - let isApp = false; - let isTopFrame = false; - let supportsTopFrame = false; - // Do not remove -- Apple devices change this when they support modern webkit messaging - let hasModernWebkitAPI = false; - // INJECT isApp HERE - // INJECT isTopFrame HERE - // INJECT supportsTopFrame HERE - // INJECT hasModernWebkitAPI HERE - - let isWindows = false; - // INJECT isWindows HERE - - // This will be used when 'hasModernWebkitAPI' is false - /** @type {string[]} */ - let webkitMessageHandlerNames = []; - // INJECT webkitMessageHandlerNames HERE - - let isDDGTestMode = false; - isDDGTestMode = true; - - let contentScope = null; - let userUnprotectedDomains = null; - /** @type {Record | null} */ - let userPreferences = null; - // INJECT contentScope HERE - // INJECT userUnprotectedDomains HERE - // INJECT userPreferences HERE - - /** @type {Record | null} */ - let availableInputTypes = null; - // INJECT availableInputTypes HERE - - // The native layer will inject a randomised secret here and use it to verify the origin - let secret = 'PLACEHOLDER_SECRET'; - - // @ts-ignore - const isAndroid = userPreferences?.platform.name === 'android'; - // @ts-ignore - const isDDGApp = ['ios', 'android', 'macos', 'windows'].includes(userPreferences?.platform.name) || isWindows; - // @ts-ignore - const isMobileApp = ['ios', 'android'].includes(userPreferences?.platform.name); - const isFirefox = navigator.userAgent.includes('Firefox'); - const isDDGDomain = Boolean(window.location.href.match(DDG_DOMAIN_REGEX)); - const isExtension = false; - const config = { - isApp, - isDDGApp, - isAndroid, - isFirefox, - isMobileApp, - isExtension, - isTopFrame, - isWindows, - secret, - supportsTopFrame, - hasModernWebkitAPI, - contentScope, - userUnprotectedDomains, - userPreferences, - isDDGTestMode, - isDDGDomain, - availableInputTypes, - webkitMessageHandlerNames, - ...overrides }; - return config; -} - -},{}],65:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.constants = void 0; -const constants = exports.constants = { - ATTR_INPUT_TYPE: 'data-ddg-inputType', - ATTR_AUTOFILL: 'data-ddg-autofill', - TEXT_LENGTH_CUTOFF: 100, - MAX_INPUTS_PER_PAGE: 100, - MAX_FORMS_PER_PAGE: 30, - MAX_INPUTS_PER_FORM: 80, - MAX_FORM_RESCANS: 50 -}; - -},{}],66:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.StoreFormDataCall = exports.StartEmailProtectionSignupCall = exports.ShowInContextEmailProtectionSignupPromptCall = exports.SetSizeCall = exports.SetIncontextSignupPermanentlyDismissedAtCall = exports.SendJSPixelCall = exports.SelectedDetailCall = exports.OpenManagePasswordsCall = exports.OpenManageIdentitiesCall = exports.OpenManageCreditCardsCall = exports.GetRuntimeConfigurationCall = exports.GetIncontextSignupDismissedAtCall = exports.GetAvailableInputTypesCall = exports.GetAutofillInitDataCall = exports.GetAutofillDataCall = exports.GetAutofillCredentialsCall = exports.EmailProtectionStoreUserDataCall = exports.EmailProtectionRemoveUserDataCall = exports.EmailProtectionRefreshPrivateAddressCall = exports.EmailProtectionGetUserDataCall = exports.EmailProtectionGetIsLoggedInCall = exports.EmailProtectionGetCapabilitiesCall = exports.EmailProtectionGetAddressesCall = exports.CloseEmailProtectionTabCall = exports.CloseAutofillParentCall = exports.CheckCredentialsProviderStatusCall = exports.AskToUnlockProviderCall = exports.AddDebugFlagCall = void 0; -var _validatorsZod = require("./validators.zod.js"); -var _deviceApi = require("../../../packages/device-api"); -/* DO NOT EDIT, this file was generated by scripts/api-call-generator.js */ -/** - * @extends {DeviceApiCall} - */ -class AddDebugFlagCall extends _deviceApi.DeviceApiCall { - method = "addDebugFlag"; - paramsValidator = _validatorsZod.addDebugFlagParamsSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.AddDebugFlagCall = AddDebugFlagCall; -class GetAutofillDataCall extends _deviceApi.DeviceApiCall { - method = "getAutofillData"; - id = "getAutofillDataResponse"; - paramsValidator = _validatorsZod.getAutofillDataRequestSchema; - resultValidator = _validatorsZod.getAutofillDataResponseSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.GetAutofillDataCall = GetAutofillDataCall; -class GetRuntimeConfigurationCall extends _deviceApi.DeviceApiCall { - method = "getRuntimeConfiguration"; - id = "getRuntimeConfigurationResponse"; - resultValidator = _validatorsZod.getRuntimeConfigurationResponseSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.GetRuntimeConfigurationCall = GetRuntimeConfigurationCall; -class StoreFormDataCall extends _deviceApi.DeviceApiCall { - method = "storeFormData"; - paramsValidator = _validatorsZod.storeFormDataSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.StoreFormDataCall = StoreFormDataCall; -class GetAvailableInputTypesCall extends _deviceApi.DeviceApiCall { - method = "getAvailableInputTypes"; - id = "getAvailableInputTypesResponse"; - resultValidator = _validatorsZod.getAvailableInputTypesResultSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.GetAvailableInputTypesCall = GetAvailableInputTypesCall; -class GetAutofillInitDataCall extends _deviceApi.DeviceApiCall { - method = "getAutofillInitData"; - id = "getAutofillInitDataResponse"; - resultValidator = _validatorsZod.getAutofillInitDataResponseSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.GetAutofillInitDataCall = GetAutofillInitDataCall; -class GetAutofillCredentialsCall extends _deviceApi.DeviceApiCall { - method = "getAutofillCredentials"; - id = "getAutofillCredentialsResponse"; - paramsValidator = _validatorsZod.getAutofillCredentialsParamsSchema; - resultValidator = _validatorsZod.getAutofillCredentialsResultSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.GetAutofillCredentialsCall = GetAutofillCredentialsCall; -class SetSizeCall extends _deviceApi.DeviceApiCall { - method = "setSize"; - paramsValidator = _validatorsZod.setSizeParamsSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.SetSizeCall = SetSizeCall; -class SelectedDetailCall extends _deviceApi.DeviceApiCall { - method = "selectedDetail"; - paramsValidator = _validatorsZod.selectedDetailParamsSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.SelectedDetailCall = SelectedDetailCall; -class CloseAutofillParentCall extends _deviceApi.DeviceApiCall { - method = "closeAutofillParent"; -} -/** - * @extends {DeviceApiCall} - */ -exports.CloseAutofillParentCall = CloseAutofillParentCall; -class AskToUnlockProviderCall extends _deviceApi.DeviceApiCall { - method = "askToUnlockProvider"; - id = "askToUnlockProviderResponse"; - resultValidator = _validatorsZod.askToUnlockProviderResultSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.AskToUnlockProviderCall = AskToUnlockProviderCall; -class CheckCredentialsProviderStatusCall extends _deviceApi.DeviceApiCall { - method = "checkCredentialsProviderStatus"; - id = "checkCredentialsProviderStatusResponse"; - resultValidator = _validatorsZod.checkCredentialsProviderStatusResultSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.CheckCredentialsProviderStatusCall = CheckCredentialsProviderStatusCall; -class SendJSPixelCall extends _deviceApi.DeviceApiCall { - method = "sendJSPixel"; - paramsValidator = _validatorsZod.sendJSPixelParamsSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.SendJSPixelCall = SendJSPixelCall; -class SetIncontextSignupPermanentlyDismissedAtCall extends _deviceApi.DeviceApiCall { - method = "setIncontextSignupPermanentlyDismissedAt"; - paramsValidator = _validatorsZod.setIncontextSignupPermanentlyDismissedAtSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.SetIncontextSignupPermanentlyDismissedAtCall = SetIncontextSignupPermanentlyDismissedAtCall; -class GetIncontextSignupDismissedAtCall extends _deviceApi.DeviceApiCall { - method = "getIncontextSignupDismissedAt"; - id = "getIncontextSignupDismissedAt"; - resultValidator = _validatorsZod.getIncontextSignupDismissedAtSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.GetIncontextSignupDismissedAtCall = GetIncontextSignupDismissedAtCall; -class OpenManagePasswordsCall extends _deviceApi.DeviceApiCall { - method = "openManagePasswords"; -} -/** - * @extends {DeviceApiCall} - */ -exports.OpenManagePasswordsCall = OpenManagePasswordsCall; -class OpenManageCreditCardsCall extends _deviceApi.DeviceApiCall { - method = "openManageCreditCards"; -} -/** - * @extends {DeviceApiCall} - */ -exports.OpenManageCreditCardsCall = OpenManageCreditCardsCall; -class OpenManageIdentitiesCall extends _deviceApi.DeviceApiCall { - method = "openManageIdentities"; -} -/** - * @extends {DeviceApiCall} - */ -exports.OpenManageIdentitiesCall = OpenManageIdentitiesCall; -class EmailProtectionStoreUserDataCall extends _deviceApi.DeviceApiCall { - method = "emailProtectionStoreUserData"; - id = "emailProtectionStoreUserDataResponse"; - paramsValidator = _validatorsZod.emailProtectionStoreUserDataParamsSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.EmailProtectionStoreUserDataCall = EmailProtectionStoreUserDataCall; -class EmailProtectionRemoveUserDataCall extends _deviceApi.DeviceApiCall { - method = "emailProtectionRemoveUserData"; -} -/** - * @extends {DeviceApiCall} - */ -exports.EmailProtectionRemoveUserDataCall = EmailProtectionRemoveUserDataCall; -class EmailProtectionGetIsLoggedInCall extends _deviceApi.DeviceApiCall { - method = "emailProtectionGetIsLoggedIn"; - id = "emailProtectionGetIsLoggedInResponse"; - resultValidator = _validatorsZod.emailProtectionGetIsLoggedInResultSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.EmailProtectionGetIsLoggedInCall = EmailProtectionGetIsLoggedInCall; -class EmailProtectionGetUserDataCall extends _deviceApi.DeviceApiCall { - method = "emailProtectionGetUserData"; - id = "emailProtectionGetUserDataResponse"; - resultValidator = _validatorsZod.emailProtectionGetUserDataResultSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.EmailProtectionGetUserDataCall = EmailProtectionGetUserDataCall; -class EmailProtectionGetCapabilitiesCall extends _deviceApi.DeviceApiCall { - method = "emailProtectionGetCapabilities"; - id = "emailProtectionGetCapabilitiesResponse"; - resultValidator = _validatorsZod.emailProtectionGetCapabilitiesResultSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.EmailProtectionGetCapabilitiesCall = EmailProtectionGetCapabilitiesCall; -class EmailProtectionGetAddressesCall extends _deviceApi.DeviceApiCall { - method = "emailProtectionGetAddresses"; - id = "emailProtectionGetAddressesResponse"; - resultValidator = _validatorsZod.emailProtectionGetAddressesResultSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.EmailProtectionGetAddressesCall = EmailProtectionGetAddressesCall; -class EmailProtectionRefreshPrivateAddressCall extends _deviceApi.DeviceApiCall { - method = "emailProtectionRefreshPrivateAddress"; - id = "emailProtectionRefreshPrivateAddressResponse"; - resultValidator = _validatorsZod.emailProtectionRefreshPrivateAddressResultSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.EmailProtectionRefreshPrivateAddressCall = EmailProtectionRefreshPrivateAddressCall; -class StartEmailProtectionSignupCall extends _deviceApi.DeviceApiCall { - method = "startEmailProtectionSignup"; -} -/** - * @extends {DeviceApiCall} - */ -exports.StartEmailProtectionSignupCall = StartEmailProtectionSignupCall; -class CloseEmailProtectionTabCall extends _deviceApi.DeviceApiCall { - method = "closeEmailProtectionTab"; -} -/** - * @extends {DeviceApiCall} - */ -exports.CloseEmailProtectionTabCall = CloseEmailProtectionTabCall; -class ShowInContextEmailProtectionSignupPromptCall extends _deviceApi.DeviceApiCall { - method = "ShowInContextEmailProtectionSignupPrompt"; - id = "ShowInContextEmailProtectionSignupPromptResponse"; - resultValidator = _validatorsZod.showInContextEmailProtectionSignupPromptSchema; -} -exports.ShowInContextEmailProtectionSignupPromptCall = ShowInContextEmailProtectionSignupPromptCall; - -},{"../../../packages/device-api":12,"./validators.zod.js":67}],67:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.userPreferencesSchema = exports.triggerContextSchema = exports.storeFormDataSchema = exports.showInContextEmailProtectionSignupPromptSchema = exports.setSizeParamsSchema = exports.setIncontextSignupPermanentlyDismissedAtSchema = exports.sendJSPixelParamsSchema = exports.selectedDetailParamsSchema = exports.runtimeConfigurationSchema = exports.providerStatusUpdatedSchema = exports.outgoingCredentialsSchema = exports.getRuntimeConfigurationResponseSchema = exports.getIncontextSignupDismissedAtSchema = exports.getAvailableInputTypesResultSchema = exports.getAutofillInitDataResponseSchema = exports.getAutofillDataResponseSchema = exports.getAutofillDataRequestSchema = exports.getAutofillCredentialsResultSchema = exports.getAutofillCredentialsParamsSchema = exports.getAliasResultSchema = exports.getAliasParamsSchema = exports.genericErrorSchema = exports.generatedPasswordSchema = exports.emailProtectionStoreUserDataParamsSchema = exports.emailProtectionRefreshPrivateAddressResultSchema = exports.emailProtectionGetUserDataResultSchema = exports.emailProtectionGetIsLoggedInResultSchema = exports.emailProtectionGetCapabilitiesResultSchema = exports.emailProtectionGetAddressesResultSchema = exports.credentialsSchema = exports.contentScopeSchema = exports.checkCredentialsProviderStatusResultSchema = exports.availableInputTypesSchema = exports.availableInputTypes1Schema = exports.autofillSettingsSchema = exports.autofillFeatureTogglesSchema = exports.askToUnlockProviderResultSchema = exports.apiSchema = exports.addDebugFlagParamsSchema = void 0; -var _zod = require("zod"); -/* DO NOT EDIT, this file was generated by scripts/api-call-generator.js */ -// Generated by ts-to-zod - -const sendJSPixelParamsSchema = exports.sendJSPixelParamsSchema = _zod.z.union([_zod.z.object({ - pixelName: _zod.z.literal("autofill_identity"), - params: _zod.z.object({ - fieldType: _zod.z.string().optional() - }).optional() -}), _zod.z.object({ - pixelName: _zod.z.literal("autofill_show") -}), _zod.z.object({ - pixelName: _zod.z.literal("autofill_personal_address") -}), _zod.z.object({ - pixelName: _zod.z.literal("autofill_private_address") -}), _zod.z.object({ - pixelName: _zod.z.literal("incontext_show") -}), _zod.z.object({ - pixelName: _zod.z.literal("incontext_primary_cta") -}), _zod.z.object({ - pixelName: _zod.z.literal("incontext_dismiss_persisted") -}), _zod.z.object({ - pixelName: _zod.z.literal("incontext_close_x") -})]); -const addDebugFlagParamsSchema = exports.addDebugFlagParamsSchema = _zod.z.object({ - flag: _zod.z.string() -}); -const getAutofillCredentialsParamsSchema = exports.getAutofillCredentialsParamsSchema = _zod.z.object({ - id: _zod.z.string() -}); -const setSizeParamsSchema = exports.setSizeParamsSchema = _zod.z.object({ - height: _zod.z.number(), - width: _zod.z.number() -}); -const selectedDetailParamsSchema = exports.selectedDetailParamsSchema = _zod.z.object({ - data: _zod.z.record(_zod.z.unknown()), - configType: _zod.z.string() -}); -const setIncontextSignupPermanentlyDismissedAtSchema = exports.setIncontextSignupPermanentlyDismissedAtSchema = _zod.z.object({ - value: _zod.z.number().optional() -}); -const getIncontextSignupDismissedAtSchema = exports.getIncontextSignupDismissedAtSchema = _zod.z.object({ - success: _zod.z.object({ - permanentlyDismissedAt: _zod.z.number().optional(), - isInstalledRecently: _zod.z.boolean().optional() - }) -}); -const getAliasParamsSchema = exports.getAliasParamsSchema = _zod.z.object({ - requiresUserPermission: _zod.z.boolean(), - shouldConsumeAliasIfProvided: _zod.z.boolean(), - isIncontextSignupAvailable: _zod.z.boolean().optional() -}); -const getAliasResultSchema = exports.getAliasResultSchema = _zod.z.object({ - success: _zod.z.object({ - alias: _zod.z.string().optional() - }) -}); -const emailProtectionStoreUserDataParamsSchema = exports.emailProtectionStoreUserDataParamsSchema = _zod.z.object({ - token: _zod.z.string(), - userName: _zod.z.string(), - cohort: _zod.z.string() -}); -const showInContextEmailProtectionSignupPromptSchema = exports.showInContextEmailProtectionSignupPromptSchema = _zod.z.object({ - success: _zod.z.object({ - isSignedIn: _zod.z.boolean() - }) -}); -const generatedPasswordSchema = exports.generatedPasswordSchema = _zod.z.object({ - value: _zod.z.string(), - username: _zod.z.string() -}); -const triggerContextSchema = exports.triggerContextSchema = _zod.z.object({ - inputTop: _zod.z.number(), - inputLeft: _zod.z.number(), - inputHeight: _zod.z.number(), - inputWidth: _zod.z.number(), - wasFromClick: _zod.z.boolean() -}); -const credentialsSchema = exports.credentialsSchema = _zod.z.object({ - id: _zod.z.string().optional(), - username: _zod.z.string(), - password: _zod.z.string(), - origin: _zod.z.object({ - url: _zod.z.string() - }).optional(), - credentialsProvider: _zod.z.union([_zod.z.literal("duckduckgo"), _zod.z.literal("bitwarden")]).optional(), - providerStatus: _zod.z.union([_zod.z.literal("locked"), _zod.z.literal("unlocked")]).optional() -}); -const genericErrorSchema = exports.genericErrorSchema = _zod.z.object({ - message: _zod.z.string() -}); -const contentScopeSchema = exports.contentScopeSchema = _zod.z.object({ - features: _zod.z.record(_zod.z.object({ - exceptions: _zod.z.array(_zod.z.unknown()), - state: _zod.z.union([_zod.z.literal("enabled"), _zod.z.literal("disabled")]), - settings: _zod.z.record(_zod.z.unknown()).optional() - })), - unprotectedTemporary: _zod.z.array(_zod.z.unknown()) -}); -const userPreferencesSchema = exports.userPreferencesSchema = _zod.z.object({ - globalPrivacyControlValue: _zod.z.boolean().optional(), - sessionKey: _zod.z.string().optional(), - debug: _zod.z.boolean(), - platform: _zod.z.object({ - name: _zod.z.union([_zod.z.literal("ios"), _zod.z.literal("macos"), _zod.z.literal("windows"), _zod.z.literal("extension"), _zod.z.literal("android"), _zod.z.literal("unknown")]) - }), - features: _zod.z.record(_zod.z.object({ - settings: _zod.z.record(_zod.z.unknown()) - })) -}); -const outgoingCredentialsSchema = exports.outgoingCredentialsSchema = _zod.z.object({ - username: _zod.z.string().optional(), - password: _zod.z.string().optional() -}); -const availableInputTypesSchema = exports.availableInputTypesSchema = _zod.z.object({ - credentials: _zod.z.object({ - username: _zod.z.boolean().optional(), - password: _zod.z.boolean().optional() - }).optional(), - identities: _zod.z.object({ - firstName: _zod.z.boolean().optional(), - middleName: _zod.z.boolean().optional(), - lastName: _zod.z.boolean().optional(), - birthdayDay: _zod.z.boolean().optional(), - birthdayMonth: _zod.z.boolean().optional(), - birthdayYear: _zod.z.boolean().optional(), - addressStreet: _zod.z.boolean().optional(), - addressStreet2: _zod.z.boolean().optional(), - addressCity: _zod.z.boolean().optional(), - addressProvince: _zod.z.boolean().optional(), - addressPostalCode: _zod.z.boolean().optional(), - addressCountryCode: _zod.z.boolean().optional(), - phone: _zod.z.boolean().optional(), - emailAddress: _zod.z.boolean().optional() - }).optional(), - creditCards: _zod.z.object({ - cardName: _zod.z.boolean().optional(), - cardSecurityCode: _zod.z.boolean().optional(), - expirationMonth: _zod.z.boolean().optional(), - expirationYear: _zod.z.boolean().optional(), - cardNumber: _zod.z.boolean().optional() - }).optional(), - email: _zod.z.boolean().optional(), - credentialsProviderStatus: _zod.z.union([_zod.z.literal("locked"), _zod.z.literal("unlocked")]).optional() -}); -const availableInputTypes1Schema = exports.availableInputTypes1Schema = _zod.z.object({ - credentials: _zod.z.object({ - username: _zod.z.boolean().optional(), - password: _zod.z.boolean().optional() - }).optional(), - identities: _zod.z.object({ - firstName: _zod.z.boolean().optional(), - middleName: _zod.z.boolean().optional(), - lastName: _zod.z.boolean().optional(), - birthdayDay: _zod.z.boolean().optional(), - birthdayMonth: _zod.z.boolean().optional(), - birthdayYear: _zod.z.boolean().optional(), - addressStreet: _zod.z.boolean().optional(), - addressStreet2: _zod.z.boolean().optional(), - addressCity: _zod.z.boolean().optional(), - addressProvince: _zod.z.boolean().optional(), - addressPostalCode: _zod.z.boolean().optional(), - addressCountryCode: _zod.z.boolean().optional(), - phone: _zod.z.boolean().optional(), - emailAddress: _zod.z.boolean().optional() - }).optional(), - creditCards: _zod.z.object({ - cardName: _zod.z.boolean().optional(), - cardSecurityCode: _zod.z.boolean().optional(), - expirationMonth: _zod.z.boolean().optional(), - expirationYear: _zod.z.boolean().optional(), - cardNumber: _zod.z.boolean().optional() - }).optional(), - email: _zod.z.boolean().optional(), - credentialsProviderStatus: _zod.z.union([_zod.z.literal("locked"), _zod.z.literal("unlocked")]).optional() -}); -const autofillFeatureTogglesSchema = exports.autofillFeatureTogglesSchema = _zod.z.object({ - inputType_credentials: _zod.z.boolean().optional(), - inputType_identities: _zod.z.boolean().optional(), - inputType_creditCards: _zod.z.boolean().optional(), - emailProtection: _zod.z.boolean().optional(), - emailProtection_incontext_signup: _zod.z.boolean().optional(), - password_generation: _zod.z.boolean().optional(), - credentials_saving: _zod.z.boolean().optional(), - inlineIcon_credentials: _zod.z.boolean().optional(), - third_party_credentials_provider: _zod.z.boolean().optional() -}); -const getAutofillDataRequestSchema = exports.getAutofillDataRequestSchema = _zod.z.object({ - generatedPassword: generatedPasswordSchema.optional(), - inputType: _zod.z.string(), - mainType: _zod.z.union([_zod.z.literal("credentials"), _zod.z.literal("identities"), _zod.z.literal("creditCards")]), - subType: _zod.z.string(), - trigger: _zod.z.union([_zod.z.literal("userInitiated"), _zod.z.literal("autoprompt"), _zod.z.literal("postSignup")]).optional(), - serializedInputContext: _zod.z.string().optional(), - triggerContext: triggerContextSchema.optional() -}); -const getAutofillDataResponseSchema = exports.getAutofillDataResponseSchema = _zod.z.object({ - type: _zod.z.literal("getAutofillDataResponse").optional(), - success: _zod.z.object({ - credentials: credentialsSchema.optional(), - action: _zod.z.union([_zod.z.literal("fill"), _zod.z.literal("focus"), _zod.z.literal("none"), _zod.z.literal("acceptGeneratedPassword"), _zod.z.literal("rejectGeneratedPassword")]) - }).optional(), - error: genericErrorSchema.optional() -}); -const storeFormDataSchema = exports.storeFormDataSchema = _zod.z.object({ - credentials: outgoingCredentialsSchema.optional(), - trigger: _zod.z.union([_zod.z.literal("formSubmission"), _zod.z.literal("passwordGeneration"), _zod.z.literal("emailProtection")]).optional() -}); -const getAvailableInputTypesResultSchema = exports.getAvailableInputTypesResultSchema = _zod.z.object({ - type: _zod.z.literal("getAvailableInputTypesResponse").optional(), - success: availableInputTypesSchema, - error: genericErrorSchema.optional() -}); -const getAutofillInitDataResponseSchema = exports.getAutofillInitDataResponseSchema = _zod.z.object({ - type: _zod.z.literal("getAutofillInitDataResponse").optional(), - success: _zod.z.object({ - credentials: _zod.z.array(credentialsSchema), - identities: _zod.z.array(_zod.z.record(_zod.z.unknown())), - creditCards: _zod.z.array(_zod.z.record(_zod.z.unknown())), - serializedInputContext: _zod.z.string() - }).optional(), - error: genericErrorSchema.optional() -}); -const getAutofillCredentialsResultSchema = exports.getAutofillCredentialsResultSchema = _zod.z.object({ - type: _zod.z.literal("getAutofillCredentialsResponse").optional(), - success: _zod.z.object({ - id: _zod.z.string().optional(), - autogenerated: _zod.z.boolean().optional(), - username: _zod.z.string(), - password: _zod.z.string().optional() - }).optional(), - error: genericErrorSchema.optional() -}); -const autofillSettingsSchema = exports.autofillSettingsSchema = _zod.z.object({ - featureToggles: autofillFeatureTogglesSchema -}); -const emailProtectionGetIsLoggedInResultSchema = exports.emailProtectionGetIsLoggedInResultSchema = _zod.z.object({ - success: _zod.z.boolean().optional(), - error: genericErrorSchema.optional() -}); -const emailProtectionGetUserDataResultSchema = exports.emailProtectionGetUserDataResultSchema = _zod.z.object({ - success: _zod.z.object({ - userName: _zod.z.string(), - nextAlias: _zod.z.string(), - token: _zod.z.string() - }).optional(), - error: genericErrorSchema.optional() -}); -const emailProtectionGetCapabilitiesResultSchema = exports.emailProtectionGetCapabilitiesResultSchema = _zod.z.object({ - success: _zod.z.object({ - addUserData: _zod.z.boolean().optional(), - getUserData: _zod.z.boolean().optional(), - removeUserData: _zod.z.boolean().optional() - }).optional(), - error: genericErrorSchema.optional() -}); -const emailProtectionGetAddressesResultSchema = exports.emailProtectionGetAddressesResultSchema = _zod.z.object({ - success: _zod.z.object({ - personalAddress: _zod.z.string(), - privateAddress: _zod.z.string() - }).optional(), - error: genericErrorSchema.optional() -}); -const emailProtectionRefreshPrivateAddressResultSchema = exports.emailProtectionRefreshPrivateAddressResultSchema = _zod.z.object({ - success: _zod.z.object({ - personalAddress: _zod.z.string(), - privateAddress: _zod.z.string() - }).optional(), - error: genericErrorSchema.optional() -}); -const runtimeConfigurationSchema = exports.runtimeConfigurationSchema = _zod.z.object({ - contentScope: contentScopeSchema, - userUnprotectedDomains: _zod.z.array(_zod.z.string()), - userPreferences: userPreferencesSchema -}); -const providerStatusUpdatedSchema = exports.providerStatusUpdatedSchema = _zod.z.object({ - status: _zod.z.union([_zod.z.literal("locked"), _zod.z.literal("unlocked")]), - credentials: _zod.z.array(credentialsSchema), - availableInputTypes: availableInputTypes1Schema -}); -const getRuntimeConfigurationResponseSchema = exports.getRuntimeConfigurationResponseSchema = _zod.z.object({ - type: _zod.z.literal("getRuntimeConfigurationResponse").optional(), - success: runtimeConfigurationSchema.optional(), - error: genericErrorSchema.optional() -}); -const askToUnlockProviderResultSchema = exports.askToUnlockProviderResultSchema = _zod.z.object({ - type: _zod.z.literal("askToUnlockProviderResponse").optional(), - success: providerStatusUpdatedSchema, - error: genericErrorSchema.optional() -}); -const checkCredentialsProviderStatusResultSchema = exports.checkCredentialsProviderStatusResultSchema = _zod.z.object({ - type: _zod.z.literal("checkCredentialsProviderStatusResponse").optional(), - success: providerStatusUpdatedSchema, - error: genericErrorSchema.optional() -}); -const apiSchema = exports.apiSchema = _zod.z.object({ - addDebugFlag: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - paramsValidator: addDebugFlagParamsSchema.optional() - })).optional(), - getAutofillData: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("getAutofillDataResponse").optional(), - paramsValidator: getAutofillDataRequestSchema.optional(), - resultValidator: getAutofillDataResponseSchema.optional() - })).optional(), - getRuntimeConfiguration: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("getRuntimeConfigurationResponse").optional(), - resultValidator: getRuntimeConfigurationResponseSchema.optional() - })).optional(), - storeFormData: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - paramsValidator: storeFormDataSchema.optional() - })).optional(), - getAvailableInputTypes: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("getAvailableInputTypesResponse").optional(), - resultValidator: getAvailableInputTypesResultSchema.optional() - })).optional(), - getAutofillInitData: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("getAutofillInitDataResponse").optional(), - resultValidator: getAutofillInitDataResponseSchema.optional() - })).optional(), - getAutofillCredentials: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("getAutofillCredentialsResponse").optional(), - paramsValidator: getAutofillCredentialsParamsSchema.optional(), - resultValidator: getAutofillCredentialsResultSchema.optional() - })).optional(), - setSize: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - paramsValidator: setSizeParamsSchema.optional() - })).optional(), - selectedDetail: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - paramsValidator: selectedDetailParamsSchema.optional() - })).optional(), - closeAutofillParent: _zod.z.record(_zod.z.unknown()).optional(), - askToUnlockProvider: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("askToUnlockProviderResponse").optional(), - resultValidator: askToUnlockProviderResultSchema.optional() - })).optional(), - checkCredentialsProviderStatus: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("checkCredentialsProviderStatusResponse").optional(), - resultValidator: checkCredentialsProviderStatusResultSchema.optional() - })).optional(), - sendJSPixel: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - paramsValidator: sendJSPixelParamsSchema.optional() - })).optional(), - setIncontextSignupPermanentlyDismissedAt: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - paramsValidator: setIncontextSignupPermanentlyDismissedAtSchema.optional() - })).optional(), - getIncontextSignupDismissedAt: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("getIncontextSignupDismissedAt").optional(), - resultValidator: getIncontextSignupDismissedAtSchema.optional() - })).optional(), - autofillSettings: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - validatorsOnly: _zod.z.literal(true).optional(), - resultValidator: autofillSettingsSchema.optional() - })).optional(), - getAlias: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - validatorsOnly: _zod.z.literal(true).optional(), - paramValidator: getAliasParamsSchema.optional(), - resultValidator: getAliasResultSchema.optional() - })).optional(), - openManagePasswords: _zod.z.record(_zod.z.unknown()).optional(), - openManageCreditCards: _zod.z.record(_zod.z.unknown()).optional(), - openManageIdentities: _zod.z.record(_zod.z.unknown()).optional(), - emailProtectionStoreUserData: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("emailProtectionStoreUserDataResponse").optional(), - paramsValidator: emailProtectionStoreUserDataParamsSchema.optional() - })).optional(), - emailProtectionRemoveUserData: _zod.z.record(_zod.z.unknown()).optional(), - emailProtectionGetIsLoggedIn: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("emailProtectionGetIsLoggedInResponse").optional(), - resultValidator: emailProtectionGetIsLoggedInResultSchema.optional() - })).optional(), - emailProtectionGetUserData: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("emailProtectionGetUserDataResponse").optional(), - resultValidator: emailProtectionGetUserDataResultSchema.optional() - })).optional(), - emailProtectionGetCapabilities: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("emailProtectionGetCapabilitiesResponse").optional(), - resultValidator: emailProtectionGetCapabilitiesResultSchema.optional() - })).optional(), - emailProtectionGetAddresses: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("emailProtectionGetAddressesResponse").optional(), - resultValidator: emailProtectionGetAddressesResultSchema.optional() - })).optional(), - emailProtectionRefreshPrivateAddress: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("emailProtectionRefreshPrivateAddressResponse").optional(), - resultValidator: emailProtectionRefreshPrivateAddressResultSchema.optional() - })).optional(), - startEmailProtectionSignup: _zod.z.record(_zod.z.unknown()).optional(), - closeEmailProtectionTab: _zod.z.record(_zod.z.unknown()).optional(), - ShowInContextEmailProtectionSignupPrompt: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("ShowInContextEmailProtectionSignupPromptResponse").optional(), - resultValidator: showInContextEmailProtectionSignupPromptSchema.optional() - })).optional() -}); - -},{"zod":9}],68:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.GetAlias = void 0; -var _index = require("../../packages/device-api/index.js"); -var _validatorsZod = require("./__generated__/validators.zod.js"); -/** - * @extends {DeviceApiCall} - */ -class GetAlias extends _index.DeviceApiCall { - method = 'emailHandlerGetAlias'; - id = 'n/a'; - paramsValidator = _validatorsZod.getAliasParamsSchema; - resultValidator = _validatorsZod.getAliasResultSchema; - preResultValidation(response) { - // convert to the correct format, because this is a legacy API + // src/DeviceInterface/overlayApi.js + function overlayApi(device) { return { - success: response + /** + * When we are inside an 'overlay' - the HTML tooltip will be opened immediately + */ + showImmediately() { + const topContextData = device.getTopContextData(); + if (!topContextData) + throw new Error("unreachable, topContextData should be available"); + const getPosition = () => { + return { + x: 0, + y: 0, + height: 50, + width: 50 + }; + }; + const tooltip = device.uiController?.createTooltip?.(getPosition, topContextData); + if (tooltip) { + device.uiController?.setActiveTooltip?.(tooltip); + } + }, + /** + * @param {IdentityObject|CreditCardObject|CredentialsObject|{email:string, id: string}} data + * @param {string} type + * @returns {Promise} + */ + async selectedDetail(data, type) { + let detailsEntries = Object.entries(data).map(([key, value]) => { + return [key, String(value)]; + }); + const entries = Object.fromEntries(detailsEntries); + await device.deviceApi.notify(new SelectedDetailCall({ data: entries, configType: type })); + } }; } -} -exports.GetAlias = GetAlias; - -},{"../../packages/device-api/index.js":12,"./__generated__/validators.zod.js":67}],69:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.AndroidTransport = void 0; -var _index = require("../../../packages/device-api/index.js"); -var _deviceApiCalls = require("../__generated__/deviceApiCalls.js"); -class AndroidTransport extends _index.DeviceApiTransport { - /** @type {GlobalConfig} */ - config; - - /** @param {GlobalConfig} globalConfig */ - constructor(globalConfig) { - super(); - this.config = globalConfig; - if (this.config.isDDGTestMode) { - if (typeof window.BrowserAutofill?.getAutofillData !== 'function') { - console.warn('window.BrowserAutofill.getAutofillData missing'); - } - if (typeof window.BrowserAutofill?.storeFormData !== 'function') { - console.warn('window.BrowserAutofill.storeFormData missing'); - } - } - } - /** - * @param {import("../../../packages/device-api").DeviceApiCall} deviceApiCall - * @returns {Promise} - */ - async send(deviceApiCall) { - if (deviceApiCall instanceof _deviceApiCalls.GetRuntimeConfigurationCall) { - return androidSpecificRuntimeConfiguration(this.config); + // src/DeviceInterface/AppleOverlayDeviceInterface.js + var AppleOverlayDeviceInterface = class extends AppleDeviceInterface { + constructor() { + super(...arguments); + /** + * Mark top frame as not stripping credential data + * @type {boolean} + */ + __publicField(this, "stripCredentials", false); + /** + * overlay API helpers + */ + __publicField(this, "overlay", overlayApi(this)); + __publicField(this, "previousX", 0); + __publicField(this, "previousY", 0); } - if (deviceApiCall instanceof _deviceApiCalls.GetAvailableInputTypesCall) { - return androidSpecificAvailableInputTypes(this.config); + /** + * Because we're running inside the Overlay, we always create the HTML + * Tooltip controller. + * + * @override + * @returns {import("../UI/controllers/UIController.js").UIController} + */ + createUIController() { + return new HTMLTooltipUIController({ + tooltipKind: ( + /** @type {const} */ + "modern" + ), + device: this + }, { + wrapperClass: "top-autofill", + tooltipPositionClass: () => ".wrapper { transform: none; }", + setSize: (details) => this.deviceApi.notify(createNotification("setSize", details)), + remove: async () => this._closeAutofillParent(), + testMode: this.isTestMode() + }); } - if (deviceApiCall instanceof _deviceApiCalls.GetIncontextSignupDismissedAtCall) { - window.BrowserAutofill.getIncontextSignupDismissedAt(JSON.stringify(deviceApiCall.params)); - return waitForResponse(deviceApiCall.id, this.config); + addDeviceListeners() { + window.addEventListener("mouseMove", (event) => { + if (!this.previousX && !this.previousY || // if no previous coords + this.previousX === event.detail.x && this.previousY === event.detail.y) { + this.previousX = event.detail.x; + this.previousY = event.detail.y; + return; + } + const activeTooltip = this.uiController?.getActiveTooltip?.(); + activeTooltip?.focus(event.detail.x, event.detail.y); + this.previousX = event.detail.x; + this.previousY = event.detail.y; + }); + return super.addDeviceListeners(); } - if (deviceApiCall instanceof _deviceApiCalls.SetIncontextSignupPermanentlyDismissedAtCall) { - return window.BrowserAutofill.setIncontextSignupPermanentlyDismissedAt(JSON.stringify(deviceApiCall.params)); + /** + * Since we're running inside the Overlay we can limit what happens here to + * be only things that are needed to power the HTML Tooltip + * + * @override + * @returns {Promise} + */ + async setupAutofill() { + await this._getAutofillInitData(); + await this.inContextSignup.init(); + const signedIn = await this._checkDeviceSignedIn(); + if (signedIn) { + await this.getAddresses(); + } } - if (deviceApiCall instanceof _deviceApiCalls.StartEmailProtectionSignupCall) { - return window.BrowserAutofill.startEmailProtectionSignup(JSON.stringify(deviceApiCall.params)); + async postInit() { + this.overlay.showImmediately(); + super.postInit(); } - if (deviceApiCall instanceof _deviceApiCalls.CloseEmailProtectionTabCall) { - return window.BrowserAutofill.closeEmailProtectionTab(JSON.stringify(deviceApiCall.params)); + /** + * In the top-frame scenario we override the base 'selectedDetail'. + * + * This + * + * @override + * @param {IdentityObject|CreditCardObject|CredentialsObject|{email:string, id: string}} data + * @param {string} type + */ + async selectedDetail(data, type) { + return this.overlay.selectedDetail(data, type); } - if (deviceApiCall instanceof _deviceApiCalls.ShowInContextEmailProtectionSignupPromptCall) { - window.BrowserAutofill.showInContextEmailProtectionSignupPrompt(JSON.stringify(deviceApiCall.params)); - return waitForResponse(deviceApiCall.id, this.config); + }; + + // src/DeviceInterface/WindowsInterface.js + var EMAIL_PROTECTION_LOGOUT_MESSAGE = "EMAIL_PROTECTION_LOGOUT"; + var WindowsInterface = class extends InterfacePrototype_default { + constructor() { + super(...arguments); + __publicField(this, "ready", false); + /** @type {AbortController|null} */ + __publicField(this, "_abortController", null); + } + async setupAutofill() { + const loggedIn = await this._getIsLoggedIn(); + if (loggedIn) { + await this.getAddresses(); + } } - if (deviceApiCall instanceof _deviceApiCalls.GetAutofillDataCall) { - window.BrowserAutofill.getAutofillData(JSON.stringify(deviceApiCall.params)); - return waitForResponse(deviceApiCall.id, this.config); + postInit() { + super.postInit(); + this.ready = true; } - if (deviceApiCall instanceof _deviceApiCalls.StoreFormDataCall) { - return window.BrowserAutofill.storeFormData(JSON.stringify(deviceApiCall.params)); + createUIController() { + return new OverlayUIController({ + remove: async () => this._closeAutofillParent(), + show: async (details) => this._show(details) + }); } - throw new Error('android: not implemented: ' + deviceApiCall.method); - } -} - -/** - * @param {string} expectedResponse - the name/id of the response - * @param {GlobalConfig} config - * @returns {Promise<*>} - */ -exports.AndroidTransport = AndroidTransport; -function waitForResponse(expectedResponse, config) { - return new Promise(resolve => { - const handler = e => { - if (!config.isDDGTestMode) { - if (e.origin !== '') { - return; + /** + * @param {GetAutofillDataRequest} details + */ + async _show(details) { + const { mainType } = details; + if (this._abortController && !this._abortController.signal.aborted) { + this._abortController.abort(); + } + this._abortController = new AbortController(); + this.deviceApi.request(new GetAutofillDataCall(details), { signal: this._abortController.signal }).then((resp) => { + if (!this.activeForm) { + throw new Error("this.currentAttached was absent"); + } + switch (resp.action) { + case "fill": { + if (mainType in resp) { + this.activeForm?.autofillData(resp[mainType], mainType); + } else { + throw new Error(`action: "fill" cannot occur because "${mainType}" was missing`); + } + break; + } + case "focus": { + this.activeForm?.activeInput?.focus(); + break; + } + case "none": { + break; + } + default: { + if (this.globalConfig.isDDGTestMode) { + console.warn("unhandled response", resp); + } + } } - } - if (!e.data) { - return; - } - if (typeof e.data !== 'string') { - if (config.isDDGTestMode) { - console.log('❌ event.data was not a string. Expected a string so that it can be JSON parsed'); + return this._closeAutofillParent(); + }).catch((e) => { + if (this.globalConfig.isDDGTestMode) { + if (e.name === "AbortError") { + console.log("Promise Aborted"); + } else { + console.error("Promise Rejected", e); + } } + }); + } + /** + * @returns {Promise} + */ + async _closeAutofillParent() { + return this.deviceApi.notify(new CloseAutofillParentCall(null)); + } + /** + * Email Protection calls + */ + /** + * @returns {Promise} + */ + getEmailProtectionCapabilities() { + return this.deviceApi.request(new EmailProtectionGetCapabilitiesCall({})); + } + async _getIsLoggedIn() { + const isLoggedIn = await this.deviceApi.request(new EmailProtectionGetIsLoggedInCall({})); + this.isDeviceSignedIn = () => isLoggedIn; + return isLoggedIn; + } + addLogoutListener(handler) { + if (!this.globalConfig.isDDGDomain) return; - } - try { - let data = JSON.parse(e.data); - if (data.type === expectedResponse) { - window.removeEventListener('message', handler); - return resolve(data); + windowsInteropAddEventListener("message", (e) => { + if (this.globalConfig.isDDGDomain && e.data === EMAIL_PROTECTION_LOGOUT_MESSAGE) { + handler(); } - if (config.isDDGTestMode) { - console.log(`❌ event.data.type was '${data.type}', which didnt match '${expectedResponse}'`, JSON.stringify(data)); - } - } catch (e) { - window.removeEventListener('message', handler); - if (config.isDDGTestMode) { - console.log('❌ Could not JSON.parse the response'); - } - } - }; - window.addEventListener('message', handler); - }); -} - -/** - * @param {GlobalConfig} globalConfig - * @returns {{success: import('../__generated__/validators-ts').RuntimeConfiguration}} - */ -function androidSpecificRuntimeConfiguration(globalConfig) { - if (!globalConfig.userPreferences) { - throw new Error('globalConfig.userPreferences not supported yet on Android'); - } - return { - success: { - // @ts-ignore - contentScope: globalConfig.contentScope, - // @ts-ignore - userPreferences: globalConfig.userPreferences, - // @ts-ignore - userUnprotectedDomains: globalConfig.userUnprotectedDomains, - // @ts-ignore - availableInputTypes: globalConfig.availableInputTypes + }); } - }; -} - -/** - * @param {GlobalConfig} globalConfig - * @returns {{success: import('../__generated__/validators-ts').AvailableInputTypes}} - */ -function androidSpecificAvailableInputTypes(globalConfig) { - if (!globalConfig.availableInputTypes) { - throw new Error('globalConfig.availableInputTypes not supported yet on Android'); - } - return { - success: globalConfig.availableInputTypes - }; -} - -},{"../../../packages/device-api/index.js":12,"../__generated__/deviceApiCalls.js":66}],70:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.AppleTransport = void 0; -var _messaging = require("../../../packages/messaging/messaging.js"); -var _index = require("../../../packages/device-api/index.js"); -class AppleTransport extends _index.DeviceApiTransport { - /** @param {GlobalConfig} globalConfig */ - constructor(globalConfig) { - super(); - this.config = globalConfig; - const webkitConfig = new _messaging.WebkitMessagingConfig({ - hasModernWebkitAPI: this.config.hasModernWebkitAPI, - webkitMessageHandlerNames: this.config.webkitMessageHandlerNames, - secret: this.config.secret - }); - this.messaging = new _messaging.Messaging(webkitConfig); - } - async send(deviceApiCall) { - try { - // if the call has an `id`, it means that it expects a response - if (deviceApiCall.id) { - return await this.messaging.request(deviceApiCall.method, deviceApiCall.params || undefined); - } else { - return this.messaging.notify(deviceApiCall.method, deviceApiCall.params || undefined); - } - } catch (e) { - if (e instanceof _messaging.MissingHandler) { - if (this.config.isDDGTestMode) { - console.log('MissingWebkitHandler error for:', deviceApiCall.method); - } - throw new Error('unimplemented handler: ' + deviceApiCall.method); - } else { - throw e; - } + /** + * @returns {Promise} + */ + storeUserData({ addUserData }) { + return this.deviceApi.request(new EmailProtectionStoreUserDataCall(addUserData)); } - } -} -exports.AppleTransport = AppleTransport; - -},{"../../../packages/device-api/index.js":12,"../../../packages/messaging/messaging.js":15}],71:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.ExtensionTransport = void 0; -var _index = require("../../../packages/device-api/index.js"); -var _deviceApiCalls = require("../__generated__/deviceApiCalls.js"); -var _autofillUtils = require("../../autofill-utils.js"); -var _Settings = require("../../Settings.js"); -class ExtensionTransport extends _index.DeviceApiTransport { - /** @param {GlobalConfig} globalConfig */ - constructor(globalConfig) { - super(); - this.config = globalConfig; - } - async send(deviceApiCall) { - if (deviceApiCall instanceof _deviceApiCalls.GetRuntimeConfigurationCall) { - return deviceApiCall.result(await extensionSpecificRuntimeConfiguration(this)); + /** + * @returns {Promise} + */ + removeUserData() { + return this.deviceApi.request(new EmailProtectionRemoveUserDataCall({})); } - if (deviceApiCall instanceof _deviceApiCalls.GetAvailableInputTypesCall) { - return deviceApiCall.result(await extensionSpecificGetAvailableInputTypes()); + /** + * @returns {Promise} + */ + getUserData() { + return this.deviceApi.request(new EmailProtectionGetUserDataCall({})); } - if (deviceApiCall instanceof _deviceApiCalls.SetIncontextSignupPermanentlyDismissedAtCall) { - return deviceApiCall.result(await extensionSpecificSetIncontextSignupPermanentlyDismissedAtCall(deviceApiCall.params)); + async refreshAlias() { + const addresses = await this.deviceApi.request(new EmailProtectionRefreshPrivateAddressCall({})); + this.storeLocalAddresses(addresses); } - if (deviceApiCall instanceof _deviceApiCalls.GetIncontextSignupDismissedAtCall) { - return deviceApiCall.result(await extensionSpecificGetIncontextSignupDismissedAt()); + async getAddresses() { + const addresses = await this.deviceApi.request(new EmailProtectionGetAddressesCall({})); + this.storeLocalAddresses(addresses); + return addresses; } + }; - // TODO: unify all calls to use deviceApiCall.method instead of all these if blocks - if (deviceApiCall instanceof _deviceApiCalls.SendJSPixelCall) { - return deviceApiCall.result(await extensionSpecificSendPixel(deviceApiCall.params)); - } - if (deviceApiCall instanceof _deviceApiCalls.AddDebugFlagCall) { - return deviceApiCall.result(await extensionSpecificAddDebugFlag(deviceApiCall.params)); + // src/DeviceInterface/WindowsOverlayDeviceInterface.js + var WindowsOverlayDeviceInterface = class extends InterfacePrototype_default { + constructor() { + super(...arguments); + /** + * Mark top frame as not stripping credential data + * @type {boolean} + */ + __publicField(this, "stripCredentials", false); + /** + * overlay API helpers + */ + __publicField(this, "overlay", overlayApi(this)); + __publicField(this, "previousScreenX", 0); + __publicField(this, "previousScreenY", 0); } - if (deviceApiCall instanceof _deviceApiCalls.CloseAutofillParentCall || deviceApiCall instanceof _deviceApiCalls.StartEmailProtectionSignupCall) { - return; // noop + /** + * Because we're running inside the Overlay, we always create the HTML + * Tooltip controller. + * + * @override + * @returns {import("../UI/controllers/UIController.js").UIController} + */ + createUIController() { + return new HTMLTooltipUIController({ + tooltipKind: ( + /** @type {const} */ + "modern" + ), + device: this + }, { + wrapperClass: "top-autofill", + tooltipPositionClass: () => ".wrapper { transform: none; }", + setSize: (details) => this.deviceApi.notify(new SetSizeCall(details)), + remove: async () => this._closeAutofillParent(), + testMode: this.isTestMode(), + /** + * Note: This is needed because Mutation observer didn't support visibility checks on Windows + */ + checkVisibility: false + }); } - - console.error('Send not implemented for ' + deviceApiCall.method); - } -} - -/** - * @param {ExtensionTransport} deviceApi - * @returns {Promise>} - */ -exports.ExtensionTransport = ExtensionTransport; -async function extensionSpecificRuntimeConfiguration(deviceApi) { - const contentScope = await getContentScopeConfig(); - const emailProtectionEnabled = (0, _autofillUtils.isAutofillEnabledFromProcessedConfig)(contentScope); - const incontextSignupEnabled = (0, _autofillUtils.isIncontextSignupEnabledFromProcessedConfig)(contentScope); - return { - success: { - // @ts-ignore - contentScope: contentScope, - // @ts-ignore - userPreferences: { - features: { - autofill: { - settings: { - featureToggles: { - ..._Settings.Settings.defaults.featureToggles, - emailProtection: emailProtectionEnabled, - emailProtection_incontext_signup: incontextSignupEnabled - } - } - } + addDeviceListeners() { + window.addEventListener("mousemove", (event) => { + if (!this.previousScreenX && !this.previousScreenY || // if no previous coords + this.previousScreenX === event.screenX && this.previousScreenY === event.screenY) { + this.previousScreenX = event.screenX; + this.previousScreenY = event.screenY; + return; } - }, - // @ts-ignore - userUnprotectedDomains: deviceApi.config?.userUnprotectedDomains || [] + const activeTooltip = this.uiController?.getActiveTooltip?.(); + activeTooltip?.focus(event.x, event.y); + this.previousScreenX = event.screenX; + this.previousScreenY = event.screenY; + }); + return super.addDeviceListeners(); } - }; -} -async function extensionSpecificGetAvailableInputTypes() { - const contentScope = await getContentScopeConfig(); - const emailProtectionEnabled = (0, _autofillUtils.isAutofillEnabledFromProcessedConfig)(contentScope); - return { - success: { - ..._Settings.Settings.defaults.availableInputTypes, - email: emailProtectionEnabled + /** + * @returns {Promise} + */ + async _closeAutofillParent() { + return this.deviceApi.notify(new CloseAutofillParentCall(null)); } - }; -} -async function getContentScopeConfig() { - return new Promise(resolve => { - chrome.runtime.sendMessage({ - registeredTempAutofillContentScript: true, - documentUrl: window.location.href - }, response => { - if (response && 'site' in response) { - resolve(response); + /** + * @returns {Promise} + */ + openManagePasswords() { + return this.deviceApi.notify(new OpenManagePasswordsCall({})); + } + /** + * @returns {Promise} + */ + openManageCreditCards() { + return this.deviceApi.notify(new OpenManageCreditCardsCall({})); + } + /** + * @returns {Promise} + */ + openManageIdentities() { + return this.deviceApi.notify(new OpenManageIdentitiesCall({})); + } + /** + * Since we're running inside the Overlay we can limit what happens here to + * be only things that are needed to power the HTML Tooltip + * + * @override + * @returns {Promise} + */ + async setupAutofill() { + const loggedIn = await this._getIsLoggedIn(); + if (loggedIn) { + await this.getAddresses(); } - }); - }); -} - -/** - * @param {import('../__generated__/validators-ts').SendJSPixelParams} params - */ -async function extensionSpecificSendPixel(params) { - return new Promise(resolve => { - chrome.runtime.sendMessage({ - messageType: 'sendJSPixel', - options: params - }, () => { - resolve(true); - }); - }); -} - -/** - * @param {import('../__generated__/validators-ts').AddDebugFlagParams} params - */ -async function extensionSpecificAddDebugFlag(params) { - return new Promise(resolve => { - chrome.runtime.sendMessage({ - messageType: 'addDebugFlag', - options: params - }, () => { - resolve(true); - }); - }); -} -async function extensionSpecificGetIncontextSignupDismissedAt() { - return new Promise(resolve => { - chrome.runtime.sendMessage({ - messageType: 'getIncontextSignupDismissedAt' - }, response => { - resolve(response); - }); - }); -} - -/** - * @param {import('../__generated__/validators-ts').SetIncontextSignupPermanentlyDismissedAt} params - */ -async function extensionSpecificSetIncontextSignupPermanentlyDismissedAtCall(params) { - return new Promise(resolve => { - chrome.runtime.sendMessage({ - messageType: 'setIncontextSignupPermanentlyDismissedAt', - options: params - }, () => { - resolve(true); - }); - }); -} - -},{"../../../packages/device-api/index.js":12,"../../Settings.js":50,"../../autofill-utils.js":62,"../__generated__/deviceApiCalls.js":66}],72:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.createTransport = createTransport; -var _appleTransport = require("./apple.transport.js"); -var _androidTransport = require("./android.transport.js"); -var _extensionTransport = require("./extension.transport.js"); -var _windowsTransport = require("./windows.transport.js"); -/** - * @param {GlobalConfig} globalConfig - * @returns {import("../../../packages/device-api").DeviceApiTransport} - */ - -function createTransport(globalConfig) { - if (typeof globalConfig.userPreferences?.platform?.name === 'string') { - switch (globalConfig.userPreferences?.platform?.name) { - case 'ios': - case 'macos': - return new _appleTransport.AppleTransport(globalConfig); - case 'android': - return new _androidTransport.AndroidTransport(globalConfig); - default: - throw new Error('selectSender unimplemented!'); + const response = await this.deviceApi.request(new GetAutofillInitDataCall(null)); + this.storeLocalData(response); } - } - if (globalConfig.isWindows) { - return new _windowsTransport.WindowsTransport(); - } - - // fallback for when `globalConfig.userPreferences.platform.name` is absent - if (globalConfig.isDDGApp) { - if (globalConfig.isAndroid) { - return new _androidTransport.AndroidTransport(globalConfig); + async postInit() { + this.overlay.showImmediately(); + super.postInit(); + } + /** + * In the top-frame scenario, we send a message to the native + * side to indicate a selection. Once received, the native side will store that selection so that a + * subsequence call from main webpage can retrieve it + * + * @override + * @param {IdentityObject|CreditCardObject|CredentialsObject|{email:string, id: string}} data + * @param {string} type + */ + async selectedDetail(data, type) { + return this.overlay.selectedDetail(data, type); + } + /** + * Email Protection calls + */ + async _getIsLoggedIn() { + const isLoggedIn = await this.deviceApi.request(new EmailProtectionGetIsLoggedInCall({})); + this.isDeviceSignedIn = () => isLoggedIn; + return isLoggedIn; + } + async getAddresses() { + const addresses = await this.deviceApi.request(new EmailProtectionGetAddressesCall({})); + this.storeLocalAddresses(addresses); + return addresses; } - throw new Error('unreachable, createTransport'); - } - - // falls back to extension... is this still the best way to determine this? - return new _extensionTransport.ExtensionTransport(globalConfig); -} - -},{"./android.transport.js":69,"./apple.transport.js":70,"./extension.transport.js":71,"./windows.transport.js":73}],73:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.WindowsTransport = void 0; -var _index = require("../../../packages/device-api/index.js"); -/** - * @typedef {import('../../../packages/device-api/lib/device-api').CallOptions} CallOptions - * @typedef {import("../../../packages/device-api").DeviceApiCall} DeviceApiCall - */ -class WindowsTransport extends _index.DeviceApiTransport { - async send(deviceApiCall, options) { - if (deviceApiCall.id) { - return windowsTransport(deviceApiCall, options).withResponse(deviceApiCall.id); - } - return windowsTransport(deviceApiCall, options); - } -} - -/** - * @param {DeviceApiCall} deviceApiCall - * @param {CallOptions} [options] - */ -exports.WindowsTransport = WindowsTransport; -function windowsTransport(deviceApiCall, options) { - windowsInteropPostMessage({ - Feature: 'Autofill', - Name: deviceApiCall.method, - Data: deviceApiCall.params - }); - return { /** - * Sends a message and returns a Promise that resolves with the response - * @param responseId - * @returns {Promise<*>} + * Gets a single identity obj once the user requests it + * @param {Number} id + * @returns {Promise<{success: IdentityObject|undefined}>} */ - withResponse(responseId) { - return waitForWindowsResponse(responseId, options); + getAutofillIdentity(id) { + const identity = this.getLocalIdentities().find(({ id: identityId }) => `${identityId}` === `${id}`); + return Promise.resolve({ success: identity }); } }; -} -/** - * @param {string} responseId - * @param {CallOptions} [options] - * @returns {Promise} - */ -function waitForWindowsResponse(responseId, options) { - return new Promise((resolve, reject) => { - // if already aborted, reject immediately - if (options?.signal?.aborted) { - return reject(new DOMException('Aborted', 'AbortError')); - } - let teardown; - // The event handler - const handler = event => { - // console.log(`📩 windows, ${window.location.href}`, [event.origin, JSON.stringify(event.data)]) - if (!event.data) { - console.warn('data absent from message'); - return; + // src/DeviceInterface.js + function createDevice() { + const globalConfig = createGlobalConfig(); + const transport = createTransport(globalConfig); + const loggingTransport = { + async send(deviceApiCall) { + console.log("[->outgoing]", "id:", deviceApiCall.method, deviceApiCall.params || null); + const result = await transport.send(deviceApiCall); + console.log("[<-incoming]", "id:", deviceApiCall.method, result || null); + return result; } - if (event.data.type === responseId) { - teardown(); - resolve(event.data); - } - }; - - // what to do if this promise is aborted - const abortHandler = () => { - teardown(); - reject(new DOMException('Aborted', 'AbortError')); }; - - // setup - windowsInteropAddEventListener('message', handler); - options?.signal?.addEventListener('abort', abortHandler); - teardown = () => { - windowsInteropRemoveEventListener('message', handler); - options?.signal?.removeEventListener('abort', abortHandler); - }; - }); -} - -},{"../../../packages/device-api/index.js":12}],74:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -/*! - * Copyright 2015 Google Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -/* - * @see https://developers.google.com/web/updates/2015/08/using-requestidlecallback - */ -// @ts-ignore -window.requestIdleCallback = window.requestIdleCallback || function (cb) { - return setTimeout(function () { - const start = Date.now(); - // eslint-disable-next-line standard/no-callback-literal - cb({ - didTimeout: false, - timeRemaining: function () { - return Math.max(0, 50 - (Date.now() - start)); + let deviceApi = new DeviceApi(globalConfig.isDDGTestMode ? loggingTransport : transport); + const settings = new Settings(globalConfig, deviceApi); + if (globalConfig.isWindows) { + if (globalConfig.isTopFrame) { + return new WindowsOverlayDeviceInterface(globalConfig, deviceApi, settings); } - }); - }, 1); -}; -window.cancelIdleCallback = window.cancelIdleCallback || function (id) { - clearTimeout(id); -}; -var _default = exports.default = {}; + return new WindowsInterface(globalConfig, deviceApi, settings); + } + if (globalConfig.isDDGApp) { + if (globalConfig.isAndroid) { + return new AndroidInterface(globalConfig, deviceApi, settings); + } + if (globalConfig.isTopFrame) { + return new AppleOverlayDeviceInterface(globalConfig, deviceApi, settings); + } + return new AppleDeviceInterface(globalConfig, deviceApi, settings); + } + globalConfig.isExtension = true; + return new ExtensionInterface(globalConfig, deviceApi, settings); + } -},{}]},{},[63]); + // src/autofill.js + (() => { + if (shouldLog()) { + console.log("DuckDuckGo Autofill Active"); + } + if (!window.isSecureContext) + return false; + try { + const startupAutofill = () => { + if (document.visibilityState === "visible") { + const deviceInterface = createDevice(); + deviceInterface.init(); + } else { + document.addEventListener("visibilitychange", startupAutofill, { once: true }); + } + }; + startupAutofill(); + } catch (e) { + console.error(e); + } + })(); +})(); diff --git a/dist/autofill.js b/dist/autofill.js index aad112632..6418c8663 100644 --- a/dist/autofill.js +++ b/dist/autofill.js @@ -1,10768 +1,8893 @@ -(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i 1 && !unprotectedDomain) { - const partialDomain = domainParts.join('.'); - unprotectedDomain = featureList.filter(domain => domain.domain === partialDomain).length > 0; - domainParts.shift(); - } - return unprotectedDomain; -} -function processConfig(data, userList, preferences) { - const topLevelUrl = getTopLevelURL(); - const allowlisted = userList.filter(domain => domain === topLevelUrl.host).length > 0; - const enabledFeatures = Object.keys(data.features).filter(featureName => { - const feature = data.features[featureName]; - return feature.state === 'enabled' && !isUnprotectedDomain(topLevelUrl, feature.exceptions); - }); - const isBroken = isUnprotectedDomain(topLevelUrl, data.unprotectedTemporary); - preferences.site = { - domain: topLevelUrl.hostname, - isBroken, - allowlisted, - enabledFeatures - }; - // TODO - preferences.cookie = {}; - return preferences; -} - -},{}],2:[function(require,module,exports){ "use strict"; +(() => { + var __defProp = Object.defineProperty; + var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __publicField = (obj, key, value) => { + __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); + return value; + }; + var __accessCheck = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); + }; + var __privateGet = (obj, member, getter) => { + __accessCheck(obj, member, "read from private field"); + return getter ? getter.call(obj) : member.get(obj); + }; + var __privateAdd = (obj, member, value) => { + if (member.has(obj)) + throw TypeError("Cannot add the same private member more than once"); + member instanceof WeakSet ? member.add(obj) : member.set(obj, value); + }; + var __privateSet = (obj, member, value, setter) => { + __accessCheck(obj, member, "write to private field"); + setter ? setter.call(obj, value) : member.set(obj, value); + return value; + }; -Object.defineProperty(exports, "__esModule", { - value: true -}); -Object.defineProperty(exports, "DeviceApi", { - enumerable: true, - get: function () { - return _deviceApi.DeviceApi; - } -}); -Object.defineProperty(exports, "DeviceApiCall", { - enumerable: true, - get: function () { - return _deviceApiCall.DeviceApiCall; - } -}); -Object.defineProperty(exports, "DeviceApiTransport", { - enumerable: true, - get: function () { - return _deviceApi.DeviceApiTransport; - } -}); -Object.defineProperty(exports, "createNotification", { - enumerable: true, - get: function () { - return _deviceApiCall.createNotification; - } -}); -Object.defineProperty(exports, "createRequest", { - enumerable: true, - get: function () { - return _deviceApiCall.createRequest; - } -}); -Object.defineProperty(exports, "validate", { - enumerable: true, - get: function () { - return _deviceApiCall.validate; - } -}); -var _deviceApiCall = require("./lib/device-api-call.js"); -var _deviceApi = require("./lib/device-api.js"); + // src/requestIdleCallback.js + /*! + * Copyright 2015 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + window.requestIdleCallback = window.requestIdleCallback || function(cb) { + return setTimeout(function() { + const start = Date.now(); + cb({ + didTimeout: false, + timeRemaining: function() { + return Math.max(0, 50 - (Date.now() - start)); + } + }); + }, 1); + }; + window.cancelIdleCallback = window.cancelIdleCallback || function(id) { + clearTimeout(id); + }; -},{"./lib/device-api-call.js":3,"./lib/device-api.js":4}],3:[function(require,module,exports){ -"use strict"; + // src/config.js + var DDG_DOMAIN_REGEX = new RegExp(/^https:\/\/(([a-z0-9-_]+?)\.)?duckduckgo\.com\/email/); + function createGlobalConfig(overrides) { + let isApp = false; + let isTopFrame = false; + let supportsTopFrame = false; + let hasModernWebkitAPI = false; + // INJECT isApp HERE + // INJECT isTopFrame HERE + // INJECT supportsTopFrame HERE + // INJECT hasModernWebkitAPI HERE + let isWindows = false; + // INJECT isWindows HERE + let webkitMessageHandlerNames = []; + // INJECT webkitMessageHandlerNames HERE + let isDDGTestMode = false; + // INJECT isDDGTestMode HERE + let contentScope = null; + let userUnprotectedDomains = null; + let userPreferences = null; + // INJECT contentScope HERE + // INJECT userUnprotectedDomains HERE + // INJECT userPreferences HERE + let availableInputTypes = null; + // INJECT availableInputTypes HERE + let secret = "PLACEHOLDER_SECRET"; + const isAndroid = userPreferences?.platform.name === "android"; + const isDDGApp = ["ios", "android", "macos", "windows"].includes(userPreferences?.platform.name) || isWindows; + const isMobileApp = ["ios", "android"].includes(userPreferences?.platform.name); + const isFirefox = navigator.userAgent.includes("Firefox"); + const isDDGDomain = Boolean(window.location.href.match(DDG_DOMAIN_REGEX)); + const isExtension = false; + const config = { + isApp, + isDDGApp, + isAndroid, + isFirefox, + isMobileApp, + isExtension, + isTopFrame, + isWindows, + secret, + supportsTopFrame, + hasModernWebkitAPI, + contentScope, + userUnprotectedDomains, + userPreferences, + isDDGTestMode, + isDDGDomain, + availableInputTypes, + webkitMessageHandlerNames, + ...overrides + }; + return config; + } + + // src/constants.js + var constants = { + ATTR_INPUT_TYPE: "data-ddg-inputType", + ATTR_AUTOFILL: "data-ddg-autofill", + TEXT_LENGTH_CUTOFF: 100, + MAX_INPUTS_PER_PAGE: 100, + MAX_FORMS_PER_PAGE: 30, + MAX_INPUTS_PER_FORM: 80, + MAX_FORM_RESCANS: 50 + }; -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.SchemaValidationError = exports.DeviceApiCallError = exports.DeviceApiCall = void 0; -exports.createDeviceApiCall = createDeviceApiCall; -exports.createNotification = void 0; -exports.createRequest = createRequest; -exports.validate = validate; -/** - * This roughly follows https://www.jsonrpc.org/specification - * @template {import("zod").ZodType} Params=import("zod").ZodType - * @template {import("zod").ZodType} Result=import("zod").ZodType - */ -class DeviceApiCall { - /** @type {string} */ - method = 'unknown'; - /** - * An optional 'id' - used to indicate if a request requires a response. - * @type {string|null} - */ - id = null; - /** @type {Params | null | undefined} */ - paramsValidator = null; - /** @type {Result | null | undefined} */ - resultValidator = null; - /** @type {import("zod").infer} */ - params; - /** - * This is a carve-out for legacy messages that are not typed yet. - * If you set this to 'true', then the response will not be checked to conform - * to any shape - * @deprecated this is here to aid migration, should be removed ASAP - * @type {boolean} - */ - throwOnResultKeysMissing = true; - /** - * New messages should be in a particular format, eg: { success: T }, - * but you can set this to false if you want to access the result as-is, - * without any unwrapping logic - * @deprecated this is here to aid migration, should be removed ASAP - * @type {boolean} - */ - unwrapResult = true; - /** - * @param {import("zod").infer} data - */ - constructor(data) { - this.params = data; - } + // src/Form/label-util.js + var EXCLUDED_TAGS = ["BR", "SCRIPT", "NOSCRIPT", "OPTION", "STYLE"]; + var extractElementStrings = (element) => { + const strings = /* @__PURE__ */ new Set(); + const _extractElementStrings = (el) => { + if (EXCLUDED_TAGS.includes(el.tagName)) { + return; + } + if (el.nodeType === el.TEXT_NODE || !el.childNodes.length) { + let trimmedText = removeExcessWhitespace(el.textContent); + if (trimmedText) { + strings.add(trimmedText); + } + return; + } + for (let node of el.childNodes) { + let nodeType = node.nodeType; + if (nodeType !== node.ELEMENT_NODE && nodeType !== node.TEXT_NODE) { + continue; + } + _extractElementStrings(node); + } + }; + _extractElementStrings(element); + return [...strings]; + }; - /** - * @returns {import("zod").infer|undefined} - */ - validateParams() { - if (this.params === undefined) { - return undefined; + // src/Form/matching-config/__generated__/compiled-matching-config.js + var matchingConfiguration = { + matchers: { + fields: { + unknown: { type: "unknown", strategies: { ddgMatcher: "unknown" } }, + emailAddress: { + type: "emailAddress", + strategies: { + cssSelector: "emailAddress", + ddgMatcher: "emailAddress", + vendorRegex: "email" + } + }, + password: { + type: "password", + strategies: { cssSelector: "password", ddgMatcher: "password" } + }, + username: { + type: "username", + strategies: { cssSelector: "username", ddgMatcher: "username" } + }, + firstName: { + type: "firstName", + strategies: { + cssSelector: "firstName", + ddgMatcher: "firstName", + vendorRegex: "given-name" + } + }, + middleName: { + type: "middleName", + strategies: { + cssSelector: "middleName", + ddgMatcher: "middleName", + vendorRegex: "additional-name" + } + }, + lastName: { + type: "lastName", + strategies: { + cssSelector: "lastName", + ddgMatcher: "lastName", + vendorRegex: "family-name" + } + }, + fullName: { + type: "fullName", + strategies: { + cssSelector: "fullName", + ddgMatcher: "fullName", + vendorRegex: "name" + } + }, + phone: { + type: "phone", + strategies: { + cssSelector: "phone", + ddgMatcher: "phone", + vendorRegex: "tel" + } + }, + addressStreet: { + type: "addressStreet", + strategies: { + cssSelector: "addressStreet", + ddgMatcher: "addressStreet", + vendorRegex: "address-line1" + } + }, + addressStreet2: { + type: "addressStreet2", + strategies: { + cssSelector: "addressStreet2", + ddgMatcher: "addressStreet2", + vendorRegex: "address-line2" + } + }, + addressCity: { + type: "addressCity", + strategies: { + cssSelector: "addressCity", + ddgMatcher: "addressCity", + vendorRegex: "address-level2" + } + }, + addressProvince: { + type: "addressProvince", + strategies: { + cssSelector: "addressProvince", + ddgMatcher: "addressProvince", + vendorRegex: "address-level1" + } + }, + addressPostalCode: { + type: "addressPostalCode", + strategies: { + cssSelector: "addressPostalCode", + ddgMatcher: "addressPostalCode", + vendorRegex: "postal-code" + } + }, + addressCountryCode: { + type: "addressCountryCode", + strategies: { + cssSelector: "addressCountryCode", + ddgMatcher: "addressCountryCode", + vendorRegex: "country" + } + }, + birthdayDay: { + type: "birthdayDay", + strategies: { cssSelector: "birthdayDay", ddgMatcher: "birthdayDay" } + }, + birthdayMonth: { + type: "birthdayMonth", + strategies: { cssSelector: "birthdayMonth", ddgMatcher: "birthdayMonth" } + }, + birthdayYear: { + type: "birthdayYear", + strategies: { cssSelector: "birthdayYear", ddgMatcher: "birthdayYear" } + }, + cardName: { + type: "cardName", + strategies: { + cssSelector: "cardName", + ddgMatcher: "cardName", + vendorRegex: "cc-name" + } + }, + cardNumber: { + type: "cardNumber", + strategies: { + cssSelector: "cardNumber", + ddgMatcher: "cardNumber", + vendorRegex: "cc-number" + } + }, + cardSecurityCode: { + type: "cardSecurityCode", + strategies: { + cssSelector: "cardSecurityCode", + ddgMatcher: "cardSecurityCode" + } + }, + expirationMonth: { + type: "expirationMonth", + strategies: { + cssSelector: "expirationMonth", + ddgMatcher: "expirationMonth", + vendorRegex: "cc-exp-month" + } + }, + expirationYear: { + type: "expirationYear", + strategies: { + cssSelector: "expirationYear", + ddgMatcher: "expirationYear", + vendorRegex: "cc-exp-year" + } + }, + expiration: { + type: "expiration", + strategies: { + cssSelector: "expiration", + ddgMatcher: "expiration", + vendorRegex: "cc-exp" + } + } + }, + lists: { + unknown: ["unknown"], + emailAddress: ["emailAddress"], + password: ["password"], + username: ["username"], + cc: [ + "cardName", + "cardNumber", + "cardSecurityCode", + "expirationMonth", + "expirationYear", + "expiration" + ], + id: [ + "firstName", + "middleName", + "lastName", + "fullName", + "phone", + "addressStreet", + "addressStreet2", + "addressCity", + "addressProvince", + "addressPostalCode", + "addressCountryCode", + "birthdayDay", + "birthdayMonth", + "birthdayYear" + ] + } + }, + strategies: { + cssSelector: { + selectors: { + genericTextField: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month])', + submitButtonSelector: 'input[type=submit], input[type=button], input[type=image], button:not([role=switch]):not([role=link]), [role=button], a[href="#"][id*=button i], a[href="#"][id*=btn i]', + formInputsSelector: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month]),[autocomplete=username],select', + safeUniversalSelector: "*:not(select):not(option):not(script):not(noscript):not(style):not(br)", + emailAddress: 'input:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]), input[type=""][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([type=tel]), input[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]):not([name*=code i]), input:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]), input[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=""][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=email], input[type=text][aria-label*=email i]:not([aria-label*=search i]), input:not([type])[aria-label*=email i]:not([aria-label*=search i]), input[name=username][type=email], input[autocomplete=username][type=email], input[autocomplete=username][placeholder*=email i], input[autocomplete=email],input[name="mail_tel" i],input[value=email i]', + username: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month])[autocomplete^=user i],input[name=username i],input[name="loginId" i],input[name="userid" i],input[id="userid" i],input[name="user_id" i],input[name="user-id" i],input[id="login-id" i],input[id="login_id" i],input[id="loginid" i],input[name="login" i],input[name=accountname i],input[autocomplete=username i],input[name*=accountid i],input[name="j_username" i],input[id="j_username" i],input[name="uwinid" i],input[name="livedoor_id" i],input[name="ssousername" i],input[name="j_userlogin_pwd" i],input[name="user[login]" i],input[name="user" i],input[name$="_username" i],input[id="lmSsoinput" i],input[name="account_subdomain" i],input[name="masterid" i],input[name="tridField" i],input[id="signInName" i],input[id="w3c_accountsbundle_accountrequeststep1_login" i],input[id="username" i],input[name="_user" i],input[name="login_username" i],input[name^="login-user-account" i],input[id="loginusuario" i],input[name="usuario" i],input[id="UserLoginFormUsername" i],input[id="nw_username" i],input[can-field="accountName"],input[placeholder^="username" i]', + password: "input[type=password]:not([autocomplete*=cc]):not([autocomplete=one-time-code]):not([name*=answer i]):not([name*=mfa i]):not([name*=tin i]):not([name*=card i]):not([name*=cvv i]),input.js-cloudsave-phrase", + cardName: 'input[autocomplete="cc-name" i], input[autocomplete="ccname" i], input[name="ccname" i], input[name="cc-name" i], input[name="ppw-accountHolderName" i], input[id*=cardname i], input[id*=card-name i], input[id*=card_name i]', + cardNumber: 'input[autocomplete="cc-number" i], input[autocomplete="ccnumber" i], input[autocomplete="cardnumber" i], input[autocomplete="card-number" i], input[name="ccnumber" i], input[name="cc-number" i], input[name*=card i][name*=number i], input[name*=cardnumber i], input[id*=cardnumber i], input[id*=card-number i], input[id*=card_number i]', + cardSecurityCode: 'input[autocomplete="cc-csc" i], input[autocomplete="csc" i], input[autocomplete="cc-cvc" i], input[autocomplete="cvc" i], input[name="cvc" i], input[name="cc-cvc" i], input[name="cc-csc" i], input[name="csc" i], input[name*=security i][name*=code i]', + expirationMonth: '[autocomplete="cc-exp-month" i], [autocomplete="cc_exp_month" i], [name="ccmonth" i], [name="ppw-expirationDate_month" i], [name=cardExpiryMonth i], [name*=ExpDate_Month i], [name*=expiration i][name*=month i], [id*=expiration i][id*=month i], [name*=cc-exp-month i], [name*="card_exp-month" i], [name*=cc_exp_month i], [id*=cc-month i]', + expirationYear: '[autocomplete="cc-exp-year" i], [autocomplete="cc_exp_year" i], [name="ccyear" i], [name="ppw-expirationDate_year" i], [name=cardExpiryYear i], [name*=ExpDate_Year i], [name*=expiration i][name*=year i], [id*=expiration i][id*=year i], [name*="cc-exp-year" i], [name*="card_exp-year" i], [name*=cc_exp_year i], [id*=cc-year i]', + expiration: '[autocomplete="cc-exp" i], [name="cc-exp" i], [name="exp-date" i], [name="expirationDate" i], input[id*=expiration i]', + firstName: "[name*=fname i], [autocomplete*=given-name i], [name*=firstname i], [autocomplete*=firstname i], [name*=first-name i], [autocomplete*=first-name i], [name*=first_name i], [autocomplete*=first_name i], [name*=givenname i], [autocomplete*=givenname i], [name*=given-name i], [name*=given_name i], [autocomplete*=given_name i], [name*=forename i], [autocomplete*=forename i]", + middleName: "[name*=mname i], [autocomplete*=additional-name i], [name*=middlename i], [autocomplete*=middlename i], [name*=middle-name i], [autocomplete*=middle-name i], [name*=middle_name i], [autocomplete*=middle_name i], [name*=additionalname i], [autocomplete*=additionalname i], [name*=additional-name i], [name*=additional_name i], [autocomplete*=additional_name i]", + lastName: "[name=lname], [autocomplete*=family-name i], [name*=lastname i], [autocomplete*=lastname i], [name*=last-name i], [autocomplete*=last-name i], [name*=last_name i], [autocomplete*=last_name i], [name*=familyname i], [autocomplete*=familyname i], [name*=family-name i], [name*=family_name i], [autocomplete*=family_name i], [name*=surname i], [autocomplete*=surname i]", + fullName: "[autocomplete=name], [name*=fullname i], [autocomplete*=fullname i], [name*=full-name i], [autocomplete*=full-name i], [name*=full_name i], [autocomplete*=full_name i], [name*=your-name i], [autocomplete*=your-name i]", + phone: '[name*=phone i]:not([name*=extension i]):not([name*=type i]):not([name*=country i]), [name*=mobile i]:not([name*=type i]), [autocomplete=tel], [autocomplete="tel-national"], [placeholder*="phone number" i]', + addressStreet: "[name=address i], [autocomplete=street-address i], [autocomplete=address-line1 i], [name=street i], [name=ppw-line1 i], [name*=addressLine1 i]", + addressStreet2: "[name=address2 i], [autocomplete=address-line2 i], [name=ppw-line2 i], [name*=addressLine2 i]", + addressCity: "[name=city i], [autocomplete=address-level2 i], [name=ppw-city i], [name*=addressCity i]", + addressProvince: "[name=province i], [name=state i], [autocomplete=address-level1 i]", + addressPostalCode: "[name=zip i], [name=zip2 i], [name=postal i], [autocomplete=postal-code i], [autocomplete=zip-code i], [name*=postalCode i], [name*=zipcode i]", + addressCountryCode: "[name=country i], [autocomplete=country i], [name*=countryCode i], [name*=country-code i], [name*=countryName i], [name*=country-name i],select.idms-address-country", + birthdayDay: '[name=bday-day i], [name*=birthday_day i], [name*=birthday-day i], [name=date_of_birth_day i], [name=date-of-birth-day i], [name^=birthdate_d i], [name^=birthdate-d i], [aria-label="birthday" i][placeholder="day" i]', + birthdayMonth: '[name=bday-month i], [name*=birthday_month i], [name*=birthday-month i], [name=date_of_birth_month i], [name=date-of-birth-month i], [name^=birthdate_m i], [name^=birthdate-m i], select[name="mm" i]', + birthdayYear: '[name=bday-year i], [name*=birthday_year i], [name*=birthday-year i], [name=date_of_birth_year i], [name=date-of-birth-year i], [name^=birthdate_y i], [name^=birthdate-y i], [aria-label="birthday" i][placeholder="year" i]' + } + }, + ddgMatcher: { + matchers: { + unknown: { + match: /search|filter|subject|title|captcha|mfa|2fa|(two|2).?factor|one-time|otp|cerca|filtr|oggetto|titolo|(due|2|più).?fattori|suche|filtern|betreff|zoeken|filter|onderwerp|titel|chercher|filtrer|objet|titre|authentification multifacteur|double authentification|à usage unique|busca|busqueda|filtra|dos pasos|un solo uso|sök|filter|ämne|multifaktorsautentisering|tvåfaktorsautentisering|två.?faktor|engångs/iu, + skip: /phone|mobile|email|password/iu + }, + emailAddress: { + match: /.mail\b|apple.?id|posta elettronica|e.?mailadres|correo electr|correo-e|^correo$|\be.?post|e.?postadress/iu, + skip: /phone|(first.?|last.?)name|number|code/iu, + forceUnknown: /search|filter|subject|title|\btab\b|otp/iu + }, + password: { + match: /password|passwort|kennwort|wachtwoord|mot de passe|clave|contraseña|lösenord/iu, + skip: /email|one-time|error|hint/iu, + forceUnknown: /captcha|mfa|2fa|two factor|otp|pin/iu + }, + newPassword: { match: /new|re.?(enter|type)|repeat|update|reset\b/iu }, + currentPassword: { match: /current|old|previous|expired|existing/iu }, + username: { + match: /(user|account|online.?id|log(i|o)n|net)((.)?(name|i.?d.?|log(i|o)n).?)?(.?((or|\/).+|\*|:)( required)?)?$|(nome|id|login).?utente|(nome|id) (dell.)?account|codice cliente|nutzername|anmeldename|gebruikersnaam|nom d.utilisateur|identifiant|pseudo|usuari|cuenta|identificador|apodo|\bdni\b|\bnie\b| del? documento|documento de identidad|användarnamn|kontonamn|användar-id/iu, + skip: /phone/iu, + forceUnknown: /search|policy|choose a user\b/iu + }, + cardName: { + match: /(card.*name|name.*card)|(card.*holder|holder.*card)|(card.*owner|owner.*card)/iu + }, + cardNumber: { + match: /card.*number|number.*card/iu, + skip: /phone/iu, + forceUnknown: /plus/iu + }, + cardSecurityCode: { + match: /security.?code|card.?verif|cvv|csc|cvc|cv2|card id/iu + }, + expirationMonth: { + match: /(card|\bcc\b)?.?(exp(iry|iration)?)?.?(month|\bmm\b(?![.\s/-]yy))/iu, + skip: /mm[/\s.\-_—–]|check/iu + }, + expirationYear: { + match: /(card|\bcc\b)?.?(exp(iry|iration)?)?.?(year|yy)/iu, + skip: /mm[/\s.\-_—–]|check/iu + }, + expiration: { + match: /(\bmm\b|\b\d\d\b)[/\s.\-_—–](\byy|\bjj|\baa|\b\d\d)|\bexp|\bvalid(idity| through| until)/iu, + skip: /invalid|^dd\/|check/iu + }, + firstName: { + match: /(first|given|fore).?name|\bnome/iu, + skip: /last|cognome|completo/iu + }, + middleName: { match: /(middle|additional).?name/iu }, + lastName: { + match: /(last|family|sur)[^i]?name|cognome/iu, + skip: /first|\bnome/iu + }, + fullName: { + match: /^(full.?|whole\s|first.*last\s|real\s|contact.?)?name\b|\bnome/iu, + forceUnknown: /company|org|item/iu + }, + phone: { + match: /phone|mobile|telefono|cellulare/iu, + skip: /code|pass|country/iu, + forceUnknown: /ext|type|otp/iu + }, + addressStreet: { + match: /address/iu, + forceUnknown: /\bip\b|duck|web|url/iu, + skip: /address.*(2|two|3|three)|email|log.?in|sign.?in|civico/iu + }, + addressStreet2: { + match: /address.*(2|two)|apartment|\bapt\b|\bflat\b|\bline.*(2|two)/iu, + forceUnknown: /\bip\b|duck/iu, + skip: /email|log.?in|sign.?in/iu + }, + addressCity: { + match: /city|town|città|comune/iu, + skip: /\bzip\b|\bcap\b/iu, + forceUnknown: /vatican/iu + }, + addressProvince: { + match: /state|province|region|county|provincia|regione/iu, + forceUnknown: /united/iu, + skip: /country/iu + }, + addressPostalCode: { + match: /\bzip\b|postal\b|post.?code|\bcap\b|codice postale/iu + }, + addressCountryCode: { match: /country|\bnation\b|nazione|paese/iu }, + birthdayDay: { match: /(birth.*day|day.*birth)/iu, skip: /month|year/iu }, + birthdayMonth: { match: /(birth.*month|month.*birth)/iu, skip: /year/iu }, + birthdayYear: { match: /(birth.*year|year.*birth)/iu }, + loginRegex: { + match: /sign(ing)?.?[io]n(?!g)|log.?[io]n|log.?out|unsubscri|(forgot(ten)?|reset) (your )?password|password (forgotten|lost)|mfa-submit-form|unlock|logged in as|entra|accedi|accesso|resetta password|password dimenticata|dimenticato la password|recuper[ao] password|(ein|aus)loggen|anmeld(eformular|ung|efeld)|abmelden|passwort (vergessen|verloren)|zugang| zugangsformular|einwahl|inloggen|se (dé)?connecter|(dé)?connexion|récupérer ((mon|ton|votre|le) )?mot de passe|mot de passe (oublié|perdu)|clave(?! su)|olvidó su (clave|contraseña)|.*sesión|conect(arse|ado)|conéctate|acce(de|so)|entrar|logga (in|ut)|avprenumerera|avregistrera|glömt lösenord|återställ lösenord/iu + }, + signupRegex: { + match: /sign(ing)?.?up|join|\bregist(er|ration)|newsletter|\bsubscri(be|ption)|contact|create|start|enroll|settings|preferences|profile|update|checkout|purchase|buy|order|schedule|estimate|request|new.?customer|(confirm|re.?(type|enter)|repeat) password|password confirm|iscri(viti|zione)|registra(ti|zione)|(?:nuovo|crea(?:zione)?) account|contatt(?:ac)i|sottoscriv|sottoscrizione|compra|acquist(a|o)|ordin[aeio]|richie(?:di|sta)|(?:conferma|ripeti) password|inizia|nuovo cliente|impostazioni|preferenze|profilo|aggiorna|paga|registrier(ung|en)|profil (anlegen|erstellen)| nachrichten|verteiler|neukunde|neuer (kunde|benutzer|nutzer)|passwort wiederholen|anmeldeseite|nieuwsbrief|aanmaken|profiel|s.inscrire|inscription|s.abonner|créer|préférences|profil|mise à jour|payer|ach(eter|at)| nouvel utilisateur|(confirmer|réessayer) ((mon|ton|votre|le) )?mot de passe|regis(trarse|tro)|regístrate|inscr(ibirse|ipción|íbete)|solicitar|crea(r cuenta)?|nueva cuenta|nuevo (cliente|usuario)|preferencias|perfil|lista de correo|registrer(a|ing)|(nytt|öppna) konto|nyhetsbrev|prenumer(era|ation)|kontakt|skapa|starta|inställningar|min (sida|kundvagn)|uppdatera|till kassan|gäst|köp|beställ|schemalägg|ny kund|(repetera|bekräfta) lösenord/iu + }, + conservativeSignupRegex: { + match: /sign.?up|join|register|enroll|(create|new).+account|newsletter|subscri(be|ption)|settings|preferences|profile|update|iscri(viti|zione)|registra(ti|zione)|(?:nuovo|crea(?:zione)?) account|contatt(?:ac)?i|sottoscriv|sottoscrizione|impostazioni|preferenze|aggiorna|anmeld(en|ung)|registrier(en|ung)|neukunde|neuer (kunde|benutzer|nutzer)|registreren|eigenschappen|profiel|bijwerken|s.inscrire|inscription|s.abonner|abonnement|préférences|profil|créer un compte|regis(trarse|tro)|regístrate|inscr(ibirse|ipción|íbete)|crea(r cuenta)?|nueva cuenta|nuevo (cliente|usuario)|preferencias|perfil|lista de correo|registrer(a|ing)|(nytt|öppna) konto|nyhetsbrev|prenumer(era|ation)|kontakt|skapa|starta|inställningar|min (sida|kundvagn)|uppdatera/iu + }, + resetPasswordLink: { + match: /(forgot(ten)?|reset|don't remember) (your )?password|password forgotten|password dimenticata|reset(?:ta) password|recuper[ao] password|(vergessen|verloren|verlegt|wiederherstellen) passwort|wachtwoord (vergeten|reset)|(oublié|récupérer) ((mon|ton|votre|le) )?mot de passe|mot de passe (oublié|perdu)|re(iniciar|cuperar) (contraseña|clave)|olvid(ó su|aste tu|é mi) (contraseña|clave)|recordar( su)? (contraseña|clave)|glömt lösenord|återställ lösenord/iu + }, + loginProvidersRegex: { match: / with | con | mit | met | avec /iu }, + submitButtonRegex: { + match: /submit|send|confirm|save|continue|next|sign|log.?([io])n|buy|purchase|check.?out|subscribe|donate|update|\bset\b|invia|conferma|salva|continua|entra|acced|accesso|compra|paga|sottoscriv|registra|dona|senden|\bja\b|bestätigen|weiter|nächste|kaufen|bezahlen|spenden|versturen|verzenden|opslaan|volgende|koop|kopen|voeg toe|aanmelden|envoyer|confirmer|sauvegarder|continuer|suivant|signer|connexion|acheter|payer|s.abonner|donner|enviar|confirmar|registrarse|continuar|siguiente|comprar|donar|skicka|bekräfta|spara|fortsätt|nästa|logga in|köp|handla|till kassan|registrera|donera/iu + }, + submitButtonUnlikelyRegex: { + match: /facebook|twitter|google|apple|cancel|show|toggle|reveal|hide|print|back|already|annulla|mostra|nascondi|stampa|indietro|già|abbrechen|passwort|zeigen|verbergen|drucken|zurück|annuleer|wachtwoord|toon|vorige|annuler|mot de passe|montrer|cacher|imprimer|retour|déjà|anular|cancelar|imprimir|cerrar|avbryt|lösenord|visa|dölj|skirv ut|tillbaka|redan/iu + } + } + }, + vendorRegex: { + rules: { + email: /((^e-?mail$)|(^email-?address$))|(e.?mail|courriel|correo.*electr(o|ó)nico|メールアドレス|электронной.?почты|邮件|邮箱|電郵地址|ഇ-മെയില്‍|ഇലക്ട്രോണിക്.?മെയിൽ|ایمیل|پست.*الکترونیک|ईमेल|इलॅक्ट्रॉनिक.?मेल|(\b|_)eposta(\b|_)|(?:이메일|전자.?우편|[ee]-?mail)(.?주소)?)/iu, + tel: /((^phone$)|(^mobile$)|(^mobile-?phone$)|(^tel$)|(^telephone$)|(^phone-?number$))|(phone|mobile|contact.?number|telefonnummer|telefono|teléfono|telfixe|電話|telefone|telemovel|телефон|मोबाइल|(\b|_|\*)telefon(\b|_|\*)|电话|മൊബൈല്‍|(?:전화|핸드폰|휴대폰|휴대전화)(?:.?번호)?)/iu, + organization: /((^company$)|(^company-?name$)|(^organization$)|(^organization-?name$))|(company|business|organization|organisation|empresa|societe|société|ragione.?sociale|会社|название.?компании|单位|公司|شرکت|회사|직장)/iu, + "street-address": /((^address$)|(^street-?address$)|(^addr$)|(^street$)|(^mailing-?addr(ess)?$)|(^billing-?addr(ess)?$)|(^mail-?addr(ess)?$)|(^bill-?addr(ess)?$))|(streetaddress|street-address)/iu, + "address-line1": /(addrline1|address_1)|((^address-?1$)|(^address-?line-?1$)|(^addr-?1$)|(^street-?1$))|(^address$|address[_-]?line[_-]?(1|one)|address1|addr1|street|(?:shipping|billing)address$|strasse|straße|hausnummer|housenumber|house.?name|direccion|dirección|adresse|indirizzo|^住所$|住所1|адрес|地址|(\b|_)adres(?! (başlığı(nız)?|tarifi))(\b|_)|^주소.?$|주소.?1)/iu, + "address-line2": /(addrline2|address_2)|((^address-?2$)|(^address-?line-?2$)|(^addr-?2$)|(^street-?2$))|(address[_-]?line(2|two)|address2|addr2|street|suite|unit(?!e)|adresszusatz|ergänzende.?angaben|direccion2|colonia|adicional|addresssuppl|complementnom|appartement|indirizzo2|住所2|complemento|addrcomplement|улица|地址2|주소.?2)/iu, + "address-line3": /(addrline3|address_3)|((^address-?3$)|(^address-?line-?3$)|(^addr-?3$)|(^street-?3$))|(address[_-]?line(3|three)|address3|addr3|street|suite|unit(?!e)|adresszusatz|ergänzende.?angaben|direccion3|colonia|adicional|addresssuppl|complementnom|appartement|indirizzo3|住所3|complemento|addrcomplement|улица|地址3|주소.?3)/iu, + "address-level2": /((^city$)|(^town$)|(^address-?level-?2$)|(^address-?city$)|(^address-?town$))|(city|town|\bort\b|stadt|suburb|ciudad|provincia|localidad|poblacion|ville|commune|localit(a|à)|citt(a|à)|市区町村|cidade|город|市|分區|شهر|शहर|ग्राम|गाँव|നഗരം|ഗ്രാമം|((\b|_|\*)([i̇ii̇]l[cç]e(miz|niz)?)(\b|_|\*))|^시[^도·・]|시[·・]?군[·・]?구)/iu, + "address-level1": /(land)|((^state$)|(^province$)|(^provence$)|(^address-?level-?1$)|(^address-?state$)|(^address-?province$))|(county|region|province|county|principality|都道府県|estado|provincia|область|省|地區|സംസ്ഥാനം|استان|राज्य|((\b|_|\*)(eyalet|[şs]ehir|[i̇ii̇]limiz|kent)(\b|_|\*))|^시[·・]?도)/iu, + "postal-code": /((^postal$)|(^zip$)|(^zip2$)|(^zip-?code$)|(^postal-?code$)|(^post-?code$)|(^address-?zip$)|(^address-?postal$)|(^address-?code$)|(^address-?postal-?code$)|(^address-?zip-?code$))|(zip|postal|post.*code|pcode|pin.?code|postleitzahl|\bcp\b|\bcdp\b|\bcap\b|郵便番号|codigo|codpos|\bcep\b|почтовый.?индекс|पिन.?कोड|പിന്‍കോഡ്|邮政编码|邮编|郵遞區號|(\b|_)posta kodu(\b|_)|우편.?번호)/iu, + country: /((^country$)|(^country-?code$)|(^country-?name$)|(^address-?country$)|(^address-?country-?name$)|(^address-?country-?code$))|(country|countries|país|pais|(\b|_)land(\b|_)(?!.*(mark.*))|国家|국가|나라|(\b|_)(ülke|ulce|ulke)(\b|_)|کشور)/iu, + "cc-name": /(accountholdername|titulaire)|(cc-?name|card-?name|cardholder-?name|cardholder|(^nom$))|(card.?(?:holder|owner)|name.*(\b)?on(\b)?.*card|(?:card|cc).?name|cc.?full.?name|karteninhaber|nombre.*tarjeta|nom.*carte|nome.*cart|名前|имя.*карты|信用卡开户名|开户名|持卡人姓名|持卡人姓名)/iu, + name: /((^name$)|full-?name|your-?name)|(^name|full.?name|your.?name|customer.?name|bill.?name|ship.?name|name.*first.*last|firstandlastname|nombre.*y.*apellidos|^nom(?!bre)\b|お名前|氏名|^nome|نام.*نام.*خانوادگی|姓名|(\b|_|\*)ad[ı]? soyad[ı]?(\b|_|\*)|성명)/iu, + "given-name": /((^f-?name$)|(^first-?name$)|(^given-?name$)|(^first-?n$))|(first.*name|initials|fname|first$|given.*name|vorname|nombre|forename|prénom|prenom|名|\bnome|имя|نام|이름|പേര്|(\b|_|\*)(isim|ad|ad(i|ı|iniz|ınız)?)(\b|_|\*)|नाम)/iu, + "additional-name": /(apellido.?materno|lastlastname)|((^m-?name$)|(^middle-?name$)|(^additional-?name$)|(^middle-?initial$)|(^middle-?n$)|(^middle-?i$))|(middle.*name|mname|middle$|middle.*initial|m\.i\.|mi$|\bmi\b)/iu, + "family-name": /((^l-?name$)|(^last-?name$)|(^s-?name$)|(^surname$)|(^family-?name$)|(^family-?n$)|(^last-?n$))|(last.*name|lname|surname|last$|secondname|family.*name|nachname|apellidos?|famille|^nom(?!bre)|cognome|姓|apelidos|surename|sobrenome|фамилия|نام.*خانوادگی|उपनाम|മറുപേര്|(\b|_|\*)(soyisim|soyad(i|ı|iniz|ınız)?)(\b|_|\*)|\b성(?:[^명]|\b))/iu, + "cc-number": /((cc|kk)nr)|(cc-?number|cc-?num|card-?number|card-?num|(^number$)|(^cc$)|cc-?no|card-?no|(^credit-?card$)|numero-?carte|(^carte$)|(^carte-?credit$)|num-?carte|cb-?num)|((add)?(?:card|cc|acct).?(?:number|#|no|num|field)|カード番号|номер.*карты|信用卡号|信用卡号码|信用卡卡號|카드|(numero|número|numéro)(?!.*(document|fono|phone|réservation)))/iu, + "cc-exp-month": /((cc|kk)month)|((^exp-?month$)|(^cc-?exp-?month$)|(^cc-?month$)|(^card-?month$)|(^cc-?mo$)|(^card-?mo$)|(^exp-?mo$)|(^card-?exp-?mo$)|(^cc-?exp-?mo$)|(^card-?expiration-?month$)|(^expiration-?month$)|(^cc-?mm$)|(^cc-?m$)|(^card-?mm$)|(^card-?m$)|(^card-?exp-?mm$)|(^cc-?exp-?mm$)|(^exp-?mm$)|(^exp-?m$)|(^expire-?month$)|(^expire-?mo$)|(^expiry-?month$)|(^expiry-?mo$)|(^card-?expire-?month$)|(^card-?expire-?mo$)|(^card-?expiry-?month$)|(^card-?expiry-?mo$)|(^mois-?validite$)|(^mois-?expiration$)|(^m-?validite$)|(^m-?expiration$)|(^expiry-?date-?field-?month$)|(^expiration-?date-?month$)|(^expiration-?date-?mm$)|(^exp-?mon$)|(^validity-?mo$)|(^exp-?date-?mo$)|(^cb-?date-?mois$)|(^date-?m$))|(gueltig|gültig|monat|fecha|date.*exp|scadenza|有効期限|validade|срок действия карты|月)/iu, + "cc-exp-year": /((cc|kk)year)|((^exp-?year$)|(^cc-?exp-?year$)|(^cc-?year$)|(^card-?year$)|(^cc-?yr$)|(^card-?yr$)|(^exp-?yr$)|(^card-?exp-?yr$)|(^cc-?exp-?yr$)|(^card-?expiration-?year$)|(^expiration-?year$)|(^cc-?yy$)|(^cc-?y$)|(^card-?yy$)|(^card-?y$)|(^card-?exp-?yy$)|(^cc-?exp-?yy$)|(^exp-?yy$)|(^exp-?y$)|(^cc-?yyyy$)|(^card-?yyyy$)|(^card-?exp-?yyyy$)|(^cc-?exp-?yyyy$)|(^expire-?year$)|(^expire-?yr$)|(^expiry-?year$)|(^expiry-?yr$)|(^card-?expire-?year$)|(^card-?expire-?yr$)|(^card-?expiry-?year$)|(^card-?expiry-?yr$)|(^an-?validite$)|(^an-?expiration$)|(^annee-?validite$)|(^annee-?expiration$)|(^expiry-?date-?field-?year$)|(^expiration-?date-?year$)|(^cb-?date-?ann$)|(^expiration-?date-?yy$)|(^expiration-?date-?yyyy$)|(^validity-?year$)|(^exp-?date-?year$)|(^date-?y$))|(ablaufdatum|gueltig|gültig|jahr|fecha|scadenza|有効期限|validade|срок действия карты|年|有效期)/iu, + "cc-exp": /((^cc-?exp$)|(^card-?exp$)|(^cc-?expiration$)|(^card-?expiration$)|(^cc-?ex$)|(^card-?ex$)|(^card-?expire$)|(^card-?expiry$)|(^validite$)|(^expiration$)|(^expiry$)|mm-?yy|mm-?yyyy|yy-?mm|yyyy-?mm|expiration-?date|payment-?card-?expiration|(^payment-?cc-?date$))|(expir|exp.*date|^expfield$|gueltig|gültig|fecha|date.*exp|scadenza|有効期限|validade|срок действия карты)/iu, + "cc-type": /(type|kartenmarke)|((^cc-?type$)|(^card-?type$)|(^card-?brand$)|(^cc-?brand$)|(^cb-?type$))/iu + }, + ruleSets: [ + { + "address-line1": "addrline1|address_1", + "address-line2": "addrline2|address_2", + "address-line3": "addrline3|address_3", + "address-level1": "land", + "additional-name": "apellido.?materno|lastlastname", + "cc-name": "accountholdername|titulaire", + "cc-number": "(cc|kk)nr", + "cc-exp-month": "(cc|kk)month", + "cc-exp-year": "(cc|kk)year", + "cc-type": "type|kartenmarke" + }, + { + email: "(^e-?mail$)|(^email-?address$)", + tel: "(^phone$)|(^mobile$)|(^mobile-?phone$)|(^tel$)|(^telephone$)|(^phone-?number$)", + organization: "(^company$)|(^company-?name$)|(^organization$)|(^organization-?name$)", + "street-address": "(^address$)|(^street-?address$)|(^addr$)|(^street$)|(^mailing-?addr(ess)?$)|(^billing-?addr(ess)?$)|(^mail-?addr(ess)?$)|(^bill-?addr(ess)?$)", + "address-line1": "(^address-?1$)|(^address-?line-?1$)|(^addr-?1$)|(^street-?1$)", + "address-line2": "(^address-?2$)|(^address-?line-?2$)|(^addr-?2$)|(^street-?2$)", + "address-line3": "(^address-?3$)|(^address-?line-?3$)|(^addr-?3$)|(^street-?3$)", + "address-level2": "(^city$)|(^town$)|(^address-?level-?2$)|(^address-?city$)|(^address-?town$)", + "address-level1": "(^state$)|(^province$)|(^provence$)|(^address-?level-?1$)|(^address-?state$)|(^address-?province$)", + "postal-code": "(^postal$)|(^zip$)|(^zip2$)|(^zip-?code$)|(^postal-?code$)|(^post-?code$)|(^address-?zip$)|(^address-?postal$)|(^address-?code$)|(^address-?postal-?code$)|(^address-?zip-?code$)", + country: "(^country$)|(^country-?code$)|(^country-?name$)|(^address-?country$)|(^address-?country-?name$)|(^address-?country-?code$)", + name: "(^name$)|full-?name|your-?name", + "given-name": "(^f-?name$)|(^first-?name$)|(^given-?name$)|(^first-?n$)", + "additional-name": "(^m-?name$)|(^middle-?name$)|(^additional-?name$)|(^middle-?initial$)|(^middle-?n$)|(^middle-?i$)", + "family-name": "(^l-?name$)|(^last-?name$)|(^s-?name$)|(^surname$)|(^family-?name$)|(^family-?n$)|(^last-?n$)", + "cc-name": "cc-?name|card-?name|cardholder-?name|cardholder|(^nom$)", + "cc-number": "cc-?number|cc-?num|card-?number|card-?num|(^number$)|(^cc$)|cc-?no|card-?no|(^credit-?card$)|numero-?carte|(^carte$)|(^carte-?credit$)|num-?carte|cb-?num", + "cc-exp": "(^cc-?exp$)|(^card-?exp$)|(^cc-?expiration$)|(^card-?expiration$)|(^cc-?ex$)|(^card-?ex$)|(^card-?expire$)|(^card-?expiry$)|(^validite$)|(^expiration$)|(^expiry$)|mm-?yy|mm-?yyyy|yy-?mm|yyyy-?mm|expiration-?date|payment-?card-?expiration|(^payment-?cc-?date$)", + "cc-exp-month": "(^exp-?month$)|(^cc-?exp-?month$)|(^cc-?month$)|(^card-?month$)|(^cc-?mo$)|(^card-?mo$)|(^exp-?mo$)|(^card-?exp-?mo$)|(^cc-?exp-?mo$)|(^card-?expiration-?month$)|(^expiration-?month$)|(^cc-?mm$)|(^cc-?m$)|(^card-?mm$)|(^card-?m$)|(^card-?exp-?mm$)|(^cc-?exp-?mm$)|(^exp-?mm$)|(^exp-?m$)|(^expire-?month$)|(^expire-?mo$)|(^expiry-?month$)|(^expiry-?mo$)|(^card-?expire-?month$)|(^card-?expire-?mo$)|(^card-?expiry-?month$)|(^card-?expiry-?mo$)|(^mois-?validite$)|(^mois-?expiration$)|(^m-?validite$)|(^m-?expiration$)|(^expiry-?date-?field-?month$)|(^expiration-?date-?month$)|(^expiration-?date-?mm$)|(^exp-?mon$)|(^validity-?mo$)|(^exp-?date-?mo$)|(^cb-?date-?mois$)|(^date-?m$)", + "cc-exp-year": "(^exp-?year$)|(^cc-?exp-?year$)|(^cc-?year$)|(^card-?year$)|(^cc-?yr$)|(^card-?yr$)|(^exp-?yr$)|(^card-?exp-?yr$)|(^cc-?exp-?yr$)|(^card-?expiration-?year$)|(^expiration-?year$)|(^cc-?yy$)|(^cc-?y$)|(^card-?yy$)|(^card-?y$)|(^card-?exp-?yy$)|(^cc-?exp-?yy$)|(^exp-?yy$)|(^exp-?y$)|(^cc-?yyyy$)|(^card-?yyyy$)|(^card-?exp-?yyyy$)|(^cc-?exp-?yyyy$)|(^expire-?year$)|(^expire-?yr$)|(^expiry-?year$)|(^expiry-?yr$)|(^card-?expire-?year$)|(^card-?expire-?yr$)|(^card-?expiry-?year$)|(^card-?expiry-?yr$)|(^an-?validite$)|(^an-?expiration$)|(^annee-?validite$)|(^annee-?expiration$)|(^expiry-?date-?field-?year$)|(^expiration-?date-?year$)|(^cb-?date-?ann$)|(^expiration-?date-?yy$)|(^expiration-?date-?yyyy$)|(^validity-?year$)|(^exp-?date-?year$)|(^date-?y$)", + "cc-type": "(^cc-?type$)|(^card-?type$)|(^card-?brand$)|(^cc-?brand$)|(^cb-?type$)" + }, + { + email: "e.?mail|courriel|correo.*electr(o|\xF3)nico|\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9|\u042D\u043B\u0435\u043A\u0442\u0440\u043E\u043D\u043D\u043E\u0439.?\u041F\u043E\u0447\u0442\u044B|\u90AE\u4EF6|\u90AE\u7BB1|\u96FB\u90F5\u5730\u5740|\u0D07-\u0D2E\u0D46\u0D2F\u0D3F\u0D32\u0D4D\u200D|\u0D07\u0D32\u0D15\u0D4D\u0D1F\u0D4D\u0D30\u0D4B\u0D23\u0D3F\u0D15\u0D4D.?\u0D2E\u0D46\u0D2F\u0D3F\u0D7D|\u0627\u06CC\u0645\u06CC\u0644|\u067E\u0633\u062A.*\u0627\u0644\u06A9\u062A\u0631\u0648\u0646\u06CC\u06A9|\u0908\u092E\u0947\u0932|\u0907\u0932\u0945\u0915\u094D\u091F\u094D\u0930\u0949\u0928\u093F\u0915.?\u092E\u0947\u0932|(\\b|_)eposta(\\b|_)|(?:\uC774\uBA54\uC77C|\uC804\uC790.?\uC6B0\uD3B8|[Ee]-?mail)(.?\uC8FC\uC18C)?", + tel: "phone|mobile|contact.?number|telefonnummer|telefono|tel\xE9fono|telfixe|\u96FB\u8A71|telefone|telemovel|\u0442\u0435\u043B\u0435\u0444\u043E\u043D|\u092E\u094B\u092C\u093E\u0907\u0932|(\\b|_|\\*)telefon(\\b|_|\\*)|\u7535\u8BDD|\u0D2E\u0D4A\u0D2C\u0D48\u0D32\u0D4D\u200D|(?:\uC804\uD654|\uD578\uB4DC\uD3F0|\uD734\uB300\uD3F0|\uD734\uB300\uC804\uD654)(?:.?\uBC88\uD638)?", + organization: "company|business|organization|organisation|empresa|societe|soci\xE9t\xE9|ragione.?sociale|\u4F1A\u793E|\u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435.?\u043A\u043E\u043C\u043F\u0430\u043D\u0438\u0438|\u5355\u4F4D|\u516C\u53F8|\u0634\u0631\u06A9\u062A|\uD68C\uC0AC|\uC9C1\uC7A5", + "street-address": "streetaddress|street-address", + "address-line1": "^address$|address[_-]?line[_-]?(1|one)|address1|addr1|street|(?:shipping|billing)address$|strasse|stra\xDFe|hausnummer|housenumber|house.?name|direccion|direcci\xF3n|adresse|indirizzo|^\u4F4F\u6240$|\u4F4F\u62401|\u0410\u0434\u0440\u0435\u0441|\u5730\u5740|(\\b|_)adres(?! (ba\u015Fl\u0131\u011F\u0131(n\u0131z)?|tarifi))(\\b|_)|^\uC8FC\uC18C.?$|\uC8FC\uC18C.?1", + "address-line2": "address[_-]?line(2|two)|address2|addr2|street|suite|unit(?!e)|adresszusatz|erg\xE4nzende.?angaben|direccion2|colonia|adicional|addresssuppl|complementnom|appartement|indirizzo2|\u4F4F\u62402|complemento|addrcomplement|\u0423\u043B\u0438\u0446\u0430|\u5730\u57402|\uC8FC\uC18C.?2", + "address-line3": "address[_-]?line(3|three)|address3|addr3|street|suite|unit(?!e)|adresszusatz|erg\xE4nzende.?angaben|direccion3|colonia|adicional|addresssuppl|complementnom|appartement|indirizzo3|\u4F4F\u62403|complemento|addrcomplement|\u0423\u043B\u0438\u0446\u0430|\u5730\u57403|\uC8FC\uC18C.?3", + "address-level2": "city|town|\\bort\\b|stadt|suburb|ciudad|provincia|localidad|poblacion|ville|commune|localit(a|\xE0)|citt(a|\xE0)|\u5E02\u533A\u753A\u6751|cidade|\u0413\u043E\u0440\u043E\u0434|\u5E02|\u5206\u5340|\u0634\u0647\u0631|\u0936\u0939\u0930|\u0917\u094D\u0930\u093E\u092E|\u0917\u093E\u0901\u0935|\u0D28\u0D17\u0D30\u0D02|\u0D17\u0D4D\u0D30\u0D3E\u0D2E\u0D02|((\\b|_|\\*)([\u0130ii\u0307]l[c\xE7]e(miz|niz)?)(\\b|_|\\*))|^\uC2DC[^\uB3C4\xB7\u30FB]|\uC2DC[\xB7\u30FB]?\uAD70[\xB7\u30FB]?\uAD6C", + "address-level1": "county|region|province|county|principality|\u90FD\u9053\u5E9C\u770C|estado|provincia|\u043E\u0431\u043B\u0430\u0441\u0442\u044C|\u7701|\u5730\u5340|\u0D38\u0D02\u0D38\u0D4D\u0D25\u0D3E\u0D28\u0D02|\u0627\u0633\u062A\u0627\u0646|\u0930\u093E\u091C\u094D\u092F|((\\b|_|\\*)(eyalet|[\u015Fs]ehir|[\u0130ii\u0307]limiz|kent)(\\b|_|\\*))|^\uC2DC[\xB7\u30FB]?\uB3C4", + "postal-code": "zip|postal|post.*code|pcode|pin.?code|postleitzahl|\\bcp\\b|\\bcdp\\b|\\bcap\\b|\u90F5\u4FBF\u756A\u53F7|codigo|codpos|\\bcep\\b|\u041F\u043E\u0447\u0442\u043E\u0432\u044B\u0439.?\u0418\u043D\u0434\u0435\u043A\u0441|\u092A\u093F\u0928.?\u0915\u094B\u0921|\u0D2A\u0D3F\u0D28\u0D4D\u200D\u0D15\u0D4B\u0D21\u0D4D|\u90AE\u653F\u7F16\u7801|\u90AE\u7F16|\u90F5\u905E\u5340\u865F|(\\b|_)posta kodu(\\b|_)|\uC6B0\uD3B8.?\uBC88\uD638", + country: "country|countries|pa\xEDs|pais|(\\b|_)land(\\b|_)(?!.*(mark.*))|\u56FD\u5BB6|\uAD6D\uAC00|\uB098\uB77C|(\\b|_)(\xFClke|ulce|ulke)(\\b|_)|\u06A9\u0634\u0648\u0631", + "cc-name": "card.?(?:holder|owner)|name.*(\\b)?on(\\b)?.*card|(?:card|cc).?name|cc.?full.?name|karteninhaber|nombre.*tarjeta|nom.*carte|nome.*cart|\u540D\u524D|\u0418\u043C\u044F.*\u043A\u0430\u0440\u0442\u044B|\u4FE1\u7528\u5361\u5F00\u6237\u540D|\u5F00\u6237\u540D|\u6301\u5361\u4EBA\u59D3\u540D|\u6301\u5361\u4EBA\u59D3\u540D", + name: "^name|full.?name|your.?name|customer.?name|bill.?name|ship.?name|name.*first.*last|firstandlastname|nombre.*y.*apellidos|^nom(?!bre)\\b|\u304A\u540D\u524D|\u6C0F\u540D|^nome|\u0646\u0627\u0645.*\u0646\u0627\u0645.*\u062E\u0627\u0646\u0648\u0627\u062F\u06AF\u06CC|\u59D3\u540D|(\\b|_|\\*)ad[\u0131]? soyad[\u0131]?(\\b|_|\\*)|\uC131\uBA85", + "given-name": "first.*name|initials|fname|first$|given.*name|vorname|nombre|forename|pr\xE9nom|prenom|\u540D|\\bnome|\u0418\u043C\u044F|\u0646\u0627\u0645|\uC774\uB984|\u0D2A\u0D47\u0D30\u0D4D|(\\b|_|\\*)(isim|ad|ad(i|\u0131|iniz|\u0131n\u0131z)?)(\\b|_|\\*)|\u0928\u093E\u092E", + "additional-name": "middle.*name|mname|middle$|middle.*initial|m\\.i\\.|mi$|\\bmi\\b", + "family-name": "last.*name|lname|surname|last$|secondname|family.*name|nachname|apellidos?|famille|^nom(?!bre)|cognome|\u59D3|apelidos|surename|sobrenome|\u0424\u0430\u043C\u0438\u043B\u0438\u044F|\u0646\u0627\u0645.*\u062E\u0627\u0646\u0648\u0627\u062F\u06AF\u06CC|\u0909\u092A\u0928\u093E\u092E|\u0D2E\u0D31\u0D41\u0D2A\u0D47\u0D30\u0D4D|(\\b|_|\\*)(soyisim|soyad(i|\u0131|iniz|\u0131n\u0131z)?)(\\b|_|\\*)|\\b\uC131(?:[^\uBA85]|\\b)", + "cc-number": "(add)?(?:card|cc|acct).?(?:number|#|no|num|field)|\u30AB\u30FC\u30C9\u756A\u53F7|\u041D\u043E\u043C\u0435\u0440.*\u043A\u0430\u0440\u0442\u044B|\u4FE1\u7528\u5361\u53F7|\u4FE1\u7528\u5361\u53F7\u7801|\u4FE1\u7528\u5361\u5361\u865F|\uCE74\uB4DC|(numero|n\xFAmero|num\xE9ro)(?!.*(document|fono|phone|r\xE9servation))", + "cc-exp-month": "gueltig|g\xFCltig|monat|fecha|date.*exp|scadenza|\u6709\u52B9\u671F\u9650|validade|\u0421\u0440\u043E\u043A \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044F \u043A\u0430\u0440\u0442\u044B|\u6708", + "cc-exp-year": "ablaufdatum|gueltig|g\xFCltig|jahr|fecha|scadenza|\u6709\u52B9\u671F\u9650|validade|\u0421\u0440\u043E\u043A \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044F \u043A\u0430\u0440\u0442\u044B|\u5E74|\u6709\u6548\u671F", + "cc-exp": "expir|exp.*date|^expfield$|gueltig|g\xFCltig|fecha|date.*exp|scadenza|\u6709\u52B9\u671F\u9650|validade|\u0421\u0440\u043E\u043A \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044F \u043A\u0430\u0440\u0442\u044B" + } + ] + } } - this._validate(this.params, this.paramsValidator); - return this.params; - } + }; - /** - * @param {any|null} incoming - * @returns {import("zod").infer} - */ - validateResult(incoming) { - this._validate(incoming, this.resultValidator); - if (!incoming) { - return incoming; + // src/Form/matching-utils.js + function logMatching(el, matchingResult) { + if (!shouldLog()) + return; + const fieldIdentifier = getInputIdentifier(el); + console.group(fieldIdentifier); + console.log(el); + const { strategyName, matchedString, matchedFrom, matcherType } = matchingResult; + const verb = getVerb(matchingResult); + let stringToLog = `${verb} for "${matcherType}" with "${strategyName}"`; + if (matchedString && matchedFrom) { + stringToLog += ` +String: "${matchedString}" +Source: "${matchedFrom}"`; + } + console.log(stringToLog); + console.groupEnd(); + } + function getVerb(matchingResult) { + if (matchingResult.matched) + return "Matched"; + if (matchingResult.proceed === false) + return "Matched forceUnknown"; + if (matchingResult.skip) + return "Skipped"; + return ""; + } + function getInputIdentifier(el) { + const label = getExplicitLabelsText(el); + const placeholder = el instanceof HTMLInputElement && el.placeholder ? `${el.placeholder}` : ""; + const name = el.name ? `${el.name}` : ""; + const id = el.id ? `#${el.id}` : ""; + return "Field: " + (label || placeholder || name || id); + } + function logUnmatched(el, allStrings) { + if (!shouldLog()) + return; + const fieldIdentifier = getInputIdentifier(el); + console.group(fieldIdentifier); + console.log(el); + const stringToLog = "Field not matched."; + console.log(stringToLog, allStrings); + console.groupEnd(); + } + + // src/Form/matching.js + var { TEXT_LENGTH_CUTOFF, ATTR_INPUT_TYPE } = constants; + var dimensionBounds = { + emailAddress: { minWidth: 35 } + }; + var _config, _cssSelectors, _ddgMatchers, _vendorRegexRules, _matcherLists, _defaultStrategyOrder; + var Matching = class { + /** + * @param {MatchingConfiguration} config + */ + constructor(config) { + /** @type {MatchingConfiguration} */ + __privateAdd(this, _config, void 0); + /** @type {CssSelectorConfiguration['selectors']} */ + __privateAdd(this, _cssSelectors, void 0); + /** @type {Record} */ + __privateAdd(this, _ddgMatchers, void 0); + /** + * This acts as an internal cache for the larger vendorRegexes + * @type {VendorRegexConfiguration['rules']} + */ + __privateAdd(this, _vendorRegexRules, void 0); + /** @type {MatcherLists} */ + __privateAdd(this, _matcherLists, void 0); + /** @type {Array} */ + __privateAdd(this, _defaultStrategyOrder, ["cssSelector", "ddgMatcher", "vendorRegex"]); + /** @type {Record} */ + __publicField(this, "activeElementStrings", { + nameAttr: "", + labelText: "", + placeholderAttr: "", + relatedText: "", + id: "" + }); + /** + * Yield strings in the order in which they should be checked against. + * + * Note: some strategies may not want to accept all strings, which is + * where `matchableStrings` helps. It defaults to when you see below but can + * be overridden. + * + * For example, `nameAttr` is first, since this has the highest chance of matching + * and then the rest are in decreasing order of value vs cost + * + * A generator function is used here to prevent any potentially expensive + * lookups occurring if they are rare. For example if 90% of all matching never needs + * to look at the output from `relatedText`, then the cost of computing it will be avoided. + * + * @param {HTMLInputElement|HTMLSelectElement} el + * @param {HTMLElement} form + * @returns {Record} + */ + __publicField(this, "_elementStringCache", /* @__PURE__ */ new WeakMap()); + __privateSet(this, _config, config); + __privateSet(this, _vendorRegexRules, __privateGet(this, _config).strategies.vendorRegex.rules); + __privateSet(this, _cssSelectors, __privateGet(this, _config).strategies.cssSelector.selectors); + __privateSet(this, _ddgMatchers, __privateGet(this, _config).strategies.ddgMatcher.matchers); + __privateSet(this, _matcherLists, { + unknown: [], + cc: [], + id: [], + password: [], + username: [], + emailAddress: [] + }); + for (let [listName, matcherNames] of Object.entries(__privateGet(this, _config).matchers.lists)) { + for (let fieldName of matcherNames) { + if (!__privateGet(this, _matcherLists)[listName]) { + __privateGet(this, _matcherLists)[listName] = []; + } + __privateGet(this, _matcherLists)[listName].push(__privateGet(this, _config).matchers.fields[fieldName]); + } + } } - if (!this.unwrapResult) { - return incoming; + /** + * @param {HTMLInputElement|HTMLSelectElement} input + * @param {HTMLElement} formEl + */ + setActiveElementStrings(input, formEl) { + this.activeElementStrings = this.getElementStrings(input, formEl); + } + /** + * Try to access a 'vendor regex' by name + * @param {string} regexName + * @returns {RegExp | undefined} + */ + vendorRegex(regexName) { + const match = __privateGet(this, _vendorRegexRules)[regexName]; + if (!match) { + console.warn("Vendor Regex not found for", regexName); + return void 0; + } + return match; } - if ('data' in incoming) { - console.warn('response had `data` property. Please migrate to `success`'); - return incoming.data; + /** + * Strategies can have different lookup names. This returns the correct one + * @param {MatcherTypeNames} matcherName + * @param {StrategyNames} vendorRegex + * @returns {MatcherTypeNames} + */ + getStrategyLookupByType(matcherName, vendorRegex) { + return __privateGet(this, _config).matchers.fields[matcherName]?.strategies[vendorRegex]; } - if ('success' in incoming) { - return incoming.success; + /** + * Try to access a 'css selector' by name from configuration + * @param {RequiredCssSelectors | string} selectorName + * @returns {string}; + */ + cssSelector(selectorName) { + const match = __privateGet(this, _cssSelectors)[selectorName]; + if (!match) { + console.warn("CSS selector not found for %s, using a default value", selectorName); + return ""; + } + return match; } - if ('error' in incoming) { - if (typeof incoming.error.message === 'string') { - throw new DeviceApiCallError(`${this.method}: ${incoming.error.message}`); + /** + * Try to access a 'ddg matcher' by name from configuration + * @param {MatcherTypeNames | string} matcherName + * @returns {DDGMatcher | undefined} + */ + ddgMatcher(matcherName) { + const match = __privateGet(this, _ddgMatchers)[matcherName]; + if (!match) { + console.warn("DDG matcher not found for", matcherName); + return void 0; } + return match; } - if (this.throwOnResultKeysMissing) { - throw new Error('unreachable. Response did not contain `success` or `data`'); + /** + * Returns the RegExp for the given matcherName, with proper flags + * @param {AllDDGMatcherNames} matcherName + * @returns {RegExp|undefined} + */ + getDDGMatcherRegex(matcherName) { + const matcher = this.ddgMatcher(matcherName); + if (!matcher || !matcher.match) { + console.warn("DDG matcher has unexpected format"); + return void 0; + } + return matcher?.match; } - return incoming; - } - - /** - * @param {any} data - * @param {import("zod").ZodType|undefined|null} [validator] - * @private - */ - _validate(data, validator) { - if (!validator) return data; - if (validator) { - const result = validator?.safeParse(data); - if (!result) { - throw new Error('unreachable, data failure', data); + /** + * Try to access a list of matchers by name - these are the ones collected in the constructor + * @param {keyof MatcherLists} listName + * @return {Matcher[]} + */ + matcherList(listName) { + const matcherList = __privateGet(this, _matcherLists)[listName]; + if (!matcherList) { + console.warn("MatcherList not found for ", listName); + return []; } - if (!result.success) { - if ('error' in result) { - this.throwError(result.error.issues); - } else { - console.error('unknown error from validate'); + return matcherList; + } + /** + * Convert a list of matchers into a single CSS selector. + * + * This will consider all matchers in the list and if it + * contains a CSS Selector it will be added to the final output + * + * @param {keyof MatcherLists} listName + * @returns {string | undefined} + */ + joinCssSelectors(listName) { + const matcherList = this.matcherList(listName); + if (!matcherList) { + console.warn("Matcher list not found for", listName); + return void 0; + } + const selectors = []; + for (let matcher of matcherList) { + if (matcher.strategies.cssSelector) { + const css = this.cssSelector(matcher.strategies.cssSelector); + if (css) { + selectors.push(css); + } } } + return selectors.join(", "); } - } - - /** - * @param {import('zod').ZodIssue[]} errors - */ - throwError(errors) { - const error = SchemaValidationError.fromZodErrors(errors, this.constructor.name); - throw error; - } - - /** - * Use this helper for creating stand-in response messages that are typed correctly. - * - * @examples - * - * ```js - * const msg = new Message(); - * const response = msg.response({}) // <-- This argument will be typed correctly - * ``` - * - * @param {import("zod").infer} response - * @returns {import("zod").infer} - */ - result(response) { - return response; - } - /** - * @returns {import("zod").infer} - */ - preResultValidation(response) { - return response; - } -} -exports.DeviceApiCall = DeviceApiCall; -class DeviceApiCallError extends Error {} - -/** - * Check for this error if you'd like to - */ -exports.DeviceApiCallError = DeviceApiCallError; -class SchemaValidationError extends Error { - /** @type {import("zod").ZodIssue[]} */ - validationErrors = []; - - /** - * @param {import("zod").ZodIssue[]} errors - * @param {string} name - * @returns {SchemaValidationError} - */ - static fromZodErrors(errors, name) { - const heading = `${errors.length} SchemaValidationError(s) errors for ` + name; - function log(issue) { - switch (issue.code) { - case 'invalid_literal': - case 'invalid_type': - { - console.log(`${name}. Path: '${issue.path.join('.')}', Error: '${issue.message}'`); - break; + /** + * Returns true if the field is visible and large enough + * @param {keyof MatcherLists} matchedType + * @param {HTMLInputElement} input + * @returns {boolean} + */ + isInputLargeEnough(matchedType, input) { + const expectedDimensionBounds = dimensionBounds[matchedType]; + if (!expectedDimensionBounds) + return true; + const width = input.offsetWidth; + const height = input.offsetHeight; + const isHidden = height === 0 && width === 0; + if (isHidden) + return true; + return width >= expectedDimensionBounds.minWidth; + } + /** + * Tries to infer the input type for an input + * + * @param {HTMLInputElement|HTMLSelectElement} input + * @param {HTMLElement} formEl + * @param {SetInputTypeOpts} [opts] + * @returns {SupportedTypes} + */ + inferInputType(input, formEl, opts = {}) { + const presetType = getInputType(input); + if (presetType !== "unknown") { + return presetType; + } + this.setActiveElementStrings(input, formEl); + if (this.subtypeFromMatchers("unknown", input)) + return "unknown"; + if (opts.isCCForm) { + const subtype = this.subtypeFromMatchers("cc", input); + if (subtype && isValidCreditCardSubtype(subtype)) { + return `creditCards.${subtype}`; + } + } + if (input instanceof HTMLInputElement) { + if (this.subtypeFromMatchers("password", input)) { + if (["password", "text"].includes(input.type) && input.name !== "email" && // pcsretirement.com, improper use of the for attribute + input.name !== "Username") { + return this.inferPasswordVariant(input, opts); } - case 'invalid_union': - { - for (let unionError of issue.unionErrors) { - for (let issue1 of unionError.issues) { - log(issue1); - } + } + if (this.subtypeFromMatchers("emailAddress", input)) { + if (!this.isInputLargeEnough("emailAddress", input)) { + if (shouldLog()) { + console.log("Field matched for Email Address, but discarded because too small when scanned"); } - break; + return "unknown"; } - default: - { - console.log(name, 'other issue:', issue); + if (opts.isLogin || opts.isHybrid) { + return "credentials.username"; + } + if (window.location.href.includes("https://accounts.google.com/v3/signin/identifier") && input.matches("[type=email][autocomplete=username]")) { + return "credentials.username"; } + return "identities.emailAddress"; + } + if (this.subtypeFromMatchers("username", input)) { + return "credentials.username"; + } } + const idSubtype = this.subtypeFromMatchers("id", input); + if (idSubtype && isValidIdentitiesSubtype(idSubtype)) { + return `identities.${idSubtype}`; + } + logUnmatched(input, this.activeElementStrings); + return "unknown"; } - for (let error of errors) { - log(error); + /** + * @typedef {{ + * isLogin?: boolean, + * isHybrid?: boolean, + * isCCForm?: boolean, + * hasCredentials?: boolean, + * supportsIdentitiesAutofill?: boolean + * }} SetInputTypeOpts + */ + /** + * Sets the input type as a data attribute to the element and returns it + * @param {HTMLInputElement} input + * @param {HTMLElement} formEl + * @param {SetInputTypeOpts} [opts] + * @returns {SupportedSubTypes | string} + */ + setInputType(input, formEl, opts = {}) { + const type = this.inferInputType(input, formEl, opts); + input.setAttribute(ATTR_INPUT_TYPE, type); + return type; } - const message = [heading, 'please see the details above'].join('\n '); - const error = new SchemaValidationError(message); - error.validationErrors = errors; - return error; - } -} - -/** - * Creates an instance of `DeviceApiCall` from only a name and 'params' - * and optional validators. Use this to help migrate existing messages. - * - * @template {import("zod").ZodType} Params - * @template {import("zod").ZodType} Result - * @param {string} method - * @param {import("zod").infer} [params] - * @param {Params|null} [paramsValidator] - * @param {Result|null} [resultValidator] - * @returns {DeviceApiCall} - */ -exports.SchemaValidationError = SchemaValidationError; -function createDeviceApiCall(method, params) { - let paramsValidator = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; - let resultValidator = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; - /** @type {DeviceApiCall} */ - const deviceApiCall = new DeviceApiCall(params); - deviceApiCall.paramsValidator = paramsValidator; - deviceApiCall.resultValidator = resultValidator; - deviceApiCall.method = method; - deviceApiCall.throwOnResultKeysMissing = false; - deviceApiCall.unwrapResult = false; - return deviceApiCall; -} - -/** - * Creates an instance of `DeviceApiCall` from only a name and 'params' - * and optional validators. Use this to help migrate existing messages. - * - * Note: This creates a regular DeviceApiCall, but adds the 'id' as a string - * so that transports know that it expects a response. - * - * @template {import("zod").ZodType} Params - * @template {import("zod").ZodType} Result - * @param {string} method - * @param {import("zod").infer} [params] - * @param {string} [id] - * @param {Params|null} [paramsValidator] - * @param {Result|null} [resultValidator] - * @returns {DeviceApiCall} - */ -function createRequest(method, params) { - let id = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'n/a'; - let paramsValidator = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; - let resultValidator = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null; - const call = createDeviceApiCall(method, params, paramsValidator, resultValidator); - call.id = id; - return call; -} -const createNotification = exports.createNotification = createDeviceApiCall; - -/** - * Validate any arbitrary data with any Zod validator - * - * @template {import("zod").ZodType} Validator - * @param {any} data - * @param {Validator | null} [validator] - * @returns {import("zod").infer} - */ -function validate(data) { - let validator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; - if (validator) { - return validator.parse(data); - } - return data; -} - -},{}],4:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.DeviceApiTransport = exports.DeviceApi = void 0; -/** - * Platforms should only need to implement this `send` method - */ -class DeviceApiTransport { - /** - * @param {import("./device-api-call.js").DeviceApiCall} _deviceApiCall - * @param {CallOptions} [_options] - * @returns {Promise} - */ - async send(_deviceApiCall, _options) { - return undefined; - } -} - -/** - * This is the base Sender class that platforms can will implement. - * - * Note: The 'handle' method must be implemented, unless you also implement 'send' - * - * @typedef CallOptions - * @property {AbortSignal} [signal] - */ -exports.DeviceApiTransport = DeviceApiTransport; -class DeviceApi { - /** @type {DeviceApiTransport} */ - transport; - /** @param {DeviceApiTransport} transport */ - constructor(transport) { - this.transport = transport; - } - /** - * @template {import("./device-api-call").DeviceApiCall} D - * @param {D} deviceApiCall - * @param {CallOptions} [options] - * @returns {Promise['success']>>} - */ - async request(deviceApiCall, options) { - deviceApiCall.validateParams(); - let result = await this.transport.send(deviceApiCall, options); - let processed = deviceApiCall.preResultValidation(result); - return deviceApiCall.validateResult(processed); - } - /** - * @template {import("./device-api-call").DeviceApiCall} P - * @param {P} deviceApiCall - * @param {CallOptions} [options] - * @returns {Promise} - */ - async notify(deviceApiCall, options) { - deviceApiCall.validateParams(); - return this.transport.send(deviceApiCall, options); - } -} -exports.DeviceApi = DeviceApi; - -},{}],5:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.MissingHandler = exports.MessagingTransport = exports.Messaging = void 0; -Object.defineProperty(exports, "WebkitMessagingConfig", { - enumerable: true, - get: function () { - return _webkit.WebkitMessagingConfig; - } -}); -var _webkit = require("./webkit.js"); -/** - * @module Messaging - * - * @description - * - * An abstraction for communications between JavaScript and host platforms. - * - * 1) First you construct your platform-specific configuration (eg: {@link WebkitMessagingConfig}) - * 2) Then use that to get an instance of the Messaging utility which allows - * you to send and receive data in a unified way - * 3) Each platform implements {@link MessagingTransport} along with its own Configuration - * - For example, to learn what configuration is required for Webkit, see: {@link "Webkit Messaging".WebkitMessagingConfig} - * - Or, to learn about how messages are sent and received in Webkit, see {@link "Webkit Messaging".WebkitMessagingTransport} - * - * @example Webkit Messaging - * - * ```js - * import { Messaging, WebkitMessagingConfig } from "@duckduckgo/content-scope-scripts/lib/messaging.js" - * - * // This config would be injected into the UserScript - * const injectedConfig = { - * hasModernWebkitAPI: true, - * webkitMessageHandlerNames: ["foo", "bar", "baz"], - * secret: "dax", - * }; - * - * // Then use that config to construct platform-specific configuration - * const config = new WebkitMessagingConfig(injectedConfig); - * - * // finally, get an instance of Messaging and start sending messages in a unified way 🚀 - * const messaging = new Messaging(config); - * messaging.notify("hello world!", {foo: "bar"}) - * - * ``` - * - * @example Windows Messaging - * - * ```js - * import { Messaging, WindowsMessagingConfig } from "@duckduckgo/content-scope-scripts/lib/messaging.js" - * - * // Messaging on Windows is namespaced, so you can create multiple messaging instances - * const autofillConfig = new WindowsMessagingConfig({ featureName: "Autofill" }); - * const debugConfig = new WindowsMessagingConfig({ featureName: "Debugging" }); - * - * const autofillMessaging = new Messaging(autofillConfig); - * const debugMessaging = new Messaging(debugConfig); - * - * // Now send messages to both features as needed 🚀 - * autofillMessaging.notify("storeFormData", { "username": "dax" }) - * debugMessaging.notify("pageLoad", { time: window.performance.now() }) - * ``` - */ - -/** - * @implements {MessagingTransport} - */ -class Messaging { - /** - * @param {WebkitMessagingConfig} config - */ - constructor(config) { - this.transport = getTransport(config); - } - /** - * Send a 'fire-and-forget' message. - * @throws {Error} - * {@link MissingHandler} - * - * @example - * - * ``` - * const messaging = new Messaging(config) - * messaging.notify("foo", {bar: "baz"}) - * ``` - * @param {string} name - * @param {Record} [data] - */ - notify(name) { - let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - this.transport.notify(name, data); - } - /** - * Send a request, and wait for a response - * @throws {Error} - * {@link MissingHandler} - * - * @example - * ``` - * const messaging = new Messaging(config) - * const response = await messaging.request("foo", {bar: "baz"}) - * ``` - * - * @param {string} name - * @param {Record} [data] - * @return {Promise} - */ - request(name) { - let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - return this.transport.request(name, data); - } -} - -/** - * @interface - */ -exports.Messaging = Messaging; -class MessagingTransport { - /** - * @param {string} name - * @param {Record} [data] - * @returns {void} - */ - // @ts-ignore - ignoring a no-unused ts error, this is only an interface. - notify(name) { - let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - throw new Error("must implement 'notify'"); - } - /** - * @param {string} name - * @param {Record} [data] - * @return {Promise} - */ - // @ts-ignore - ignoring a no-unused ts error, this is only an interface. - request(name) { - let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - throw new Error('must implement'); - } -} - -/** - * @param {WebkitMessagingConfig} config - * @returns {MessagingTransport} - */ -exports.MessagingTransport = MessagingTransport; -function getTransport(config) { - if (config instanceof _webkit.WebkitMessagingConfig) { - return new _webkit.WebkitMessagingTransport(config); - } - throw new Error('unreachable'); -} - -/** - * Thrown when a handler cannot be found - */ -class MissingHandler extends Error { - /** - * @param {string} message - * @param {string} handlerName - */ - constructor(message, handlerName) { - super(message); - this.handlerName = handlerName; - } -} - -/** - * Some re-exports for convenience - */ -exports.MissingHandler = MissingHandler; - -},{"./webkit.js":6}],6:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.WebkitMessagingTransport = exports.WebkitMessagingConfig = exports.SecureMessagingParams = void 0; -var _messaging = require("./messaging.js"); -/** - * @module Webkit Messaging - * - * @description - * - * A wrapper for messaging on WebKit platforms. It supports modern WebKit messageHandlers - * along with encryption for older versions (like macOS Catalina) - * - * Note: If you wish to support Catalina then you'll need to implement the native - * part of the message handling, see {@link WebkitMessagingTransport} for details. - * - * ```js - * import { Messaging, WebkitMessagingConfig } from "@duckduckgo/content-scope-scripts/lib/messaging.js" - * - * // This config would be injected into the UserScript - * const injectedConfig = { - * hasModernWebkitAPI: true, - * webkitMessageHandlerNames: ["foo", "bar", "baz"], - * secret: "dax", - * }; - * - * // Then use that config to construct platform-specific configuration - * const config = new WebkitMessagingConfig(injectedConfig); - * - * // finally, get an instance of Messaging and start sending messages in a unified way 🚀 - * const messaging = new Messaging(config); - * messaging.notify("hello world!", {foo: "bar"}) - * - * ``` - */ - -/** - * @typedef {import("./messaging").MessagingTransport} MessagingTransport - */ - -/** - * @example - * On macOS 11+, this will just call through to `window.webkit.messageHandlers.x.postMessage` - * - * Eg: for a `foo` message defined in Swift that accepted the payload `{"bar": "baz"}`, the following - * would occur: - * - * ```js - * const json = await window.webkit.messageHandlers.foo.postMessage({ bar: "baz" }); - * const response = JSON.parse(json) - * ``` - * - * @example - * On macOS 10 however, the process is a little more involved. A method will be appended to `window` - * that allows the response to be delivered there instead. It's not exactly this, but you can visualize the flow - * as being something along the lines of: - * - * ```js - * // add the window method - * window["_0123456"] = (response) => { - * // decrypt `response` and deliver the result to the caller here - * // then remove the temporary method - * delete window["_0123456"] - * }; - * - * // send the data + `messageHanding` values - * window.webkit.messageHandlers.foo.postMessage({ - * bar: "baz", - * messagingHandling: { - * methodName: "_0123456", - * secret: "super-secret", - * key: [1, 2, 45, 2], - * iv: [34, 4, 43], - * } - * }); - * - * // later in swift, the following JavaScript snippet will be executed - * (() => { - * window["_0123456"]({ - * ciphertext: [12, 13, 4], - * tag: [3, 5, 67, 56] - * }) - * })() - * ``` - * @implements {MessagingTransport} - */ -class WebkitMessagingTransport { - /** @type {WebkitMessagingConfig} */ - config; - globals; - /** - * @param {WebkitMessagingConfig} config - */ - constructor(config) { - this.config = config; - this.globals = captureGlobals(); - if (!this.config.hasModernWebkitAPI) { - this.captureWebkitHandlers(this.config.webkitMessageHandlerNames); + /** + * Tries to infer input subtype, with checks in decreasing order of reliability + * @param {keyof MatcherLists} listName + * @param {HTMLInputElement|HTMLSelectElement} el + * @return {MatcherTypeNames|undefined} + */ + subtypeFromMatchers(listName, el) { + const matchers = this.matcherList(listName); + for (let strategyName of __privateGet(this, _defaultStrategyOrder)) { + let result; + for (let matcher of matchers) { + const lookup = matcher.strategies[strategyName]; + if (!lookup) + continue; + if (strategyName === "cssSelector") { + result = this.execCssSelector(lookup, el); + } + if (strategyName === "ddgMatcher") { + result = this.execDDGMatcher(lookup); + } + if (strategyName === "vendorRegex") { + result = this.execVendorRegex(lookup); + } + if (result?.matched) { + logMatching(el, result); + return matcher.type; + } + if (!result?.matched && result?.proceed === false) { + logMatching(el, result); + return void 0; + } + } + if (result?.skip) { + logMatching(el, result); + break; + } + } + return void 0; } - } - /** - * Sends message to the webkit layer (fire and forget) - * @param {String} handler - * @param {*} data - * @internal - */ - wkSend(handler) { - let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - if (!(handler in this.globals.window.webkit.messageHandlers)) { - throw new _messaging.MissingHandler(`Missing webkit handler: '${handler}'`, handler); - } - const outgoing = { - ...data, - messageHandling: { - ...data.messageHandling, - secret: this.config.secret + /** + * Returns the password type string including the variant + * @param {HTMLInputElement} input + * @param opts + * @returns {'credentials.password.new'|'credentials.password.current'} + */ + inferPasswordVariant(input, opts) { + const attrsToCheck = [input.autocomplete, input.name, input.id]; + if (opts.isSignup && attrsToCheck.some((str) => safeRegexTest(/new.?password|password.?new/i, str))) { + return "credentials.password.new"; } - }; - if (!this.config.hasModernWebkitAPI) { - if (!(handler in this.globals.capturedWebkitHandlers)) { - throw new _messaging.MissingHandler(`cannot continue, method ${handler} not captured on macos < 11`, handler); - } else { - return this.globals.capturedWebkitHandlers[handler](outgoing); + if ((opts.isLogin || opts.isHybrid) && attrsToCheck.some((str) => safeRegexTest(/(current|old|previous).?password|password.?(current|old|previous)/i, str))) { + return "credentials.password.current"; } - } - return this.globals.window.webkit.messageHandlers[handler].postMessage?.(outgoing); - } - - /** - * Sends message to the webkit layer and waits for the specified response - * @param {String} handler - * @param {*} data - * @returns {Promise<*>} - * @internal - */ - async wkSendAndWait(handler) { - let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - if (this.config.hasModernWebkitAPI) { - const response = await this.wkSend(handler, data); - return this.globals.JSONparse(response || '{}'); - } - try { - const randMethodName = this.createRandMethodName(); - const key = await this.createRandKey(); - const iv = this.createRandIv(); - const { - ciphertext, - tag - } = await new this.globals.Promise(( /** @type {any} */resolve) => { - this.generateRandomMethod(randMethodName, resolve); - data.messageHandling = new SecureMessagingParams({ - methodName: randMethodName, - secret: this.config.secret, - key: this.globals.Arrayfrom(key), - iv: this.globals.Arrayfrom(iv) - }); - this.wkSend(handler, data); - }); - const cipher = new this.globals.Uint8Array([...ciphertext, ...tag]); - const decrypted = await this.decrypt(cipher, key, iv); - return this.globals.JSONparse(decrypted || '{}'); - } catch (e) { - // re-throw when the error is just a 'MissingHandler' - if (e instanceof _messaging.MissingHandler) { - throw e; - } else { - console.error('decryption failed', e); - console.error(e); - return { - error: e - }; + const newPasswordMatch = this.execDDGMatcher("newPassword"); + if (newPasswordMatch.matched) { + return "credentials.password.new"; } - } - } - /** - * @param {string} name - * @param {Record} [data] - */ - notify(name) { - let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - this.wkSend(name, data); - } - /** - * @param {string} name - * @param {Record} [data] - */ - request(name) { - let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - return this.wkSendAndWait(name, data); - } - /** - * Generate a random method name and adds it to the global scope - * The native layer will use this method to send the response - * @param {string | number} randomMethodName - * @param {Function} callback - */ - generateRandomMethod(randomMethodName, callback) { - var _this = this; - this.globals.ObjectDefineProperty(this.globals.window, randomMethodName, { - enumerable: false, - // configurable, To allow for deletion later - configurable: true, - writable: false, - /** - * @param {any[]} args - */ - value: function () { - callback(...arguments); - // @ts-ignore - we want this to throw if it fails as it would indicate a fatal error. - delete _this.globals.window[randomMethodName]; + const currentPasswordMatch = this.execDDGMatcher("currentPassword"); + if (currentPasswordMatch.matched) { + return "credentials.password.current"; } - }); - } - randomString() { - return '' + this.globals.getRandomValues(new this.globals.Uint32Array(1))[0]; - } - createRandMethodName() { - return '_' + this.randomString(); - } - - /** - * @type {{name: string, length: number}} - */ - algoObj = { - name: 'AES-GCM', - length: 256 - }; - - /** - * @returns {Promise} - */ - async createRandKey() { - const key = await this.globals.generateKey(this.algoObj, true, ['encrypt', 'decrypt']); - const exportedKey = await this.globals.exportKey('raw', key); - return new this.globals.Uint8Array(exportedKey); - } - - /** - * @returns {Uint8Array} - */ - createRandIv() { - return this.globals.getRandomValues(new this.globals.Uint8Array(12)); - } - - /** - * @param {BufferSource} ciphertext - * @param {BufferSource} key - * @param {Uint8Array} iv - * @returns {Promise} - */ - async decrypt(ciphertext, key, iv) { - const cryptoKey = await this.globals.importKey('raw', key, 'AES-GCM', false, ['decrypt']); - const algo = { - name: 'AES-GCM', - iv - }; - let decrypted = await this.globals.decrypt(algo, cryptoKey, ciphertext); - let dec = new this.globals.TextDecoder(); - return dec.decode(decrypted); - } - - /** - * When required (such as on macos 10.x), capture the `postMessage` method on - * each webkit messageHandler - * - * @param {string[]} handlerNames - */ - captureWebkitHandlers(handlerNames) { - const handlers = window.webkit.messageHandlers; - if (!handlers) throw new _messaging.MissingHandler('window.webkit.messageHandlers was absent', 'all'); - for (let webkitMessageHandlerName of handlerNames) { - if (typeof handlers[webkitMessageHandlerName]?.postMessage === 'function') { - /** - * `bind` is used here to ensure future calls to the captured - * `postMessage` have the correct `this` context - */ - const original = handlers[webkitMessageHandlerName]; - const bound = handlers[webkitMessageHandlerName].postMessage?.bind(original); - this.globals.capturedWebkitHandlers[webkitMessageHandlerName] = bound; - delete handlers[webkitMessageHandlerName].postMessage; + if (opts.isLogin || opts.isHybrid) { + return "credentials.password.current"; } + return "credentials.password.new"; } - } -} - -/** - * Use this configuration to create an instance of {@link Messaging} for WebKit - * - * ```js - * import { fromConfig, WebkitMessagingConfig } from "@duckduckgo/content-scope-scripts/lib/messaging.js" - * - * const config = new WebkitMessagingConfig({ - * hasModernWebkitAPI: true, - * webkitMessageHandlerNames: ["foo", "bar", "baz"], - * secret: "dax", - * }); - * - * const messaging = new Messaging(config) - * const resp = await messaging.request("debugConfig") - * ``` - */ -exports.WebkitMessagingTransport = WebkitMessagingTransport; -class WebkitMessagingConfig { - /** - * @param {object} params - * @param {boolean} params.hasModernWebkitAPI - * @param {string[]} params.webkitMessageHandlerNames - * @param {string} params.secret - */ - constructor(params) { /** - * Whether or not the current WebKit Platform supports secure messaging - * by default (eg: macOS 11+) + * CSS selector matching just leverages the `.matches` method on elements + * + * @param {MatcherTypeNames} lookup + * @param {HTMLInputElement|HTMLSelectElement} el + * @returns {MatchingResult} */ - this.hasModernWebkitAPI = params.hasModernWebkitAPI; - /** - * A list of WebKit message handler names that a user script can send - */ - this.webkitMessageHandlerNames = params.webkitMessageHandlerNames; - /** - * A string provided by native platforms to be sent with future outgoing - * messages - */ - this.secret = params.secret; - } -} - -/** - * This is the additional payload that gets appended to outgoing messages. - * It's used in the Swift side to encrypt the response that comes back - */ -exports.WebkitMessagingConfig = WebkitMessagingConfig; -class SecureMessagingParams { - /** - * @param {object} params - * @param {string} params.methodName - * @param {string} params.secret - * @param {number[]} params.key - * @param {number[]} params.iv - */ - constructor(params) { + execCssSelector(lookup, el) { + const selector = this.cssSelector(lookup); + return { + matched: el.matches(selector), + strategyName: "cssSelector", + matcherType: lookup + }; + } /** - * The method that's been appended to `window` to be called later + * A DDG Matcher can have a `match` regex along with a `not` regex. This is done + * to allow it to be driven by configuration as it avoids needing to invoke custom functions. + * + * todo: maxDigits was added as an edge-case when converting this over to be declarative, but I'm + * unsure if it's actually needed. It's not urgent, but we should consider removing it if that's the case + * + * @param {MatcherTypeNames} lookup + * @returns {MatchingResult} */ - this.methodName = params.methodName; - /** - * The secret used to ensure message sender validity - */ - this.secret = params.secret; - /** - * The CipherKey as number[] - */ - this.key = params.key; + execDDGMatcher(lookup) { + const defaultResult = { matched: false, strategyName: "ddgMatcher", matcherType: lookup }; + const ddgMatcher = this.ddgMatcher(lookup); + if (!ddgMatcher || !ddgMatcher.match) { + return defaultResult; + } + let matchRexExp = this.getDDGMatcherRegex(lookup); + if (!matchRexExp) { + return defaultResult; + } + let requiredScore = ["match", "forceUnknown", "maxDigits"].filter((ddgMatcherProp) => ddgMatcherProp in ddgMatcher).length; + const matchableStrings = ddgMatcher.matchableStrings || ["labelText", "placeholderAttr", "relatedText"]; + for (let stringName of matchableStrings) { + let elementString = this.activeElementStrings[stringName]; + if (!elementString) + continue; + let score = 0; + const result = { + ...defaultResult, + matchedString: elementString, + matchedFrom: stringName + }; + if (ddgMatcher.forceUnknown) { + let notRegex = ddgMatcher.forceUnknown; + if (!notRegex) { + return { ...result, matched: false }; + } + if (safeRegexTest(notRegex, elementString)) { + return { ...result, matched: false, proceed: false }; + } else { + score++; + } + } + if (ddgMatcher.skip) { + let skipRegex = ddgMatcher.skip; + if (!skipRegex) { + return { ...result, matched: false }; + } + if (safeRegexTest(skipRegex, elementString)) { + return { ...result, matched: false, skip: true }; + } + } + if (!safeRegexTest(matchRexExp, elementString)) { + continue; + } + score++; + if (ddgMatcher.maxDigits) { + const digitLength = elementString.replace(/[^0-9]/g, "").length; + if (digitLength > ddgMatcher.maxDigits) { + return { ...result, matched: false }; + } else { + score++; + } + } + if (score === requiredScore) { + return { ...result, matched: true }; + } + } + return defaultResult; + } /** - * The Initial Vector as number[] - */ - this.iv = params.iv; - } -} - -/** - * Capture some globals used for messaging handling to prevent page - * scripts from tampering with this - */ -exports.SecureMessagingParams = SecureMessagingParams; -function captureGlobals() { - // Creat base with null prototype - return { - window, - // Methods must be bound to their interface, otherwise they throw Illegal invocation - encrypt: window.crypto.subtle.encrypt.bind(window.crypto.subtle), - decrypt: window.crypto.subtle.decrypt.bind(window.crypto.subtle), - generateKey: window.crypto.subtle.generateKey.bind(window.crypto.subtle), - exportKey: window.crypto.subtle.exportKey.bind(window.crypto.subtle), - importKey: window.crypto.subtle.importKey.bind(window.crypto.subtle), - getRandomValues: window.crypto.getRandomValues.bind(window.crypto), - TextEncoder, - TextDecoder, - Uint8Array, - Uint16Array, - Uint32Array, - JSONstringify: window.JSON.stringify, - JSONparse: window.JSON.parse, - Arrayfrom: window.Array.from, - Promise: window.Promise, - ObjectDefineProperty: window.Object.defineProperty, - addEventListener: window.addEventListener.bind(window), - /** @type {Record} */ - capturedWebkitHandlers: {} - }; -} - -},{"./messaging.js":5}],7:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.HostnameInputError = void 0; -Object.defineProperty(exports, "ParserError", { - enumerable: true, - get: function () { - return _rulesParser.ParserError; - } -}); -exports._selectPasswordRules = _selectPasswordRules; -Object.defineProperty(exports, "constants", { - enumerable: true, - get: function () { - return _constants.constants; - } -}); -exports.generate = generate; -var _applePassword = require("./lib/apple.password.js"); -var _rulesParser = require("./lib/rules-parser.js"); -var _constants = require("./lib/constants.js"); -/** - * @typedef {{ - * domain?: string | null | undefined; - * input?: string | null | undefined; - * rules?: RulesFormat | null | undefined; - * onError?: ((error: unknown) => void) | null | undefined; - * }} GenerateOptions - */ -/** - * Generate a random password based on the following attempts - * - * 1) using `options.input` if provided -> falling back to default ruleset - * 2) using `options.domain` if provided -> falling back to default ruleset - * 3) using default ruleset - * - * Note: This API is designed to never throw - if you want to observe errors - * during development, you can provide an `onError` callback - * - * @param {GenerateOptions} [options] - */ -function generate() { - let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - try { - if (typeof options?.input === 'string') { - return _applePassword.Password.generateOrThrow(options.input); - } - if (typeof options?.domain === 'string') { - if (options?.rules) { - const rules = _selectPasswordRules(options.domain, options.rules); - if (rules) { - return _applePassword.Password.generateOrThrow(rules); - } - } - } - } catch (e) { - // if an 'onError' callback was provided, forward all errors - if (options?.onError && typeof options?.onError === 'function') { - options.onError(e); - } else { - // otherwise, only console.error unknown errors (which could be implementation bugs) - const isKnownError = e instanceof _rulesParser.ParserError || e instanceof HostnameInputError; - if (!isKnownError) { - console.error(e); + * If we get here, a firefox/vendor regex was given and we can execute it on the element + * strings + * @param {MatcherTypeNames} lookup + * @return {MatchingResult} + */ + execVendorRegex(lookup) { + const defaultResult = { matched: false, strategyName: "vendorRegex", matcherType: lookup }; + const regex = this.vendorRegex(lookup); + if (!regex) { + return defaultResult; + } + const stringsToMatch = ["placeholderAttr", "nameAttr", "labelText", "id", "relatedText"]; + for (let stringName of stringsToMatch) { + let elementString = this.activeElementStrings[stringName]; + if (!elementString) + continue; + if (safeRegexTest(regex, elementString)) { + return { + ...defaultResult, + matched: true, + matchedString: elementString, + matchedFrom: stringName + }; + } } + return defaultResult; } - } - - // At this point, we have to trust the generation will not throw - // as it is NOT using any user/page-provided data - return _applePassword.Password.generateDefault(); -} - -// An extension type to differentiate between known errors -class HostnameInputError extends Error {} - -/** - * @typedef {Record} RulesFormat - */ - -/** - * @private - * @param {string} inputHostname - * @param {RulesFormat} rules - * @returns {string | undefined} - * @throws {HostnameInputError} - */ -exports.HostnameInputError = HostnameInputError; -function _selectPasswordRules(inputHostname, rules) { - const hostname = _safeHostname(inputHostname); - // direct match - if (rules[hostname]) { - return rules[hostname]['password-rules']; - } - - // otherwise, start chopping off subdomains and re-joining to compare - const pieces = hostname.split('.'); - while (pieces.length > 1) { - pieces.shift(); - const joined = pieces.join('.'); - if (rules[joined]) { - return rules[joined]['password-rules']; + getElementStrings(el, form) { + if (this._elementStringCache.has(el)) { + return this._elementStringCache.get(el); + } + const explicitLabelsText = getExplicitLabelsText(el); + const next = { + nameAttr: el.name, + labelText: explicitLabelsText, + placeholderAttr: el.placeholder || "", + id: el.id, + relatedText: explicitLabelsText ? "" : getRelatedText(el, form, this.cssSelector("formInputsSelector")) + }; + this._elementStringCache.set(el, next); + return next; + } + clear() { + this._elementStringCache = /* @__PURE__ */ new WeakMap(); } - } - return undefined; -} - -/** - * @private - * @param {string} inputHostname; - * @throws {HostnameInputError} - * @returns {string} - */ -function _safeHostname(inputHostname) { - if (inputHostname.startsWith('http:') || inputHostname.startsWith('https:')) { - throw new HostnameInputError('invalid input, you can only provide a hostname but you gave a scheme'); - } - if (inputHostname.includes(':')) { - throw new HostnameInputError('invalid input, you can only provide a hostname but you gave a :port'); - } - try { - const asUrl = new URL('https://' + inputHostname); - return asUrl.hostname; - } catch (e) { - throw new HostnameInputError(`could not instantiate a URL from that hostname ${inputHostname}`); - } -} - -},{"./lib/apple.password.js":8,"./lib/constants.js":9,"./lib/rules-parser.js":10}],8:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.Password = void 0; -var parser = _interopRequireWildcard(require("./rules-parser.js")); -var _constants = require("./constants.js"); -function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } -function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } -/* - * - * NOTE: - * - * This file was created with inspiration from https://developer.apple.com/password-rules - * - * * The changes made by DuckDuckGo employees are: - * - * 1) removed all logic relating to 'more typeable passwords' - * 2) reduced the number of password styles from 4 to only the 1 which suits our needs - * 2) added JSDoc comments (for Typescript checking) - * - */ - -/** - * @typedef {{ - * PasswordAllowedCharacters?: string, - * PasswordRequiredCharacters?: string[], - * PasswordRepeatedCharacterLimit?: number, - * PasswordConsecutiveCharacterLimit?: number, - * PasswordMinLength?: number, - * PasswordMaxLength?: number, - * }} Requirements - */ -/** - * @typedef {{ - * NumberOfRequiredRandomCharacters: number, - * PasswordAllowedCharacters: string, - * RequiredCharacterSets: string[] - * }} PasswordParameters - */ -const defaults = Object.freeze({ - SCAN_SET_ORDER: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-~!@#$%^&*_+=`|(){}[:;\\\"'<>,.?/ ]", - defaultUnambiguousCharacters: 'abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ0123456789', - defaultPasswordLength: _constants.constants.DEFAULT_MIN_LENGTH, - defaultPasswordRules: _constants.constants.DEFAULT_PASSWORD_RULES, - defaultRequiredCharacterSets: ['abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', '0123456789'], - /** - * @type {typeof window.crypto.getRandomValues | null} - */ - getRandomValues: null -}); - -/** - * This is added here to ensure: - * - * 1) `getRandomValues` is called with the correct prototype chain - * 2) `window` is not accessed when in a node environment - * 3) `bind` is not called in a hot code path - * - * @type {{ getRandomValues: typeof window.crypto.getRandomValues }} - */ -const safeGlobals = {}; -if (typeof window !== 'undefined') { - safeGlobals.getRandomValues = window.crypto.getRandomValues.bind(window.crypto); -} -class Password { - /** - * @param {Partial} [options] - */ - constructor() { - let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; /** - * @type {typeof defaults} + * Only used for testing + * @param {HTMLInputElement|HTMLSelectElement} input + * @param {HTMLElement} form + * @returns {Matching} */ - this.options = { - ...defaults, - ...options - }; - return this; - } - static get defaults() { - return defaults; - } - - /** - * Generates a password from the given input. - * - * Note: This method will throw an error if parsing fails - use with caution - * - * @example - * - * ```javascript - * const password = Password.generateOrThrow("minlength: 20") - * ``` - * @public - * @param {string} inputString - * @param {Partial} [options] - * @throws {ParserError|Error} - * @returns {string} - */ - static generateOrThrow(inputString) { - let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - return new Password(options).parse(inputString).generate(); - } - /** - * Generates a password using the default ruleset. - * - * @example - * - * ```javascript - * const password = Password.generateDefault() - * ``` - * - * @public - * @param {Partial} [options] - * @returns {string} - */ - static generateDefault() { - let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - return new Password(options).parse(Password.defaults.defaultPasswordRules).generate(); - } - + forInput(input, form) { + this.setActiveElementStrings(input, form); + return this; + } + }; + _config = new WeakMap(); + _cssSelectors = new WeakMap(); + _ddgMatchers = new WeakMap(); + _vendorRegexRules = new WeakMap(); + _matcherLists = new WeakMap(); + _defaultStrategyOrder = new WeakMap(); /** - * Convert a ruleset into it's internally-used component pieces. - * - * @param {string} inputString - * @throws {parser.ParserError|Error} - * @returns {{ - * requirements: Requirements; - * parameters: PasswordParameters; - * rules: parser.Rule[], - * get entropy(): number; - * generate: () => string; - * }} + * @type {MatchingConfiguration} */ - parse(inputString) { - const rules = parser.parsePasswordRules(inputString); - const requirements = this._requirementsFromRules(rules); - if (!requirements) throw new Error('could not generate requirements for ' + JSON.stringify(inputString)); - const parameters = this._passwordGenerationParametersDictionary(requirements); - return { - requirements, - parameters, - rules, - get entropy() { - return Math.log2(parameters.PasswordAllowedCharacters.length ** parameters.NumberOfRequiredRandomCharacters); + __publicField(Matching, "emptyConfig", { + matchers: { + lists: {}, + fields: {} + }, + strategies: { + "vendorRegex": { + rules: {}, + ruleSets: [] }, - generate: () => { - const password = this._generatedPasswordMatchingRequirements(requirements, parameters); - /** - * The following is unreachable because if user input was incorrect then - * the parsing phase would throw. The following lines is to satisfy Typescript - */ - if (password === '') throw new Error('unreachable'); - return password; + "ddgMatcher": { + matchers: {} + }, + "cssSelector": { + selectors: {} } - }; - } - - /** - * Given an array of `Rule's`, convert into `Requirements` - * - * @param {parser.Rule[]} passwordRules - * @returns {Requirements | null} - */ - _requirementsFromRules(passwordRules) { - /** @type {Requirements} */ - const requirements = {}; - for (let rule of passwordRules) { - if (rule.name === parser.RuleName.ALLOWED) { - console.assert(!('PasswordAllowedCharacters' in requirements)); - const chars = this._charactersFromCharactersClasses(rule.value); - const scanSet = this._canonicalizedScanSetFromCharacters(chars); - if (scanSet) { - requirements.PasswordAllowedCharacters = scanSet; - } - } else if (rule.name === parser.RuleName.MAX_CONSECUTIVE) { - console.assert(!('PasswordRepeatedCharacterLimit' in requirements)); - requirements.PasswordRepeatedCharacterLimit = rule.value; - } else if (rule.name === parser.RuleName.REQUIRED) { - let requiredCharacters = requirements.PasswordRequiredCharacters; - if (!requiredCharacters) { - requiredCharacters = requirements.PasswordRequiredCharacters = []; - } - requiredCharacters.push(this._canonicalizedScanSetFromCharacters(this._charactersFromCharactersClasses(rule.value))); - } else if (rule.name === parser.RuleName.MIN_LENGTH) { - requirements.PasswordMinLength = rule.value; - } else if (rule.name === parser.RuleName.MAX_LENGTH) { - requirements.PasswordMaxLength = rule.value; + } + }); + function getInputType(input) { + const attr = input?.getAttribute(ATTR_INPUT_TYPE); + if (isValidSupportedType(attr)) { + return attr; + } + return "unknown"; + } + function getMainTypeFromType(type) { + const mainType = type.split(".")[0]; + switch (mainType) { + case "credentials": + case "creditCards": + case "identities": + return mainType; + } + return "unknown"; + } + var getInputMainType = (input) => getMainTypeFromType(getInputType(input)); + var supportedIdentitiesSubtypes = ( + /** @type {const} */ + [ + "emailAddress", + "firstName", + "middleName", + "lastName", + "fullName", + "phone", + "addressStreet", + "addressStreet2", + "addressCity", + "addressProvince", + "addressPostalCode", + "addressCountryCode", + "birthdayDay", + "birthdayMonth", + "birthdayYear" + ] + ); + function isValidIdentitiesSubtype(supportedType) { + return supportedIdentitiesSubtypes.includes(supportedType); + } + var supportedCreditCardSubtypes = ( + /** @type {const} */ + [ + "cardName", + "cardNumber", + "cardSecurityCode", + "expirationMonth", + "expirationYear", + "expiration" + ] + ); + function isValidCreditCardSubtype(supportedType) { + return supportedCreditCardSubtypes.includes(supportedType); + } + var supportedCredentialsSubtypes = ( + /** @type {const} */ + [ + "password", + "password.new", + "password.current", + "username" + ] + ); + var supportedVariants = ( + /** @type {const} */ + [ + "new", + "current" + ] + ); + function isValidCredentialsSubtype(supportedType) { + return supportedCredentialsSubtypes.includes(supportedType); + } + var supportedTypes = [ + ...supportedIdentitiesSubtypes.map((type) => `identities.${type}`), + ...supportedCreditCardSubtypes.map((type) => `creditCards.${type}`), + ...supportedCredentialsSubtypes.map((type) => `credentials.${type}`) + ]; + function getSubtypeFromType(type) { + const subType = type?.split(".")[1]; + const validType = isValidSubtype(subType); + return validType ? subType : "unknown"; + } + function getVariantFromType(type) { + const variant = type?.split(".")[2]; + const validVariant = isValidVariant(variant); + return validVariant ? variant : ""; + } + function isValidSubtype(supportedSubType) { + return isValidIdentitiesSubtype(supportedSubType) || isValidCreditCardSubtype(supportedSubType) || isValidCredentialsSubtype(supportedSubType); + } + function isValidSupportedType(supportedType) { + return supportedTypes.includes(supportedType); + } + function isValidVariant(supportedVariant) { + return supportedVariants.includes(supportedVariant); + } + function getInputSubtype(input) { + const type = getInputType(input); + return getSubtypeFromType(type); + } + function getInputVariant(input) { + const type = getInputType(input); + return getVariantFromType(type); + } + var removeExcessWhitespace = (string = "") => { + string = string?.trim() || ""; + if (!string || string.length > TEXT_LENGTH_CUTOFF + 50) + return ""; + return string.replace(/\n/g, " ").replace(/\s{2,}/g, " "); + }; + var getExplicitLabelsText = (el) => { + const labelTextCandidates = []; + for (let label of el.labels || []) { + labelTextCandidates.push(...extractElementStrings(label)); + } + if (el.hasAttribute("aria-label")) { + labelTextCandidates.push(removeExcessWhitespace(el.getAttribute("aria-label"))); + } + const ariaLabelAttr = removeExcessWhitespace(el.getAttribute("aria-labelled") || el.getAttribute("aria-labelledby")); + if (ariaLabelAttr) { + const labelledByElement = document.getElementById(ariaLabelAttr); + if (labelledByElement) { + labelTextCandidates.push(...extractElementStrings(labelledByElement)); } } - - // Only include an allowed rule matching SCAN_SET_ORDER (all characters) when a required rule is also present. - if (requirements.PasswordAllowedCharacters === this.options.SCAN_SET_ORDER && !requirements.PasswordRequiredCharacters) { - delete requirements.PasswordAllowedCharacters; + const filteredLabels = labelTextCandidates.filter((string) => string.length < 65); + if (filteredLabels.length > 0) { + return filteredLabels.join(" "); } - - // Fix up PasswordRequiredCharacters, if needed. - if (requirements.PasswordRequiredCharacters && requirements.PasswordRequiredCharacters.length === 1 && requirements.PasswordRequiredCharacters[0] === this.options.SCAN_SET_ORDER) { - delete requirements.PasswordRequiredCharacters; + return ""; + }; + var recursiveGetPreviousElSibling = (el) => { + const previousEl = el.previousElementSibling; + if (!previousEl) + return null; + if (EXCLUDED_TAGS.includes(previousEl.tagName)) { + return recursiveGetPreviousElSibling(previousEl); } - return Object.keys(requirements).length ? requirements : null; - } - - /** - * @param {number} range - * @returns {number} - */ - _randomNumberWithUniformDistribution(range) { - const getRandomValues = this.options.getRandomValues || safeGlobals.getRandomValues; - // Based on the algorithm described in https://pthree.org/2018/06/13/why-the-multiply-and-floor-rng-method-is-biased/ - const max = Math.floor(2 ** 32 / range) * range; - let x; - do { - x = getRandomValues(new Uint32Array(1))[0]; - } while (x >= max); - return x % range; - } - - /** - * @param {number} numberOfRequiredRandomCharacters - * @param {string} allowedCharacters - */ - _classicPassword(numberOfRequiredRandomCharacters, allowedCharacters) { - const length = allowedCharacters.length; - const randomCharArray = Array(numberOfRequiredRandomCharacters); - for (let i = 0; i < numberOfRequiredRandomCharacters; i++) { - const index = this._randomNumberWithUniformDistribution(length); - randomCharArray[i] = allowedCharacters[index]; - } - return randomCharArray.join(''); - } - - /** - * @param {string} password - * @param {number} consecutiveCharLimit - * @returns {boolean} - */ - _passwordHasNotExceededConsecutiveCharLimit(password, consecutiveCharLimit) { - let longestConsecutiveCharLength = 1; - let firstConsecutiveCharIndex = 0; - // Both "123" or "abc" and "321" or "cba" are considered consecutive. - let isSequenceAscending; - for (let i = 1; i < password.length; i++) { - const currCharCode = password.charCodeAt(i); - const prevCharCode = password.charCodeAt(i - 1); - if (isSequenceAscending) { - // If `isSequenceAscending` is defined, then we know that we are in the middle of an existing - // pattern. Check if the pattern continues based on whether the previous pattern was - // ascending or descending. - if (isSequenceAscending.valueOf() && currCharCode === prevCharCode + 1 || !isSequenceAscending.valueOf() && currCharCode === prevCharCode - 1) { - continue; - } - - // Take into account the case when the sequence transitions from descending - // to ascending. - if (currCharCode === prevCharCode + 1) { - firstConsecutiveCharIndex = i - 1; - isSequenceAscending = Boolean(true); - continue; - } - - // Take into account the case when the sequence transitions from ascending - // to descending. - if (currCharCode === prevCharCode - 1) { - firstConsecutiveCharIndex = i - 1; - isSequenceAscending = Boolean(false); - continue; + return previousEl; + }; + var getRelatedText = (el, form, cssSelector) => { + let scope = getLargestMeaningfulContainer(el, form, cssSelector); + if (scope === el) { + let previousEl = recursiveGetPreviousElSibling(el); + if (previousEl instanceof HTMLElement) { + scope = previousEl; + } + if (scope === el || scope instanceof HTMLSelectElement) { + if (el.previousSibling instanceof Text) { + return removeExcessWhitespace(el.previousSibling.textContent); } - isSequenceAscending = null; - } else if (currCharCode === prevCharCode + 1) { - isSequenceAscending = Boolean(true); - continue; - } else if (currCharCode === prevCharCode - 1) { - isSequenceAscending = Boolean(false); - continue; + return ""; } - const currConsecutiveCharLength = i - firstConsecutiveCharIndex; - if (currConsecutiveCharLength > longestConsecutiveCharLength) { - longestConsecutiveCharLength = currConsecutiveCharLength; - } - firstConsecutiveCharIndex = i; } - if (isSequenceAscending) { - const currConsecutiveCharLength = password.length - firstConsecutiveCharIndex; - if (currConsecutiveCharLength > longestConsecutiveCharLength) { - longestConsecutiveCharLength = currConsecutiveCharLength; + if (scope === el || scope instanceof HTMLSelectElement) { + if (el.previousSibling instanceof Text) { + return removeExcessWhitespace(el.previousSibling.textContent); } + return ""; } - return longestConsecutiveCharLength <= consecutiveCharLimit; + let trimmedText = ""; + const label = scope.querySelector("label"); + if (label) { + trimmedText = getTextShallow(label); + } else { + trimmedText = extractElementStrings(scope).join(" "); + } + if (trimmedText.length < TEXT_LENGTH_CUTOFF) + return trimmedText; + return ""; + }; + var getLargestMeaningfulContainer = (el, form, cssSelector) => { + const parentElement = el.parentElement; + if (!parentElement || el === form || !cssSelector) + return el; + const inputsInParentsScope = parentElement.querySelectorAll(cssSelector); + if (inputsInParentsScope.length === 1) { + return getLargestMeaningfulContainer(parentElement, form, cssSelector); + } + return el; + }; + var matchInPlaceholderAndLabels = (input, regex, form, cssSelector) => { + return input.placeholder?.match(regex) || getExplicitLabelsText(input).match(regex) || getRelatedText(input, form, cssSelector).match(regex); + }; + var checkPlaceholderAndLabels = (input, regex, form, cssSelector) => { + return !!matchInPlaceholderAndLabels(input, regex, form, cssSelector); + }; + function createMatching() { + return new Matching(matchingConfiguration); } - /** - * @param {string} password - * @param {number} repeatedCharLimit - * @returns {boolean} - */ - _passwordHasNotExceededRepeatedCharLimit(password, repeatedCharLimit) { - let longestRepeatedCharLength = 1; - let lastRepeatedChar = password.charAt(0); - let lastRepeatedCharIndex = 0; - for (let i = 1; i < password.length; i++) { - const currChar = password.charAt(i); - if (currChar === lastRepeatedChar) { - continue; - } - const currRepeatedCharLength = i - lastRepeatedCharIndex; - if (currRepeatedCharLength > longestRepeatedCharLength) { - longestRepeatedCharLength = currRepeatedCharLength; + // node_modules/@duckduckgo/content-scope-scripts/src/apple-utils.js + function getTopLevelURL() { + try { + if (window.location !== window.parent.location) { + return new URL(window.location.href !== "about:blank" ? document.referrer : window.parent.location.href); + } else { + return new URL(window.location.href); } - lastRepeatedChar = currChar; - lastRepeatedCharIndex = i; + } catch (error) { + return new URL(location.href); } - return longestRepeatedCharLength <= repeatedCharLimit; } - - /** - * @param {string} password - * @param {string[]} requiredCharacterSets - * @returns {boolean} - */ - _passwordContainsRequiredCharacters(password, requiredCharacterSets) { - const requiredCharacterSetsLength = requiredCharacterSets.length; - const passwordLength = password.length; - for (let i = 0; i < requiredCharacterSetsLength; i++) { - const requiredCharacterSet = requiredCharacterSets[i]; - let hasRequiredChar = false; - for (let j = 0; j < passwordLength; j++) { - const char = password.charAt(j); - if (requiredCharacterSet.indexOf(char) !== -1) { - hasRequiredChar = true; - break; - } - } - if (!hasRequiredChar) { - return false; - } + function isUnprotectedDomain(topLevelUrl, featureList) { + let unprotectedDomain = false; + const domainParts = topLevelUrl && topLevelUrl.host ? topLevelUrl.host.split(".") : []; + while (domainParts.length > 1 && !unprotectedDomain) { + const partialDomain = domainParts.join("."); + unprotectedDomain = featureList.filter((domain) => domain.domain === partialDomain).length > 0; + domainParts.shift(); } - return true; + return unprotectedDomain; } - - /** - * @param {string} string1 - * @param {string} string2 - * @returns {boolean} - */ - _stringsHaveAtLeastOneCommonCharacter(string1, string2) { - const string2Length = string2.length; - for (let i = 0; i < string2Length; i++) { - const char = string2.charAt(i); - if (string1.indexOf(char) !== -1) { - return true; - } - } - return false; + function processConfig(data, userList, preferences) { + const topLevelUrl = getTopLevelURL(); + const allowlisted = userList.filter((domain) => domain === topLevelUrl.host).length > 0; + const enabledFeatures = Object.keys(data.features).filter((featureName) => { + const feature = data.features[featureName]; + return feature.state === "enabled" && !isUnprotectedDomain(topLevelUrl, feature.exceptions); + }); + const isBroken = isUnprotectedDomain(topLevelUrl, data.unprotectedTemporary); + preferences.site = { + domain: topLevelUrl.hostname, + isBroken, + allowlisted, + enabledFeatures + }; + preferences.cookie = {}; + return preferences; } - /** - * @param {Requirements} requirements - * @returns {PasswordParameters} - */ - _passwordGenerationParametersDictionary(requirements) { - let minPasswordLength = requirements.PasswordMinLength; - const maxPasswordLength = requirements.PasswordMaxLength; - - // @ts-ignore - if (minPasswordLength > maxPasswordLength) { - // Resetting invalid value of min length to zero means "ignore min length parameter in password generation". - minPasswordLength = 0; - } - const requiredCharacterArray = requirements.PasswordRequiredCharacters; - let allowedCharacters = requirements.PasswordAllowedCharacters; - let requiredCharacterSets = this.options.defaultRequiredCharacterSets; - if (requiredCharacterArray) { - const mutatedRequiredCharacterSets = []; - const requiredCharacterArrayLength = requiredCharacterArray.length; - for (let i = 0; i < requiredCharacterArrayLength; i++) { - const requiredCharacters = requiredCharacterArray[i]; - if (allowedCharacters && this._stringsHaveAtLeastOneCommonCharacter(requiredCharacters, allowedCharacters)) { - mutatedRequiredCharacterSets.push(requiredCharacters); - } - } - requiredCharacterSets = mutatedRequiredCharacterSets; - } - - // If requirements allow, we will generateOrThrow the password in default format: "xxx-xxx-xxx-xxx". - let numberOfRequiredRandomCharacters = this.options.defaultPasswordLength; - if (minPasswordLength && minPasswordLength > numberOfRequiredRandomCharacters) { - numberOfRequiredRandomCharacters = minPasswordLength; - } - if (maxPasswordLength && maxPasswordLength < numberOfRequiredRandomCharacters) { - numberOfRequiredRandomCharacters = maxPasswordLength; + // src/autofill-utils.js + var SIGN_IN_MSG = { signMeIn: true }; + var notifyWebApp = (message) => { + window.postMessage(message, window.origin); + }; + var sendAndWaitForAnswer = (msgOrFn, expectedResponse) => { + if (typeof msgOrFn === "function") { + msgOrFn(); + } else { + window.postMessage(msgOrFn, window.origin); } - if (!allowedCharacters) { - allowedCharacters = this.options.defaultUnambiguousCharacters; + return new Promise((resolve) => { + const handler = (e) => { + if (e.origin !== window.origin) + return; + if (!e.data || e.data && !(e.data[expectedResponse] || e.data.type === expectedResponse)) + return; + resolve(e.data); + window.removeEventListener("message", handler); + }; + window.addEventListener("message", handler); + }); + }; + var autofillEnabled = (globalConfig) => { + if (!globalConfig.contentScope) { + return true; } - - // In default password format, we use dashes only as separators, not as symbols you can encounter at a random position. - - if (!requiredCharacterSets) { - requiredCharacterSets = this.options.defaultRequiredCharacterSets; + if ("site" in globalConfig.contentScope) { + const enabled = isAutofillEnabledFromProcessedConfig(globalConfig.contentScope); + return enabled; } - - // If we have more requirements of the type "need a character from set" than the length of the password we want to generateOrThrow, then - // we will never be able to meet these requirements, and we'll end up in an infinite loop generating passwords. To avoid this, - // reset required character sets if the requirements are impossible to meet. - if (requiredCharacterSets.length > numberOfRequiredRandomCharacters) { - requiredCharacterSets = []; + const { contentScope, userUnprotectedDomains, userPreferences } = globalConfig; + const processedConfig = processConfig(contentScope, userUnprotectedDomains, userPreferences); + return isAutofillEnabledFromProcessedConfig(processedConfig); + }; + var isAutofillEnabledFromProcessedConfig = (processedConfig) => { + const site = processedConfig.site; + if (site.isBroken || !site.enabledFeatures.includes("autofill")) { + if (shouldLog()) { + console.log("\u26A0\uFE0F Autofill disabled by remote config"); + } + return false; } - - // Do not require any character sets that do not contain allowed characters. - const requiredCharacterSetsLength = requiredCharacterSets.length; - const mutatedRequiredCharacterSets = []; - const allowedCharactersLength = allowedCharacters.length; - for (let i = 0; i < requiredCharacterSetsLength; i++) { - const requiredCharacterSet = requiredCharacterSets[i]; - let requiredCharacterSetContainsAllowedCharacters = false; - for (let j = 0; j < allowedCharactersLength; j++) { - const character = allowedCharacters.charAt(j); - if (requiredCharacterSet.indexOf(character) !== -1) { - requiredCharacterSetContainsAllowedCharacters = true; - break; - } + return true; + }; + var isIncontextSignupEnabledFromProcessedConfig = (processedConfig) => { + const site = processedConfig.site; + if (site.isBroken || !site.enabledFeatures.includes("incontextSignup")) { + if (shouldLog()) { + console.log("\u26A0\uFE0F In-context signup disabled by remote config"); } - if (requiredCharacterSetContainsAllowedCharacters) { - mutatedRequiredCharacterSets.push(requiredCharacterSet); + return false; + } + return true; + }; + var originalSet = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set; + var setValueForInput = (el, val, config) => { + if (!config?.isAndroid) { + el.focus(); + } + el.dispatchEvent(new Event("keydown", { bubbles: true })); + originalSet?.call(el, val); + const events = [ + new Event("input", { bubbles: true }), + // todo(Shane): Not sending a 'key' property on these events can cause exceptions on 3rd party listeners that expect it + new Event("keyup", { bubbles: true }), + new Event("change", { bubbles: true }) + ]; + events.forEach((ev) => el.dispatchEvent(ev)); + originalSet?.call(el, val); + events.forEach((ev) => el.dispatchEvent(ev)); + el.blur(); + return true; + }; + var fireEventsOnSelect = (el) => { + const events = [ + new Event("mousedown", { bubbles: true }), + new Event("mouseup", { bubbles: true }), + new Event("click", { bubbles: true }), + new Event("change", { bubbles: true }) + ]; + events.forEach((ev) => el.dispatchEvent(ev)); + events.forEach((ev) => el.dispatchEvent(ev)); + el.blur(); + }; + var setValueForSelect = (el, val) => { + const subtype = getInputSubtype(el); + const isMonth = subtype.includes("Month"); + const isZeroBasedNumber = isMonth && el.options[0].value === "0" && el.options.length === 12; + const stringVal = String(val); + const numberVal = Number(val); + for (const option of el.options) { + let value = option.value; + if (isZeroBasedNumber) { + value = `${Number(value) + 1}`; + } + if (value === stringVal || Number(value) === numberVal) { + if (option.selected) + return false; + option.selected = true; + fireEventsOnSelect(el); + return true; } } - requiredCharacterSets = mutatedRequiredCharacterSets; - return { - NumberOfRequiredRandomCharacters: numberOfRequiredRandomCharacters, - PasswordAllowedCharacters: allowedCharacters, - RequiredCharacterSets: requiredCharacterSets - }; - } - - /** - * @param {Requirements | null} requirements - * @param {PasswordParameters} [parameters] - * @returns {string} - */ - _generatedPasswordMatchingRequirements(requirements, parameters) { - requirements = requirements || {}; - parameters = parameters || this._passwordGenerationParametersDictionary(requirements); - const numberOfRequiredRandomCharacters = parameters.NumberOfRequiredRandomCharacters; - const repeatedCharLimit = requirements.PasswordRepeatedCharacterLimit; - const allowedCharacters = parameters.PasswordAllowedCharacters; - const shouldCheckRepeatedCharRequirement = !!repeatedCharLimit; - while (true) { - const password = this._classicPassword(numberOfRequiredRandomCharacters, allowedCharacters); - if (!this._passwordContainsRequiredCharacters(password, parameters.RequiredCharacterSets)) { - continue; + for (const option of el.options) { + if (option.innerText === stringVal || Number(option.innerText) === numberVal) { + if (option.selected) + return false; + option.selected = true; + fireEventsOnSelect(el); + return true; } - if (shouldCheckRepeatedCharRequirement) { - if (repeatedCharLimit !== undefined && repeatedCharLimit >= 1 && !this._passwordHasNotExceededRepeatedCharLimit(password, repeatedCharLimit)) { - continue; + } + return false; + }; + var setValue = (el, val, config) => { + if (el instanceof HTMLInputElement) + return setValueForInput(el, val, config); + if (el instanceof HTMLSelectElement) + return setValueForSelect(el, val); + return false; + }; + var safeExecute = (el, fn, _opts = {}) => { + const intObs = new IntersectionObserver((changes) => { + for (const change of changes) { + if (typeof change.isVisible === "undefined") { + change.isVisible = true; } - } - const consecutiveCharLimit = requirements.PasswordConsecutiveCharacterLimit; - if (consecutiveCharLimit && consecutiveCharLimit >= 1) { - if (!this._passwordHasNotExceededConsecutiveCharLimit(password, consecutiveCharLimit)) { - continue; + if (change.isIntersecting) { + fn(); } } - return password || ''; + intObs.disconnect(); + }, { trackVisibility: true, delay: 100 }); + intObs.observe(el); + }; + var isPotentiallyViewable = (el) => { + const computedStyle = window.getComputedStyle(el); + const opacity = parseFloat(computedStyle.getPropertyValue("opacity") || "1"); + const visibility = computedStyle.getPropertyValue("visibility"); + const opacityThreshold = 0.6; + return el.clientWidth !== 0 && el.clientHeight !== 0 && opacity > opacityThreshold && visibility !== "hidden"; + }; + var getDaxBoundingBox = (input) => { + const { right: inputRight, top: inputTop, height: inputHeight } = input.getBoundingClientRect(); + const inputRightPadding = parseInt(getComputedStyle(input).paddingRight); + const width = 30; + const height = 30; + const top = inputTop + (inputHeight - height) / 2; + const right = inputRight - inputRightPadding; + const left = right - width; + const bottom = top + height; + return { bottom, height, left, right, top, width, x: left, y: top }; + }; + var isEventWithinDax = (e, input) => { + const { left, right, top, bottom } = getDaxBoundingBox(input); + const withinX = e.clientX >= left && e.clientX <= right; + const withinY = e.clientY >= top && e.clientY <= bottom; + return withinX && withinY; + }; + var addInlineStyles = (el, styles) => Object.entries(styles).forEach(([property, val]) => el.style.setProperty(property, val, "important")); + var removeInlineStyles = (el, styles) => Object.keys(styles).forEach((property) => el.style.removeProperty(property)); + var ADDRESS_DOMAIN = "@duck.com"; + var formatDuckAddress = (address) => address + ADDRESS_DOMAIN; + function escapeXML(str) { + const replacements = { "&": "&", '"': """, "'": "'", "<": "<", ">": ">", "/": "/" }; + return String(str).replace(/[&"'<>/]/g, (m) => replacements[m]); + } + var isLikelyASubmitButton = (el, matching) => { + const text = getTextShallow(el); + const ariaLabel = el.getAttribute("aria-label") || ""; + const dataTestId = el.getAttribute("data-test-id") || ""; + if ((el.getAttribute("type") === "submit" || // is explicitly set as "submit" + el.getAttribute("name") === "submit") && // is called "submit" + !safeRegexTest(matching.getDDGMatcherRegex("submitButtonUnlikelyRegex"), text + " " + ariaLabel)) + return true; + return (safeRegexTest(/primary|submit/i, el.className) || // has high-signal submit classes + safeRegexTest(/submit/i, dataTestId) || safeRegexTest(matching.getDDGMatcherRegex("submitButtonRegex"), text) || // has high-signal text + el.offsetHeight * el.offsetWidth >= 1e4 && !safeRegexTest(/secondary/i, el.className)) && el.offsetHeight * el.offsetWidth >= 2e3 && // it's not a very small button like inline links and such + !safeRegexTest(matching.getDDGMatcherRegex("submitButtonUnlikelyRegex"), text + " " + ariaLabel); + }; + var buttonMatchesFormType = (el, formObj) => { + if (formObj.isLogin) { + return !safeRegexTest(/sign.?up|register|join/i, el.textContent || ""); + } else if (formObj.isSignup) { + return !safeRegexTest(/(log|sign).?([io])n/i, el.textContent || ""); + } else { + return true; } - } - - /** - * @param {parser.CustomCharacterClass | parser.NamedCharacterClass} characterClass - * @returns {string[]} - */ - _scanSetFromCharacterClass(characterClass) { - if (characterClass instanceof parser.CustomCharacterClass) { - return characterClass.characters; - } - console.assert(characterClass instanceof parser.NamedCharacterClass); - switch (characterClass.name) { - case parser.Identifier.ASCII_PRINTABLE: - case parser.Identifier.UNICODE: - return this.options.SCAN_SET_ORDER.split(''); - case parser.Identifier.DIGIT: - return this.options.SCAN_SET_ORDER.substring(this.options.SCAN_SET_ORDER.indexOf('0'), this.options.SCAN_SET_ORDER.indexOf('9') + 1).split(''); - case parser.Identifier.LOWER: - return this.options.SCAN_SET_ORDER.substring(this.options.SCAN_SET_ORDER.indexOf('a'), this.options.SCAN_SET_ORDER.indexOf('z') + 1).split(''); - case parser.Identifier.SPECIAL: - return this.options.SCAN_SET_ORDER.substring(this.options.SCAN_SET_ORDER.indexOf('-'), this.options.SCAN_SET_ORDER.indexOf(']') + 1).split(''); - case parser.Identifier.UPPER: - return this.options.SCAN_SET_ORDER.substring(this.options.SCAN_SET_ORDER.indexOf('A'), this.options.SCAN_SET_ORDER.indexOf('Z') + 1).split(''); - } - console.assert(false, parser.SHOULD_NOT_BE_REACHED); - return []; - } - - /** - * @param {(parser.CustomCharacterClass | parser.NamedCharacterClass)[]} characterClasses - */ - _charactersFromCharactersClasses(characterClasses) { - const output = []; - for (let characterClass of characterClasses) { - output.push(...this._scanSetFromCharacterClass(characterClass)); + }; + var buttonInputTypes = ["submit", "button"]; + var getTextShallow = (el) => { + if (el instanceof HTMLButtonElement) + return removeExcessWhitespace(el.textContent); + if (el instanceof HTMLInputElement) { + if (buttonInputTypes.includes(el.type)) { + return el.value; + } + if (el.type === "image") { + return removeExcessWhitespace(el.alt || el.value || el.title || el.name); + } } - return output; - } - - /** - * @param {string[]} characters - * @returns {string} - */ - _canonicalizedScanSetFromCharacters(characters) { - if (!characters.length) { - return ''; - } - let shadowCharacters = Array.prototype.slice.call(characters); - shadowCharacters.sort((a, b) => this.options.SCAN_SET_ORDER.indexOf(a) - this.options.SCAN_SET_ORDER.indexOf(b)); - let uniqueCharacters = [shadowCharacters[0]]; - for (let i = 1, length = shadowCharacters.length; i < length; ++i) { - if (shadowCharacters[i] === shadowCharacters[i - 1]) { - continue; + let text = ""; + for (const childNode of el.childNodes) { + if (childNode instanceof Text) { + text += " " + childNode.textContent; } - uniqueCharacters.push(shadowCharacters[i]); } - return uniqueCharacters.join(''); - } -} -exports.Password = Password; - -},{"./constants.js":9,"./rules-parser.js":10}],9:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.constants = void 0; -const DEFAULT_MIN_LENGTH = 20; -const DEFAULT_MAX_LENGTH = 30; -const DEFAULT_REQUIRED_CHARS = '-!?$&#%'; -const DEFAULT_UNAMBIGUOUS_CHARS = 'abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ0123456789'; -const DEFAULT_PASSWORD_RULES = [`minlength: ${DEFAULT_MIN_LENGTH}`, `maxlength: ${DEFAULT_MAX_LENGTH}`, `required: [${DEFAULT_REQUIRED_CHARS}]`, `allowed: [${DEFAULT_UNAMBIGUOUS_CHARS}]`].join('; '); -const constants = exports.constants = { - DEFAULT_MIN_LENGTH, - DEFAULT_MAX_LENGTH, - DEFAULT_PASSWORD_RULES, - DEFAULT_REQUIRED_CHARS, - DEFAULT_UNAMBIGUOUS_CHARS -}; - -},{}],10:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.SHOULD_NOT_BE_REACHED = exports.RuleName = exports.Rule = exports.ParserError = exports.NamedCharacterClass = exports.Identifier = exports.CustomCharacterClass = void 0; -exports.parsePasswordRules = parsePasswordRules; -// Copyright (c) 2019 - 2020 Apple Inc. Licensed under MIT License. - -/* - * - * NOTE: - * - * This file was taken as intended from https://github.com/apple/password-manager-resources. - * - * The only additions from DuckDuckGo employees are - * - * 1) exporting some identifiers - * 2) adding some JSDoc comments - * 3) making this parser throw when it cannot produce any rules - * ^ the default implementation still returns a base-line ruleset, which we didn't want. - * - */ - -const Identifier = exports.Identifier = { - ASCII_PRINTABLE: 'ascii-printable', - DIGIT: 'digit', - LOWER: 'lower', - SPECIAL: 'special', - UNICODE: 'unicode', - UPPER: 'upper' -}; -const RuleName = exports.RuleName = { - ALLOWED: 'allowed', - MAX_CONSECUTIVE: 'max-consecutive', - REQUIRED: 'required', - MIN_LENGTH: 'minlength', - MAX_LENGTH: 'maxlength' -}; -const CHARACTER_CLASS_START_SENTINEL = '['; -const CHARACTER_CLASS_END_SENTINEL = ']'; -const PROPERTY_VALUE_SEPARATOR = ','; -const PROPERTY_SEPARATOR = ';'; -const PROPERTY_VALUE_START_SENTINEL = ':'; -const SPACE_CODE_POINT = ' '.codePointAt(0); -const SHOULD_NOT_BE_REACHED = exports.SHOULD_NOT_BE_REACHED = 'Should not be reached'; -class Rule { - constructor(name, value) { - this._name = name; - this.value = value; - } - get name() { - return this._name; - } - toString() { - return JSON.stringify(this); + return removeExcessWhitespace(text); + }; + function isLocalNetwork(hostname = window.location.hostname) { + return ["localhost", "", "::1"].includes(hostname) || hostname.includes("127.0.0.1") || hostname.includes("192.168.") || hostname.startsWith("10.0.") || hostname.endsWith(".local") || hostname.endsWith(".internal"); } -} -exports.Rule = Rule; -; -class NamedCharacterClass { - constructor(name) { - console.assert(_isValidRequiredOrAllowedPropertyValueIdentifier(name)); - this._name = name; + var tldrs = /\.(?:c(?:o(?:m|op)?|at?|[iykgdmnxruhcfzvl])|o(?:rg|m)|n(?:et?|a(?:me)?|[ucgozrfpil])|e(?:d?u|[gechstr])|i(?:n(?:t|fo)?|[stqldroem])|m(?:o(?:bi)?|u(?:seum)?|i?l|[mcyvtsqhaerngxzfpwkd])|g(?:ov|[glqeriabtshdfmuywnp])|b(?:iz?|[drovfhtaywmzjsgbenl])|t(?:r(?:avel)?|[ncmfzdvkopthjwg]|e?l)|k[iemygznhwrp]|s[jtvberindlucygkhaozm]|u[gymszka]|h[nmutkr]|r[owesu]|d[kmzoej]|a(?:e(?:ro)?|r(?:pa)?|[qofiumsgzlwcnxdt])|p(?:ro?|[sgnthfymakwle])|v[aegiucn]|l[sayuvikcbrt]|j(?:o(?:bs)?|[mep])|w[fs]|z[amw]|f[rijkom]|y[eut]|qa)$/i; + function isValidTLD(hostname = window.location.hostname) { + return tldrs.test(hostname) || hostname === "fill.dev"; } - get name() { - return this._name.toLowerCase(); + var wasAutofilledByChrome = (input) => { + try { + return input.matches("input:-internal-autofill-selected"); + } catch (e) { + return false; + } + }; + function shouldLog() { + return readDebugSetting("ddg-autofill-debug"); } - toString() { - return this._name; + function shouldLogPerformance() { + return readDebugSetting("ddg-autofill-perf"); } - toHTMLString() { - return this._name; + function readDebugSetting(setting) { + try { + return window.sessionStorage?.getItem(setting) === "true"; + } catch (e) { + return false; + } } -} -exports.NamedCharacterClass = NamedCharacterClass; -; -class ParserError extends Error {} -exports.ParserError = ParserError; -; -class CustomCharacterClass { - constructor(characters) { - console.assert(characters instanceof Array); - this._characters = characters; + function logPerformance(markName) { + if (shouldLogPerformance()) { + const measurement = window.performance?.measure(`${markName}:init`, `${markName}:init:start`, `${markName}:init:end`); + console.log(`${markName} took ${Math.round(measurement?.duration)}ms`); + window.performance?.clearMarks(); + } } - get characters() { - return this._characters; + function whenIdle(callback) { + let timer; + return (...args) => { + cancelIdleCallback(timer); + timer = requestIdleCallback(() => callback.apply(this, args)); + }; } - toString() { - return `[${this._characters.join('')}]`; + function truncateFromMiddle(string, totalLength = 30) { + if (totalLength < 4) { + throw new Error("Do not use with strings shorter than 4"); + } + if (string.length <= totalLength) + return string; + const truncated = string.slice(0, totalLength / 2).concat("\u2026", string.slice(totalLength / -2)); + return truncated; } - toHTMLString() { - return `[${this._characters.join('').replace('"', '"')}]`; + function isFormLikelyToBeUsedAsPageWrapper(form) { + if (form.parentElement !== document.body) + return false; + const formChildren = form.querySelectorAll("*").length; + if (formChildren < 100) + return false; + const bodyChildren = document.body.querySelectorAll("*").length; + const formChildrenPercentage = formChildren * 100 / bodyChildren; + return formChildrenPercentage > 50; } -} -exports.CustomCharacterClass = CustomCharacterClass; -; - -// MARK: Lexer functions - -function _isIdentifierCharacter(c) { - console.assert(c.length === 1); - // eslint-disable-next-line no-mixed-operators - return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c === '-'; -} -function _isASCIIDigit(c) { - console.assert(c.length === 1); - return c >= '0' && c <= '9'; -} -function _isASCIIPrintableCharacter(c) { - console.assert(c.length === 1); - return c >= ' ' && c <= '~'; -} -function _isASCIIWhitespace(c) { - console.assert(c.length === 1); - return c === ' ' || c === '\f' || c === '\n' || c === '\r' || c === '\t'; -} - -// MARK: ASCII printable character bit set and canonicalization functions + function safeRegexTest(regex, string) { + if (!string || !regex || string.length > constants.TEXT_LENGTH_CUTOFF) + return false; + return regex.test(string); + } + function pierceShadowTree(event, wantedTargetType) { + const { target } = event; + if (!(target instanceof Element) || !target?.shadowRoot || !event.composedPath) + return target; + const clickStack = event.composedPath(); + if (!wantedTargetType) { + return clickStack[0]; + } + return clickStack.find((el) => el instanceof wantedTargetType) || target; + } + function getActiveElement(root = document) { + const activeElement = root.activeElement; + if (!(activeElement instanceof Element) || !activeElement.shadowRoot) + return activeElement; + const innerActiveElement = activeElement.shadowRoot.activeElement; + if (innerActiveElement?.shadowRoot) { + return getActiveElement(innerActiveElement.shadowRoot); + } + return innerActiveElement; + } + + // src/Form/countryNames.js + var COUNTRY_CODES_TO_NAMES = { + AC: "Ascension Island", + AD: "Andorra", + AE: "United Arab Emirates", + AF: "Afghanistan", + AG: "Antigua & Barbuda", + AI: "Anguilla", + AL: "Albania", + AM: "Armenia", + AN: "Cura\xE7ao", + AO: "Angola", + AQ: "Antarctica", + AR: "Argentina", + AS: "American Samoa", + AT: "Austria", + AU: "Australia", + AW: "Aruba", + AX: "\xC5land Islands", + AZ: "Azerbaijan", + BA: "Bosnia & Herzegovina", + BB: "Barbados", + BD: "Bangladesh", + BE: "Belgium", + BF: "Burkina Faso", + BG: "Bulgaria", + BH: "Bahrain", + BI: "Burundi", + BJ: "Benin", + BL: "St. Barth\xE9lemy", + BM: "Bermuda", + BN: "Brunei", + BO: "Bolivia", + BQ: "Caribbean Netherlands", + BR: "Brazil", + BS: "Bahamas", + BT: "Bhutan", + BU: "Myanmar (Burma)", + BV: "Bouvet Island", + BW: "Botswana", + BY: "Belarus", + BZ: "Belize", + CA: "Canada", + CC: "Cocos (Keeling) Islands", + CD: "Congo - Kinshasa", + CF: "Central African Republic", + CG: "Congo - Brazzaville", + CH: "Switzerland", + CI: "C\xF4te d\u2019Ivoire", + CK: "Cook Islands", + CL: "Chile", + CM: "Cameroon", + CN: "China mainland", + CO: "Colombia", + CP: "Clipperton Island", + CR: "Costa Rica", + CS: "Serbia", + CU: "Cuba", + CV: "Cape Verde", + CW: "Cura\xE7ao", + CX: "Christmas Island", + CY: "Cyprus", + CZ: "Czechia", + DD: "Germany", + DE: "Germany", + DG: "Diego Garcia", + DJ: "Djibouti", + DK: "Denmark", + DM: "Dominica", + DO: "Dominican Republic", + DY: "Benin", + DZ: "Algeria", + EA: "Ceuta & Melilla", + EC: "Ecuador", + EE: "Estonia", + EG: "Egypt", + EH: "Western Sahara", + ER: "Eritrea", + ES: "Spain", + ET: "Ethiopia", + EU: "European Union", + EZ: "Eurozone", + FI: "Finland", + FJ: "Fiji", + FK: "Falkland Islands", + FM: "Micronesia", + FO: "Faroe Islands", + FR: "France", + FX: "France", + GA: "Gabon", + GB: "United Kingdom", + GD: "Grenada", + GE: "Georgia", + GF: "French Guiana", + GG: "Guernsey", + GH: "Ghana", + GI: "Gibraltar", + GL: "Greenland", + GM: "Gambia", + GN: "Guinea", + GP: "Guadeloupe", + GQ: "Equatorial Guinea", + GR: "Greece", + GS: "So. Georgia & So. Sandwich Isl.", + GT: "Guatemala", + GU: "Guam", + GW: "Guinea-Bissau", + GY: "Guyana", + HK: "Hong Kong", + HM: "Heard & McDonald Islands", + HN: "Honduras", + HR: "Croatia", + HT: "Haiti", + HU: "Hungary", + HV: "Burkina Faso", + IC: "Canary Islands", + ID: "Indonesia", + IE: "Ireland", + IL: "Israel", + IM: "Isle of Man", + IN: "India", + IO: "Chagos Archipelago", + IQ: "Iraq", + IR: "Iran", + IS: "Iceland", + IT: "Italy", + JE: "Jersey", + JM: "Jamaica", + JO: "Jordan", + JP: "Japan", + KE: "Kenya", + KG: "Kyrgyzstan", + KH: "Cambodia", + KI: "Kiribati", + KM: "Comoros", + KN: "St. Kitts & Nevis", + KP: "North Korea", + KR: "South Korea", + KW: "Kuwait", + KY: "Cayman Islands", + KZ: "Kazakhstan", + LA: "Laos", + LB: "Lebanon", + LC: "St. Lucia", + LI: "Liechtenstein", + LK: "Sri Lanka", + LR: "Liberia", + LS: "Lesotho", + LT: "Lithuania", + LU: "Luxembourg", + LV: "Latvia", + LY: "Libya", + MA: "Morocco", + MC: "Monaco", + MD: "Moldova", + ME: "Montenegro", + MF: "St. Martin", + MG: "Madagascar", + MH: "Marshall Islands", + MK: "North Macedonia", + ML: "Mali", + MM: "Myanmar (Burma)", + MN: "Mongolia", + MO: "Macao", + MP: "Northern Mariana Islands", + MQ: "Martinique", + MR: "Mauritania", + MS: "Montserrat", + MT: "Malta", + MU: "Mauritius", + MV: "Maldives", + MW: "Malawi", + MX: "Mexico", + MY: "Malaysia", + MZ: "Mozambique", + NA: "Namibia", + NC: "New Caledonia", + NE: "Niger", + NF: "Norfolk Island", + NG: "Nigeria", + NH: "Vanuatu", + NI: "Nicaragua", + NL: "Netherlands", + NO: "Norway", + NP: "Nepal", + NR: "Nauru", + NU: "Niue", + NZ: "New Zealand", + OM: "Oman", + PA: "Panama", + PE: "Peru", + PF: "French Polynesia", + PG: "Papua New Guinea", + PH: "Philippines", + PK: "Pakistan", + PL: "Poland", + PM: "St. Pierre & Miquelon", + PN: "Pitcairn Islands", + PR: "Puerto Rico", + PS: "Palestinian Territories", + PT: "Portugal", + PW: "Palau", + PY: "Paraguay", + QA: "Qatar", + QO: "Outlying Oceania", + RE: "R\xE9union", + RH: "Zimbabwe", + RO: "Romania", + RS: "Serbia", + RU: "Russia", + RW: "Rwanda", + SA: "Saudi Arabia", + SB: "Solomon Islands", + SC: "Seychelles", + SD: "Sudan", + SE: "Sweden", + SG: "Singapore", + SH: "St. Helena", + SI: "Slovenia", + SJ: "Svalbard & Jan Mayen", + SK: "Slovakia", + SL: "Sierra Leone", + SM: "San Marino", + SN: "Senegal", + SO: "Somalia", + SR: "Suriname", + SS: "South Sudan", + ST: "S\xE3o Tom\xE9 & Pr\xEDncipe", + SU: "Russia", + SV: "El Salvador", + SX: "Sint Maarten", + SY: "Syria", + SZ: "Eswatini", + TA: "Tristan da Cunha", + TC: "Turks & Caicos Islands", + TD: "Chad", + TF: "French Southern Territories", + TG: "Togo", + TH: "Thailand", + TJ: "Tajikistan", + TK: "Tokelau", + TL: "Timor-Leste", + TM: "Turkmenistan", + TN: "Tunisia", + TO: "Tonga", + TP: "Timor-Leste", + TR: "Turkey", + TT: "Trinidad & Tobago", + TV: "Tuvalu", + TW: "Taiwan", + TZ: "Tanzania", + UA: "Ukraine", + UG: "Uganda", + UK: "United Kingdom", + UM: "U.S. Outlying Islands", + UN: "United Nations", + US: "United States", + UY: "Uruguay", + UZ: "Uzbekistan", + VA: "Vatican City", + VC: "St. Vincent & Grenadines", + VD: "Vietnam", + VE: "Venezuela", + VG: "British Virgin Islands", + VI: "U.S. Virgin Islands", + VN: "Vietnam", + VU: "Vanuatu", + WF: "Wallis & Futuna", + WS: "Samoa", + XA: "Pseudo-Accents", + XB: "Pseudo-Bidi", + XK: "Kosovo", + YD: "Yemen", + YE: "Yemen", + YT: "Mayotte", + YU: "Serbia", + ZA: "South Africa", + ZM: "Zambia", + ZR: "Congo - Kinshasa", + ZW: "Zimbabwe", + ZZ: "Unknown Region" + }; + var COUNTRY_NAMES_TO_CODES = { + "Ascension Island": "AC", + Andorra: "AD", + "United Arab Emirates": "AE", + Afghanistan: "AF", + "Antigua & Barbuda": "AG", + Anguilla: "AI", + Albania: "AL", + Armenia: "AM", + "Cura\xE7ao": "CW", + Angola: "AO", + Antarctica: "AQ", + Argentina: "AR", + "American Samoa": "AS", + Austria: "AT", + Australia: "AU", + Aruba: "AW", + "\xC5land Islands": "AX", + Azerbaijan: "AZ", + "Bosnia & Herzegovina": "BA", + Barbados: "BB", + Bangladesh: "BD", + Belgium: "BE", + "Burkina Faso": "HV", + Bulgaria: "BG", + Bahrain: "BH", + Burundi: "BI", + Benin: "DY", + "St. Barth\xE9lemy": "BL", + Bermuda: "BM", + Brunei: "BN", + Bolivia: "BO", + "Caribbean Netherlands": "BQ", + Brazil: "BR", + Bahamas: "BS", + Bhutan: "BT", + "Myanmar (Burma)": "MM", + "Bouvet Island": "BV", + Botswana: "BW", + Belarus: "BY", + Belize: "BZ", + Canada: "CA", + "Cocos (Keeling) Islands": "CC", + "Congo - Kinshasa": "ZR", + "Central African Republic": "CF", + "Congo - Brazzaville": "CG", + Switzerland: "CH", + "C\xF4te d\u2019Ivoire": "CI", + "Cook Islands": "CK", + Chile: "CL", + Cameroon: "CM", + "China mainland": "CN", + Colombia: "CO", + "Clipperton Island": "CP", + "Costa Rica": "CR", + Serbia: "YU", + Cuba: "CU", + "Cape Verde": "CV", + "Christmas Island": "CX", + Cyprus: "CY", + Czechia: "CZ", + Germany: "DE", + "Diego Garcia": "DG", + Djibouti: "DJ", + Denmark: "DK", + Dominica: "DM", + "Dominican Republic": "DO", + Algeria: "DZ", + "Ceuta & Melilla": "EA", + Ecuador: "EC", + Estonia: "EE", + Egypt: "EG", + "Western Sahara": "EH", + Eritrea: "ER", + Spain: "ES", + Ethiopia: "ET", + "European Union": "EU", + Eurozone: "EZ", + Finland: "FI", + Fiji: "FJ", + "Falkland Islands": "FK", + Micronesia: "FM", + "Faroe Islands": "FO", + France: "FX", + Gabon: "GA", + "United Kingdom": "UK", + Grenada: "GD", + Georgia: "GE", + "French Guiana": "GF", + Guernsey: "GG", + Ghana: "GH", + Gibraltar: "GI", + Greenland: "GL", + Gambia: "GM", + Guinea: "GN", + Guadeloupe: "GP", + "Equatorial Guinea": "GQ", + Greece: "GR", + "So. Georgia & So. Sandwich Isl.": "GS", + Guatemala: "GT", + Guam: "GU", + "Guinea-Bissau": "GW", + Guyana: "GY", + "Hong Kong": "HK", + "Heard & McDonald Islands": "HM", + Honduras: "HN", + Croatia: "HR", + Haiti: "HT", + Hungary: "HU", + "Canary Islands": "IC", + Indonesia: "ID", + Ireland: "IE", + Israel: "IL", + "Isle of Man": "IM", + India: "IN", + "Chagos Archipelago": "IO", + Iraq: "IQ", + Iran: "IR", + Iceland: "IS", + Italy: "IT", + Jersey: "JE", + Jamaica: "JM", + Jordan: "JO", + Japan: "JP", + Kenya: "KE", + Kyrgyzstan: "KG", + Cambodia: "KH", + Kiribati: "KI", + Comoros: "KM", + "St. Kitts & Nevis": "KN", + "North Korea": "KP", + "South Korea": "KR", + Kuwait: "KW", + "Cayman Islands": "KY", + Kazakhstan: "KZ", + Laos: "LA", + Lebanon: "LB", + "St. Lucia": "LC", + Liechtenstein: "LI", + "Sri Lanka": "LK", + Liberia: "LR", + Lesotho: "LS", + Lithuania: "LT", + Luxembourg: "LU", + Latvia: "LV", + Libya: "LY", + Morocco: "MA", + Monaco: "MC", + Moldova: "MD", + Montenegro: "ME", + "St. Martin": "MF", + Madagascar: "MG", + "Marshall Islands": "MH", + "North Macedonia": "MK", + Mali: "ML", + Mongolia: "MN", + Macao: "MO", + "Northern Mariana Islands": "MP", + Martinique: "MQ", + Mauritania: "MR", + Montserrat: "MS", + Malta: "MT", + Mauritius: "MU", + Maldives: "MV", + Malawi: "MW", + Mexico: "MX", + Malaysia: "MY", + Mozambique: "MZ", + Namibia: "NA", + "New Caledonia": "NC", + Niger: "NE", + "Norfolk Island": "NF", + Nigeria: "NG", + Vanuatu: "VU", + Nicaragua: "NI", + Netherlands: "NL", + Norway: "NO", + Nepal: "NP", + Nauru: "NR", + Niue: "NU", + "New Zealand": "NZ", + Oman: "OM", + Panama: "PA", + Peru: "PE", + "French Polynesia": "PF", + "Papua New Guinea": "PG", + Philippines: "PH", + Pakistan: "PK", + Poland: "PL", + "St. Pierre & Miquelon": "PM", + "Pitcairn Islands": "PN", + "Puerto Rico": "PR", + "Palestinian Territories": "PS", + Portugal: "PT", + Palau: "PW", + Paraguay: "PY", + Qatar: "QA", + "Outlying Oceania": "QO", + "R\xE9union": "RE", + Zimbabwe: "ZW", + Romania: "RO", + Russia: "SU", + Rwanda: "RW", + "Saudi Arabia": "SA", + "Solomon Islands": "SB", + Seychelles: "SC", + Sudan: "SD", + Sweden: "SE", + Singapore: "SG", + "St. Helena": "SH", + Slovenia: "SI", + "Svalbard & Jan Mayen": "SJ", + Slovakia: "SK", + "Sierra Leone": "SL", + "San Marino": "SM", + Senegal: "SN", + Somalia: "SO", + Suriname: "SR", + "South Sudan": "SS", + "S\xE3o Tom\xE9 & Pr\xEDncipe": "ST", + "El Salvador": "SV", + "Sint Maarten": "SX", + Syria: "SY", + Eswatini: "SZ", + "Tristan da Cunha": "TA", + "Turks & Caicos Islands": "TC", + Chad: "TD", + "French Southern Territories": "TF", + Togo: "TG", + Thailand: "TH", + Tajikistan: "TJ", + Tokelau: "TK", + "Timor-Leste": "TP", + Turkmenistan: "TM", + Tunisia: "TN", + Tonga: "TO", + Turkey: "TR", + "Trinidad & Tobago": "TT", + Tuvalu: "TV", + Taiwan: "TW", + Tanzania: "TZ", + Ukraine: "UA", + Uganda: "UG", + "U.S. Outlying Islands": "UM", + "United Nations": "UN", + "United States": "US", + Uruguay: "UY", + Uzbekistan: "UZ", + "Vatican City": "VA", + "St. Vincent & Grenadines": "VC", + Vietnam: "VN", + Venezuela: "VE", + "British Virgin Islands": "VG", + "U.S. Virgin Islands": "VI", + "Wallis & Futuna": "WF", + Samoa: "WS", + "Pseudo-Accents": "XA", + "Pseudo-Bidi": "XB", + Kosovo: "XK", + Yemen: "YE", + Mayotte: "YT", + "South Africa": "ZA", + Zambia: "ZM", + "Unknown Region": "ZZ" + }; -function _bitSetIndexForCharacter(c) { - console.assert(c.length === 1); - // @ts-ignore - return c.codePointAt(0) - SPACE_CODE_POINT; -} -function _characterAtBitSetIndex(index) { - return String.fromCodePoint(index + SPACE_CODE_POINT); -} -function _markBitsForNamedCharacterClass(bitSet, namedCharacterClass) { - console.assert(bitSet instanceof Array); - console.assert(namedCharacterClass.name !== Identifier.UNICODE); - console.assert(namedCharacterClass.name !== Identifier.ASCII_PRINTABLE); - if (namedCharacterClass.name === Identifier.UPPER) { - bitSet.fill(true, _bitSetIndexForCharacter('A'), _bitSetIndexForCharacter('Z') + 1); - } else if (namedCharacterClass.name === Identifier.LOWER) { - bitSet.fill(true, _bitSetIndexForCharacter('a'), _bitSetIndexForCharacter('z') + 1); - } else if (namedCharacterClass.name === Identifier.DIGIT) { - bitSet.fill(true, _bitSetIndexForCharacter('0'), _bitSetIndexForCharacter('9') + 1); - } else if (namedCharacterClass.name === Identifier.SPECIAL) { - bitSet.fill(true, _bitSetIndexForCharacter(' '), _bitSetIndexForCharacter('/') + 1); - bitSet.fill(true, _bitSetIndexForCharacter(':'), _bitSetIndexForCharacter('@') + 1); - bitSet.fill(true, _bitSetIndexForCharacter('['), _bitSetIndexForCharacter('`') + 1); - bitSet.fill(true, _bitSetIndexForCharacter('{'), _bitSetIndexForCharacter('~') + 1); - } else { - console.assert(false, SHOULD_NOT_BE_REACHED, namedCharacterClass); - } -} -function _markBitsForCustomCharacterClass(bitSet, customCharacterClass) { - for (let character of customCharacterClass.characters) { - bitSet[_bitSetIndexForCharacter(character)] = true; - } -} -function _canonicalizedPropertyValues(propertyValues, keepCustomCharacterClassFormatCompliant) { - // @ts-ignore - let asciiPrintableBitSet = new Array('~'.codePointAt(0) - ' '.codePointAt(0) + 1); - for (let propertyValue of propertyValues) { - if (propertyValue instanceof NamedCharacterClass) { - if (propertyValue.name === Identifier.UNICODE) { - return [new NamedCharacterClass(Identifier.UNICODE)]; - } - if (propertyValue.name === Identifier.ASCII_PRINTABLE) { - return [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)]; - } - _markBitsForNamedCharacterClass(asciiPrintableBitSet, propertyValue); - } else if (propertyValue instanceof CustomCharacterClass) { - _markBitsForCustomCharacterClass(asciiPrintableBitSet, propertyValue); + // src/Form/formatters.js + var DATE_SEPARATOR_REGEX = /\b((.)\2{1,3}|\d+)(?\s?[/\s.\-_—–]\s?)((.)\5{1,3}|\d+)\b/i; + var FOUR_DIGIT_YEAR_REGEX = /(\D)\1{3}|\d{4}/i; + var formatCCYear = (input, year, form) => { + const selector = form.matching.cssSelector("formInputsSelector"); + if (input.maxLength === 4 || checkPlaceholderAndLabels(input, FOUR_DIGIT_YEAR_REGEX, form.form, selector)) + return year; + return `${Number(year) - 2e3}`; + }; + var getUnifiedExpiryDate = (input, month, year, form) => { + const formattedYear = formatCCYear(input, year, form); + const paddedMonth = `${month}`.padStart(2, "0"); + const cssSelector = form.matching.cssSelector("formInputsSelector"); + const separator = matchInPlaceholderAndLabels(input, DATE_SEPARATOR_REGEX, form.form, cssSelector)?.groups?.separator || "/"; + return `${paddedMonth}${separator}${formattedYear}`; + }; + var formatFullName = ({ firstName = "", middleName = "", lastName = "" }) => `${firstName} ${middleName ? middleName + " " : ""}${lastName}`.trim(); + var getCountryDisplayName = (locale, addressCountryCode) => { + try { + const regionNames = new Intl.DisplayNames([locale], { type: "region" }); + return regionNames.of(addressCountryCode); + } catch (e) { + return COUNTRY_CODES_TO_NAMES[addressCountryCode] || addressCountryCode; } - } - let charactersSeen = []; - function checkRange(start, end) { - let temp = []; - for (let i = _bitSetIndexForCharacter(start); i <= _bitSetIndexForCharacter(end); ++i) { - if (asciiPrintableBitSet[i]) { - temp.push(_characterAtBitSetIndex(i)); + }; + var inferElementLocale = (el) => el.lang || el.form?.lang || document.body.lang || document.documentElement.lang || "en"; + var getCountryName = (el, options = {}) => { + const { addressCountryCode } = options; + if (!addressCountryCode) + return ""; + const elLocale = inferElementLocale(el); + const localisedCountryName = getCountryDisplayName(elLocale, addressCountryCode); + if (el.nodeName === "SELECT") { + const englishCountryName = getCountryDisplayName("en", addressCountryCode); + const countryNameRegex = new RegExp(String.raw`${localisedCountryName.replace(/ /g, ".?")}|${englishCountryName.replace(/ /g, ".?")}`, "i"); + const countryCodeRegex = new RegExp(String.raw`\b${addressCountryCode}\b`, "i"); + if (el instanceof HTMLSelectElement) { + for (const option of el.options) { + if (countryCodeRegex.test(option.value)) { + return option.value; + } + } + for (const option of el.options) { + if (countryNameRegex.test(option.value) || countryNameRegex.test(option.innerText)) + return option.value; + } } } - let result = temp.length === _bitSetIndexForCharacter(end) - _bitSetIndexForCharacter(start) + 1; - if (!result) { - charactersSeen = charactersSeen.concat(temp); + return localisedCountryName; + }; + var getLocalisedCountryNamesToCodes = (el) => { + if (typeof Intl.DisplayNames !== "function") + return COUNTRY_NAMES_TO_CODES; + const elLocale = inferElementLocale(el); + return Object.fromEntries( + Object.entries(COUNTRY_CODES_TO_NAMES).map(([code]) => [getCountryDisplayName(elLocale, code), code]) + ); + }; + var inferCountryCodeFromElement = (el) => { + if (COUNTRY_CODES_TO_NAMES[el.value]) + return el.value; + if (COUNTRY_NAMES_TO_CODES[el.value]) + return COUNTRY_NAMES_TO_CODES[el.value]; + const localisedCountryNamesToCodes = getLocalisedCountryNamesToCodes(el); + if (localisedCountryNamesToCodes[el.value]) + return localisedCountryNamesToCodes[el.value]; + if (el instanceof HTMLSelectElement) { + const selectedText = el.selectedOptions[0]?.text; + if (COUNTRY_CODES_TO_NAMES[selectedText]) + return selectedText; + if (COUNTRY_NAMES_TO_CODES[selectedText]) + return localisedCountryNamesToCodes[selectedText]; + if (localisedCountryNamesToCodes[selectedText]) + return localisedCountryNamesToCodes[selectedText]; + } + return ""; + }; + var getMMAndYYYYFromString = (expiration) => { + const values = expiration.match(/(\d+)/g) || []; + return values?.reduce((output, current) => { + if (Number(current) > 12) { + output.expirationYear = current.padStart(4, "20"); + } else { + output.expirationMonth = current.padStart(2, "0"); + } + return output; + }, { expirationYear: "", expirationMonth: "" }); + }; + var shouldStoreCredentials = ({ credentials }) => Boolean(credentials.password); + var shouldStoreIdentities = ({ identities }) => Boolean( + (identities.firstName || identities.fullName) && identities.addressStreet && identities.addressCity + ); + var shouldStoreCreditCards = ({ creditCards }) => { + if (!creditCards.cardNumber) + return false; + if (creditCards.cardSecurityCode) + return true; + if (creditCards.expiration) + return true; + return Boolean(creditCards.expirationYear && creditCards.expirationMonth); + }; + var formatPhoneNumber = (phone) => phone.replaceAll(/[^0-9|+]/g, ""); + var prepareFormValuesForStorage = (formValues) => { + let { credentials, identities, creditCards } = formValues; + if (!creditCards.cardName && (identities?.fullName || identities?.firstName)) { + creditCards.cardName = identities?.fullName || formatFullName(identities); } - return result; - } - let hasAllUpper = checkRange('A', 'Z'); - let hasAllLower = checkRange('a', 'z'); - let hasAllDigits = checkRange('0', '9'); - - // Check for special characters, accounting for characters that are given special treatment (i.e. '-' and ']') - let hasAllSpecial = false; - let hasDash = false; - let hasRightSquareBracket = false; - let temp = []; - for (let i = _bitSetIndexForCharacter(' '); i <= _bitSetIndexForCharacter('/'); ++i) { - if (!asciiPrintableBitSet[i]) { - continue; - } - let character = _characterAtBitSetIndex(i); - if (keepCustomCharacterClassFormatCompliant && character === '-') { - hasDash = true; + if (shouldStoreCredentials(formValues)) { + if (credentials.password && !credentials.username && identities.emailAddress) { + credentials.username = identities.emailAddress; + } + } else { + credentials = void 0; + } + if (shouldStoreIdentities(formValues)) { + if (identities.fullName) { + if (!(identities.firstName && identities.lastName)) { + const nameParts = identities.fullName.trim().split(/\s+/); + if (nameParts.length === 2) { + identities.firstName = nameParts[0]; + identities.lastName = nameParts[1]; + } else { + identities.firstName = identities.fullName; + } + } + delete identities.fullName; + } + if (identities.phone) { + identities.phone = formatPhoneNumber(identities.phone); + } } else { - temp.push(character); + identities = void 0; } - } - for (let i = _bitSetIndexForCharacter(':'); i <= _bitSetIndexForCharacter('@'); ++i) { - if (asciiPrintableBitSet[i]) { - temp.push(_characterAtBitSetIndex(i)); + if (shouldStoreCreditCards(formValues)) { + if (creditCards.expiration) { + const { expirationMonth, expirationYear } = getMMAndYYYYFromString(creditCards.expiration); + creditCards.expirationMonth = expirationMonth; + creditCards.expirationYear = expirationYear; + delete creditCards.expiration; + } + creditCards.expirationYear = creditCards.expirationYear?.padStart(4, "20"); + if (creditCards.cardNumber) { + creditCards.cardNumber = creditCards.cardNumber.replace(/\D/g, ""); + } + } else { + creditCards = void 0; } - } - for (let i = _bitSetIndexForCharacter('['); i <= _bitSetIndexForCharacter('`'); ++i) { - if (!asciiPrintableBitSet[i]) { - continue; + return { credentials, identities, creditCards }; + }; + + // src/InputTypes/Credentials.js + var AUTOGENERATED_KEY = "autogenerated"; + var PROVIDER_LOCKED = "provider_locked"; + var _data; + var CredentialsTooltipItem = class { + /** @param {CredentialsObject} data */ + constructor(data) { + /** @type {CredentialsObject} */ + __privateAdd(this, _data, void 0); + __publicField(this, "id", () => String(__privateGet(this, _data).id)); + __publicField(this, "labelMedium", (_subtype) => { + if (__privateGet(this, _data).username) { + return __privateGet(this, _data).username; + } + if (__privateGet(this, _data).origin?.url) { + return `Password for ${truncateFromMiddle(__privateGet(this, _data).origin.url)}`; + } + return ""; + }); + __publicField(this, "labelSmall", (_subtype) => { + if (__privateGet(this, _data).origin?.url) { + return truncateFromMiddle(__privateGet(this, _data).origin.url); + } + return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"; + }); + __publicField(this, "credentialsProvider", () => __privateGet(this, _data).credentialsProvider); + __privateSet(this, _data, data); } - let character = _characterAtBitSetIndex(i); - if (keepCustomCharacterClassFormatCompliant && character === ']') { - hasRightSquareBracket = true; - } else { - temp.push(character); + }; + _data = new WeakMap(); + var _data2; + var AutoGeneratedCredential = class { + /** @param {CredentialsObject} data */ + constructor(data) { + /** @type {CredentialsObject} */ + __privateAdd(this, _data2, void 0); + __publicField(this, "id", () => String(__privateGet(this, _data2).id)); + __publicField(this, "label", (_subtype) => __privateGet(this, _data2).password); + __publicField(this, "labelMedium", (_subtype) => "Generated password"); + __publicField(this, "labelSmall", (_subtype) => "Password will be saved for this website"); + __privateSet(this, _data2, data); } + }; + _data2 = new WeakMap(); + function fromPassword(password, username) { + return { + [AUTOGENERATED_KEY]: true, + password, + username + }; } - for (let i = _bitSetIndexForCharacter('{'); i <= _bitSetIndexForCharacter('~'); ++i) { - if (asciiPrintableBitSet[i]) { - temp.push(_characterAtBitSetIndex(i)); + var _data3; + var ProviderLockedItem = class { + /** @param {CredentialsObject} data */ + constructor(data) { + /** @type {CredentialsObject} */ + __privateAdd(this, _data3, void 0); + __publicField(this, "id", () => String(__privateGet(this, _data3).id)); + __publicField(this, "labelMedium", (_subtype) => "Bitwarden is locked"); + __publicField(this, "labelSmall", (_subtype) => "Unlock your vault to access credentials or generate passwords"); + __publicField(this, "credentialsProvider", () => __privateGet(this, _data3).credentialsProvider); + __privateSet(this, _data3, data); } - } - if (hasDash) { - temp.unshift('-'); - } - if (hasRightSquareBracket) { - temp.push(']'); - } - let numberOfSpecialCharacters = _bitSetIndexForCharacter('/') - _bitSetIndexForCharacter(' ') + 1 + (_bitSetIndexForCharacter('@') - _bitSetIndexForCharacter(':') + 1) + (_bitSetIndexForCharacter('`') - _bitSetIndexForCharacter('[') + 1) + (_bitSetIndexForCharacter('~') - _bitSetIndexForCharacter('{') + 1); - hasAllSpecial = temp.length === numberOfSpecialCharacters; - if (!hasAllSpecial) { - charactersSeen = charactersSeen.concat(temp); - } - let result = []; - if (hasAllUpper && hasAllLower && hasAllDigits && hasAllSpecial) { - return [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)]; - } - if (hasAllUpper) { - result.push(new NamedCharacterClass(Identifier.UPPER)); - } - if (hasAllLower) { - result.push(new NamedCharacterClass(Identifier.LOWER)); - } - if (hasAllDigits) { - result.push(new NamedCharacterClass(Identifier.DIGIT)); - } - if (hasAllSpecial) { - result.push(new NamedCharacterClass(Identifier.SPECIAL)); - } - if (charactersSeen.length) { - result.push(new CustomCharacterClass(charactersSeen)); - } - return result; -} - -// MARK: Parser functions - -function _indexOfNonWhitespaceCharacter(input) { - let position = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; - console.assert(position >= 0); - console.assert(position <= input.length); - let length = input.length; - while (position < length && _isASCIIWhitespace(input[position])) { - ++position; - } - return position; -} -function _parseIdentifier(input, position) { - console.assert(position >= 0); - console.assert(position < input.length); - console.assert(_isIdentifierCharacter(input[position])); - let length = input.length; - let seenIdentifiers = []; - do { - let c = input[position]; - if (!_isIdentifierCharacter(c)) { - break; - } - seenIdentifiers.push(c); - ++position; - } while (position < length); - return [seenIdentifiers.join(''), position]; -} -function _isValidRequiredOrAllowedPropertyValueIdentifier(identifier) { - return identifier && Object.values(Identifier).includes(identifier.toLowerCase()); -} -function _parseCustomCharacterClass(input, position) { - console.assert(position >= 0); - console.assert(position < input.length); - console.assert(input[position] === CHARACTER_CLASS_START_SENTINEL); - let length = input.length; - ++position; - if (position >= length) { - // console.error('Found end-of-line instead of character class character') - return [null, position]; - } - let initialPosition = position; - let result = []; - do { - let c = input[position]; - if (!_isASCIIPrintableCharacter(c)) { - ++position; - continue; + }; + _data3 = new WeakMap(); + function appendGeneratedKey(data, autofilledFields = {}) { + let autogenerated = false; + if (autofilledFields.password && data.credentials?.password === autofilledFields.password) { + autogenerated = true; } - if (c === '-' && position - initialPosition > 0) { - // FIXME: Should this be an error? - console.warn("Ignoring '-'; a '-' may only appear as the first character in a character class"); - ++position; - continue; + if (autofilledFields.username && data.credentials?.username === autofilledFields.username) { + autogenerated = true; } - result.push(c); - ++position; - if (c === CHARACTER_CLASS_END_SENTINEL) { - break; - } - } while (position < length); - if (position < length && input[position] !== CHARACTER_CLASS_END_SENTINEL) { - // Fix up result; we over consumed. - result.pop(); - return [result, position]; - } else if (position === length && input[position - 1] === CHARACTER_CLASS_END_SENTINEL) { - // Fix up result; we over consumed. - result.pop(); - return [result, position]; + if (!autogenerated) + return data; + return { + ...data, + credentials: { + ...data.credentials, + [AUTOGENERATED_KEY]: true + } + }; } - if (position < length && input[position] === CHARACTER_CLASS_END_SENTINEL) { - return [result, position + 1]; + function createCredentialsTooltipItem(data) { + if (data.id === PROVIDER_LOCKED) { + return new ProviderLockedItem(data); + } + if (AUTOGENERATED_KEY in data && data.password) { + return new AutoGeneratedCredential(data); + } + return new CredentialsTooltipItem(data); } - // console.error('Found end-of-line instead of end of character class') - return [null, position]; -} -function _parsePasswordRequiredOrAllowedPropertyValue(input, position) { - console.assert(position >= 0); - console.assert(position < input.length); - let length = input.length; - let propertyValues = []; - while (true) { - if (_isIdentifierCharacter(input[position])) { - let identifierStartPosition = position; - // eslint-disable-next-line no-redeclare - var [propertyValue, position] = _parseIdentifier(input, position); - if (!_isValidRequiredOrAllowedPropertyValueIdentifier(propertyValue)) { - // console.error('Unrecognized property value identifier: ' + propertyValue) - return [null, identifierStartPosition]; - } - propertyValues.push(new NamedCharacterClass(propertyValue)); - } else if (input[position] === CHARACTER_CLASS_START_SENTINEL) { - // eslint-disable-next-line no-redeclare - var [propertyValue, position] = _parseCustomCharacterClass(input, position); - if (propertyValue && propertyValue.length) { - propertyValues.push(new CustomCharacterClass(propertyValue)); - } - } else { - // console.error('Failed to find start of property value: ' + input.substr(position)) - return [null, position]; + // packages/password/lib/rules-parser.js + var Identifier = { + ASCII_PRINTABLE: "ascii-printable", + DIGIT: "digit", + LOWER: "lower", + SPECIAL: "special", + UNICODE: "unicode", + UPPER: "upper" + }; + var RuleName = { + ALLOWED: "allowed", + MAX_CONSECUTIVE: "max-consecutive", + REQUIRED: "required", + MIN_LENGTH: "minlength", + MAX_LENGTH: "maxlength" + }; + var CHARACTER_CLASS_START_SENTINEL = "["; + var CHARACTER_CLASS_END_SENTINEL = "]"; + var PROPERTY_VALUE_SEPARATOR = ","; + var PROPERTY_SEPARATOR = ";"; + var PROPERTY_VALUE_START_SENTINEL = ":"; + var SPACE_CODE_POINT = " ".codePointAt(0); + var SHOULD_NOT_BE_REACHED = "Should not be reached"; + var Rule = class { + constructor(name, value) { + this._name = name; + this.value = value; + } + get name() { + return this._name; + } + toString() { + return JSON.stringify(this); } - position = _indexOfNonWhitespaceCharacter(input, position); - if (position >= length || input[position] === PROPERTY_SEPARATOR) { - break; + }; + var NamedCharacterClass = class { + constructor(name) { + console.assert(_isValidRequiredOrAllowedPropertyValueIdentifier(name)); + this._name = name; } - if (input[position] === PROPERTY_VALUE_SEPARATOR) { - position = _indexOfNonWhitespaceCharacter(input, position + 1); - if (position >= length) { - // console.error('Found end-of-line instead of start of next property value') - return [null, position]; - } - continue; + get name() { + return this._name.toLowerCase(); + } + toString() { + return this._name; + } + toHTMLString() { + return this._name; + } + }; + var ParserError = class extends Error { + }; + var CustomCharacterClass = class { + constructor(characters) { + console.assert(characters instanceof Array); + this._characters = characters; + } + get characters() { + return this._characters; + } + toString() { + return `[${this._characters.join("")}]`; + } + toHTMLString() { + return `[${this._characters.join("").replace('"', """)}]`; + } + }; + function _isIdentifierCharacter(c) { + console.assert(c.length === 1); + return c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c === "-"; + } + function _isASCIIDigit(c) { + console.assert(c.length === 1); + return c >= "0" && c <= "9"; + } + function _isASCIIPrintableCharacter(c) { + console.assert(c.length === 1); + return c >= " " && c <= "~"; + } + function _isASCIIWhitespace(c) { + console.assert(c.length === 1); + return c === " " || c === "\f" || c === "\n" || c === "\r" || c === " "; + } + function _bitSetIndexForCharacter(c) { + console.assert(c.length === 1); + return c.codePointAt(0) - SPACE_CODE_POINT; + } + function _characterAtBitSetIndex(index) { + return String.fromCodePoint(index + SPACE_CODE_POINT); + } + function _markBitsForNamedCharacterClass(bitSet, namedCharacterClass) { + console.assert(bitSet instanceof Array); + console.assert(namedCharacterClass.name !== Identifier.UNICODE); + console.assert(namedCharacterClass.name !== Identifier.ASCII_PRINTABLE); + if (namedCharacterClass.name === Identifier.UPPER) { + bitSet.fill(true, _bitSetIndexForCharacter("A"), _bitSetIndexForCharacter("Z") + 1); + } else if (namedCharacterClass.name === Identifier.LOWER) { + bitSet.fill(true, _bitSetIndexForCharacter("a"), _bitSetIndexForCharacter("z") + 1); + } else if (namedCharacterClass.name === Identifier.DIGIT) { + bitSet.fill(true, _bitSetIndexForCharacter("0"), _bitSetIndexForCharacter("9") + 1); + } else if (namedCharacterClass.name === Identifier.SPECIAL) { + bitSet.fill(true, _bitSetIndexForCharacter(" "), _bitSetIndexForCharacter("/") + 1); + bitSet.fill(true, _bitSetIndexForCharacter(":"), _bitSetIndexForCharacter("@") + 1); + bitSet.fill(true, _bitSetIndexForCharacter("["), _bitSetIndexForCharacter("`") + 1); + bitSet.fill(true, _bitSetIndexForCharacter("{"), _bitSetIndexForCharacter("~") + 1); + } else { + console.assert(false, SHOULD_NOT_BE_REACHED, namedCharacterClass); } - - // console.error('Failed to find start of next property or property value: ' + input.substr(position)) - return [null, position]; - } - return [propertyValues, position]; -} - -/** - * @param input - * @param position - * @returns {[Rule|null, number, string|undefined]} - * @private - */ -function _parsePasswordRule(input, position) { - console.assert(position >= 0); - console.assert(position < input.length); - console.assert(_isIdentifierCharacter(input[position])); - let length = input.length; - var mayBeIdentifierStartPosition = position; - // eslint-disable-next-line no-redeclare - var [identifier, position] = _parseIdentifier(input, position); - if (!Object.values(RuleName).includes(identifier)) { - // console.error('Unrecognized property name: ' + identifier) - return [null, mayBeIdentifierStartPosition, undefined]; - } - if (position >= length) { - // console.error('Found end-of-line instead of start of property value') - return [null, position, undefined]; - } - if (input[position] !== PROPERTY_VALUE_START_SENTINEL) { - // console.error('Failed to find start of property value: ' + input.substr(position)) - return [null, position, undefined]; } - let property = { - name: identifier, - value: null - }; - position = _indexOfNonWhitespaceCharacter(input, position + 1); - // Empty value - if (position >= length || input[position] === PROPERTY_SEPARATOR) { - return [new Rule(property.name, property.value), position, undefined]; + function _markBitsForCustomCharacterClass(bitSet, customCharacterClass) { + for (let character of customCharacterClass.characters) { + bitSet[_bitSetIndexForCharacter(character)] = true; + } } - switch (identifier) { - case RuleName.ALLOWED: - case RuleName.REQUIRED: - { - // eslint-disable-next-line no-redeclare - var [propertyValue, position] = _parsePasswordRequiredOrAllowedPropertyValue(input, position); - if (propertyValue) { - property.value = propertyValue; + function _canonicalizedPropertyValues(propertyValues, keepCustomCharacterClassFormatCompliant) { + let asciiPrintableBitSet = new Array("~".codePointAt(0) - " ".codePointAt(0) + 1); + for (let propertyValue of propertyValues) { + if (propertyValue instanceof NamedCharacterClass) { + if (propertyValue.name === Identifier.UNICODE) { + return [new NamedCharacterClass(Identifier.UNICODE)]; } - return [new Rule(property.name, property.value), position, undefined]; - } - case RuleName.MAX_CONSECUTIVE: - { - // eslint-disable-next-line no-redeclare - var [propertyValue, position] = _parseMaxConsecutivePropertyValue(input, position); - if (propertyValue) { - property.value = propertyValue; + if (propertyValue.name === Identifier.ASCII_PRINTABLE) { + return [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)]; } - return [new Rule(property.name, property.value), position, undefined]; + _markBitsForNamedCharacterClass(asciiPrintableBitSet, propertyValue); + } else if (propertyValue instanceof CustomCharacterClass) { + _markBitsForCustomCharacterClass(asciiPrintableBitSet, propertyValue); } - case RuleName.MIN_LENGTH: - case RuleName.MAX_LENGTH: - { - // eslint-disable-next-line no-redeclare - var [propertyValue, position] = _parseMinLengthMaxLengthPropertyValue(input, position); - if (propertyValue) { - property.value = propertyValue; + } + let charactersSeen = []; + function checkRange(start, end) { + let temp2 = []; + for (let i = _bitSetIndexForCharacter(start); i <= _bitSetIndexForCharacter(end); ++i) { + if (asciiPrintableBitSet[i]) { + temp2.push(_characterAtBitSetIndex(i)); } - return [new Rule(property.name, property.value), position, undefined]; } - } - console.assert(false, SHOULD_NOT_BE_REACHED); - return [null, -1, undefined]; -} -function _parseMinLengthMaxLengthPropertyValue(input, position) { - return _parseInteger(input, position); -} -function _parseMaxConsecutivePropertyValue(input, position) { - return _parseInteger(input, position); -} -function _parseInteger(input, position) { - console.assert(position >= 0); - console.assert(position < input.length); - if (!_isASCIIDigit(input[position])) { - // console.error('Failed to parse value of type integer; not a number: ' + input.substr(position)) - return [null, position]; - } - let length = input.length; - // let initialPosition = position - let result = 0; - do { - result = 10 * result + parseInt(input[position], 10); - ++position; - } while (position < length && input[position] !== PROPERTY_SEPARATOR && _isASCIIDigit(input[position])); - if (position >= length || input[position] === PROPERTY_SEPARATOR) { - return [result, position]; - } - - // console.error('Failed to parse value of type integer; not a number: ' + input.substr(initialPosition)) - return [null, position]; -} - -/** - * @param input - * @returns {[Rule[]|null, string|undefined]} - * @private - */ -function _parsePasswordRulesInternal(input) { - let parsedProperties = []; - let length = input.length; - var position = _indexOfNonWhitespaceCharacter(input); - while (position < length) { - if (!_isIdentifierCharacter(input[position])) { - // console.warn('Failed to find start of property: ' + input.substr(position)) - return [parsedProperties, undefined]; + let result2 = temp2.length === _bitSetIndexForCharacter(end) - _bitSetIndexForCharacter(start) + 1; + if (!result2) { + charactersSeen = charactersSeen.concat(temp2); + } + return result2; } - - // eslint-disable-next-line no-redeclare - var [parsedProperty, position, message] = _parsePasswordRule(input, position); - if (parsedProperty && parsedProperty.value) { - parsedProperties.push(parsedProperty); + let hasAllUpper = checkRange("A", "Z"); + let hasAllLower = checkRange("a", "z"); + let hasAllDigits = checkRange("0", "9"); + let hasAllSpecial = false; + let hasDash = false; + let hasRightSquareBracket = false; + let temp = []; + for (let i = _bitSetIndexForCharacter(" "); i <= _bitSetIndexForCharacter("/"); ++i) { + if (!asciiPrintableBitSet[i]) { + continue; + } + let character = _characterAtBitSetIndex(i); + if (keepCustomCharacterClassFormatCompliant && character === "-") { + hasDash = true; + } else { + temp.push(character); + } } - position = _indexOfNonWhitespaceCharacter(input, position); - if (position >= length) { - break; + for (let i = _bitSetIndexForCharacter(":"); i <= _bitSetIndexForCharacter("@"); ++i) { + if (asciiPrintableBitSet[i]) { + temp.push(_characterAtBitSetIndex(i)); + } } - if (input[position] === PROPERTY_SEPARATOR) { - position = _indexOfNonWhitespaceCharacter(input, position + 1); - if (position >= length) { - return [parsedProperties, undefined]; + for (let i = _bitSetIndexForCharacter("["); i <= _bitSetIndexForCharacter("`"); ++i) { + if (!asciiPrintableBitSet[i]) { + continue; + } + let character = _characterAtBitSetIndex(i); + if (keepCustomCharacterClassFormatCompliant && character === "]") { + hasRightSquareBracket = true; + } else { + temp.push(character); } - continue; } - - // console.error('Failed to find start of next property: ' + input.substr(position)) - return [null, message || 'Failed to find start of next property: ' + input.substr(position)]; - } - return [parsedProperties, undefined]; -} - -/** - * @param {string} input - * @param {boolean} [formatRulesForMinifiedVersion] - * @returns {Rule[]} - */ -function parsePasswordRules(input, formatRulesForMinifiedVersion) { - let [passwordRules, maybeMessage] = _parsePasswordRulesInternal(input); - if (!passwordRules) { - throw new ParserError(maybeMessage); + for (let i = _bitSetIndexForCharacter("{"); i <= _bitSetIndexForCharacter("~"); ++i) { + if (asciiPrintableBitSet[i]) { + temp.push(_characterAtBitSetIndex(i)); + } + } + if (hasDash) { + temp.unshift("-"); + } + if (hasRightSquareBracket) { + temp.push("]"); + } + let numberOfSpecialCharacters = _bitSetIndexForCharacter("/") - _bitSetIndexForCharacter(" ") + 1 + (_bitSetIndexForCharacter("@") - _bitSetIndexForCharacter(":") + 1) + (_bitSetIndexForCharacter("`") - _bitSetIndexForCharacter("[") + 1) + (_bitSetIndexForCharacter("~") - _bitSetIndexForCharacter("{") + 1); + hasAllSpecial = temp.length === numberOfSpecialCharacters; + if (!hasAllSpecial) { + charactersSeen = charactersSeen.concat(temp); + } + let result = []; + if (hasAllUpper && hasAllLower && hasAllDigits && hasAllSpecial) { + return [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)]; + } + if (hasAllUpper) { + result.push(new NamedCharacterClass(Identifier.UPPER)); + } + if (hasAllLower) { + result.push(new NamedCharacterClass(Identifier.LOWER)); + } + if (hasAllDigits) { + result.push(new NamedCharacterClass(Identifier.DIGIT)); + } + if (hasAllSpecial) { + result.push(new NamedCharacterClass(Identifier.SPECIAL)); + } + if (charactersSeen.length) { + result.push(new CustomCharacterClass(charactersSeen)); + } + return result; } - if (passwordRules.length === 0) { - throw new ParserError('No valid rules were provided'); + function _indexOfNonWhitespaceCharacter(input, position = 0) { + console.assert(position >= 0); + console.assert(position <= input.length); + let length = input.length; + while (position < length && _isASCIIWhitespace(input[position])) { + ++position; + } + return position; } - - // When formatting rules for minified version, we should keep the formatted rules - // as similar to the input as possible. Avoid copying required rules to allowed rules. - let suppressCopyingRequiredToAllowed = formatRulesForMinifiedVersion; - let requiredRules = []; - let newAllowedValues = []; - let minimumMaximumConsecutiveCharacters = null; - let maximumMinLength = 0; - let minimumMaxLength = null; - for (let rule of passwordRules) { - switch (rule.name) { - case RuleName.MAX_CONSECUTIVE: - minimumMaximumConsecutiveCharacters = minimumMaximumConsecutiveCharacters ? Math.min(rule.value, minimumMaximumConsecutiveCharacters) : rule.value; - break; - case RuleName.MIN_LENGTH: - maximumMinLength = Math.max(rule.value, maximumMinLength); + function _parseIdentifier(input, position) { + console.assert(position >= 0); + console.assert(position < input.length); + console.assert(_isIdentifierCharacter(input[position])); + let length = input.length; + let seenIdentifiers = []; + do { + let c = input[position]; + if (!_isIdentifierCharacter(c)) { break; - case RuleName.MAX_LENGTH: - minimumMaxLength = minimumMaxLength ? Math.min(rule.value, minimumMaxLength) : rule.value; + } + seenIdentifiers.push(c); + ++position; + } while (position < length); + return [seenIdentifiers.join(""), position]; + } + function _isValidRequiredOrAllowedPropertyValueIdentifier(identifier) { + return identifier && Object.values(Identifier).includes(identifier.toLowerCase()); + } + function _parseCustomCharacterClass(input, position) { + console.assert(position >= 0); + console.assert(position < input.length); + console.assert(input[position] === CHARACTER_CLASS_START_SENTINEL); + let length = input.length; + ++position; + if (position >= length) { + return [null, position]; + } + let initialPosition = position; + let result = []; + do { + let c = input[position]; + if (!_isASCIIPrintableCharacter(c)) { + ++position; + continue; + } + if (c === "-" && position - initialPosition > 0) { + console.warn("Ignoring '-'; a '-' may only appear as the first character in a character class"); + ++position; + continue; + } + result.push(c); + ++position; + if (c === CHARACTER_CLASS_END_SENTINEL) { break; - case RuleName.REQUIRED: - rule.value = _canonicalizedPropertyValues(rule.value, formatRulesForMinifiedVersion); - requiredRules.push(rule); - if (!suppressCopyingRequiredToAllowed) { - newAllowedValues = newAllowedValues.concat(rule.value); + } + } while (position < length); + if (position < length && input[position] !== CHARACTER_CLASS_END_SENTINEL) { + result.pop(); + return [result, position]; + } else if (position === length && input[position - 1] === CHARACTER_CLASS_END_SENTINEL) { + result.pop(); + return [result, position]; + } + if (position < length && input[position] === CHARACTER_CLASS_END_SENTINEL) { + return [result, position + 1]; + } + return [null, position]; + } + function _parsePasswordRequiredOrAllowedPropertyValue(input, position) { + console.assert(position >= 0); + console.assert(position < input.length); + let length = input.length; + let propertyValues = []; + while (true) { + if (_isIdentifierCharacter(input[position])) { + let identifierStartPosition = position; + var [propertyValue, position] = _parseIdentifier(input, position); + if (!_isValidRequiredOrAllowedPropertyValueIdentifier(propertyValue)) { + return [null, identifierStartPosition]; } + propertyValues.push(new NamedCharacterClass(propertyValue)); + } else if (input[position] === CHARACTER_CLASS_START_SENTINEL) { + var [propertyValue, position] = _parseCustomCharacterClass(input, position); + if (propertyValue && propertyValue.length) { + propertyValues.push(new CustomCharacterClass(propertyValue)); + } + } else { + return [null, position]; + } + position = _indexOfNonWhitespaceCharacter(input, position); + if (position >= length || input[position] === PROPERTY_SEPARATOR) { break; - case RuleName.ALLOWED: - newAllowedValues = newAllowedValues.concat(rule.value); - break; + } + if (input[position] === PROPERTY_VALUE_SEPARATOR) { + position = _indexOfNonWhitespaceCharacter(input, position + 1); + if (position >= length) { + return [null, position]; + } + continue; + } + return [null, position]; } + return [propertyValues, position]; } - let newPasswordRules = []; - if (maximumMinLength > 0) { - newPasswordRules.push(new Rule(RuleName.MIN_LENGTH, maximumMinLength)); - } - if (minimumMaxLength !== null) { - newPasswordRules.push(new Rule(RuleName.MAX_LENGTH, minimumMaxLength)); - } - if (minimumMaximumConsecutiveCharacters !== null) { - newPasswordRules.push(new Rule(RuleName.MAX_CONSECUTIVE, minimumMaximumConsecutiveCharacters)); - } - let sortedRequiredRules = requiredRules.sort(function (a, b) { - const namedCharacterClassOrder = [Identifier.LOWER, Identifier.UPPER, Identifier.DIGIT, Identifier.SPECIAL, Identifier.ASCII_PRINTABLE, Identifier.UNICODE]; - let aIsJustOneNamedCharacterClass = a.value.length === 1 && a.value[0] instanceof NamedCharacterClass; - let bIsJustOneNamedCharacterClass = b.value.length === 1 && b.value[0] instanceof NamedCharacterClass; - if (aIsJustOneNamedCharacterClass && !bIsJustOneNamedCharacterClass) { - return -1; - } - if (!aIsJustOneNamedCharacterClass && bIsJustOneNamedCharacterClass) { - return 1; - } - if (aIsJustOneNamedCharacterClass && bIsJustOneNamedCharacterClass) { - let aIndex = namedCharacterClassOrder.indexOf(a.value[0].name); - let bIndex = namedCharacterClassOrder.indexOf(b.value[0].name); - return aIndex - bIndex; - } - return 0; - }); - newPasswordRules = newPasswordRules.concat(sortedRequiredRules); - newAllowedValues = _canonicalizedPropertyValues(newAllowedValues, suppressCopyingRequiredToAllowed); - if (!suppressCopyingRequiredToAllowed && !newAllowedValues.length) { - newAllowedValues = [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)]; - } - if (newAllowedValues.length) { - newPasswordRules.push(new Rule(RuleName.ALLOWED, newAllowedValues)); - } - return newPasswordRules; -} - -},{}],11:[function(require,module,exports){ -module.exports={ - "163.com": { - "password-rules": "minlength: 6; maxlength: 16;" - }, - "1800flowers.com": { - "password-rules": "minlength: 6; required: lower, upper; required: digit;" - }, - "access.service.gov.uk": { - "password-rules": "minlength: 10; required: lower; required: upper; required: digit; required: special;" - }, - "admiral.com": { - "password-rules": "minlength: 8; required: digit; required: [- !\"#$&'()*+,.:;<=>?@[^_`{|}~]]; allowed: lower, upper;" - }, - "ae.com": { - "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit;" - }, - "aetna.com": { - "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 2; required: upper; required: digit; allowed: lower, [-_&#@];" - }, - "airasia.com": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;" - }, - "airfrance.com": { - "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit; allowed: [-!#$&+/?@_];" - }, - "airfrance.us": { - "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit; allowed: [-!#$&+/?@_];" - }, - "ajisushionline.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; allowed: [ !#$%&*?@];" - }, - "aliexpress.com": { - "password-rules": "minlength: 6; maxlength: 20; allowed: lower, upper, digit;" - }, - "alliantcreditunion.com": { - "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower, upper; required: digit; allowed: [!#$*];" - }, - "allianz.com.br": { - "password-rules": "minlength: 4; maxlength: 4;" - }, - "americanexpress.com": { - "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 4; required: lower, upper; required: digit; allowed: [%&_?#=];" - }, - "anatel.gov.br": { - "password-rules": "minlength: 6; maxlength: 15; allowed: lower, upper, digit;" - }, - "ancestry.com": { - "password-rules": "minlength: 8; required: lower, upper; required: digit;" - }, - "angieslist.com": { - "password-rules": "minlength: 6; maxlength: 15;" - }, - "anthem.com": { - "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower, upper; required: digit; allowed: [!$*?@|];" - }, - "app.digio.in": { - "password-rules": "minlength: 8; maxlength: 15;" - }, - "app.parkmobile.io": { - "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit; required: [!@#$%^&];" - }, - "apple.com": { - "password-rules": "minlength: 8; maxlength: 63; required: lower; required: upper; required: digit; allowed: ascii-printable;" - }, - "areariservata.bancaetica.it": { - "password-rules": "minlength: 8; maxlength: 10; required: lower; required: upper; required: digit; required: [!#&*+/=@_];" - }, - "artscyclery.com": { - "password-rules": "minlength: 6; maxlength: 19;" - }, - "astonmartinf1.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: special;" - }, - "auth.readymag.com": { - "password-rules": "minlength: 8; maxlength: 128; required: lower; required: upper; allowed: special;" - }, - "autify.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!\"#$%&'()*+,./:;<=>?@[^_`{|}~]];" - }, - "axa.de": { - "password-rules": "minlength: 8; maxlength: 65; required: lower; required: upper; required: digit; allowed: [-!\"§$%&/()=?;:_+*'#];" - }, - "baidu.com": { - "password-rules": "minlength: 6; maxlength: 14;" - }, - "bancochile.cl": { - "password-rules": "minlength: 8; maxlength: 8; required: lower; required: upper; required: digit;" - }, - "bankofamerica.com": { - "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower; required: upper; required: digit; allowed: [-@#*()+={}/?~;,._];" - }, - "battle.net": { - "password-rules": "minlength: 8; maxlength: 16; required: lower, upper; allowed: digit, special;" - }, - "bcassessment.ca": { - "password-rules": "minlength: 8; maxlength: 14;" - }, - "belkin.com": { - "password-rules": "minlength: 8; required: lower, upper; required: digit; required: [$!@~_,%&];" - }, - "benefitslogin.discoverybenefits.com": { - "password-rules": "minlength: 10; required: upper; required: digit; required: [!#$%&*?@]; allowed: lower;" - }, - "benjerry.com": { - "password-rules": "required: upper; required: upper; required: digit; required: digit; required: special; required: special; allowed: lower;" - }, - "bestbuy.com": { - "password-rules": "minlength: 20; required: lower; required: upper; required: digit; required: special;" - }, - "bhphotovideo.com": { - "password-rules": "maxlength: 15;" - }, - "bilibili.com": { - "password-rules": "maxlength: 16;" - }, - "billerweb.com": { - "password-rules": "minlength: 8; max-consecutive: 2; required: digit; required: upper,lower;" - }, - "biovea.com": { - "password-rules": "maxlength: 19;" - }, - "bitly.com": { - "password-rules": "minlength: 6; required: lower; required: upper; required: digit; required: [`!@#$%^&*()+~{}'\";:<>?]];" - }, - "bloomingdales.com": { - "password-rules": "minlength: 7; maxlength: 16; required: lower, upper; required: digit; required: [`!@#$%^&*()+~{}'\";:<>?]];" - }, - "bluesguitarunleashed.com": { - "password-rules": "allowed: lower, upper, digit, [!$#@];" - }, - "bochk.com": { - "password-rules": "minlength: 8; maxlength: 12; max-consecutive: 3; required: lower; required: upper; required: digit; allowed: [#$%&()*+,.:;<=>?@_];" - }, - "box.com": { - "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: digit;" - }, - "brighthorizons.com": { - "password-rules": "minlength: 8; maxlength: 16;" - }, - "callofduty.com": { - "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 2; required: lower, upper; required: digit;" - }, - "capitalone.com": { - "password-rules": "minlength: 8; maxlength: 32; required: lower, upper; required: digit; allowed: [-_./\\@$*&!#];" - }, - "cardbenefitservices.com": { - "password-rules": "minlength: 7; maxlength: 100; required: lower, upper; required: digit;" - }, - "carrefour.it": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&*?@_];" - }, - "cb2.com": { - "password-rules": "minlength: 7; maxlength: 18; required: lower, upper; required: digit;" - }, - "ccs-grp.com": { - "password-rules": "minlength: 8; maxlength: 16; required: digit; required: upper,lower; allowed: [-!#$%&'+./=?\\^_`{|}~];" - }, - "cecredentialtrust.com": { - "password-rules": "minlength: 12; required: lower; required: upper; required: digit; required: [!#$%&*@^];" - }, - "chase.com": { - "password-rules": "minlength: 8; maxlength: 32; max-consecutive: 2; required: lower, upper; required: digit; required: [!#$%+/=@~];" - }, - "cigna.co.uk": { - "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit;" - }, - "citi.com": { - "password-rules": "minlength: 8; maxlength: 64; max-consecutive: 2; required: digit; required: upper; required: lower; required: [-~`!@#$%^&*()_\\/|];" - }, - "claimlookup.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [@#$%^&+=!];" - }, - "claro.com.br": { - "password-rules": "minlength: 8; required: lower; allowed: upper, digit, [-!@#$%&*_+=<>];" - }, - "classmates.com": { - "password-rules": "minlength: 6; maxlength: 20; allowed: lower, upper, digit, [!@#$%^&*];" - }, - "clegc-gckey.gc.ca": { - "password-rules": "minlength: 8; maxlength: 16; required: lower, upper, digit;" - }, - "clien.net": { - "password-rules": "minlength: 5; required: lower, upper; required: digit;" - }, - "cogmembers.org": { - "password-rules": "minlength: 8; maxlength: 14; required: upper; required: digit, allowed: lower;" - }, - "collectivehealth.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" - }, - "comcastpaymentcenter.com": { - "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 2;required: lower, upper; required: digit;" - }, - "comed.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [-~!@#$%^&*_+=`|(){}[:;\"'<>,.?/\\]];" - }, - "commerzbank.de": { - "password-rules": "minlength: 5; maxlength: 8; required: lower, upper; required: digit;" - }, - "consorsbank.de": { - "password-rules": "minlength: 5; maxlength: 5; required: lower, upper, digit;" - }, - "consorsfinanz.de": { - "password-rules": "minlength: 6; maxlength: 15; allowed: lower, upper, digit, [-.];" - }, - "costco.com": { - "password-rules": "minlength: 8; maxlength: 20; required: lower, upper; allowed: digit, [-!#$%&'()*+/:;=?@[^_`{|}~]];" - }, - "coursera.com": { - "password-rules": "minlength: 8; maxlength: 72;" - }, - "cox.com": { - "password-rules": "minlength: 8; maxlength: 24; required: digit; required: upper,lower; allowed: [!#$%()*@^];" - }, - "crateandbarrel.com": { - "password-rules": "minlength: 9; maxlength: 64; required: lower; required: upper; required: digit; required: [!\"#$%&()*,.:<>?@^_{|}];" - }, - "cvs.com": { - "password-rules": "minlength: 8; maxlength: 25; required: lower, upper; required: digit; allowed: [!@#$%^&*()];" - }, - "dailymail.co.uk": { - "password-rules": "minlength: 5; maxlength: 15;" - }, - "dan.org": { - "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit; required: [!@$%^&*];" - }, - "danawa.com": { - "password-rules": "minlength: 8; maxlength: 21; required: lower, upper; required: digit; required: [!@$%^&*];" - }, - "darty.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" - }, - "dbs.com.hk": { - "password-rules": "minlength: 8; maxlength: 30; required: lower; required: upper; required: digit;" - }, - "decluttr.com": { - "password-rules": "minlength: 8; maxlength: 45; required: lower; required: upper; required: digit;" - }, - "delta.com": { - "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit;" - }, - "deutsche-bank.de": { - "password-rules": "minlength: 5; maxlength: 5; required: lower, upper, digit;" - }, - "devstore.cn": { - "password-rules": "minlength: 6; maxlength: 12;" - }, - "dickssportinggoods.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&*?@^];" - }, - "dkb.de": { - "password-rules": "minlength: 8; maxlength: 38; required: lower, upper; required: digit; allowed: [-äüöÄÜÖß!$%&/()=?+#,.:];" - }, - "dmm.com": { - "password-rules": "minlength: 4; maxlength: 16; required: lower; required: upper; required: digit;" - }, - "dowjones.com": { - "password-rules": "maxlength: 15;" - }, - "ea.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: special;" - }, - "easycoop.com": { - "password-rules": "minlength: 8; required: upper; required: special; allowed: lower, digit;" - }, - "easyjet.com": { - "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: [-];" - }, - "ebrap.org": { - "password-rules": "minlength: 15; required: lower; required: lower; required: upper; required: upper; required: digit; required: digit; required: [-!@#$%^&*()_+|~=`{}[:\";'?,./.]]; required: [-!@#$%^&*()_+|~=`{}[:\";'?,./.]];" - }, - "ecompanystore.com": { - "password-rules": "minlength: 8; maxlength: 16; max-consecutive: 2; required: lower; required: upper; required: digit; required: [#$%*+.=@^_];" - }, - "eddservices.edd.ca.gov": { - "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit; required: [!@#$%^&*()];" - }, - "edistrict.kerala.gov.in": { - "password-rules": "minlength: 5; maxlength: 15; required: lower; required: upper; required: digit; required: [!@#$];" - }, - "empower-retirement.com": { - "password-rules": "minlength: 8; maxlength: 16;" - }, - "epicgames.com": { - "password-rules": "minlength: 7; required: lower; required: upper; required: digit; required: [-!\"#$%&'()*+,./:;<=>?@[^_`{|}~]];" - }, - "epicmix.com": { - "password-rules": "minlength: 8; maxlength: 16;" - }, - "equifax.com": { - "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; required: [!$*+@];" - }, - "essportal.excelityglobal.com": { - "password-rules": "minlength: 6; maxlength: 8; allowed: lower, upper, digit;" - }, - "ettoday.net": { - "password-rules": "minlength: 6; maxlength: 12;" - }, - "examservice.com.tw": { - "password-rules": "minlength: 6; maxlength: 8;" - }, - "expertflyer.com": { - "password-rules": "minlength: 5; maxlength: 16; required: lower, upper; required: digit;" - }, - "extraspace.com": { - "password-rules": "minlength: 8; maxlength: 20; allowed: lower; required: upper, digit, [!#$%&*?@];" - }, - "ezpassva.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;" - }, - "fc2.com": { - "password-rules": "minlength: 8; maxlength: 16;" - }, - "fccaccessonline.com": { - "password-rules": "minlength: 8; maxlength: 14; max-consecutive: 3; required: lower; required: upper; required: digit; required: [!#$%*^_];" - }, - "fedex.com": { - "password-rules": "minlength: 8; max-consecutive: 3; required: lower; required: upper; required: digit; allowed: [-!@#$%^&*_+=`|(){}[:;,.?]];" - }, - "fidelity.com": { - "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: [-!$%+,./:;=?@^_|]; max-consecutive: 2;" - }, - "flysas.com": { - "password-rules": "minlength: 8; maxlength: 14; required: lower; required: upper; required: digit; required: [-~!@#$%^&_+=`|(){}[:\"'<>,.?]];" - }, - "fnac.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" - }, - "fuelrewards.com": { - "password-rules": "minlength: 8; maxlength: 16; allowed: upper,lower,digit,[!#$%@];" - }, - "gamestop.com": { - "password-rules": "minlength: 8; maxlength: 225; required: lower; required: upper; required: digit; required: [!@#$%];" - }, - "garmin.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" - }, - "getflywheel.com": { - "password-rules": "minlength: 7; maxlength: 72;" - }, - "girlscouts.org": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [$#!];" - }, - "gmx.net": { - "password-rules": "minlength: 8; maxlength: 40; allowed: lower, upper, digit, [-<=>~!|()@#{}$%,.?^'&*_+`:;\"[]];" - }, - "gocurb.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [$%&#*?!@^];" - }, - "google.com": { - "password-rules": "minlength: 8; allowed: lower, upper, digit, [-!\"#$%&'()*+,./:;<=>?@[^_{|}~]];" - }, - "guardiananytime.com": { - "password-rules": "minlength: 8; maxlength: 50; max-consecutive: 2; required: lower; required: upper; required: digit, [-~!@#$%^&*_+=`|(){}[:;,.?]];" - }, - "gwl.greatwestlife.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [-!#$%_=+<>];" - }, - "hangseng.com": { - "password-rules": "minlength: 8; maxlength: 30; required: lower; required: upper; required: digit;" - }, - "hawaiianairlines.com": { - "password-rules": "maxlength: 16;" - }, - "hertz-japan.com": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz-kuwait.com": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz-saudi.com": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.at": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.be": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.bh": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.ca": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.ch": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.cn": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.co.ao": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.co.id": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.co.kr": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.co.nz": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.co.th": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.co.uk": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.au": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.bh": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.hk": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.kw": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.mt": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.pl": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.pt": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.sg": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.tw": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.cv": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.cz": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.de": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.ee": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.es": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.fi": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.fr": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.hu": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.ie": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.it": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.jo": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.lt": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.nl": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.no": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.nu": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.pl": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.pt": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.qa": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.ru": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.se": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.si": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertzcaribbean.com": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hetzner.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit, special;" - }, - "hilton.com": { - "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit;" - }, - "hkbea.com": { - "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit;" - }, - "hkexpress.com": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: special;" - }, - "hotels.com": { - "password-rules": "minlength: 6; maxlength: 20; required: digit; required: [-~#@$%&!*_?^]; allowed: lower, upper;" - }, - "hotwire.com": { - "password-rules": "minlength: 6; maxlength: 30; allowed: lower, upper, digit, [-~!@#$%^&*_+=`|(){}[:;\"'<>,.?]];" - }, - "hrblock.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [$#%!];" - }, - "hsbc.com.hk": { - "password-rules": "minlength: 6; maxlength: 30; required: lower; required: upper; required: digit; allowed: ['.@_];" - }, - "hsbc.com.my": { - "password-rules": "minlength: 8; maxlength: 30; required: lower, upper; required: digit; allowed: [-!$*.=?@_'];" - }, - "hypovereinsbank.de": { - "password-rules": "minlength: 6; maxlength: 10; required: lower, upper, digit; allowed: [!\"#$%&()*+:;<=>?@[{}~]];" - }, - "hyresbostader.se": { - "password-rules": "minlength: 6; maxlength: 20; required: lower, upper; required: digit;" - }, - "ichunqiu.com": { - "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit;" - }, - "id.sonyentertainmentnetwork.com": { - "password-rules": "minlength: 8; maxlength: 30; required: lower, upper; required: digit; allowed: [-!@#^&*=+;:];" - }, - "identitytheft.gov": { - "password-rules": "allowed: lower, upper, digit, [!#%&*@^];" - }, - "idestination.info": { - "password-rules": "maxlength: 15;" - }, - "impots.gouv.fr": { - "password-rules": "minlength: 12; maxlength: 128; required: lower; required: digit; allowed: [-!#$%&*+/=?^_'.{|}];" - }, - "indochino.com": { - "password-rules": "minlength: 6; maxlength: 15; required: upper; required: digit; allowed: lower, special;" - }, - "inntopia.travel": { - "password-rules": "minlength: 7; maxlength: 19; required: digit; allowed: upper,lower,[-];" - }, - "internationalsos.com": { - "password-rules": "required: lower; required: upper; required: digit; required: [@#$%^&+=_];" - }, - "irctc.co.in": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [!@#$%^&*()+];" - }, - "irs.gov": { - "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit; required: [!#$%&*@];" - }, - "jal.co.jp": { - "password-rules": "minlength: 8; maxlength: 16;" - }, - "japanpost.jp": { - "password-rules": "minlength: 8; maxlength: 16; required: digit; required: upper,lower;" - }, - "jordancu-onlinebanking.org": { - "password-rules": "minlength: 6; maxlength: 32; allowed: upper, lower, digit,[-!\"#$%&'()*+,.:;<=>?@[^_`{|}~]];" - }, - "keldoc.com": { - "password-rules": "minlength: 12; required: lower; required: upper; required: digit; required: [!@#$%^&*];" - }, - "key.harvard.edu": { - "password-rules": "minlength: 10; maxlength: 100; required: lower; required: upper; required: digit; allowed: [-@_#!&$`%*+()./,;~:{}|?>=<^[']];" - }, - "kfc.ca": { - "password-rules": "minlength: 6; maxlength: 15; required: lower; required: upper; required: digit; required: [!@#$%&?*];" - }, - "klm.com": { - "password-rules": "minlength: 8; maxlength: 12;" - }, - "kundenportal.edeka-smart.de": { - "password-rules": "minlength: 8; maxlength: 16; required: digit; required: upper, lower; required: [!\"§$%&#];" - }, - "la-z-boy.com": { - "password-rules": "minlength: 6; maxlength: 15; required: lower, upper; required: digit;" - }, - "labcorp.com": { - "password-rules": "minlength: 8; maxlength: 20; required: upper; required: lower; required: digit; required: [!@#$%^&*];" - }, - "ladwp.com": { - "password-rules": "minlength: 8; maxlength: 20; required: digit; allowed: lower, upper;" - }, - "launtel.net.au": { - "password-rules": "minlength: 8; required: digit; required: digit; allowed: lower, upper;" - }, - "leetchi.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&()*+,./:;<>?@\"_];" - }, - "lepida.it": { - "password-rules": "minlength: 8; maxlength: 16; max-consecutive: 2; required: lower; required: upper; required: digit; required: [-!\"#$%&'()*+,.:;<=>?@[^_`{|}~]];" - }, - "lg.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [-!#$%&'()*+,.:;=?@[^_{|}~]];" - }, - "linearity.io": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: special;" - }, - "live.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; allowed: [-@_#!&$`%*+()./,;~:{}|?>=<^'[]];" - }, - "lloydsbank.co.uk": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: digit; allowed: upper;" - }, - "lowes.com": { - "password-rules": "minlength: 8; maxlength: 128; max-consecutive: 3; required: lower, upper; required: digit;" - }, - "loyalty.accor.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&=@];" - }, - "lsacsso.b2clogin.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit, [-!#$%&*?@^_];" - }, - "lufthansa.com": { - "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit; required: [!#$%&()*+,./:;<>?@\"_];" - }, - "macys.com": { - "password-rules": "minlength: 7; maxlength: 16; allowed: lower, upper, digit, [~!@#$%^&*+`(){}[:;\"'<>?]];" - }, - "mailbox.org": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; allowed: [-!$\"%&/()=*+#.,;:@?{}[]];" - }, - "makemytrip.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [@$!%*#?&];" - }, - "marriott.com": { - "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; allowed: [$!#&@?%=];" - }, - "maybank2u.com.my": { - "password-rules": "minlength: 8; maxlength: 12; max-consecutive: 2; required: lower; required: upper; required: digit; required: [-~!@#$%^&*_+=`|(){}[:;\"'<>,.?];" - }, - "medicare.gov": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [@!$%^*()];" - }, - "member.everbridge.net": { - "password-rules": "minlength: 8; required: lower, upper; required: digit; allowed: [!@#$%^&*()];" - }, - "metlife.com": { - "password-rules": "minlength: 6; maxlength: 20;" - }, - "microsoft.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: special;" - }, - "milogin.michigan.gov": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [@#$!~&];" - }, - "mintmobile.com": { - "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; required: special; allowed: [!#$%&()*+:;=@[^_`{}~]];" - }, - "mlb.com": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;" - }, - "mpv.tickets.com": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;" - }, - "museumofflight.org": { - "password-rules": "minlength: 8; maxlength: 15;" - }, - "my.konami.net": { - "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit;" - }, - "myaccess.dmdc.osd.mil": { - "password-rules": "minlength: 9; maxlength: 20; required: lower; required: upper; required: digit; allowed: [-@_#!&$`%*+()./,;~:{}|?>=<^'[]];" - }, - "mygoodtogo.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower, upper, digit;" - }, - "myhealthrecord.com": { - "password-rules": "minlength: 8; maxlength: 20; allowed: lower, upper, digit, [_.!$*=];" - }, - "mysavings.breadfinancial.com": { - "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit; required: [+_%@!$*~];" - }, - "mysedgwick.com": { - "password-rules": "minlength: 8; maxlength: 16; allowed: lower; required: upper; required: digit; required: [@#%^&+=!]; allowed: [-~_$.,;]" - }, - "mysubaru.com": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; allowed: [!#$%()*+,./:;=?@\\^`~];" - }, - "naver.com": { - "password-rules": "minlength: 6; maxlength: 16;" - }, - "nelnet.net": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit, [!@#$&*];" - }, - "netflix.com": { - "password-rules": "minlength: 4; maxlength: 60; required: lower, upper, digit; allowed: special;" - }, - "netgear.com": { - "password-rules": "minlength: 6; maxlength: 128; allowed: lower, upper, digit, [!@#$%^&*()];" - }, - "nowinstock.net": { - "password-rules": "minlength: 6; maxlength: 20; allowed: lower, upper, digit;" - }, - "order.wendys.com": { - "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; allowed: [!#$%&()*+/=?^_{}];" - }, - "ototoy.jp": { - "password-rules": "minlength: 8; allowed: upper,lower,digit,[- .=_];" - }, - "packageconciergeadmin.com": { - "password-rules": "minlength: 4; maxlength: 4; allowed: digit;" - }, - "paypal.com": { - "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower, upper; required: digit, [!@#$%^&*()];" - }, - "payvgm.youraccountadvantage.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: special;" - }, - "pilotflyingj.com": { - "password-rules": "minlength: 7; required: digit; allowed: lower, upper;" - }, - "pixnet.cc": { - "password-rules": "minlength: 4; maxlength: 16; allowed: lower, upper;" - }, - "planetary.org": { - "password-rules": "minlength: 5; maxlength: 20; required: lower; required: upper; required: digit; allowed: ascii-printable;" - }, - "plazapremiumlounge.com": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; allowed: [!#$%&*,@^];" - }, - "portal.edd.ca.gov": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&()*@^];" - }, - "portals.emblemhealth.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&'()*+,./:;<>?@\\^_`{|}~[]];" - }, - "portlandgeneral.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [!#$%&*?@];" - }, - "poste.it": { - "password-rules": "minlength: 8; maxlength: 16; max-consecutive: 2; required: lower; required: upper; required: digit; required: special;" - }, - "posteo.de": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit, [-~!#$%&_+=|(){}[:;\"’<>,.? ]];" - }, - "powells.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [\"!@#$%^&*(){}[]];" - }, - "preferredhotels.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&()*+@^_];" - }, - "premier.ticketek.com.au": { - "password-rules": "minlength: 6; maxlength: 16;" - }, - "premierinn.com": { - "password-rules": "minlength: 8; required: upper; required: digit; allowed: lower;" - }, - "prepaid.bankofamerica.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [!@#$%^&*()+~{}'\";:<>?];" - }, - "prestocard.ca": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit,[!\"#$%&'()*+,<>?@];" - }, - "propelfuels.com": { - "password-rules": "minlength: 6; maxlength: 16;" - }, - "qdosstatusreview.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&@^];" - }, - "questdiagnostics.com": { - "password-rules": "minlength: 8; maxlength: 30; required: upper, lower; required: digit, [!#$%&()*+<>?@^_~];" - }, - "rejsekort.dk": { - "password-rules": "minlength: 7; maxlength: 15; required: lower; required: upper; required: digit;" - }, - "renaud-bray.com": { - "password-rules": "minlength: 8; maxlength: 38; allowed: upper,lower,digit;" - }, - "ring.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!@#$%^&*<>?];" - }, - "riteaid.com": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;" - }, - "robinhood.com": { - "password-rules": "minlength: 10;" - }, - "rogers.com": { - "password-rules": "minlength: 8; required: lower, upper; required: digit; required: [!@#$];" - }, - "ruc.dk": { - "password-rules": "minlength: 6; maxlength: 8; required: lower, upper; required: [-!#%&(){}*+;%/<=>?_];" - }, - "runescape.com": { - "password-rules": "minlength: 5; maxlength: 20; required: lower; required: upper; required: digit;" - }, - "ruten.com.tw": { - "password-rules": "minlength: 6; maxlength: 15; required: lower, upper;" - }, - "salslimo.com": { - "password-rules": "minlength: 8; maxlength: 50; required: upper; required: lower; required: digit; required: [!@#$&*];" - }, - "santahelenasaude.com.br": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [-!@#$%&*_+=<>];" - }, - "santander.de": { - "password-rules": "minlength: 8; maxlength: 12; required: lower, upper; required: digit; allowed: [-!#$%&'()*,.:;=?^{}];" - }, - "sbisec.co.jp": { - "password-rules": "minlength: 10; maxlength: 20; allowed: upper,lower,digit;" - }, - "secure-arborfcu.org": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [!#$%&'()+,.:?@[_`~]];" - }, - "secure.orclinic.com": { - "password-rules": "minlength: 6; maxlength: 15; required: lower; required: digit; allowed: ascii-printable;" - }, - "secure.snnow.ca": { - "password-rules": "minlength: 7; maxlength: 16; required: digit; allowed: lower, upper;" - }, - "sephora.com": { - "password-rules": "minlength: 6; maxlength: 12;" - }, - "serviziconsolari.esteri.it": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;" - }, - "servizioelettriconazionale.it": { - "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; required: [!#$%&*?@^_~];" - }, - "sfwater.org": { - "password-rules": "minlength: 10; maxlength: 30; required: digit; allowed: lower, upper, [!@#$%*()_+^}{:;?.];" - }, - "signin.ea.com": { - "password-rules": "minlength: 8; maxlength: 64; required: lower, upper; required: digit; allowed: [-!@#^&*=+;:];" - }, - "southwest.com": { - "password-rules": "minlength: 8; maxlength: 16; required: upper; required: digit; allowed: lower, [!@#$%^*(),.;:/\\];" - }, - "speedway.com": { - "password-rules": "minlength: 4; maxlength: 8; required: digit;" - }, - "spirit.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [!@#$%^&*()];" - }, - "splunk.com": { - "password-rules": "minlength: 8; maxlength: 64; required: lower; required: upper; required: digit; required: [-!@#$%&*_+=<>];" - }, - "ssa.gov": { - "password-rules": "required: lower; required: upper; required: digit; required: [~!@#$%^&*];" - }, - "store.nintendo.co.uk": { - "password-rules": "minlength: 8; maxlength: 20;" - }, - "store.nvidia.com": { - "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit; required: [-!@#$%^*~:;&><[{}|_+=?]];" - }, - "store.steampowered.com": { - "password-rules": "minlength: 6; required: lower; required: upper; required: digit; allowed: [~!@#$%^&*];" - }, - "successfactors.eu": { - "password-rules": "minlength: 8; maxlength: 18; required: lower; required: upper; required: digit,[-!\"#$%&'()*+,.:;<=>?@[^_`{|}~]];" - }, - "sulamericaseguros.com.br": { - "password-rules": "minlength: 6; maxlength: 6;" - }, - "sunlife.com": { - "password-rules": "minlength: 8; maxlength: 10; required: digit; required: lower, upper;" - }, - "t-mobile.net": { - "password-rules": "minlength: 8; maxlength: 16;" - }, - "target.com": { - "password-rules": "minlength: 8; maxlength: 20; required: lower, upper; required: digit, [-!\"#$%&'()*+,./:;=?@[\\^_`{|}~];" - }, - "tdscpc.gov.in": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [ &',;\"];" - }, - "telekom-dienste.de": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [#$%&()*+,./<=>?@_{|}~];" - }, - "thameswater.co.uk": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;" - }, - "tix.soundrink.com": { - "password-rules": "minlength: 6; maxlength: 16;" - }, - "training.confluent.io": { - "password-rules": "minlength: 6; maxlength: 16; required: lower; required: upper; required: digit; allowed: [!#$%*@^_~];" - }, - "treasurer.mo.gov": { - "password-rules": "minlength: 8; maxlength: 26; required: lower; required: upper; required: digit; required: [!#$&];" - }, - "twitch.tv": { - "password-rules": "minlength: 8; maxlength: 71;" - }, - "twitter.com": { - "password-rules": "minlength: 8;" - }, - "ubisoft.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [-]; required: [!@#$%^&*()+];" - }, - "udel.edu": { - "password-rules": "minlength: 12; maxlength: 30; required: lower; required: upper; required: digit; required: [!@#$%^&*()+];" - }, - "user.ornl.gov": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower, upper; required: digit; allowed: [!#$%./_];" - }, - "usps.com": { - "password-rules": "minlength: 8; maxlength: 50; max-consecutive: 2; required: lower; required: upper; required: digit; allowed: [-!\"#&'()+,./?@];" - }, - "vanguard.com": { - "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: digit;" - }, - "vanguardinvestor.co.uk": { - "password-rules": "minlength: 8; maxlength: 50; required: lower; required: upper; required: digit; required: digit;" - }, - "ventrachicago.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit, [!@#$%^];" - }, - "verizonwireless.com": { - "password-rules": "minlength: 8; maxlength: 20; required: lower, upper; required: digit; allowed: unicode;" - }, - "vetsfirstchoice.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; allowed: [?!@$%^+=&];" - }, - "vince.com": { - "password-rules": "minlength: 8; required: digit; required: lower; required: upper; required: [$%/(){}=?!.,_*|+~#[]];" - }, - "virginmobile.ca": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$@];" - }, - "visa.com": { - "password-rules": "minlength: 6; maxlength: 32;" - }, - "visabenefits-auth.axa-assistance.us": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!\"#$%&()*,.:<>?@^{|}];" - }, - "vivo.com.br": { - "password-rules": "maxlength: 6; max-consecutive: 3; allowed: digit;" - }, - "volaris.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;" - }, - "wa.aaa.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: ascii-printable;" - }, - "walkhighlands.co.uk": { - "password-rules": "minlength: 9; maxlength: 15; required: lower; required: upper; required: digit; allowed: special;" - }, - "walmart.com": { - "password-rules": "allowed: lower, upper, digit, [-(~!@#$%^&*_+=`|(){}[:;\"'<>,.?]];" - }, - "waze.com": { - "password-rules": "minlength: 8; maxlength: 64; required: lower, upper, digit;" - }, - "wccls.org": { - "password-rules": "minlength: 4; maxlength: 16; allowed: lower, upper, digit;" - }, - "web.de": { - "password-rules": "minlength: 8; maxlength: 40; allowed: lower, upper, digit, [-<=>~!|()@#{}$%,.?^'&*_+`:;\"[]];" - }, - "wegmans.com": { - "password-rules": "minlength: 8; required: digit; required: upper,lower; required: [!#$%&*+=?@^];" - }, - "weibo.com": { - "password-rules": "minlength: 6; maxlength: 16;" - }, - "wellsfargo.com": { - "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit;" - }, - "wmata.com": { - "password-rules": "minlength: 8; required: lower, upper; required: digit; required: digit; required: [-!@#$%^&*~/\"()_=+\\|,.?[]];" - }, - "worldstrides.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [-!#$%&*+=?@^_~];" - }, - "wsj.com": { - "password-rules": "minlength: 5; maxlength: 15; required: digit; allowed: lower, upper, [-~!@#$^*_=`|(){}[:;\"'<>,.?]];" - }, - "xfinity.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower, upper; required: digit;" - }, - "xvoucher.com": { - "password-rules": "minlength: 11; required: upper; required: digit; required: [!@#$%&_];" - }, - "yatra.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&'()+,.:?@[_`~]];" - }, - "zara.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" - }, - "zdf.de": { - "password-rules": "minlength: 8; required: upper; required: digit; allowed: lower, special;" - }, - "zoom.us": { - "password-rules": "minlength: 8; maxlength: 32; max-consecutive: 6; required: lower; required: upper; required: digit;" - } -} -},{}],12:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.createDevice = createDevice; -var _config = require("./config.js"); -var _AndroidInterface = require("./DeviceInterface/AndroidInterface.js"); -var _ExtensionInterface = require("./DeviceInterface/ExtensionInterface.js"); -var _AppleDeviceInterface = require("./DeviceInterface/AppleDeviceInterface.js"); -var _AppleOverlayDeviceInterface = require("./DeviceInterface/AppleOverlayDeviceInterface.js"); -var _transports = require("./deviceApiCalls/transports/transports.js"); -var _index = require("../packages/device-api/index.js"); -var _Settings = require("./Settings.js"); -var _WindowsInterface = require("./DeviceInterface/WindowsInterface.js"); -var _WindowsOverlayDeviceInterface = require("./DeviceInterface/WindowsOverlayDeviceInterface.js"); -function createDevice() { - const globalConfig = (0, _config.createGlobalConfig)(); - const transport = (0, _transports.createTransport)(globalConfig); - - /** - * A wrapper around transports to assist in debugging/integrations - * @type {import("../packages/device-api").DeviceApiTransport} - */ - const loggingTransport = { - async send(deviceApiCall) { - console.log('[->outgoing]', 'id:', deviceApiCall.method, deviceApiCall.params || null); - const result = await transport.send(deviceApiCall); - console.log('[<-incoming]', 'id:', deviceApiCall.method, result || null); - return result; + function _parsePasswordRule(input, position) { + console.assert(position >= 0); + console.assert(position < input.length); + console.assert(_isIdentifierCharacter(input[position])); + let length = input.length; + var mayBeIdentifierStartPosition = position; + var [identifier, position] = _parseIdentifier(input, position); + if (!Object.values(RuleName).includes(identifier)) { + return [null, mayBeIdentifierStartPosition, void 0]; } - }; - - // Create the DeviceAPI + Setting - let deviceApi = new _index.DeviceApi(globalConfig.isDDGTestMode ? loggingTransport : transport); - const settings = new _Settings.Settings(globalConfig, deviceApi); - if (globalConfig.isWindows) { - if (globalConfig.isTopFrame) { - return new _WindowsOverlayDeviceInterface.WindowsOverlayDeviceInterface(globalConfig, deviceApi, settings); - } - return new _WindowsInterface.WindowsInterface(globalConfig, deviceApi, settings); - } - if (globalConfig.isDDGApp) { - if (globalConfig.isAndroid) { - return new _AndroidInterface.AndroidInterface(globalConfig, deviceApi, settings); + if (position >= length) { + return [null, position, void 0]; + } + if (input[position] !== PROPERTY_VALUE_START_SENTINEL) { + return [null, position, void 0]; + } + let property = { name: identifier, value: null }; + position = _indexOfNonWhitespaceCharacter(input, position + 1); + if (position >= length || input[position] === PROPERTY_SEPARATOR) { + return [new Rule(property.name, property.value), position, void 0]; } - if (globalConfig.isTopFrame) { - return new _AppleOverlayDeviceInterface.AppleOverlayDeviceInterface(globalConfig, deviceApi, settings); + switch (identifier) { + case RuleName.ALLOWED: + case RuleName.REQUIRED: { + var [propertyValue, position] = _parsePasswordRequiredOrAllowedPropertyValue(input, position); + if (propertyValue) { + property.value = propertyValue; + } + return [new Rule(property.name, property.value), position, void 0]; + } + case RuleName.MAX_CONSECUTIVE: { + var [propertyValue, position] = _parseMaxConsecutivePropertyValue(input, position); + if (propertyValue) { + property.value = propertyValue; + } + return [new Rule(property.name, property.value), position, void 0]; + } + case RuleName.MIN_LENGTH: + case RuleName.MAX_LENGTH: { + var [propertyValue, position] = _parseMinLengthMaxLengthPropertyValue(input, position); + if (propertyValue) { + property.value = propertyValue; + } + return [new Rule(property.name, property.value), position, void 0]; + } } - return new _AppleDeviceInterface.AppleDeviceInterface(globalConfig, deviceApi, settings); + console.assert(false, SHOULD_NOT_BE_REACHED); + return [null, -1, void 0]; } - globalConfig.isExtension = true; - return new _ExtensionInterface.ExtensionInterface(globalConfig, deviceApi, settings); -} - -},{"../packages/device-api/index.js":2,"./DeviceInterface/AndroidInterface.js":13,"./DeviceInterface/AppleDeviceInterface.js":14,"./DeviceInterface/AppleOverlayDeviceInterface.js":15,"./DeviceInterface/ExtensionInterface.js":16,"./DeviceInterface/WindowsInterface.js":18,"./DeviceInterface/WindowsOverlayDeviceInterface.js":19,"./Settings.js":40,"./config.js":54,"./deviceApiCalls/transports/transports.js":62}],13:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.AndroidInterface = void 0; -var _InterfacePrototype = _interopRequireDefault(require("./InterfacePrototype.js")); -var _autofillUtils = require("../autofill-utils.js"); -var _NativeUIController = require("../UI/controllers/NativeUIController.js"); -var _InContextSignup = require("../InContextSignup.js"); -var _deviceApiCalls = require("../deviceApiCalls/__generated__/deviceApiCalls.js"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -class AndroidInterface extends _InterfacePrototype.default { - inContextSignup = new _InContextSignup.InContextSignup(this); - - /** - * @returns {Promise} - */ - async getAlias() { - const { - alias - } = await (0, _autofillUtils.sendAndWaitForAnswer)(async () => { - if (this.inContextSignup.isAvailable()) { - const { - isSignedIn - } = await this.deviceApi.request(new _deviceApiCalls.ShowInContextEmailProtectionSignupPromptCall(null)); - // On Android we can't get the input type data again without - // refreshing the page, so instead we can mutate it now that we - // know the user has Email Protection available. - if (this.globalConfig.availableInputTypes) { - this.globalConfig.availableInputTypes.email = isSignedIn; - } - this.updateForStateChange(); - this.onFinishedAutofill(); - } - return window.EmailInterface.showTooltip(); - }, 'getAliasResponse'); - return alias; + function _parseMinLengthMaxLengthPropertyValue(input, position) { + return _parseInteger(input, position); } - - /** - * @override - */ - createUIController() { - return new _NativeUIController.NativeUIController(); + function _parseMaxConsecutivePropertyValue(input, position) { + return _parseInteger(input, position); } - - /** - * @deprecated use `this.settings.availableInputTypes.email` in the future - * @returns {boolean} - */ - isDeviceSignedIn() { - // on DDG domains, always check via `window.EmailInterface.isSignedIn()` - if (this.globalConfig.isDDGDomain) { - return window.EmailInterface.isSignedIn() === 'true'; + function _parseInteger(input, position) { + console.assert(position >= 0); + console.assert(position < input.length); + if (!_isASCIIDigit(input[position])) { + return [null, position]; } - - // on non-DDG domains, where `availableInputTypes.email` is present, use it - if (typeof this.globalConfig.availableInputTypes?.email === 'boolean') { - return this.globalConfig.availableInputTypes.email; + let length = input.length; + let result = 0; + do { + result = 10 * result + parseInt(input[position], 10); + ++position; + } while (position < length && input[position] !== PROPERTY_SEPARATOR && _isASCIIDigit(input[position])); + if (position >= length || input[position] === PROPERTY_SEPARATOR) { + return [result, position]; } - - // ...on other domains we assume true because the script wouldn't exist otherwise - return true; - } - async setupAutofill() { - await this.inContextSignup.init(); + return [null, position]; } - - /** - * Used by the email web app - * Settings page displays data of the logged in user data - */ - getUserData() { - let userData = null; - try { - userData = JSON.parse(window.EmailInterface.getUserData()); - } catch (e) { - if (this.globalConfig.isDDGTestMode) { - console.error(e); + function _parsePasswordRulesInternal(input) { + let parsedProperties = []; + let length = input.length; + var position = _indexOfNonWhitespaceCharacter(input); + while (position < length) { + if (!_isIdentifierCharacter(input[position])) { + return [parsedProperties, void 0]; } - } - return Promise.resolve(userData); - } - - /** - * Used by the email web app - * Device capabilities determine which functionality is available to the user - */ - getEmailProtectionCapabilities() { - let deviceCapabilities = null; - try { - deviceCapabilities = JSON.parse(window.EmailInterface.getDeviceCapabilities()); - } catch (e) { - if (this.globalConfig.isDDGTestMode) { - console.error(e); + var [parsedProperty, position, message] = _parsePasswordRule(input, position); + if (parsedProperty && parsedProperty.value) { + parsedProperties.push(parsedProperty); + } + position = _indexOfNonWhitespaceCharacter(input, position); + if (position >= length) { + break; } + if (input[position] === PROPERTY_SEPARATOR) { + position = _indexOfNonWhitespaceCharacter(input, position + 1); + if (position >= length) { + return [parsedProperties, void 0]; + } + continue; + } + return [null, message || "Failed to find start of next property: " + input.substr(position)]; } - return Promise.resolve(deviceCapabilities); - } - storeUserData(_ref) { - let { - addUserData: { - token, - userName, - cohort - } - } = _ref; - return window.EmailInterface.storeCredentials(token, userName, cohort); + return [parsedProperties, void 0]; } - - /** - * Used by the email web app - * Provides functionality to log the user out - */ - removeUserData() { - try { - return window.EmailInterface.removeCredentials(); - } catch (e) { - if (this.globalConfig.isDDGTestMode) { - console.error(e); + function parsePasswordRules(input, formatRulesForMinifiedVersion) { + let [passwordRules, maybeMessage] = _parsePasswordRulesInternal(input); + if (!passwordRules) { + throw new ParserError(maybeMessage); + } + if (passwordRules.length === 0) { + throw new ParserError("No valid rules were provided"); + } + let suppressCopyingRequiredToAllowed = formatRulesForMinifiedVersion; + let requiredRules = []; + let newAllowedValues = []; + let minimumMaximumConsecutiveCharacters = null; + let maximumMinLength = 0; + let minimumMaxLength = null; + for (let rule of passwordRules) { + switch (rule.name) { + case RuleName.MAX_CONSECUTIVE: + minimumMaximumConsecutiveCharacters = minimumMaximumConsecutiveCharacters ? Math.min(rule.value, minimumMaximumConsecutiveCharacters) : rule.value; + break; + case RuleName.MIN_LENGTH: + maximumMinLength = Math.max(rule.value, maximumMinLength); + break; + case RuleName.MAX_LENGTH: + minimumMaxLength = minimumMaxLength ? Math.min(rule.value, minimumMaxLength) : rule.value; + break; + case RuleName.REQUIRED: + rule.value = _canonicalizedPropertyValues(rule.value, formatRulesForMinifiedVersion); + requiredRules.push(rule); + if (!suppressCopyingRequiredToAllowed) { + newAllowedValues = newAllowedValues.concat(rule.value); + } + break; + case RuleName.ALLOWED: + newAllowedValues = newAllowedValues.concat(rule.value); + break; } } - } - - /** - * Used by the email web app - * Provides functionality to close the window after in-context sign-up or sign-in - */ - closeEmailProtection() { - this.deviceApi.request(new _deviceApiCalls.CloseEmailProtectionTabCall(null)); - } - addLogoutListener(handler) { - // Only deal with logging out if we're in the email web app - if (!this.globalConfig.isDDGDomain) return; - window.addEventListener('message', e => { - if (this.globalConfig.isDDGDomain && e.data.emailProtectionSignedOut) { - handler(); + let newPasswordRules = []; + if (maximumMinLength > 0) { + newPasswordRules.push(new Rule(RuleName.MIN_LENGTH, maximumMinLength)); + } + if (minimumMaxLength !== null) { + newPasswordRules.push(new Rule(RuleName.MAX_LENGTH, minimumMaxLength)); + } + if (minimumMaximumConsecutiveCharacters !== null) { + newPasswordRules.push(new Rule(RuleName.MAX_CONSECUTIVE, minimumMaximumConsecutiveCharacters)); + } + let sortedRequiredRules = requiredRules.sort(function(a, b) { + const namedCharacterClassOrder = [Identifier.LOWER, Identifier.UPPER, Identifier.DIGIT, Identifier.SPECIAL, Identifier.ASCII_PRINTABLE, Identifier.UNICODE]; + let aIsJustOneNamedCharacterClass = a.value.length === 1 && a.value[0] instanceof NamedCharacterClass; + let bIsJustOneNamedCharacterClass = b.value.length === 1 && b.value[0] instanceof NamedCharacterClass; + if (aIsJustOneNamedCharacterClass && !bIsJustOneNamedCharacterClass) { + return -1; } + if (!aIsJustOneNamedCharacterClass && bIsJustOneNamedCharacterClass) { + return 1; + } + if (aIsJustOneNamedCharacterClass && bIsJustOneNamedCharacterClass) { + let aIndex = namedCharacterClassOrder.indexOf(a.value[0].name); + let bIndex = namedCharacterClassOrder.indexOf(b.value[0].name); + return aIndex - bIndex; + } + return 0; }); - } - - /** Noop */ - firePixel(_pixelParam) {} -} -exports.AndroidInterface = AndroidInterface; - -},{"../InContextSignup.js":34,"../UI/controllers/NativeUIController.js":47,"../autofill-utils.js":52,"../deviceApiCalls/__generated__/deviceApiCalls.js":56,"./InterfacePrototype.js":17}],14:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.AppleDeviceInterface = void 0; -var _InterfacePrototype = _interopRequireDefault(require("./InterfacePrototype.js")); -var _autofillUtils = require("../autofill-utils.js"); -var _HTMLTooltip = require("../UI/HTMLTooltip.js"); -var _HTMLTooltipUIController = require("../UI/controllers/HTMLTooltipUIController.js"); -var _OverlayUIController = require("../UI/controllers/OverlayUIController.js"); -var _index = require("../../packages/device-api/index.js"); -var _additionalDeviceApiCalls = require("../deviceApiCalls/additionalDeviceApiCalls.js"); -var _NativeUIController = require("../UI/controllers/NativeUIController.js"); -var _deviceApiCalls = require("../deviceApiCalls/__generated__/deviceApiCalls.js"); -var _matching = require("../Form/matching.js"); -var _InContextSignup = require("../InContextSignup.js"); -var _ThirdPartyProvider = require("../ThirdPartyProvider.js"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -/** - * @typedef {import('../deviceApiCalls/__generated__/validators-ts').GetAutofillDataRequest} GetAutofillDataRequest - */ - -class AppleDeviceInterface extends _InterfacePrototype.default { - inContextSignup = new _InContextSignup.InContextSignup(this); - - /** @override */ - initialSetupDelayMs = 300; - thirdPartyProvider = new _ThirdPartyProvider.ThirdPartyProvider(this); + newPasswordRules = newPasswordRules.concat(sortedRequiredRules); + newAllowedValues = _canonicalizedPropertyValues(newAllowedValues, suppressCopyingRequiredToAllowed); + if (!suppressCopyingRequiredToAllowed && !newAllowedValues.length) { + newAllowedValues = [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)]; + } + if (newAllowedValues.length) { + newPasswordRules.push(new Rule(RuleName.ALLOWED, newAllowedValues)); + } + return newPasswordRules; + } + + // packages/password/lib/constants.js + var DEFAULT_MIN_LENGTH = 20; + var DEFAULT_MAX_LENGTH = 30; + var DEFAULT_REQUIRED_CHARS = "-!?$&#%"; + var DEFAULT_UNAMBIGUOUS_CHARS = "abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ0123456789"; + var DEFAULT_PASSWORD_RULES = [ + `minlength: ${DEFAULT_MIN_LENGTH}`, + `maxlength: ${DEFAULT_MAX_LENGTH}`, + `required: [${DEFAULT_REQUIRED_CHARS}]`, + `allowed: [${DEFAULT_UNAMBIGUOUS_CHARS}]` + ].join("; "); + var constants2 = { + DEFAULT_MIN_LENGTH, + DEFAULT_MAX_LENGTH, + DEFAULT_PASSWORD_RULES, + DEFAULT_REQUIRED_CHARS, + DEFAULT_UNAMBIGUOUS_CHARS + }; - /** - * The default functionality of this class is to operate as an 'overlay controller' - - * which means it's purpose is to message the native layer about when to open/close the overlay. - * - * There is an additional use-case though, when running on older macOS versions, we just display the - * HTMLTooltip in-page (like the extension does). This is why the `!this.globalConfig.supportsTopFrame` - * check exists below - if we know we don't support the overlay, we fall back to in-page. - * - * @override - * @returns {import("../UI/controllers/UIController.js").UIController} - */ - createUIController() { - if (this.globalConfig.userPreferences?.platform?.name === 'ios') { - return new _NativeUIController.NativeUIController(); + // packages/password/lib/apple.password.js + var defaults = Object.freeze({ + SCAN_SET_ORDER: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-~!@#$%^&*_+=`|(){}[:;\\\"'<>,.?/ ]", + defaultUnambiguousCharacters: "abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ0123456789", + defaultPasswordLength: constants2.DEFAULT_MIN_LENGTH, + defaultPasswordRules: constants2.DEFAULT_PASSWORD_RULES, + defaultRequiredCharacterSets: ["abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "0123456789"], + /** + * @type {typeof window.crypto.getRandomValues | null} + */ + getRandomValues: null + }); + var safeGlobals = {}; + if (typeof window !== "undefined") { + safeGlobals.getRandomValues = window.crypto.getRandomValues.bind(window.crypto); + } + var Password = class { + /** + * @param {Partial} [options] + */ + constructor(options = {}) { + this.options = { + ...defaults, + ...options + }; + return this; } - if (!this.globalConfig.supportsTopFrame) { - const options = { - ..._HTMLTooltip.defaultOptions, - testMode: this.isTestMode() + static get defaults() { + return defaults; + } + /** + * Generates a password from the given input. + * + * Note: This method will throw an error if parsing fails - use with caution + * + * @example + * + * ```javascript + * const password = Password.generateOrThrow("minlength: 20") + * ``` + * @public + * @param {string} inputString + * @param {Partial} [options] + * @throws {ParserError|Error} + * @returns {string} + */ + static generateOrThrow(inputString, options = {}) { + return new Password(options).parse(inputString).generate(); + } + /** + * Generates a password using the default ruleset. + * + * @example + * + * ```javascript + * const password = Password.generateDefault() + * ``` + * + * @public + * @param {Partial} [options] + * @returns {string} + */ + static generateDefault(options = {}) { + return new Password(options).parse(Password.defaults.defaultPasswordRules).generate(); + } + /** + * Convert a ruleset into it's internally-used component pieces. + * + * @param {string} inputString + * @throws {parser.ParserError|Error} + * @returns {{ + * requirements: Requirements; + * parameters: PasswordParameters; + * rules: parser.Rule[], + * get entropy(): number; + * generate: () => string; + * }} + */ + parse(inputString) { + const rules = parsePasswordRules(inputString); + const requirements = this._requirementsFromRules(rules); + if (!requirements) + throw new Error("could not generate requirements for " + JSON.stringify(inputString)); + const parameters = this._passwordGenerationParametersDictionary(requirements); + return { + requirements, + parameters, + rules, + get entropy() { + return Math.log2(parameters.PasswordAllowedCharacters.length ** parameters.NumberOfRequiredRandomCharacters); + }, + generate: () => { + const password = this._generatedPasswordMatchingRequirements(requirements, parameters); + if (password === "") + throw new Error("unreachable"); + return password; + } }; - return new _HTMLTooltipUIController.HTMLTooltipUIController({ - device: this, - tooltipKind: 'modern' - }, options); } - /** - * If we get here, we're just a controller for an overlay + * Given an array of `Rule's`, convert into `Requirements` + * + * @param {parser.Rule[]} passwordRules + * @returns {Requirements | null} */ - return new _OverlayUIController.OverlayUIController({ - remove: async () => this._closeAutofillParent(), - show: async details => this._show(details) - }); - } - - /** - * For now, this could be running - * 1) on iOS - * 2) on macOS + Overlay - * 3) on macOS + in-page HTMLTooltip - * - * @override - * @returns {Promise} - */ - async setupAutofill() { - if (!this.globalConfig.supportsTopFrame) { - await this._getAutofillInitData(); + _requirementsFromRules(passwordRules) { + const requirements = {}; + for (let rule of passwordRules) { + if (rule.name === RuleName.ALLOWED) { + console.assert(!("PasswordAllowedCharacters" in requirements)); + const chars = this._charactersFromCharactersClasses(rule.value); + const scanSet = this._canonicalizedScanSetFromCharacters(chars); + if (scanSet) { + requirements.PasswordAllowedCharacters = scanSet; + } + } else if (rule.name === RuleName.MAX_CONSECUTIVE) { + console.assert(!("PasswordRepeatedCharacterLimit" in requirements)); + requirements.PasswordRepeatedCharacterLimit = rule.value; + } else if (rule.name === RuleName.REQUIRED) { + let requiredCharacters = requirements.PasswordRequiredCharacters; + if (!requiredCharacters) { + requiredCharacters = requirements.PasswordRequiredCharacters = []; + } + requiredCharacters.push(this._canonicalizedScanSetFromCharacters(this._charactersFromCharactersClasses(rule.value))); + } else if (rule.name === RuleName.MIN_LENGTH) { + requirements.PasswordMinLength = rule.value; + } else if (rule.name === RuleName.MAX_LENGTH) { + requirements.PasswordMaxLength = rule.value; + } + } + if (requirements.PasswordAllowedCharacters === this.options.SCAN_SET_ORDER && !requirements.PasswordRequiredCharacters) { + delete requirements.PasswordAllowedCharacters; + } + if (requirements.PasswordRequiredCharacters && requirements.PasswordRequiredCharacters.length === 1 && requirements.PasswordRequiredCharacters[0] === this.options.SCAN_SET_ORDER) { + delete requirements.PasswordRequiredCharacters; + } + return Object.keys(requirements).length ? requirements : null; } - await this.inContextSignup.init(); - const signedIn = await this._checkDeviceSignedIn(); - if (signedIn) { - if (this.globalConfig.isApp) { - await this.getAddresses(); + /** + * @param {number} range + * @returns {number} + */ + _randomNumberWithUniformDistribution(range) { + const getRandomValues = this.options.getRandomValues || safeGlobals.getRandomValues; + const max = Math.floor(2 ** 32 / range) * range; + let x; + do { + x = getRandomValues(new Uint32Array(1))[0]; + } while (x >= max); + return x % range; + } + /** + * @param {number} numberOfRequiredRandomCharacters + * @param {string} allowedCharacters + */ + _classicPassword(numberOfRequiredRandomCharacters, allowedCharacters) { + const length = allowedCharacters.length; + const randomCharArray = Array(numberOfRequiredRandomCharacters); + for (let i = 0; i < numberOfRequiredRandomCharacters; i++) { + const index = this._randomNumberWithUniformDistribution(length); + randomCharArray[i] = allowedCharacters[index]; } + return randomCharArray.join(""); } - } - - /** - * Used by the email web app - * Settings page displays data of the logged in user data - */ - getUserData() { - return this.deviceApi.request((0, _index.createRequest)('emailHandlerGetUserData')); - } - - /** - * Used by the email web app - * Device capabilities determine which functionality is available to the user - */ - getEmailProtectionCapabilities() { - return this.deviceApi.request((0, _index.createRequest)('emailHandlerGetCapabilities')); - } - - /** - */ - async getSelectedCredentials() { - return this.deviceApi.request((0, _index.createRequest)('getSelectedCredentials')); - } - - /** - * The data format provided here for `parentArgs` matches Window now. - * @param {GetAutofillDataRequest} parentArgs - */ - async _showAutofillParent(parentArgs) { - const applePayload = { - ...parentArgs.triggerContext, - serializedInputContext: parentArgs.serializedInputContext - }; - return this.deviceApi.notify((0, _index.createNotification)('showAutofillParent', applePayload)); - } - - /** - * @returns {Promise} - */ - async _closeAutofillParent() { - return this.deviceApi.notify((0, _index.createNotification)('closeAutofillParent', {})); - } - - /** - * @param {GetAutofillDataRequest} details - */ - async _show(details) { - await this._showAutofillParent(details); - this._listenForSelectedCredential(async response => { - if (!response) return; - if ('configType' in response) { - this.selectedDetail(response.data, response.configType); - } else if ('stop' in response) { - await this.onFinishedAutofill(); - } else if ('stateChange' in response) { - await this.updateForStateChange(); - } - }); - } - async refreshData() { - await super.refreshData(); - await this._checkDeviceSignedIn(); - } - async getAddresses() { - if (!this.globalConfig.isApp) return this.getAlias(); - const { - addresses - } = await this.deviceApi.request((0, _index.createRequest)('emailHandlerGetAddresses')); - this.storeLocalAddresses(addresses); - return addresses; - } - async refreshAlias() { - await this.deviceApi.notify((0, _index.createNotification)('emailHandlerRefreshAlias')); - // On macOS we also update the addresses stored locally - if (this.globalConfig.isApp) this.getAddresses(); - } - async _checkDeviceSignedIn() { - const { - isAppSignedIn - } = await this.deviceApi.request((0, _index.createRequest)('emailHandlerCheckAppSignedInStatus')); - this.isDeviceSignedIn = () => !!isAppSignedIn; - return !!isAppSignedIn; - } - storeUserData(_ref) { - let { - addUserData: { - token, - userName, - cohort - } - } = _ref; - return this.deviceApi.notify((0, _index.createNotification)('emailHandlerStoreToken', { - token, - username: userName, - cohort - })); - } - - /** - * Used by the email web app - * Provides functionality to log the user out - */ - removeUserData() { - this.deviceApi.notify((0, _index.createNotification)('emailHandlerRemoveToken')); - } - - /** - * Used by the email web app - * Provides functionality to close the window after in-context sign-up or sign-in - */ - closeEmailProtection() { - this.deviceApi.request(new _deviceApiCalls.CloseEmailProtectionTabCall(null)); - } - - /** - * PM endpoints - */ - - /** - * Gets the init data from the device - * @returns {APIResponse} - */ - async _getAutofillInitData() { - const response = await this.deviceApi.request((0, _index.createRequest)('pmHandlerGetAutofillInitData')); - this.storeLocalData(response.success); - return response; - } - - /** - * Gets credentials ready for autofill - * @param {CredentialsObject['id']} id - the credential id - * @returns {APIResponseSingle} - */ - getAutofillCredentials(id) { - return this.deviceApi.request((0, _index.createRequest)('pmHandlerGetAutofillCredentials', { - id - })); - } - - /** - * Opens the native UI for managing passwords - */ - openManagePasswords() { - return this.deviceApi.notify((0, _index.createNotification)('pmHandlerOpenManagePasswords')); - } - - /** - * Gets a single identity obj once the user requests it - * @param {IdentityObject['id']} id - * @returns {Promise<{success: IdentityObject|undefined}>} - */ - getAutofillIdentity(id) { - const identity = this.getLocalIdentities().find(_ref2 => { - let { - id: identityId - } = _ref2; - return `${identityId}` === `${id}`; - }); - return Promise.resolve({ - success: identity - }); - } - - /** - * Gets a single complete credit card obj once the user requests it - * @param {CreditCardObject['id']} id - * @returns {APIResponse} - */ - getAutofillCreditCard(id) { - return this.deviceApi.request((0, _index.createRequest)('pmHandlerGetCreditCard', { - id - })); - } - getCurrentInputType() { - const topContextData = this.getTopContextData(); - return topContextData?.inputType ? topContextData.inputType : (0, _matching.getInputType)(this.activeForm?.activeInput); - } - - /** - * @returns {Promise} - */ - async getAlias() { - const { - alias - } = await this.deviceApi.request(new _additionalDeviceApiCalls.GetAlias({ - requiresUserPermission: !this.globalConfig.isApp, - shouldConsumeAliasIfProvided: !this.globalConfig.isApp, - isIncontextSignupAvailable: this.inContextSignup.isAvailable() - })); - return alias ? (0, _autofillUtils.formatDuckAddress)(alias) : alias; - } - addLogoutListener(handler) { - // Only deal with logging out if we're in the email web app - if (!this.globalConfig.isDDGDomain) return; - window.addEventListener('message', e => { - if (this.globalConfig.isDDGDomain && e.data.emailProtectionSignedOut) { - handler(); - } - }); - } - async addDeviceListeners() { - this.thirdPartyProvider.init(); - } - - /** @type {any} */ - pollingTimeout = null; - /** - * Poll the native listener until the user has selected a credential. - * Message return types are: - * - 'stop' is returned whenever the message sent doesn't match the native last opened tooltip. - * - This also is triggered when the close event is called and prevents any edge case continued polling. - * - 'ok' is when the user has selected a credential and the value can be injected into the page. - * - 'none' is when the tooltip is open in the native window however hasn't been entered. - * @param {(response: {data:IdentityObject|CreditCardObject|CredentialsObject, configType: string} | {stateChange: boolean} | {stop: boolean} | null) => void} callback - */ - async _listenForSelectedCredential(callback) { - // Prevent two timeouts from happening - const poll = async () => { - clearTimeout(this.pollingTimeout); - const response = await this.getSelectedCredentials(); - switch (response.type) { - case 'none': - // Parent hasn't got a selected credential yet - this.pollingTimeout = setTimeout(() => poll(), 100); - return; - case 'ok': - { - await callback({ - data: response.data, - configType: response.configType - }); - return; + /** + * @param {string} password + * @param {number} consecutiveCharLimit + * @returns {boolean} + */ + _passwordHasNotExceededConsecutiveCharLimit(password, consecutiveCharLimit) { + let longestConsecutiveCharLength = 1; + let firstConsecutiveCharIndex = 0; + let isSequenceAscending; + for (let i = 1; i < password.length; i++) { + const currCharCode = password.charCodeAt(i); + const prevCharCode = password.charCodeAt(i - 1); + if (isSequenceAscending) { + if (isSequenceAscending.valueOf() && currCharCode === prevCharCode + 1 || !isSequenceAscending.valueOf() && currCharCode === prevCharCode - 1) { + continue; } - case 'state': - { - // Inform that state has changed, but continue polling - // e.g. in-context signup has been dismissed - await callback({ - stateChange: true - }); - this.pollingTimeout = setTimeout(() => poll(), 100); - return; + if (currCharCode === prevCharCode + 1) { + firstConsecutiveCharIndex = i - 1; + isSequenceAscending = Boolean(true); + continue; } - case 'stop': - // Parent wants us to stop polling - await callback({ - stop: true - }); + if (currCharCode === prevCharCode - 1) { + firstConsecutiveCharIndex = i - 1; + isSequenceAscending = Boolean(false); + continue; + } + isSequenceAscending = null; + } else if (currCharCode === prevCharCode + 1) { + isSequenceAscending = Boolean(true); + continue; + } else if (currCharCode === prevCharCode - 1) { + isSequenceAscending = Boolean(false); + continue; + } + const currConsecutiveCharLength = i - firstConsecutiveCharIndex; + if (currConsecutiveCharLength > longestConsecutiveCharLength) { + longestConsecutiveCharLength = currConsecutiveCharLength; + } + firstConsecutiveCharIndex = i; } - }; - poll(); - } -} -exports.AppleDeviceInterface = AppleDeviceInterface; - -},{"../../packages/device-api/index.js":2,"../Form/matching.js":33,"../InContextSignup.js":34,"../ThirdPartyProvider.js":41,"../UI/HTMLTooltip.js":45,"../UI/controllers/HTMLTooltipUIController.js":46,"../UI/controllers/NativeUIController.js":47,"../UI/controllers/OverlayUIController.js":48,"../autofill-utils.js":52,"../deviceApiCalls/__generated__/deviceApiCalls.js":56,"../deviceApiCalls/additionalDeviceApiCalls.js":58,"./InterfacePrototype.js":17}],15:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.AppleOverlayDeviceInterface = void 0; -var _AppleDeviceInterface = require("./AppleDeviceInterface.js"); -var _HTMLTooltipUIController = require("../UI/controllers/HTMLTooltipUIController.js"); -var _overlayApi = require("./overlayApi.js"); -var _index = require("../../packages/device-api/index.js"); -/** - * This subclass is designed to separate code that *only* runs inside the - * Overlay into a single place. - * - * It will only run inside the macOS overlay, therefor all code here - * can be viewed as *not* executing within a regular page context. - */ -class AppleOverlayDeviceInterface extends _AppleDeviceInterface.AppleDeviceInterface { - /** - * Mark top frame as not stripping credential data - * @type {boolean} - */ - stripCredentials = false; - - /** - * overlay API helpers - */ - overlay = (0, _overlayApi.overlayApi)(this); - previousX = 0; - previousY = 0; - - /** - * Because we're running inside the Overlay, we always create the HTML - * Tooltip controller. - * - * @override - * @returns {import("../UI/controllers/UIController.js").UIController} - */ - createUIController() { - return new _HTMLTooltipUIController.HTMLTooltipUIController({ - tooltipKind: /** @type {const} */'modern', - device: this - }, { - wrapperClass: 'top-autofill', - tooltipPositionClass: () => '.wrapper { transform: none; }', - setSize: details => this.deviceApi.notify((0, _index.createNotification)('setSize', details)), - remove: async () => this._closeAutofillParent(), - testMode: this.isTestMode() - }); - } - addDeviceListeners() { + if (isSequenceAscending) { + const currConsecutiveCharLength = password.length - firstConsecutiveCharIndex; + if (currConsecutiveCharLength > longestConsecutiveCharLength) { + longestConsecutiveCharLength = currConsecutiveCharLength; + } + } + return longestConsecutiveCharLength <= consecutiveCharLimit; + } /** - * The native side will send a custom event 'mouseMove' to indicate - * that the HTMLTooltip should fake an element being focused. - * - * Note: There's no cleanup required here since the Overlay has a fresh - * page load every time it's opened. - */ - window.addEventListener('mouseMove', event => { - // Don't set focus if the mouse hasn't moved ever - // This is to avoid clickjacking where an attacker puts the pulldown under the cursor - // and tricks the user into clicking - if (!this.previousX && !this.previousY || - // if no previous coords - this.previousX === event.detail.x && this.previousY === event.detail.y // or the mouse hasn't moved - ) { - this.previousX = event.detail.x; - this.previousY = event.detail.y; - return; + * @param {string} password + * @param {number} repeatedCharLimit + * @returns {boolean} + */ + _passwordHasNotExceededRepeatedCharLimit(password, repeatedCharLimit) { + let longestRepeatedCharLength = 1; + let lastRepeatedChar = password.charAt(0); + let lastRepeatedCharIndex = 0; + for (let i = 1; i < password.length; i++) { + const currChar = password.charAt(i); + if (currChar === lastRepeatedChar) { + continue; + } + const currRepeatedCharLength = i - lastRepeatedCharIndex; + if (currRepeatedCharLength > longestRepeatedCharLength) { + longestRepeatedCharLength = currRepeatedCharLength; + } + lastRepeatedChar = currChar; + lastRepeatedCharIndex = i; } - const activeTooltip = this.uiController?.getActiveTooltip?.(); - activeTooltip?.focus(event.detail.x, event.detail.y); - this.previousX = event.detail.x; - this.previousY = event.detail.y; - }); - return super.addDeviceListeners(); - } - - /** - * Since we're running inside the Overlay we can limit what happens here to - * be only things that are needed to power the HTML Tooltip - * - * @override - * @returns {Promise} - */ - async setupAutofill() { - await this._getAutofillInitData(); - await this.inContextSignup.init(); - const signedIn = await this._checkDeviceSignedIn(); - if (signedIn) { - await this.getAddresses(); + return longestRepeatedCharLength <= repeatedCharLimit; } - } - async postInit() { - // setup overlay API pieces - this.overlay.showImmediately(); - super.postInit(); - } - - /** - * In the top-frame scenario we override the base 'selectedDetail'. - * - * This - * - * @override - * @param {IdentityObject|CreditCardObject|CredentialsObject|{email:string, id: string}} data - * @param {string} type - */ - async selectedDetail(data, type) { - return this.overlay.selectedDetail(data, type); - } -} -exports.AppleOverlayDeviceInterface = AppleOverlayDeviceInterface; - -},{"../../packages/device-api/index.js":2,"../UI/controllers/HTMLTooltipUIController.js":46,"./AppleDeviceInterface.js":14,"./overlayApi.js":21}],16:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.ExtensionInterface = void 0; -var _InterfacePrototype = _interopRequireDefault(require("./InterfacePrototype.js")); -var _autofillUtils = require("../autofill-utils.js"); -var _HTMLTooltipUIController = require("../UI/controllers/HTMLTooltipUIController.js"); -var _HTMLTooltip = require("../UI/HTMLTooltip.js"); -var _InContextSignup = require("../InContextSignup.js"); -var _matching = require("../Form/matching.js"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const TOOLTIP_TYPES = { - EmailProtection: 'EmailProtection', - EmailSignup: 'EmailSignup' -}; -class ExtensionInterface extends _InterfacePrototype.default { - /** - * Adding this here since only the extension currently supports this - */ - inContextSignup = new _InContextSignup.InContextSignup(this); - - /** - * @override - */ - createUIController() { - /** @type {import('../UI/HTMLTooltip.js').HTMLTooltipOptions} */ - const htmlTooltipOptions = { - ..._HTMLTooltip.defaultOptions, - css: ``, - testMode: this.isTestMode(), - hasCaret: true - }; - const tooltipKinds = { - [TOOLTIP_TYPES.EmailProtection]: 'legacy', - [TOOLTIP_TYPES.EmailSignup]: 'emailsignup' - }; - const tooltipKind = tooltipKinds[this.getActiveTooltipType()] || tooltipKinds[TOOLTIP_TYPES.EmailProtection]; - return new _HTMLTooltipUIController.HTMLTooltipUIController({ - tooltipKind, - device: this - }, htmlTooltipOptions); - } - getActiveTooltipType() { - if (this.hasLocalAddresses) { - return TOOLTIP_TYPES.EmailProtection; + /** + * @param {string} password + * @param {string[]} requiredCharacterSets + * @returns {boolean} + */ + _passwordContainsRequiredCharacters(password, requiredCharacterSets) { + const requiredCharacterSetsLength = requiredCharacterSets.length; + const passwordLength = password.length; + for (let i = 0; i < requiredCharacterSetsLength; i++) { + const requiredCharacterSet = requiredCharacterSets[i]; + let hasRequiredChar = false; + for (let j = 0; j < passwordLength; j++) { + const char = password.charAt(j); + if (requiredCharacterSet.indexOf(char) !== -1) { + hasRequiredChar = true; + break; + } + } + if (!hasRequiredChar) { + return false; + } + } + return true; } - const inputType = this.activeForm?.activeInput ? (0, _matching.getInputSubtype)(this.activeForm.activeInput) : undefined; - if (this.inContextSignup?.isAvailable(inputType)) { - return TOOLTIP_TYPES.EmailSignup; + /** + * @param {string} string1 + * @param {string} string2 + * @returns {boolean} + */ + _stringsHaveAtLeastOneCommonCharacter(string1, string2) { + const string2Length = string2.length; + for (let i = 0; i < string2Length; i++) { + const char = string2.charAt(i); + if (string1.indexOf(char) !== -1) { + return true; + } + } + return false; } - return null; - } - async resetAutofillUI(callback) { - this.removeAutofillUIFromPage('Resetting autofill.'); - await this.setupAutofill(); - if (callback) await callback(); - this.uiController = this.createUIController(); - await this.postInit(); - } - isDeviceSignedIn() { - return this.hasLocalAddresses; - } - async setupAutofill() { /** - * In the extension, we must resolve `inContextSignup` data as part of setup + * @param {Requirements} requirements + * @returns {PasswordParameters} */ - await this.inContextSignup.init(); - return this.getAddresses(); - } - postInit() { - switch (this.getActiveTooltipType()) { - case TOOLTIP_TYPES.EmailProtection: - { - this._scannerCleanup = this.scanner.init(); - this.addLogoutListener(() => { - this.resetAutofillUI(); - if (this.globalConfig.isDDGDomain) { - (0, _autofillUtils.notifyWebApp)({ - deviceSignedIn: { - value: false - } - }); - } - }); - if (this.activeForm?.activeInput) { - this.attachTooltip({ - form: this.activeForm, - input: this.activeForm?.activeInput, - click: null, - trigger: 'postSignup', - triggerMetaData: { - type: 'transactional' - } - }); + _passwordGenerationParametersDictionary(requirements) { + let minPasswordLength = requirements.PasswordMinLength; + const maxPasswordLength = requirements.PasswordMaxLength; + if (minPasswordLength > maxPasswordLength) { + minPasswordLength = 0; + } + const requiredCharacterArray = requirements.PasswordRequiredCharacters; + let allowedCharacters = requirements.PasswordAllowedCharacters; + let requiredCharacterSets = this.options.defaultRequiredCharacterSets; + if (requiredCharacterArray) { + const mutatedRequiredCharacterSets2 = []; + const requiredCharacterArrayLength = requiredCharacterArray.length; + for (let i = 0; i < requiredCharacterArrayLength; i++) { + const requiredCharacters = requiredCharacterArray[i]; + if (allowedCharacters && this._stringsHaveAtLeastOneCommonCharacter(requiredCharacters, allowedCharacters)) { + mutatedRequiredCharacterSets2.push(requiredCharacters); } - break; } - case TOOLTIP_TYPES.EmailSignup: - { - this._scannerCleanup = this.scanner.init(); - break; + requiredCharacterSets = mutatedRequiredCharacterSets2; + } + let numberOfRequiredRandomCharacters = this.options.defaultPasswordLength; + if (minPasswordLength && minPasswordLength > numberOfRequiredRandomCharacters) { + numberOfRequiredRandomCharacters = minPasswordLength; + } + if (maxPasswordLength && maxPasswordLength < numberOfRequiredRandomCharacters) { + numberOfRequiredRandomCharacters = maxPasswordLength; + } + if (!allowedCharacters) { + allowedCharacters = this.options.defaultUnambiguousCharacters; + } + if (!requiredCharacterSets) { + requiredCharacterSets = this.options.defaultRequiredCharacterSets; + } + if (requiredCharacterSets.length > numberOfRequiredRandomCharacters) { + requiredCharacterSets = []; + } + const requiredCharacterSetsLength = requiredCharacterSets.length; + const mutatedRequiredCharacterSets = []; + const allowedCharactersLength = allowedCharacters.length; + for (let i = 0; i < requiredCharacterSetsLength; i++) { + const requiredCharacterSet = requiredCharacterSets[i]; + let requiredCharacterSetContainsAllowedCharacters = false; + for (let j = 0; j < allowedCharactersLength; j++) { + const character = allowedCharacters.charAt(j); + if (requiredCharacterSet.indexOf(character) !== -1) { + requiredCharacterSetContainsAllowedCharacters = true; + break; + } } - default: - { - // Don't do anyhing if we don't have a tooltip to show - break; + if (requiredCharacterSetContainsAllowedCharacters) { + mutatedRequiredCharacterSets.push(requiredCharacterSet); } + } + requiredCharacterSets = mutatedRequiredCharacterSets; + return { + NumberOfRequiredRandomCharacters: numberOfRequiredRandomCharacters, + PasswordAllowedCharacters: allowedCharacters, + RequiredCharacterSets: requiredCharacterSets + }; } - } - getAddresses() { - return new Promise(resolve => chrome.runtime.sendMessage({ - getAddresses: true - }, data => { - this.storeLocalAddresses(data); - return resolve(data); - })); - } - - /** - * Used by the email web app - * Settings page displays data of the logged in user data - */ - getUserData() { - return new Promise(resolve => chrome.runtime.sendMessage({ - getUserData: true - }, data => resolve(data))); - } - - /** - * Used by the email web app - * Device capabilities determine which functionality is available to the user - */ - getEmailProtectionCapabilities() { - return new Promise(resolve => chrome.runtime.sendMessage({ - getEmailProtectionCapabilities: true - }, data => resolve(data))); - } - refreshAlias() { - return chrome.runtime.sendMessage({ - refreshAlias: true - }, addresses => this.storeLocalAddresses(addresses)); - } - async trySigningIn() { - if (this.globalConfig.isDDGDomain) { - const data = await (0, _autofillUtils.sendAndWaitForAnswer)(_autofillUtils.SIGN_IN_MSG, 'addUserData'); - this.storeUserData(data); + /** + * @param {Requirements | null} requirements + * @param {PasswordParameters} [parameters] + * @returns {string} + */ + _generatedPasswordMatchingRequirements(requirements, parameters) { + requirements = requirements || {}; + parameters = parameters || this._passwordGenerationParametersDictionary(requirements); + const numberOfRequiredRandomCharacters = parameters.NumberOfRequiredRandomCharacters; + const repeatedCharLimit = requirements.PasswordRepeatedCharacterLimit; + const allowedCharacters = parameters.PasswordAllowedCharacters; + const shouldCheckRepeatedCharRequirement = !!repeatedCharLimit; + while (true) { + const password = this._classicPassword(numberOfRequiredRandomCharacters, allowedCharacters); + if (!this._passwordContainsRequiredCharacters(password, parameters.RequiredCharacterSets)) { + continue; + } + if (shouldCheckRepeatedCharRequirement) { + if (repeatedCharLimit !== void 0 && repeatedCharLimit >= 1 && !this._passwordHasNotExceededRepeatedCharLimit(password, repeatedCharLimit)) { + continue; + } + } + const consecutiveCharLimit = requirements.PasswordConsecutiveCharacterLimit; + if (consecutiveCharLimit && consecutiveCharLimit >= 1) { + if (!this._passwordHasNotExceededConsecutiveCharLimit(password, consecutiveCharLimit)) { + continue; + } + } + return password || ""; + } } - } - - /** - * @param {object} message - * @param {object} message.addUserData - * @param {string} message.addUserData.token - * @param {string} message.addUserData.userName - * @param {string} message.addUserData.cohort - */ - storeUserData(message) { - return chrome.runtime.sendMessage(message); - } - - /** - * Used by the email web app - * Provides functionality to log the user out - */ - removeUserData() { - return chrome.runtime.sendMessage({ - removeUserData: true - }); - } - addDeviceListeners() { - // Add contextual menu listeners - let activeEl = null; - document.addEventListener('contextmenu', e => { - activeEl = e.target; - }); - chrome.runtime.onMessage.addListener((message, sender) => { - if (sender.id !== chrome.runtime.id) return; - switch (message.type) { - case 'ddgUserReady': - this.resetAutofillUI(() => this.setupSettingsPage({ - shouldLog: true - })); - break; - case 'contextualAutofill': - (0, _autofillUtils.setValue)(activeEl, (0, _autofillUtils.formatDuckAddress)(message.alias), this.globalConfig); - activeEl.classList.add('ddg-autofilled'); - this.refreshAlias(); - - // If the user changes the alias, remove the decoration - activeEl.addEventListener('input', e => e.target.classList.remove('ddg-autofilled'), { - once: true - }); - break; - default: - break; + /** + * @param {parser.CustomCharacterClass | parser.NamedCharacterClass} characterClass + * @returns {string[]} + */ + _scanSetFromCharacterClass(characterClass) { + if (characterClass instanceof CustomCharacterClass) { + return characterClass.characters; + } + console.assert(characterClass instanceof NamedCharacterClass); + switch (characterClass.name) { + case Identifier.ASCII_PRINTABLE: + case Identifier.UNICODE: + return this.options.SCAN_SET_ORDER.split(""); + case Identifier.DIGIT: + return this.options.SCAN_SET_ORDER.substring(this.options.SCAN_SET_ORDER.indexOf("0"), this.options.SCAN_SET_ORDER.indexOf("9") + 1).split(""); + case Identifier.LOWER: + return this.options.SCAN_SET_ORDER.substring(this.options.SCAN_SET_ORDER.indexOf("a"), this.options.SCAN_SET_ORDER.indexOf("z") + 1).split(""); + case Identifier.SPECIAL: + return this.options.SCAN_SET_ORDER.substring(this.options.SCAN_SET_ORDER.indexOf("-"), this.options.SCAN_SET_ORDER.indexOf("]") + 1).split(""); + case Identifier.UPPER: + return this.options.SCAN_SET_ORDER.substring(this.options.SCAN_SET_ORDER.indexOf("A"), this.options.SCAN_SET_ORDER.indexOf("Z") + 1).split(""); + } + console.assert(false, SHOULD_NOT_BE_REACHED); + return []; + } + /** + * @param {(parser.CustomCharacterClass | parser.NamedCharacterClass)[]} characterClasses + */ + _charactersFromCharactersClasses(characterClasses) { + const output = []; + for (let characterClass of characterClasses) { + output.push(...this._scanSetFromCharacterClass(characterClass)); } - }); - } - addLogoutListener(handler) { - // Make sure there's only one log out listener attached by removing the - // previous logout listener first, if it exists. - if (this._logoutListenerHandler) { - chrome.runtime.onMessage.removeListener(this._logoutListenerHandler); + return output; + } + /** + * @param {string[]} characters + * @returns {string} + */ + _canonicalizedScanSetFromCharacters(characters) { + if (!characters.length) { + return ""; + } + let shadowCharacters = Array.prototype.slice.call(characters); + shadowCharacters.sort((a, b) => this.options.SCAN_SET_ORDER.indexOf(a) - this.options.SCAN_SET_ORDER.indexOf(b)); + let uniqueCharacters = [shadowCharacters[0]]; + for (let i = 1, length = shadowCharacters.length; i < length; ++i) { + if (shadowCharacters[i] === shadowCharacters[i - 1]) { + continue; + } + uniqueCharacters.push(shadowCharacters[i]); + } + return uniqueCharacters.join(""); } + }; - // Cleanup on logout events - this._logoutListenerHandler = (message, sender) => { - if (sender.id === chrome.runtime.id && message.type === 'logout') { - handler(); + // packages/password/index.js + function generate(options = {}) { + try { + if (typeof options?.input === "string") { + return Password.generateOrThrow(options.input); + } + if (typeof options?.domain === "string") { + if (options?.rules) { + const rules = _selectPasswordRules(options.domain, options.rules); + if (rules) { + return Password.generateOrThrow(rules); + } + } } - }; - chrome.runtime.onMessage.addListener(this._logoutListenerHandler); + } catch (e) { + if (options?.onError && typeof options?.onError === "function") { + options.onError(e); + } else { + const isKnownError = e instanceof ParserError || e instanceof HostnameInputError; + if (!isKnownError) { + console.error(e); + } + } + } + return Password.generateDefault(); } -} -exports.ExtensionInterface = ExtensionInterface; - -},{"../Form/matching.js":33,"../InContextSignup.js":34,"../UI/HTMLTooltip.js":45,"../UI/controllers/HTMLTooltipUIController.js":46,"../autofill-utils.js":52,"./InterfacePrototype.js":17}],17:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _autofillUtils = require("../autofill-utils.js"); -var _matching = require("../Form/matching.js"); -var _formatters = require("../Form/formatters.js"); -var _Credentials = require("../InputTypes/Credentials.js"); -var _PasswordGenerator = require("../PasswordGenerator.js"); -var _Scanner = require("../Scanner.js"); -var _config = require("../config.js"); -var _NativeUIController = require("../UI/controllers/NativeUIController.js"); -var _transports = require("../deviceApiCalls/transports/transports.js"); -var _Settings = require("../Settings.js"); -var _index = require("../../packages/device-api/index.js"); -var _deviceApiCalls = require("../deviceApiCalls/__generated__/deviceApiCalls.js"); -var _initFormSubmissionsApi = require("./initFormSubmissionsApi.js"); -var _EmailProtection = require("../EmailProtection.js"); -/** - * @typedef {import('../deviceApiCalls/__generated__/validators-ts').StoreFormData} StoreFormData - */ -/** - * @implements {GlobalConfigImpl} - * @implements {FormExtensionPoints} - * @implements {DeviceExtensionPoints} - */ -class InterfacePrototype { - attempts = 0; - /** @type {import("../Form/Form").Form | null} */ - activeForm = null; - /** @type {import("../UI/HTMLTooltip.js").default | null} */ - currentTooltip = null; - /** @type {number} */ - initialSetupDelayMs = 0; - autopromptFired = false; - - /** @type {PasswordGenerator} */ - passwordGenerator = new _PasswordGenerator.PasswordGenerator(); - emailProtection = new _EmailProtection.EmailProtection(this); - - /** @type {import("../InContextSignup.js").InContextSignup | null} */ - inContextSignup = null; - /** @type {import("../ThirdPartyProvider.js").ThirdPartyProvider | null} */ - thirdPartyProvider = null; - - /** @type {{privateAddress: string, personalAddress: string}} */ - #addresses = { - privateAddress: '', - personalAddress: '' + var HostnameInputError = class extends Error { }; - - /** @type {GlobalConfig} */ - globalConfig; - - /** @type {import('../Scanner').Scanner} */ - scanner; - - /** @type {import("../UI/controllers/UIController.js").UIController | null} */ - uiController; - - /** @type {import("../../packages/device-api").DeviceApi} */ - deviceApi; - - /** @type {boolean} */ - isInitializationStarted; - - /** @type {((reason, ...rest) => void) | null} */ - _scannerCleanup = null; - - /** - * @param {GlobalConfig} config - * @param {import("../../packages/device-api").DeviceApi} deviceApi - * @param {Settings} settings - */ - constructor(config, deviceApi, settings) { - this.globalConfig = config; - this.deviceApi = deviceApi; - this.settings = settings; - this.uiController = null; - this.scanner = (0, _Scanner.createScanner)(this, { - initialDelay: this.initialSetupDelayMs - }); - this.isInitializationStarted = false; + function _selectPasswordRules(inputHostname, rules) { + const hostname = _safeHostname(inputHostname); + if (rules[hostname]) { + return rules[hostname]["password-rules"]; + } + const pieces = hostname.split("."); + while (pieces.length > 1) { + pieces.shift(); + const joined = pieces.join("."); + if (rules[joined]) { + return rules[joined]["password-rules"]; + } + } + return void 0; } - - /** - * Implementors should override this with a UI controller that suits - * their platform. - * - * @returns {import("../UI/controllers/UIController.js").UIController} - */ - createUIController() { - return new _NativeUIController.NativeUIController(); + function _safeHostname(inputHostname) { + if (inputHostname.startsWith("http:") || inputHostname.startsWith("https:")) { + throw new HostnameInputError("invalid input, you can only provide a hostname but you gave a scheme"); + } + if (inputHostname.includes(":")) { + throw new HostnameInputError("invalid input, you can only provide a hostname but you gave a :port"); + } + try { + const asUrl = new URL("https://" + inputHostname); + return asUrl.hostname; + } catch (e) { + throw new HostnameInputError(`could not instantiate a URL from that hostname ${inputHostname}`); + } } - /** - * @param {string} reason - */ - removeAutofillUIFromPage(reason) { - this.uiController?.destroy(); - this._scannerCleanup?.(reason); - } - get hasLocalAddresses() { - return !!(this.#addresses?.privateAddress && this.#addresses?.personalAddress); - } - getLocalAddresses() { - return this.#addresses; - } - storeLocalAddresses(addresses) { - this.#addresses = addresses; - // When we get new duck addresses, add them to the identities list - const identities = this.getLocalIdentities(); - const privateAddressIdentity = identities.find(_ref => { - let { - id - } = _ref; - return id === 'privateAddress'; - }); - // If we had previously stored them, just update the private address - if (privateAddressIdentity) { - privateAddressIdentity.emailAddress = (0, _autofillUtils.formatDuckAddress)(addresses.privateAddress); - } else { - // Otherwise, add both addresses - this.#data.identities = this.addDuckAddressesToIdentities(identities); + // packages/password/rules.json + var rules_default = { + "163.com": { + "password-rules": "minlength: 6; maxlength: 16;" + }, + "1800flowers.com": { + "password-rules": "minlength: 6; required: lower, upper; required: digit;" + }, + "access.service.gov.uk": { + "password-rules": "minlength: 10; required: lower; required: upper; required: digit; required: special;" + }, + "admiral.com": { + "password-rules": "minlength: 8; required: digit; required: [- !\"#$&'()*+,.:;<=>?@[^_`{|}~]]; allowed: lower, upper;" + }, + "ae.com": { + "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit;" + }, + "aetna.com": { + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 2; required: upper; required: digit; allowed: lower, [-_&#@];" + }, + "airasia.com": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;" + }, + "airfrance.com": { + "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit; allowed: [-!#$&+/?@_];" + }, + "airfrance.us": { + "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit; allowed: [-!#$&+/?@_];" + }, + "ajisushionline.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; allowed: [ !#$%&*?@];" + }, + "aliexpress.com": { + "password-rules": "minlength: 6; maxlength: 20; allowed: lower, upper, digit;" + }, + "alliantcreditunion.com": { + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower, upper; required: digit; allowed: [!#$*];" + }, + "allianz.com.br": { + "password-rules": "minlength: 4; maxlength: 4;" + }, + "americanexpress.com": { + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 4; required: lower, upper; required: digit; allowed: [%&_?#=];" + }, + "anatel.gov.br": { + "password-rules": "minlength: 6; maxlength: 15; allowed: lower, upper, digit;" + }, + "ancestry.com": { + "password-rules": "minlength: 8; required: lower, upper; required: digit;" + }, + "angieslist.com": { + "password-rules": "minlength: 6; maxlength: 15;" + }, + "anthem.com": { + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower, upper; required: digit; allowed: [!$*?@|];" + }, + "app.digio.in": { + "password-rules": "minlength: 8; maxlength: 15;" + }, + "app.parkmobile.io": { + "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit; required: [!@#$%^&];" + }, + "apple.com": { + "password-rules": "minlength: 8; maxlength: 63; required: lower; required: upper; required: digit; allowed: ascii-printable;" + }, + "areariservata.bancaetica.it": { + "password-rules": "minlength: 8; maxlength: 10; required: lower; required: upper; required: digit; required: [!#&*+/=@_];" + }, + "artscyclery.com": { + "password-rules": "minlength: 6; maxlength: 19;" + }, + "astonmartinf1.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: special;" + }, + "auth.readymag.com": { + "password-rules": "minlength: 8; maxlength: 128; required: lower; required: upper; allowed: special;" + }, + "autify.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!\"#$%&'()*+,./:;<=>?@[^_`{|}~]];" + }, + "axa.de": { + "password-rules": `minlength: 8; maxlength: 65; required: lower; required: upper; required: digit; allowed: [-!"\xA7$%&/()=?;:_+*'#];` + }, + "baidu.com": { + "password-rules": "minlength: 6; maxlength: 14;" + }, + "bancochile.cl": { + "password-rules": "minlength: 8; maxlength: 8; required: lower; required: upper; required: digit;" + }, + "bankofamerica.com": { + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower; required: upper; required: digit; allowed: [-@#*()+={}/?~;,._];" + }, + "battle.net": { + "password-rules": "minlength: 8; maxlength: 16; required: lower, upper; allowed: digit, special;" + }, + "bcassessment.ca": { + "password-rules": "minlength: 8; maxlength: 14;" + }, + "belkin.com": { + "password-rules": "minlength: 8; required: lower, upper; required: digit; required: [$!@~_,%&];" + }, + "benefitslogin.discoverybenefits.com": { + "password-rules": "minlength: 10; required: upper; required: digit; required: [!#$%&*?@]; allowed: lower;" + }, + "benjerry.com": { + "password-rules": "required: upper; required: upper; required: digit; required: digit; required: special; required: special; allowed: lower;" + }, + "bestbuy.com": { + "password-rules": "minlength: 20; required: lower; required: upper; required: digit; required: special;" + }, + "bhphotovideo.com": { + "password-rules": "maxlength: 15;" + }, + "bilibili.com": { + "password-rules": "maxlength: 16;" + }, + "billerweb.com": { + "password-rules": "minlength: 8; max-consecutive: 2; required: digit; required: upper,lower;" + }, + "biovea.com": { + "password-rules": "maxlength: 19;" + }, + "bitly.com": { + "password-rules": "minlength: 6; required: lower; required: upper; required: digit; required: [`!@#$%^&*()+~{}'\";:<>?]];" + }, + "bloomingdales.com": { + "password-rules": "minlength: 7; maxlength: 16; required: lower, upper; required: digit; required: [`!@#$%^&*()+~{}'\";:<>?]];" + }, + "bluesguitarunleashed.com": { + "password-rules": "allowed: lower, upper, digit, [!$#@];" + }, + "bochk.com": { + "password-rules": "minlength: 8; maxlength: 12; max-consecutive: 3; required: lower; required: upper; required: digit; allowed: [#$%&()*+,.:;<=>?@_];" + }, + "box.com": { + "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: digit;" + }, + "brighthorizons.com": { + "password-rules": "minlength: 8; maxlength: 16;" + }, + "callofduty.com": { + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 2; required: lower, upper; required: digit;" + }, + "capitalone.com": { + "password-rules": "minlength: 8; maxlength: 32; required: lower, upper; required: digit; allowed: [-_./\\@$*&!#];" + }, + "cardbenefitservices.com": { + "password-rules": "minlength: 7; maxlength: 100; required: lower, upper; required: digit;" + }, + "carrefour.it": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&*?@_];" + }, + "cb2.com": { + "password-rules": "minlength: 7; maxlength: 18; required: lower, upper; required: digit;" + }, + "ccs-grp.com": { + "password-rules": "minlength: 8; maxlength: 16; required: digit; required: upper,lower; allowed: [-!#$%&'+./=?\\^_`{|}~];" + }, + "cecredentialtrust.com": { + "password-rules": "minlength: 12; required: lower; required: upper; required: digit; required: [!#$%&*@^];" + }, + "chase.com": { + "password-rules": "minlength: 8; maxlength: 32; max-consecutive: 2; required: lower, upper; required: digit; required: [!#$%+/=@~];" + }, + "cigna.co.uk": { + "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit;" + }, + "citi.com": { + "password-rules": "minlength: 8; maxlength: 64; max-consecutive: 2; required: digit; required: upper; required: lower; required: [-~`!@#$%^&*()_\\/|];" + }, + "claimlookup.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [@#$%^&+=!];" + }, + "claro.com.br": { + "password-rules": "minlength: 8; required: lower; allowed: upper, digit, [-!@#$%&*_+=<>];" + }, + "classmates.com": { + "password-rules": "minlength: 6; maxlength: 20; allowed: lower, upper, digit, [!@#$%^&*];" + }, + "clegc-gckey.gc.ca": { + "password-rules": "minlength: 8; maxlength: 16; required: lower, upper, digit;" + }, + "clien.net": { + "password-rules": "minlength: 5; required: lower, upper; required: digit;" + }, + "cogmembers.org": { + "password-rules": "minlength: 8; maxlength: 14; required: upper; required: digit, allowed: lower;" + }, + "collectivehealth.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" + }, + "comcastpaymentcenter.com": { + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 2;required: lower, upper; required: digit;" + }, + "comed.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [-~!@#$%^&*_+=`|(){}[:;\"'<>,.?/\\]];" + }, + "commerzbank.de": { + "password-rules": "minlength: 5; maxlength: 8; required: lower, upper; required: digit;" + }, + "consorsbank.de": { + "password-rules": "minlength: 5; maxlength: 5; required: lower, upper, digit;" + }, + "consorsfinanz.de": { + "password-rules": "minlength: 6; maxlength: 15; allowed: lower, upper, digit, [-.];" + }, + "costco.com": { + "password-rules": "minlength: 8; maxlength: 20; required: lower, upper; allowed: digit, [-!#$%&'()*+/:;=?@[^_`{|}~]];" + }, + "coursera.com": { + "password-rules": "minlength: 8; maxlength: 72;" + }, + "cox.com": { + "password-rules": "minlength: 8; maxlength: 24; required: digit; required: upper,lower; allowed: [!#$%()*@^];" + }, + "crateandbarrel.com": { + "password-rules": 'minlength: 9; maxlength: 64; required: lower; required: upper; required: digit; required: [!"#$%&()*,.:<>?@^_{|}];' + }, + "cvs.com": { + "password-rules": "minlength: 8; maxlength: 25; required: lower, upper; required: digit; allowed: [!@#$%^&*()];" + }, + "dailymail.co.uk": { + "password-rules": "minlength: 5; maxlength: 15;" + }, + "dan.org": { + "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit; required: [!@$%^&*];" + }, + "danawa.com": { + "password-rules": "minlength: 8; maxlength: 21; required: lower, upper; required: digit; required: [!@$%^&*];" + }, + "darty.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" + }, + "dbs.com.hk": { + "password-rules": "minlength: 8; maxlength: 30; required: lower; required: upper; required: digit;" + }, + "decluttr.com": { + "password-rules": "minlength: 8; maxlength: 45; required: lower; required: upper; required: digit;" + }, + "delta.com": { + "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit;" + }, + "deutsche-bank.de": { + "password-rules": "minlength: 5; maxlength: 5; required: lower, upper, digit;" + }, + "devstore.cn": { + "password-rules": "minlength: 6; maxlength: 12;" + }, + "dickssportinggoods.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&*?@^];" + }, + "dkb.de": { + "password-rules": "minlength: 8; maxlength: 38; required: lower, upper; required: digit; allowed: [-\xE4\xFC\xF6\xC4\xDC\xD6\xDF!$%&/()=?+#,.:];" + }, + "dmm.com": { + "password-rules": "minlength: 4; maxlength: 16; required: lower; required: upper; required: digit;" + }, + "dowjones.com": { + "password-rules": "maxlength: 15;" + }, + "ea.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: special;" + }, + "easycoop.com": { + "password-rules": "minlength: 8; required: upper; required: special; allowed: lower, digit;" + }, + "easyjet.com": { + "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: [-];" + }, + "ebrap.org": { + "password-rules": "minlength: 15; required: lower; required: lower; required: upper; required: upper; required: digit; required: digit; required: [-!@#$%^&*()_+|~=`{}[:\";'?,./.]]; required: [-!@#$%^&*()_+|~=`{}[:\";'?,./.]];" + }, + "ecompanystore.com": { + "password-rules": "minlength: 8; maxlength: 16; max-consecutive: 2; required: lower; required: upper; required: digit; required: [#$%*+.=@^_];" + }, + "eddservices.edd.ca.gov": { + "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit; required: [!@#$%^&*()];" + }, + "edistrict.kerala.gov.in": { + "password-rules": "minlength: 5; maxlength: 15; required: lower; required: upper; required: digit; required: [!@#$];" + }, + "empower-retirement.com": { + "password-rules": "minlength: 8; maxlength: 16;" + }, + "epicgames.com": { + "password-rules": "minlength: 7; required: lower; required: upper; required: digit; required: [-!\"#$%&'()*+,./:;<=>?@[^_`{|}~]];" + }, + "epicmix.com": { + "password-rules": "minlength: 8; maxlength: 16;" + }, + "equifax.com": { + "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; required: [!$*+@];" + }, + "essportal.excelityglobal.com": { + "password-rules": "minlength: 6; maxlength: 8; allowed: lower, upper, digit;" + }, + "ettoday.net": { + "password-rules": "minlength: 6; maxlength: 12;" + }, + "examservice.com.tw": { + "password-rules": "minlength: 6; maxlength: 8;" + }, + "expertflyer.com": { + "password-rules": "minlength: 5; maxlength: 16; required: lower, upper; required: digit;" + }, + "extraspace.com": { + "password-rules": "minlength: 8; maxlength: 20; allowed: lower; required: upper, digit, [!#$%&*?@];" + }, + "ezpassva.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;" + }, + "fc2.com": { + "password-rules": "minlength: 8; maxlength: 16;" + }, + "fccaccessonline.com": { + "password-rules": "minlength: 8; maxlength: 14; max-consecutive: 3; required: lower; required: upper; required: digit; required: [!#$%*^_];" + }, + "fedex.com": { + "password-rules": "minlength: 8; max-consecutive: 3; required: lower; required: upper; required: digit; allowed: [-!@#$%^&*_+=`|(){}[:;,.?]];" + }, + "fidelity.com": { + "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: [-!$%+,./:;=?@^_|]; max-consecutive: 2;" + }, + "flysas.com": { + "password-rules": "minlength: 8; maxlength: 14; required: lower; required: upper; required: digit; required: [-~!@#$%^&_+=`|(){}[:\"'<>,.?]];" + }, + "fnac.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" + }, + "fuelrewards.com": { + "password-rules": "minlength: 8; maxlength: 16; allowed: upper,lower,digit,[!#$%@];" + }, + "gamestop.com": { + "password-rules": "minlength: 8; maxlength: 225; required: lower; required: upper; required: digit; required: [!@#$%];" + }, + "garmin.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" + }, + "getflywheel.com": { + "password-rules": "minlength: 7; maxlength: 72;" + }, + "girlscouts.org": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [$#!];" + }, + "gmx.net": { + "password-rules": "minlength: 8; maxlength: 40; allowed: lower, upper, digit, [-<=>~!|()@#{}$%,.?^'&*_+`:;\"[]];" + }, + "gocurb.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [$%&#*?!@^];" + }, + "google.com": { + "password-rules": `minlength: 8; allowed: lower, upper, digit, [-!"#$%&'()*+,./:;<=>?@[^_{|}~]];` + }, + "guardiananytime.com": { + "password-rules": "minlength: 8; maxlength: 50; max-consecutive: 2; required: lower; required: upper; required: digit, [-~!@#$%^&*_+=`|(){}[:;,.?]];" + }, + "gwl.greatwestlife.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [-!#$%_=+<>];" + }, + "hangseng.com": { + "password-rules": "minlength: 8; maxlength: 30; required: lower; required: upper; required: digit;" + }, + "hawaiianairlines.com": { + "password-rules": "maxlength: 16;" + }, + "hertz-japan.com": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz-kuwait.com": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz-saudi.com": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.at": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.be": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.bh": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.ca": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.ch": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.cn": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.co.ao": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.co.id": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.co.kr": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.co.nz": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.co.th": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.co.uk": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.au": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.bh": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.hk": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.kw": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.mt": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.pl": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.pt": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.sg": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.tw": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.cv": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.cz": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.de": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.ee": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.es": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.fi": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.fr": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.hu": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.ie": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.it": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.jo": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.lt": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.nl": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.no": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.nu": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.pl": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.pt": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.qa": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.ru": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.se": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.si": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertzcaribbean.com": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hetzner.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit, special;" + }, + "hilton.com": { + "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit;" + }, + "hkbea.com": { + "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit;" + }, + "hkexpress.com": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: special;" + }, + "hotels.com": { + "password-rules": "minlength: 6; maxlength: 20; required: digit; required: [-~#@$%&!*_?^]; allowed: lower, upper;" + }, + "hotwire.com": { + "password-rules": "minlength: 6; maxlength: 30; allowed: lower, upper, digit, [-~!@#$%^&*_+=`|(){}[:;\"'<>,.?]];" + }, + "hrblock.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [$#%!];" + }, + "hsbc.com.hk": { + "password-rules": "minlength: 6; maxlength: 30; required: lower; required: upper; required: digit; allowed: ['.@_];" + }, + "hsbc.com.my": { + "password-rules": "minlength: 8; maxlength: 30; required: lower, upper; required: digit; allowed: [-!$*.=?@_'];" + }, + "hypovereinsbank.de": { + "password-rules": 'minlength: 6; maxlength: 10; required: lower, upper, digit; allowed: [!"#$%&()*+:;<=>?@[{}~]];' + }, + "hyresbostader.se": { + "password-rules": "minlength: 6; maxlength: 20; required: lower, upper; required: digit;" + }, + "ichunqiu.com": { + "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit;" + }, + "id.sonyentertainmentnetwork.com": { + "password-rules": "minlength: 8; maxlength: 30; required: lower, upper; required: digit; allowed: [-!@#^&*=+;:];" + }, + "identitytheft.gov": { + "password-rules": "allowed: lower, upper, digit, [!#%&*@^];" + }, + "idestination.info": { + "password-rules": "maxlength: 15;" + }, + "impots.gouv.fr": { + "password-rules": "minlength: 12; maxlength: 128; required: lower; required: digit; allowed: [-!#$%&*+/=?^_'.{|}];" + }, + "indochino.com": { + "password-rules": "minlength: 6; maxlength: 15; required: upper; required: digit; allowed: lower, special;" + }, + "inntopia.travel": { + "password-rules": "minlength: 7; maxlength: 19; required: digit; allowed: upper,lower,[-];" + }, + "internationalsos.com": { + "password-rules": "required: lower; required: upper; required: digit; required: [@#$%^&+=_];" + }, + "irctc.co.in": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [!@#$%^&*()+];" + }, + "irs.gov": { + "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit; required: [!#$%&*@];" + }, + "jal.co.jp": { + "password-rules": "minlength: 8; maxlength: 16;" + }, + "japanpost.jp": { + "password-rules": "minlength: 8; maxlength: 16; required: digit; required: upper,lower;" + }, + "jordancu-onlinebanking.org": { + "password-rules": "minlength: 6; maxlength: 32; allowed: upper, lower, digit,[-!\"#$%&'()*+,.:;<=>?@[^_`{|}~]];" + }, + "keldoc.com": { + "password-rules": "minlength: 12; required: lower; required: upper; required: digit; required: [!@#$%^&*];" + }, + "key.harvard.edu": { + "password-rules": "minlength: 10; maxlength: 100; required: lower; required: upper; required: digit; allowed: [-@_#!&$`%*+()./,;~:{}|?>=<^[']];" + }, + "kfc.ca": { + "password-rules": "minlength: 6; maxlength: 15; required: lower; required: upper; required: digit; required: [!@#$%&?*];" + }, + "klm.com": { + "password-rules": "minlength: 8; maxlength: 12;" + }, + "kundenportal.edeka-smart.de": { + "password-rules": 'minlength: 8; maxlength: 16; required: digit; required: upper, lower; required: [!"\xA7$%&#];' + }, + "la-z-boy.com": { + "password-rules": "minlength: 6; maxlength: 15; required: lower, upper; required: digit;" + }, + "labcorp.com": { + "password-rules": "minlength: 8; maxlength: 20; required: upper; required: lower; required: digit; required: [!@#$%^&*];" + }, + "ladwp.com": { + "password-rules": "minlength: 8; maxlength: 20; required: digit; allowed: lower, upper;" + }, + "launtel.net.au": { + "password-rules": "minlength: 8; required: digit; required: digit; allowed: lower, upper;" + }, + "leetchi.com": { + "password-rules": 'minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&()*+,./:;<>?@"_];' + }, + "lepida.it": { + "password-rules": "minlength: 8; maxlength: 16; max-consecutive: 2; required: lower; required: upper; required: digit; required: [-!\"#$%&'()*+,.:;<=>?@[^_`{|}~]];" + }, + "lg.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [-!#$%&'()*+,.:;=?@[^_{|}~]];" + }, + "linearity.io": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: special;" + }, + "live.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; allowed: [-@_#!&$`%*+()./,;~:{}|?>=<^'[]];" + }, + "lloydsbank.co.uk": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: digit; allowed: upper;" + }, + "lowes.com": { + "password-rules": "minlength: 8; maxlength: 128; max-consecutive: 3; required: lower, upper; required: digit;" + }, + "loyalty.accor.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&=@];" + }, + "lsacsso.b2clogin.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit, [-!#$%&*?@^_];" + }, + "lufthansa.com": { + "password-rules": 'minlength: 8; maxlength: 32; required: lower; required: upper; required: digit; required: [!#$%&()*+,./:;<>?@"_];' + }, + "macys.com": { + "password-rules": "minlength: 7; maxlength: 16; allowed: lower, upper, digit, [~!@#$%^&*+`(){}[:;\"'<>?]];" + }, + "mailbox.org": { + "password-rules": 'minlength: 8; required: lower; required: upper; required: digit; allowed: [-!$"%&/()=*+#.,;:@?{}[]];' + }, + "makemytrip.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [@$!%*#?&];" + }, + "marriott.com": { + "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; allowed: [$!#&@?%=];" + }, + "maybank2u.com.my": { + "password-rules": "minlength: 8; maxlength: 12; max-consecutive: 2; required: lower; required: upper; required: digit; required: [-~!@#$%^&*_+=`|(){}[:;\"'<>,.?];" + }, + "medicare.gov": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [@!$%^*()];" + }, + "member.everbridge.net": { + "password-rules": "minlength: 8; required: lower, upper; required: digit; allowed: [!@#$%^&*()];" + }, + "metlife.com": { + "password-rules": "minlength: 6; maxlength: 20;" + }, + "microsoft.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: special;" + }, + "milogin.michigan.gov": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [@#$!~&];" + }, + "mintmobile.com": { + "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; required: special; allowed: [!#$%&()*+:;=@[^_`{}~]];" + }, + "mlb.com": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;" + }, + "mpv.tickets.com": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;" + }, + "museumofflight.org": { + "password-rules": "minlength: 8; maxlength: 15;" + }, + "my.konami.net": { + "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit;" + }, + "myaccess.dmdc.osd.mil": { + "password-rules": "minlength: 9; maxlength: 20; required: lower; required: upper; required: digit; allowed: [-@_#!&$`%*+()./,;~:{}|?>=<^'[]];" + }, + "mygoodtogo.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower, upper, digit;" + }, + "myhealthrecord.com": { + "password-rules": "minlength: 8; maxlength: 20; allowed: lower, upper, digit, [_.!$*=];" + }, + "mysavings.breadfinancial.com": { + "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit; required: [+_%@!$*~];" + }, + "mysedgwick.com": { + "password-rules": "minlength: 8; maxlength: 16; allowed: lower; required: upper; required: digit; required: [@#%^&+=!]; allowed: [-~_$.,;]" + }, + "mysubaru.com": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; allowed: [!#$%()*+,./:;=?@\\^`~];" + }, + "naver.com": { + "password-rules": "minlength: 6; maxlength: 16;" + }, + "nelnet.net": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit, [!@#$&*];" + }, + "netflix.com": { + "password-rules": "minlength: 4; maxlength: 60; required: lower, upper, digit; allowed: special;" + }, + "netgear.com": { + "password-rules": "minlength: 6; maxlength: 128; allowed: lower, upper, digit, [!@#$%^&*()];" + }, + "nowinstock.net": { + "password-rules": "minlength: 6; maxlength: 20; allowed: lower, upper, digit;" + }, + "order.wendys.com": { + "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; allowed: [!#$%&()*+/=?^_{}];" + }, + "ototoy.jp": { + "password-rules": "minlength: 8; allowed: upper,lower,digit,[- .=_];" + }, + "packageconciergeadmin.com": { + "password-rules": "minlength: 4; maxlength: 4; allowed: digit;" + }, + "paypal.com": { + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower, upper; required: digit, [!@#$%^&*()];" + }, + "payvgm.youraccountadvantage.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: special;" + }, + "pilotflyingj.com": { + "password-rules": "minlength: 7; required: digit; allowed: lower, upper;" + }, + "pixnet.cc": { + "password-rules": "minlength: 4; maxlength: 16; allowed: lower, upper;" + }, + "planetary.org": { + "password-rules": "minlength: 5; maxlength: 20; required: lower; required: upper; required: digit; allowed: ascii-printable;" + }, + "plazapremiumlounge.com": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; allowed: [!#$%&*,@^];" + }, + "portal.edd.ca.gov": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&()*@^];" + }, + "portals.emblemhealth.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&'()*+,./:;<>?@\\^_`{|}~[]];" + }, + "portlandgeneral.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [!#$%&*?@];" + }, + "poste.it": { + "password-rules": "minlength: 8; maxlength: 16; max-consecutive: 2; required: lower; required: upper; required: digit; required: special;" + }, + "posteo.de": { + "password-rules": 'minlength: 8; required: lower; required: upper; required: digit, [-~!#$%&_+=|(){}[:;"\u2019<>,.? ]];' + }, + "powells.com": { + "password-rules": 'minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: ["!@#$%^&*(){}[]];' + }, + "preferredhotels.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&()*+@^_];" + }, + "premier.ticketek.com.au": { + "password-rules": "minlength: 6; maxlength: 16;" + }, + "premierinn.com": { + "password-rules": "minlength: 8; required: upper; required: digit; allowed: lower;" + }, + "prepaid.bankofamerica.com": { + "password-rules": `minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [!@#$%^&*()+~{}'";:<>?];` + }, + "prestocard.ca": { + "password-rules": `minlength: 8; required: lower; required: upper; required: digit,[!"#$%&'()*+,<>?@];` + }, + "propelfuels.com": { + "password-rules": "minlength: 6; maxlength: 16;" + }, + "qdosstatusreview.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&@^];" + }, + "questdiagnostics.com": { + "password-rules": "minlength: 8; maxlength: 30; required: upper, lower; required: digit, [!#$%&()*+<>?@^_~];" + }, + "rejsekort.dk": { + "password-rules": "minlength: 7; maxlength: 15; required: lower; required: upper; required: digit;" + }, + "renaud-bray.com": { + "password-rules": "minlength: 8; maxlength: 38; allowed: upper,lower,digit;" + }, + "ring.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!@#$%^&*<>?];" + }, + "riteaid.com": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;" + }, + "robinhood.com": { + "password-rules": "minlength: 10;" + }, + "rogers.com": { + "password-rules": "minlength: 8; required: lower, upper; required: digit; required: [!@#$];" + }, + "ruc.dk": { + "password-rules": "minlength: 6; maxlength: 8; required: lower, upper; required: [-!#%&(){}*+;%/<=>?_];" + }, + "runescape.com": { + "password-rules": "minlength: 5; maxlength: 20; required: lower; required: upper; required: digit;" + }, + "ruten.com.tw": { + "password-rules": "minlength: 6; maxlength: 15; required: lower, upper;" + }, + "salslimo.com": { + "password-rules": "minlength: 8; maxlength: 50; required: upper; required: lower; required: digit; required: [!@#$&*];" + }, + "santahelenasaude.com.br": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [-!@#$%&*_+=<>];" + }, + "santander.de": { + "password-rules": "minlength: 8; maxlength: 12; required: lower, upper; required: digit; allowed: [-!#$%&'()*,.:;=?^{}];" + }, + "sbisec.co.jp": { + "password-rules": "minlength: 10; maxlength: 20; allowed: upper,lower,digit;" + }, + "secure-arborfcu.org": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [!#$%&'()+,.:?@[_`~]];" + }, + "secure.orclinic.com": { + "password-rules": "minlength: 6; maxlength: 15; required: lower; required: digit; allowed: ascii-printable;" + }, + "secure.snnow.ca": { + "password-rules": "minlength: 7; maxlength: 16; required: digit; allowed: lower, upper;" + }, + "sephora.com": { + "password-rules": "minlength: 6; maxlength: 12;" + }, + "serviziconsolari.esteri.it": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;" + }, + "servizioelettriconazionale.it": { + "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; required: [!#$%&*?@^_~];" + }, + "sfwater.org": { + "password-rules": "minlength: 10; maxlength: 30; required: digit; allowed: lower, upper, [!@#$%*()_+^}{:;?.];" + }, + "signin.ea.com": { + "password-rules": "minlength: 8; maxlength: 64; required: lower, upper; required: digit; allowed: [-!@#^&*=+;:];" + }, + "southwest.com": { + "password-rules": "minlength: 8; maxlength: 16; required: upper; required: digit; allowed: lower, [!@#$%^*(),.;:/\\];" + }, + "speedway.com": { + "password-rules": "minlength: 4; maxlength: 8; required: digit;" + }, + "spirit.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [!@#$%^&*()];" + }, + "splunk.com": { + "password-rules": "minlength: 8; maxlength: 64; required: lower; required: upper; required: digit; required: [-!@#$%&*_+=<>];" + }, + "ssa.gov": { + "password-rules": "required: lower; required: upper; required: digit; required: [~!@#$%^&*];" + }, + "store.nintendo.co.uk": { + "password-rules": "minlength: 8; maxlength: 20;" + }, + "store.nvidia.com": { + "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit; required: [-!@#$%^*~:;&><[{}|_+=?]];" + }, + "store.steampowered.com": { + "password-rules": "minlength: 6; required: lower; required: upper; required: digit; allowed: [~!@#$%^&*];" + }, + "successfactors.eu": { + "password-rules": "minlength: 8; maxlength: 18; required: lower; required: upper; required: digit,[-!\"#$%&'()*+,.:;<=>?@[^_`{|}~]];" + }, + "sulamericaseguros.com.br": { + "password-rules": "minlength: 6; maxlength: 6;" + }, + "sunlife.com": { + "password-rules": "minlength: 8; maxlength: 10; required: digit; required: lower, upper;" + }, + "t-mobile.net": { + "password-rules": "minlength: 8; maxlength: 16;" + }, + "target.com": { + "password-rules": "minlength: 8; maxlength: 20; required: lower, upper; required: digit, [-!\"#$%&'()*+,./:;=?@[\\^_`{|}~];" + }, + "tdscpc.gov.in": { + "password-rules": `minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [ &',;"];` + }, + "telekom-dienste.de": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [#$%&()*+,./<=>?@_{|}~];" + }, + "thameswater.co.uk": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;" + }, + "tix.soundrink.com": { + "password-rules": "minlength: 6; maxlength: 16;" + }, + "training.confluent.io": { + "password-rules": "minlength: 6; maxlength: 16; required: lower; required: upper; required: digit; allowed: [!#$%*@^_~];" + }, + "treasurer.mo.gov": { + "password-rules": "minlength: 8; maxlength: 26; required: lower; required: upper; required: digit; required: [!#$&];" + }, + "twitch.tv": { + "password-rules": "minlength: 8; maxlength: 71;" + }, + "twitter.com": { + "password-rules": "minlength: 8;" + }, + "ubisoft.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [-]; required: [!@#$%^&*()+];" + }, + "udel.edu": { + "password-rules": "minlength: 12; maxlength: 30; required: lower; required: upper; required: digit; required: [!@#$%^&*()+];" + }, + "user.ornl.gov": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower, upper; required: digit; allowed: [!#$%./_];" + }, + "usps.com": { + "password-rules": `minlength: 8; maxlength: 50; max-consecutive: 2; required: lower; required: upper; required: digit; allowed: [-!"#&'()+,./?@];` + }, + "vanguard.com": { + "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: digit;" + }, + "vanguardinvestor.co.uk": { + "password-rules": "minlength: 8; maxlength: 50; required: lower; required: upper; required: digit; required: digit;" + }, + "ventrachicago.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit, [!@#$%^];" + }, + "verizonwireless.com": { + "password-rules": "minlength: 8; maxlength: 20; required: lower, upper; required: digit; allowed: unicode;" + }, + "vetsfirstchoice.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; allowed: [?!@$%^+=&];" + }, + "vince.com": { + "password-rules": "minlength: 8; required: digit; required: lower; required: upper; required: [$%/(){}=?!.,_*|+~#[]];" + }, + "virginmobile.ca": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$@];" + }, + "visa.com": { + "password-rules": "minlength: 6; maxlength: 32;" + }, + "visabenefits-auth.axa-assistance.us": { + "password-rules": 'minlength: 8; required: lower; required: upper; required: digit; required: [!"#$%&()*,.:<>?@^{|}];' + }, + "vivo.com.br": { + "password-rules": "maxlength: 6; max-consecutive: 3; allowed: digit;" + }, + "volaris.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;" + }, + "wa.aaa.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: ascii-printable;" + }, + "walkhighlands.co.uk": { + "password-rules": "minlength: 9; maxlength: 15; required: lower; required: upper; required: digit; allowed: special;" + }, + "walmart.com": { + "password-rules": "allowed: lower, upper, digit, [-(~!@#$%^&*_+=`|(){}[:;\"'<>,.?]];" + }, + "waze.com": { + "password-rules": "minlength: 8; maxlength: 64; required: lower, upper, digit;" + }, + "wccls.org": { + "password-rules": "minlength: 4; maxlength: 16; allowed: lower, upper, digit;" + }, + "web.de": { + "password-rules": "minlength: 8; maxlength: 40; allowed: lower, upper, digit, [-<=>~!|()@#{}$%,.?^'&*_+`:;\"[]];" + }, + "wegmans.com": { + "password-rules": "minlength: 8; required: digit; required: upper,lower; required: [!#$%&*+=?@^];" + }, + "weibo.com": { + "password-rules": "minlength: 6; maxlength: 16;" + }, + "wellsfargo.com": { + "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit;" + }, + "wmata.com": { + "password-rules": 'minlength: 8; required: lower, upper; required: digit; required: digit; required: [-!@#$%^&*~/"()_=+\\|,.?[]];' + }, + "worldstrides.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [-!#$%&*+=?@^_~];" + }, + "wsj.com": { + "password-rules": "minlength: 5; maxlength: 15; required: digit; allowed: lower, upper, [-~!@#$^*_=`|(){}[:;\"'<>,.?]];" + }, + "xfinity.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower, upper; required: digit;" + }, + "xvoucher.com": { + "password-rules": "minlength: 11; required: upper; required: digit; required: [!@#$%&_];" + }, + "yatra.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&'()+,.:?@[_`~]];" + }, + "zara.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" + }, + "zdf.de": { + "password-rules": "minlength: 8; required: upper; required: digit; allowed: lower, special;" + }, + "zoom.us": { + "password-rules": "minlength: 8; maxlength: 32; max-consecutive: 6; required: lower; required: upper; required: digit;" } - } - - /** @type { PMData } */ - #data = { - credentials: [], - creditCards: [], - identities: [], - topContextData: undefined }; - /** - * @returns {import('../Form/matching').SupportedTypes} - */ - getCurrentInputType() { - throw new Error('Not implemented'); - } - addDuckAddressesToIdentities(identities) { - if (!this.hasLocalAddresses) return identities; - const newIdentities = []; - let { - privateAddress, - personalAddress - } = this.getLocalAddresses(); - privateAddress = (0, _autofillUtils.formatDuckAddress)(privateAddress); - personalAddress = (0, _autofillUtils.formatDuckAddress)(personalAddress); - - // Get the duck addresses in identities - const duckEmailsInIdentities = identities.reduce((duckEmails, _ref2) => { - let { - emailAddress: email - } = _ref2; - return email?.includes(_autofillUtils.ADDRESS_DOMAIN) ? duckEmails.concat(email) : duckEmails; - }, []); + // src/PasswordGenerator.js + var _previous; + var PasswordGenerator = class { + constructor() { + /** @type {string|null} */ + __privateAdd(this, _previous, null); + } + /** @returns {boolean} */ + get generated() { + return __privateGet(this, _previous) !== null; + } + /** @returns {string|null} */ + get password() { + return __privateGet(this, _previous); + } + /** @param {import('../packages/password').GenerateOptions} [params] */ + generate(params = {}) { + if (__privateGet(this, _previous)) { + return __privateGet(this, _previous); + } + __privateSet(this, _previous, generate({ ...params, rules: rules_default })); + return __privateGet(this, _previous); + } + }; + _previous = new WeakMap(); - // Only add the personal duck address to identities if the user hasn't - // already manually added it - if (!duckEmailsInIdentities.includes(personalAddress)) { - newIdentities.push({ - id: 'personalAddress', - emailAddress: personalAddress, - title: 'Block email trackers' + // src/Form/FormAnalyzer.js + var FormAnalyzer = class { + /** + * @param {HTMLElement} form + * @param {HTMLInputElement|HTMLSelectElement} input + * @param {Matching} [matching] + */ + constructor(form, input, matching) { + /** @type HTMLElement */ + __publicField(this, "form"); + /** @type Matching */ + __publicField(this, "matching"); + /** @type {undefined|boolean} */ + __publicField(this, "_isCCForm"); + this.form = form; + this.matching = matching || new Matching(matchingConfiguration); + this.autofillSignal = 0; + this.hybridSignal = 0; + this.signals = []; + this.evaluateElAttributes(input, 1, true); + form ? this.evaluateForm() : this.evaluatePage(); + return this; + } + /** + * Hybrid forms can be used for both login and signup + * @returns {boolean} + */ + get isHybrid() { + const areOtherSignalsWeak = Math.abs(this.autofillSignal) < 10; + return this.hybridSignal > 0 && areOtherSignalsWeak; + } + get isLogin() { + if (this.isHybrid) + return false; + return this.autofillSignal < 0; + } + get isSignup() { + if (this.isHybrid) + return false; + return this.autofillSignal >= 0; + } + /** + * Tilts the scoring towards Signup + * @param {number} strength + * @param {string} signal + * @returns {FormAnalyzer} + */ + increaseSignalBy(strength, signal) { + this.autofillSignal += strength; + this.signals.push(`${signal}: +${strength}`); + return this; + } + /** + * Tilts the scoring towards Login + * @param {number} strength + * @param {string} signal + * @returns {FormAnalyzer} + */ + decreaseSignalBy(strength, signal) { + this.autofillSignal -= strength; + this.signals.push(`${signal}: -${strength}`); + return this; + } + /** + * Increases the probability that this is a hybrid form (can be either login or signup) + * @param {number} strength + * @param {string} signal + * @returns {FormAnalyzer} + */ + increaseHybridSignal(strength, signal) { + this.hybridSignal += strength; + this.signals.push(`${signal} (hybrid): +${strength}`); + return this; + } + /** + * Updates the Login<->Signup signal according to the provided parameters + * @param {object} p + * @param {string} p.string - The string to check + * @param {number} p.strength - Strength of the signal + * @param {string} [p.signalType] - For debugging purposes, we give a name to the signal + * @param {boolean} [p.shouldFlip] - Flips the signals, i.e. when a link points outside. See below + * @param {boolean} [p.shouldCheckUnifiedForm] - Should check for login/signup forms + * @param {boolean} [p.shouldBeConservative] - Should use the conservative signup regex + * @returns {FormAnalyzer} + */ + updateSignal({ + string, + strength, + signalType = "generic", + shouldFlip = false, + shouldCheckUnifiedForm = false, + shouldBeConservative = false + }) { + if (!string || string.length > constants.TEXT_LENGTH_CUTOFF) + return this; + const matchesLogin = safeRegexTest(/current.?password/i, string) || safeRegexTest(this.matching.getDDGMatcherRegex("loginRegex"), string) || safeRegexTest(this.matching.getDDGMatcherRegex("resetPasswordLink"), string); + if (shouldCheckUnifiedForm && matchesLogin && safeRegexTest(this.matching.getDDGMatcherRegex("conservativeSignupRegex"), string)) { + this.increaseHybridSignal(strength, signalType); + return this; + } + const signupRegexToUse = this.matching.getDDGMatcherRegex(shouldBeConservative ? "conservativeSignupRegex" : "signupRegex"); + const matchesSignup = safeRegexTest(/new.?password/i, string) || safeRegexTest(signupRegexToUse, string); + if (shouldFlip) { + if (matchesLogin) + this.increaseSignalBy(strength, signalType); + if (matchesSignup) + this.decreaseSignalBy(strength, signalType); + } else { + if (matchesLogin) + this.decreaseSignalBy(strength, signalType); + if (matchesSignup) + this.increaseSignalBy(strength, signalType); + } + return this; + } + evaluateElAttributes(el, signalStrength = 3, isInput = false) { + Array.from(el.attributes).forEach((attr) => { + if (attr.name === "style") + return; + const attributeString = `${attr.name}=${attr.value}`; + this.updateSignal({ + string: attributeString, + strength: signalStrength, + signalType: `${el.name} attr: ${attributeString}`, + shouldCheckUnifiedForm: isInput + }); }); } - newIdentities.push({ - id: 'privateAddress', - emailAddress: privateAddress, - title: 'Block email trackers & hide address' - }); - return [...identities, ...newIdentities]; - } - - /** - * Stores init data coming from the tooltipHandler - * @param { InboundPMData } data - */ - storeLocalData(data) { - this.storeLocalCredentials(data.credentials); - data.creditCards.forEach(cc => delete cc.cardNumber && delete cc.cardSecurityCode); - // Store the full name as a separate field to simplify autocomplete - const updatedIdentities = data.identities.map(identity => ({ - ...identity, - fullName: (0, _formatters.formatFullName)(identity) - })); - // Add addresses - this.#data.identities = this.addDuckAddressesToIdentities(updatedIdentities); - this.#data.creditCards = data.creditCards; - - // Top autofill only - if (data.serializedInputContext) { - try { - this.#data.topContextData = JSON.parse(data.serializedInputContext); - } catch (e) { - console.error(e); - this.removeTooltip(); + evaluateUrl() { + const path = window.location.pathname; + const matchesLogin = safeRegexTest(this.matching.getDDGMatcherRegex("loginRegex"), path); + const matchesSignup = safeRegexTest(this.matching.getDDGMatcherRegex("conservativeSignupRegex"), path); + if (matchesLogin && matchesSignup) + return; + if (matchesLogin) { + this.decreaseSignalBy(1, "url matches login"); + } + if (matchesSignup) { + this.increaseSignalBy(1, "url matches signup"); + } + } + evaluatePageTitle() { + const pageTitle = document.title; + this.updateSignal({ string: pageTitle, strength: 2, signalType: `page title: ${pageTitle}`, shouldCheckUnifiedForm: true }); + } + evaluatePageHeadings() { + const headings = document.querySelectorAll("h1, h2, h3"); + headings.forEach((heading) => { + const textContent = removeExcessWhitespace(heading.textContent || ""); + this.updateSignal({ + string: textContent, + strength: 0.5, + signalType: `heading: ${textContent}`, + shouldCheckUnifiedForm: true, + shouldBeConservative: true + }); + }); + } + evaluatePage() { + this.evaluatePageTitle(); + this.evaluatePageHeadings(); + const buttons = document.querySelectorAll(this.matching.cssSelector("submitButtonSelector")); + buttons.forEach((button) => { + if (button instanceof HTMLButtonElement) { + if (!button.form && !button.closest("form")) { + this.evaluateElement(button); + this.evaluateElAttributes(button, 0.5); + } + } + }); + } + evaluateElement(el) { + const string = getTextShallow(el); + if (el.matches(this.matching.cssSelector("password"))) { + this.updateSignal({ + string: el.getAttribute("autocomplete") || el.getAttribute("name") || "", + strength: 5, + signalType: `explicit: ${el.getAttribute("autocomplete")}` + }); + return; + } + if (el.matches(this.matching.cssSelector("submitButtonSelector") + ", *[class*=button]")) { + let likelyASubmit = isLikelyASubmitButton(el, this.matching); + let shouldFlip = false; + if (likelyASubmit) { + this.form.querySelectorAll("input[type=submit], button[type=submit]").forEach( + (submit) => { + if (el.type !== "submit" && el !== submit) { + likelyASubmit = false; + } + } + ); + } else { + const thereIsASubmitButton = Boolean(this.form.querySelector("input[type=submit], button[type=submit]")); + const isSocialButton = /facebook|twitter|google|apple/i.test(string); + shouldFlip = thereIsASubmitButton && !isSocialButton; + } + const strength = likelyASubmit ? 20 : 4; + this.updateSignal({ string, strength, signalType: `button: ${string}`, shouldFlip }); + return; + } + if (el instanceof HTMLAnchorElement && el.href && el.getAttribute("href") !== "#" || (el.getAttribute("role") || "").toUpperCase() === "LINK" || el.matches("button[class*=secondary]")) { + let shouldFlip = true; + let strength = 1; + if (safeRegexTest(this.matching.getDDGMatcherRegex("resetPasswordLink"), string)) { + shouldFlip = false; + strength = 3; + } else if (safeRegexTest(this.matching.getDDGMatcherRegex("loginProvidersRegex"), string)) { + shouldFlip = false; + } + this.updateSignal({ string, strength, signalType: `external link: ${string}`, shouldFlip }); + } else { + this.updateSignal({ string, strength: 1, signalType: `generic: ${string}`, shouldCheckUnifiedForm: true }); } } - } + evaluateForm() { + this.evaluateUrl(); + this.evaluatePageTitle(); + this.evaluateElAttributes(this.form); + this.form.querySelectorAll(this.matching.cssSelector("safeUniversalSelector")).forEach((el) => { + const displayValue = window.getComputedStyle(el, null).getPropertyValue("display"); + if (displayValue !== "none") + this.evaluateElement(el); + }); + const relevantFields = this.form.querySelectorAll(this.matching.cssSelector("genericTextField")); + if (relevantFields.length >= 4) { + this.increaseSignalBy(relevantFields.length * 1.5, "many fields: it is probably not a login"); + } + if (this.autofillSignal === 0) { + this.evaluatePageHeadings(); + } + return this; + } + /** + * Tries to infer if it's a credit card form + * @returns {boolean} + */ + isCCForm() { + if (this._isCCForm !== void 0) + return this._isCCForm; + const formEl = this.form; + const ccFieldSelector = this.matching.joinCssSelectors("cc"); + if (!ccFieldSelector) { + this._isCCForm = false; + return this._isCCForm; + } + const hasCCSelectorChild = formEl.matches(ccFieldSelector) || formEl.querySelector(ccFieldSelector); + if (hasCCSelectorChild) { + this._isCCForm = true; + return this._isCCForm; + } + const hasCCAttribute = [...formEl.attributes].some( + ({ name, value }) => safeRegexTest(/(credit|payment).?card/i, `${name}=${value}`) + ); + if (hasCCAttribute) { + this._isCCForm = true; + return this._isCCForm; + } + const textMatches = formEl.textContent?.match(/(credit|payment).?card(.?number)?|ccv|security.?code|cvv|cvc|csc/ig); + const deDupedMatches = new Set(textMatches?.map((match) => match.toLowerCase())); + this._isCCForm = Boolean(textMatches && deDupedMatches.size > 1); + return this._isCCForm; + } + }; + var FormAnalyzer_default = FormAnalyzer; - /** - * Stores credentials locally - * @param {CredentialsObject[]} credentials - */ - storeLocalCredentials(credentials) { - credentials.forEach(cred => delete cred.password); - this.#data.credentials = credentials; - } - getTopContextData() { - return this.#data.topContextData; - } + // src/Form/logo-svg.js + var daxSvg = ` + + + + + + + + + + + + + +`.trim(); + var daxBase64 = `data:image/svg+xml;base64,${window.btoa(daxSvg)}`; + var daxGrayscaleSvg = ` + + + + + + +`.trim(); + var daxGrayscaleBase64 = `data:image/svg+xml;base64,${window.btoa(daxGrayscaleSvg)}`; + + // src/UI/img/ddgPasswordIcon.js + var ddgPasswordIconBase = "data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNS4zMzQgNi42NjdhMiAyIDAgMSAwIDAgNCAyIDIgMCAwIDAgMC00Wm0tLjY2NyAyYS42NjcuNjY3IDAgMSAxIDEuMzMzIDAgLjY2Ny42NjcgMCAwIDEtMS4zMzMgMFoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTQuNjY3IDRhNS4zMzMgNS4zMzMgMCAwIDAtNS4xODggNi41NzhsLTUuMjg0IDUuMjg0YS42NjcuNjY3IDAgMCAwLS4xOTUuNDcxdjNjMCAuMzY5LjI5OC42NjcuNjY3LjY2N2gyLjY2NmMuNzM3IDAgMS4zMzQtLjU5NyAxLjMzNC0xLjMzM1YxOGguNjY2Yy43MzcgMCAxLjMzNC0uNTk3IDEuMzM0LTEuMzMzdi0xLjMzNEgxMmMuMTc3IDAgLjM0Ni0uMDcuNDcxLS4xOTVsLjY4OC0uNjg4QTUuMzMzIDUuMzMzIDAgMSAwIDE0LjY2NyA0Wm0tNCA1LjMzM2E0IDQgMCAxIDEgMi41NTUgMy43MzIuNjY3LjY2NyAwIDAgMC0uNzEzLjE1bC0uNzg1Ljc4NUgxMGEuNjY3LjY2NyAwIDAgMC0uNjY3LjY2N3YySDhhLjY2Ny42NjcgMCAwIDAtLjY2Ny42NjZ2MS4zMzRoLTJ2LTIuMDU4bDUuMzY1LTUuMzY0YS42NjcuNjY3IDAgMCAwIC4xNjMtLjY3NyAzLjk5NiAzLjk5NiAwIDAgMS0uMTk0LTEuMjM1WiIgY2xpcC1ydWxlPSJldmVub2RkIi8+Cjwvc3ZnPgo="; + var ddgPasswordIconFilled = "data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjNzY0MzEwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNS4yNSA2Ljc1YTIgMiAwIDEgMCAwIDQgMiAyIDAgMCAwIDAtNFptLS41IDJhLjUuNSAwIDEgMSAxIDAgLjUuNSAwIDAgMS0xIDBaIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz4KICAgIDxwYXRoIGZpbGw9IiM3NjQzMTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTE0LjYyNSA0YTUuMzc1IDUuMzc1IDAgMCAwLTUuMjQ0IDYuNTU5bC01LjE2MSA1LjE2YS43NS43NSAwIDAgMC0uMjIuNTMxdjNjMCAuNDE0LjMzNi43NS43NS43NWgyLjk5N2MuNTU0IDAgMS4wMDMtLjQ1IDEuMDAzLTEuMDAzVjE4aC45OTdjLjU1NCAwIDEuMDAzLS40NSAxLjAwMy0xLjAwM1YxNS41SDEyYS43NS43NSAwIDAgMCAuNTMtLjIybC43MS0uNzFBNS4zOCA1LjM4IDAgMCAwIDIwIDkuMzc1IDUuMzc1IDUuMzc1IDAgMCAwIDE0LjYyNSA0Wk0xMC43NSA5LjM3NWEzLjg3NSAzLjg3NSAwIDEgMSAyLjU0IDMuNjQuNzUuNzUgMCAwIDAtLjc4OS4xNzNMMTEuNjkgMTRIMTBhLjc1Ljc1IDAgMCAwLS43NS43NXYxLjc1SDhhLjc1Ljc1IDAgMCAwLS43NS43NXYxLjI1SDUuNXYtMS45NGw1LjI0OC01LjI0OGEuNzUuNzUgMCAwIDAgLjE4NC0uNzU4IDMuODcyIDMuODcyIDAgMCAxLS4xODItMS4xNzlaIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz4KPC9zdmc+Cg=="; + var ddgPasswordGenIconBase = "data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjMDAwIiBkPSJNOC4wNDcgNC42MjVDNy45MzcgNC4xMjUgNy44NjIgNCA3LjUgNGMtLjM2MiAwLS40MzguMTI1LS41NDcuNjI1LS4wNjguMzEtLjE3NyAxLjMzOC0uMjUxIDIuMDc3LS43MzguMDc0LTEuNzY3LjE4My0yLjA3Ny4yNTEtLjUuMTEtLjYyNS4xODQtLjYyNS41NDcgMCAuMzYyLjEyNS40MzcuNjI1LjU0Ny4zMS4wNjcgMS4zMzYuMTc3IDIuMDc0LjI1LjA3My43NjcuMTg1IDEuODQyLjI1NCAyLjA3OC4xMS4zNzUuMTg1LjYyNS41NDcuNjI1LjM2MiAwIC40MzgtLjEyNS41NDctLjYyNS4wNjgtLjMxLjE3Ny0xLjMzNi4yNS0yLjA3NC43NjctLjA3MyAxLjg0Mi0uMTg1IDIuMDc4LS4yNTQuMzc1LS4xMS42MjUtLjE4NS42MjUtLjU0NyAwLS4zNjMtLjEyNS0uNDM4LS42MjUtLjU0Ny0uMzEtLjA2OC0xLjMzOS0uMTc3LTIuMDc3LS4yNTEtLjA3NC0uNzM5LS4xODMtMS43NjctLjI1MS0yLjA3N1oiLz4KICAgIDxwYXRoIGZpbGw9IiMwMDAiIGQ9Ik0xNC42ODEgNS4xOTljLS43NjYgMC0xLjQ4Mi4yMS0yLjA5My41NzhhLjYzNi42MzYgMCAwIDEtLjY1NS0xLjA5IDUuMzQgNS4zNCAwIDEgMSAxLjMwMiA5LjcyMmwtLjc3NS43NzZhLjYzNi42MzYgMCAwIDEtLjQ1LjE4NmgtMS4zOTh2MS42NWMwIC40OTMtLjQuODkzLS44OTMuODkzSDguNTc4djEuMTQxYzAgLjQ5NC0uNC44OTMtLjg5NC44OTNINC42MzZBLjYzNi42MzYgMCAwIDEgNCAxOS4zMTNWMTYuMjZjMC0uMTY5LjA2Ny0uMzMuMTg2LS40NWw1LjU2Mi01LjU2MmEuNjM2LjYzNiAwIDEgMSAuOS45bC01LjM3NiA1LjM3NXYyLjE1M2gyLjAzNHYtMS4zOTljMC0uMzUuMjg1LS42MzYuNjM2LS42MzZIOS4zNHYtMS45MDdjMC0uMzUxLjI4NC0uNjM2LjYzNS0uNjM2aDEuNzcxbC44NjQtLjg2M2EuNjM2LjYzNiAwIDAgMSAuNjY4LS4xNDcgNC4wNjkgNC4wNjkgMCAxIDAgMS40MDItNy44OVoiLz4KICAgIDxwYXRoIGZpbGw9IiMwMDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTEzLjYyNSA4LjQ5OWExLjg3NSAxLjg3NSAwIDEgMSAzLjc1IDAgMS44NzUgMS44NzUgMCAwIDEtMy43NSAwWm0xLjg3NS0uNjI1YS42MjUuNjI1IDAgMSAwIDAgMS4yNS42MjUuNjI1IDAgMCAwIDAtMS4yNVoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZmlsbD0iIzAwMCIgZD0iTTQuNjI1IDEyLjEyNWEuNjI1LjYyNSAwIDEgMCAwLTEuMjUuNjI1LjYyNSAwIDAgMCAwIDEuMjVaIi8+Cjwvc3ZnPgo="; + var ddgPasswordGenIconFilled = "data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjNzY0MzEwIiBkPSJNOC4wNDcgNC42MjVDNy45MzcgNC4xMjUgNy44NjIgNCA3LjUgNGMtLjM2MiAwLS40MzguMTI1LS41NDcuNjI1LS4wNjguMzEtLjE3NyAxLjMzOS0uMjUxIDIuMDc3LS43MzguMDc0LTEuNzY3LjE4My0yLjA3Ny4yNTEtLjUuMTEtLjYyNS4xODUtLjYyNS41NDcgMCAuMzYyLjEyNS40MzcuNjI1LjU0Ny4zMS4wNjcgMS4zMzYuMTc3IDIuMDc0LjI1LjA3My43NjcuMTg1IDEuODQyLjI1NCAyLjA3OC4xMS4zNzUuMTg1LjYyNS41NDcuNjI1LjM2MiAwIC40MzgtLjEyNS41NDctLjYyNS4wNjgtLjMxLjE3Ny0xLjMzNi4yNS0yLjA3NC43NjctLjA3MyAxLjg0Mi0uMTg1IDIuMDc4LS4yNTQuMzc1LS4xMS42MjUtLjE4NS42MjUtLjU0NyAwLS4zNjItLjEyNS0uNDM4LS42MjUtLjU0Ny0uMzEtLjA2OC0xLjMzOS0uMTc3LTIuMDc3LS4yNTEtLjA3NC0uNzM4LS4xODMtMS43NjctLjI1MS0yLjA3N1oiLz4KICAgIDxwYXRoIGZpbGw9IiM3NjQzMTAiIGQ9Ik0xNC42ODEgNS4xOTljLS43NjYgMC0xLjQ4Mi4yMTEtMi4wOTMuNTc4YS42MzYuNjM2IDAgMCAxLS42NTUtMS4wOSA1LjM0IDUuMzQgMCAxIDEgMS4zMDIgOS43MjNsLS43NzUuNzc1YS42MzYuNjM2IDAgMCAxLS40NS4xODZoLTEuMzk4djEuNjVjMCAuNDkzLS40Ljg5My0uODkzLjg5M0g4LjU3OHYxLjE0MWMwIC40OTQtLjQuODk0LS44OTQuODk0SDQuNjM2QS42MzYuNjM2IDAgMCAxIDQgMTkuMzEzVjE2LjI2YS42NC42NCAwIDAgMSAuMTg2LS40NWw1LjU2Mi01LjU2MmEuNjM2LjYzNiAwIDEgMSAuOS45bC01LjM3NiA1LjM3NXYyLjE1M2gyLjAzNHYtMS4zOTljMC0uMzUuMjg1LS42MzYuNjM2LS42MzZIOS4zNHYtMS45MDdjMC0uMzUuMjg0LS42MzYuNjM1LS42MzZoMS43NzFsLjg2NC0uODYzYS42MzYuNjM2IDAgMCAxIC42NjgtLjE0NyA0LjA2OSA0LjA2OSAwIDEgMCAxLjQwMi03Ljg5WiIvPgogICAgPHBhdGggZmlsbD0iIzc2NDMxMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTMuNjI1IDguNWExLjg3NSAxLjg3NSAwIDEgMSAzLjc1IDAgMS44NzUgMS44NzUgMCAwIDEtMy43NSAwWm0xLjg3NS0uNjI2YS42MjUuNjI1IDAgMSAwIDAgMS4yNS42MjUuNjI1IDAgMCAwIDAtMS4yNVoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZmlsbD0iIzc2NDMxMCIgZD0iTTQuNjI1IDEyLjEyNWEuNjI1LjYyNSAwIDEgMCAwLTEuMjUuNjI1LjYyNSAwIDAgMCAwIDEuMjVaIi8+Cjwvc3ZnPgo="; + + // src/InputTypes/CreditCard.js + var _data4; + var CreditCardTooltipItem = class { + /** @param {CreditCardObject} data */ + constructor(data) { + /** @type {CreditCardObject} */ + __privateAdd(this, _data4, void 0); + __publicField(this, "id", () => String(__privateGet(this, _data4).id)); + __publicField(this, "labelMedium", (_) => __privateGet(this, _data4).title); + __publicField(this, "labelSmall", (_) => __privateGet(this, _data4).displayNumber); + __privateSet(this, _data4, data); + } + }; + _data4 = new WeakMap(); + + // src/InputTypes/Identity.js + var _data5; + var IdentityTooltipItem = class { + /** @param {IdentityObject} data */ + constructor(data) { + /** @type {IdentityObject} */ + __privateAdd(this, _data5, void 0); + __publicField(this, "id", () => String(__privateGet(this, _data5).id)); + __publicField(this, "labelMedium", (subtype) => { + if (subtype === "addressCountryCode") { + return getCountryDisplayName("en", __privateGet(this, _data5).addressCountryCode || ""); + } + if (__privateGet(this, _data5).id === "privateAddress") { + return "Generate Private Duck Address"; + } + return __privateGet(this, _data5)[subtype]; + }); + __publicField(this, "labelSmall", (_) => { + return __privateGet(this, _data5).title; + }); + __privateSet(this, _data5, data); + } + label(subtype) { + if (__privateGet(this, _data5).id === "privateAddress") { + return __privateGet(this, _data5)[subtype]; + } + return null; + } + }; + _data5 = new WeakMap(); - /** - * @deprecated use `availableInputTypes.credentials` directly instead - * @returns {boolean} - */ - get hasLocalCredentials() { - return this.#data.credentials.length > 0; - } - getLocalCredentials() { - return this.#data.credentials.map(cred => { - const { - password, - ...rest - } = cred; - return rest; - }); - } - /** - * @deprecated use `availableInputTypes.identities` directly instead - * @returns {boolean} - */ - get hasLocalIdentities() { - return this.#data.identities.length > 0; - } - getLocalIdentities() { - return this.#data.identities; - } + // src/Form/inputTypeConfig.js + var getIdentitiesIcon = (input, { device }) => { + if (!canBeInteractedWith(input)) + return ""; + const { isDDGApp, isFirefox, isExtension } = device.globalConfig; + const subtype = getInputSubtype(input); + if (device.inContextSignup?.isAvailable(subtype)) { + if (isDDGApp || isFirefox) { + return daxGrayscaleBase64; + } else if (isExtension) { + return chrome.runtime.getURL("img/logo-small-grayscale.svg"); + } + } + if (subtype === "emailAddress" && device.isDeviceSignedIn()) { + if (isDDGApp || isFirefox) { + return daxBase64; + } else if (isExtension) { + return chrome.runtime.getURL("img/logo-small.svg"); + } + } + return ""; + }; + var getIdentitiesAlternateIcon = (input, { device }) => { + if (!canBeInteractedWith(input)) + return ""; + const { isDDGApp, isFirefox, isExtension } = device.globalConfig; + const subtype = getInputSubtype(input); + const isIncontext = device.inContextSignup?.isAvailable(subtype); + const isEmailProtection = subtype === "emailAddress" && device.isDeviceSignedIn(); + if (isIncontext || isEmailProtection) { + if (isDDGApp || isFirefox) { + return daxBase64; + } else if (isExtension) { + return chrome.runtime.getURL("img/logo-small.svg"); + } + } + return ""; + }; + var canBeInteractedWith = (input) => !input.readOnly && !input.disabled; + var canBeAutofilled = async (input, device) => { + if (!canBeInteractedWith(input)) + return false; + const mainType = getInputMainType(input); + const subtype = getInputSubtype(input); + await device.settings.populateDataIfNeeded({ mainType, subtype }); + const canAutofill = device.settings.canAutofillType({ mainType, subtype }, device.inContextSignup); + return Boolean(canAutofill); + }; + var inputTypeConfig = { + /** @type {CredentialsInputTypeConfig} */ + credentials: { + type: "credentials", + displayName: "passwords", + getIconBase: (input, { device }) => { + if (!canBeInteractedWith(input)) + return ""; + if (device.settings.featureToggles.inlineIcon_credentials) { + const subtype = getInputSubtype(input); + const variant = getInputVariant(input); + if (subtype === "password" && variant === "new") { + return ddgPasswordGenIconBase; + } + return ddgPasswordIconBase; + } + return ""; + }, + getIconFilled: (input, { device }) => { + if (device.settings.featureToggles.inlineIcon_credentials) { + const subtype = getInputSubtype(input); + const variant = getInputVariant(input); + if (subtype === "password" && variant === "new") { + return ddgPasswordGenIconFilled; + } + return ddgPasswordIconFilled; + } + return ""; + }, + getIconAlternate: () => "", + shouldDecorate: async (input, { isLogin, isHybrid, device }) => { + const subtype = getInputSubtype(input); + const variant = getInputVariant(input); + if (device.settings.featureToggles.password_generation) { + if (subtype === "password" && variant === "new") { + return canBeInteractedWith(input); + } + } + if (isLogin || isHybrid || variant === "current") { + return canBeAutofilled(input, device); + } + return false; + }, + dataType: "Credentials", + tooltipItem: (data) => createCredentialsTooltipItem(data) + }, + /** @type {CreditCardsInputTypeConfig} */ + creditCards: { + type: "creditCards", + displayName: "credit cards", + getIconBase: () => "", + getIconFilled: () => "", + getIconAlternate: () => "", + shouldDecorate: async (input, { device }) => { + return canBeAutofilled(input, device); + }, + dataType: "CreditCards", + tooltipItem: (data) => new CreditCardTooltipItem(data) + }, + /** @type {IdentitiesInputTypeConfig} */ + identities: { + type: "identities", + displayName: "identities", + getIconBase: getIdentitiesIcon, + getIconFilled: getIdentitiesIcon, + getIconAlternate: getIdentitiesAlternateIcon, + shouldDecorate: async (input, { device }) => { + return canBeAutofilled(input, device); + }, + dataType: "Identities", + tooltipItem: (data) => new IdentityTooltipItem(data) + }, + /** @type {UnknownInputTypeConfig} */ + unknown: { + type: "unknown", + displayName: "", + getIconBase: () => "", + getIconFilled: () => "", + getIconAlternate: () => "", + shouldDecorate: async () => false, + dataType: "", + tooltipItem: (_data7) => { + throw new Error("unreachable - setting tooltip to unknown field type"); + } + } + }; + var getInputConfig = (input) => { + const inputType = getInputType(input); + return getInputConfigFromType(inputType); + }; + var getInputConfigFromType = (inputType) => { + const inputMainType = getMainTypeFromType(inputType); + return inputTypeConfig[inputMainType]; + }; + var isFieldDecorated = (input) => { + return input.hasAttribute(constants.ATTR_INPUT_TYPE); + }; - /** - * @deprecated use `availableInputTypes.creditCards` directly instead - * @returns {boolean} - */ - get hasLocalCreditCards() { - return this.#data.creditCards.length > 0; - } - /** @return {CreditCardObject[]} */ - getLocalCreditCards() { - return this.#data.creditCards; - } - async startInit() { - if (this.isInitializationStarted) return; - this.alreadyInitialized = true; - await this.settings.refresh(); - this.addDeviceListeners(); - await this.setupAutofill(); - this.uiController = this.createUIController(); + // src/Form/inputStyles.js + var getIcon = (input, form, type = "base") => { + const config = getInputConfig(input); + if (type === "base") { + return config.getIconBase(input, form); + } + if (type === "filled") { + return config.getIconFilled(input, form); + } + if (type === "alternate") { + return config.getIconAlternate(input, form); + } + return ""; + }; + var getBasicStyles = (input, icon) => ({ + // Height must be > 0 to account for fields initially hidden + "background-size": `auto ${input.offsetHeight <= 30 && input.offsetHeight > 0 ? "100%" : "24px"}`, + "background-position": "center right", + "background-repeat": "no-repeat", + "background-origin": "content-box", + "background-image": `url(${icon})`, + "transition": "background 0s" + }); + var getIconStylesBase = (input, form) => { + const icon = getIcon(input, form); + if (!icon) + return {}; + return getBasicStyles(input, icon); + }; + var getIconStylesAlternate = (input, form) => { + const icon = getIcon(input, form, "alternate"); + if (!icon) + return {}; + return { + ...getBasicStyles(input, icon) + }; + }; + var getIconStylesAutofilled = (input, form) => { + const icon = getIcon(input, form, "filled"); + const iconStyle = icon ? getBasicStyles(input, icon) : {}; + return { + ...iconStyle, + "background-color": "#F8F498", + "color": "#333333" + }; + }; - // this is the temporary measure to support windows whilst we still have 'setupAutofill' - // eventually all interfaces will use this - if (!this.settings.enabled) { - return; + // src/Form/Form.js + var { + ATTR_AUTOFILL, + ATTR_INPUT_TYPE: ATTR_INPUT_TYPE2, + MAX_INPUTS_PER_FORM, + MAX_FORM_RESCANS + } = constants; + var Form = class { + /** + * @param {HTMLElement} form + * @param {HTMLInputElement|HTMLSelectElement} input + * @param {import("../DeviceInterface/InterfacePrototype").default} deviceInterface + * @param {import("../Form/matching").Matching} [matching] + * @param {Boolean} [shouldAutoprompt] + */ + constructor(form, input, deviceInterface, matching, shouldAutoprompt = false) { + /** @type {import("../Form/matching").Matching} */ + __publicField(this, "matching"); + /** @type {HTMLElement} */ + __publicField(this, "form"); + /** @type {HTMLInputElement | null} */ + __publicField(this, "activeInput"); + this.form = form; + this.matching = matching || createMatching(); + this.formAnalyzer = new FormAnalyzer_default(form, input, matching); + this.device = deviceInterface; + this.inputs = { + all: /* @__PURE__ */ new Set(), + credentials: /* @__PURE__ */ new Set(), + creditCards: /* @__PURE__ */ new Set(), + identities: /* @__PURE__ */ new Set(), + unknown: /* @__PURE__ */ new Set() + }; + this.touched = /* @__PURE__ */ new Set(); + this.listeners = /* @__PURE__ */ new Set(); + this.activeInput = null; + this.isAutofilling = false; + this.submitHandlerExecuted = false; + this.shouldPromptToStoreData = deviceInterface.settings.featureToggles.credentials_saving; + this.shouldAutoSubmit = this.device.globalConfig.isMobileApp; + this.intObs = new IntersectionObserver((entries) => { + for (const entry of entries) { + if (!entry.isIntersecting) + this.removeTooltip(); + } + }); + this.rescanCount = 0; + this.mutObsConfig = { childList: true, subtree: true }; + this.mutObs = new MutationObserver( + (records) => { + const anythingRemoved = records.some((record) => record.removedNodes.length > 0); + if (anythingRemoved) { + if (!this.form.isConnected) { + this.destroy(); + return; + } + if ([...this.inputs.all].some((input2) => !input2.isConnected)) { + this.mutObs.disconnect(); + window.requestIdleCallback(() => { + this.formAnalyzer = new FormAnalyzer_default(this.form, input, this.matching); + this.recategorizeAllInputs(); + }); + } + } + } + ); + this.initFormListeners(); + this.categorizeInputs(); + this.logFormInfo(); + if (shouldAutoprompt) { + this.promptLoginIfNeeded(); + } } - await this.setupSettingsPage(); - await this.postInit(); - if (this.settings.featureToggles.credentials_saving) { - (0, _initFormSubmissionsApi.initFormSubmissionsApi)(this.scanner.forms, this.scanner.matching); + get isLogin() { + return this.formAnalyzer.isLogin; } - } - async init() { - // bail very early if we can - const isEnabledInitiallyViaConfig = (0, _autofillUtils.autofillEnabled)(this.globalConfig); - if (!isEnabledInitiallyViaConfig) { - return; + get isSignup() { + return this.formAnalyzer.isSignup; } - const handler = async () => { - if (document.readyState === 'complete') { - window.removeEventListener('load', handler); - document.removeEventListener('readystatechange', handler); - await this.startInit(); + get isHybrid() { + return this.formAnalyzer.isHybrid; + } + get isCCForm() { + return this.formAnalyzer.isCCForm(); + } + logFormInfo() { + if (!shouldLog()) + return; + console.log(`Form type: %c${this.getFormType()}`, "font-weight: bold"); + console.log("Signals: ", this.formAnalyzer.signals); + console.log("Wrapping element: ", this.form); + console.log("Inputs: ", this.inputs); + console.log("Submit Buttons: ", this.submitButtons); + } + getFormType() { + if (this.isHybrid) + return `hybrid (hybrid score: ${this.formAnalyzer.hybridSignal}, score: ${this.formAnalyzer.autofillSignal})`; + if (this.isLogin) + return `login (score: ${this.formAnalyzer.autofillSignal}, hybrid score: ${this.formAnalyzer.hybridSignal})`; + if (this.isSignup) + return `signup (score: ${this.formAnalyzer.autofillSignal}, hybrid score: ${this.formAnalyzer.hybridSignal})`; + return "something went wrong"; + } + /** + * Checks if the form element contains the activeElement or the event target + * @return {boolean} + * @param {KeyboardEvent | null} [e] + */ + hasFocus(e) { + return this.form.contains(getActiveElement()) || this.form.contains( + /** @type HTMLElement */ + e?.target + ); + } + submitHandler(via = "unknown") { + if (this.device.globalConfig.isDDGTestMode) { + console.log("Form.submitHandler via:", via, this); } - }; - if (document.readyState === 'complete') { - await this.startInit(); - } else { - window.addEventListener('load', handler); - document.addEventListener('readystatechange', handler); + if (this.submitHandlerExecuted) + return; + const values = this.getValuesReadyForStorage(); + this.device.postSubmit?.(values, this); + this.submitHandlerExecuted = true; } - } - postInit() { - const cleanup = this.scanner.init(); - this.addLogoutListener(() => { - cleanup('Logged out'); - if (this.globalConfig.isDDGDomain) { - (0, _autofillUtils.notifyWebApp)({ - deviceSignedIn: { - value: false - } + /** + * Reads the values from the form without preparing to store them + * @return {InternalDataStorageObject} + */ + getRawValues() { + const formValues = [...this.inputs.credentials, ...this.inputs.identities, ...this.inputs.creditCards].reduce((output, inputEl) => { + const mainType = getInputMainType(inputEl); + const subtype = getInputSubtype(inputEl); + let value = inputEl.value || output[mainType]?.[subtype]; + if (subtype === "addressCountryCode") { + value = inferCountryCodeFromElement(inputEl); + } + if (subtype === "password" && value?.length <= 3) { + value = void 0; + } + if (value) { + output[mainType][subtype] = value; + } + return output; + }, { credentials: {}, creditCards: {}, identities: {} }); + if (!formValues.credentials.username && !formValues.identities.emailAddress) { + const hiddenFields = ( + /** @type [HTMLInputElement] */ + [...this.form.querySelectorAll("input[type=hidden]")] + ); + const probableField = hiddenFields.find((field) => { + const regex = new RegExp("email|" + this.matching.getDDGMatcherRegex("username")?.source); + const attributeText = field.id + " " + field.name; + return safeRegexTest(regex, attributeText); }); + if (probableField?.value) { + formValues.credentials.username = probableField.value; + } else if ( + // If a form has phone + password(s) fields, save the phone as username + formValues.identities.phone && this.inputs.all.size - this.inputs.unknown.size < 4 + ) { + formValues.credentials.username = formValues.identities.phone; + } else { + this.form.querySelectorAll(this.matching.cssSelector("safeUniversalSelector")).forEach((el) => { + const elText = getTextShallow(el); + if (elText.length > 70) + return; + const emailOrUsername = elText.match( + // https://www.emailregex.com/ + /[a-zA-Z\d.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z\d-]+(?:\.[a-zA-Z\d-]+)*/ + )?.[0]; + if (emailOrUsername) { + formValues.credentials.username = emailOrUsername; + } + }); + } } - }); - } - - /** - * @deprecated This was a port from the macOS implementation so the API may not be suitable for all - * @returns {Promise} - */ - async getSelectedCredentials() { - throw new Error('`getSelectedCredentials` not implemented'); - } - isTestMode() { - return this.globalConfig.isDDGTestMode; - } - - /** - * This indicates an item was selected on Desktop, and we should try to autofill - * - * Note: When we're in a top-frame scenario, like on like macOS & Windows in the webview, - * this method gets overridden {@see WindowsOverlayDeviceInterface} {@see AppleOverlayDeviceInterface} - * - * @param {IdentityObject|CreditCardObject|CredentialsObject|{email:string, id: string}} data - * @param {string} type - */ - async selectedDetail(data, type) { - const form = this.activeForm; - if (!form) { - return; + return formValues; } - - // are we autofilling email? - if (type === 'email' && 'email' in data) { - form.autofillEmail(data.email); - } else { - form.autofillData(data, type); + /** + * Return form values ready for storage + * @returns {DataStorageObject} + */ + getValuesReadyForStorage() { + const formValues = this.getRawValues(); + return prepareFormValuesForStorage(formValues); } - const isPrivateAddress = data.id === 'privateAddress'; - /** - * This is desktop only: was it a private address? if so, save it with - * the trigger 'emailProtection' so that native sides can use it + * Determine if the form has values we want to store in the device + * @param {DataStorageObject} [values] + * @return {boolean} */ - if (isPrivateAddress) { - this.refreshAlias(); - if ('emailAddress' in data && data.emailAddress) { - this.emailProtection.storeReceived(data.emailAddress); - - /** @type {DataStorageObject} */ - const formValues = { - credentials: { - username: data.emailAddress, - autogenerated: true - } - }; - this.storeFormData(formValues, 'emailProtection'); + hasValues(values) { + const { credentials, creditCards, identities } = values || this.getValuesReadyForStorage(); + return Boolean(credentials || creditCards || identities); + } + async removeTooltip() { + const tooltip = this.device.isTooltipActive(); + if (this.isAutofilling || !tooltip) { + return; } + await this.device.removeTooltip(); + this.intObs?.disconnect(); } - await this.removeTooltip(); - } - - /** - * Before the DataWebTooltip opens, we collect the data based on the config.type - * @param {InputTypeConfigs} config - * @param {import('../Form/matching').SupportedTypes} inputType - * @param {TopContextData} [data] - * @returns {(CredentialsObject|CreditCardObject|IdentityObject)[]} - */ - dataForAutofill(config, inputType, data) { - const subtype = (0, _matching.getSubtypeFromType)(inputType); - if (config.type === 'identities') { - return this.getLocalIdentities().filter(identity => !!identity[subtype]); - } - if (config.type === 'creditCards') { - return this.getLocalCreditCards(); - } - if (config.type === 'credentials') { - if (data) { - if (Array.isArray(data.credentials) && data.credentials.length > 0) { - return data.credentials; - } else { - return this.getLocalCredentials().filter(cred => !!cred[subtype] || subtype === 'password' || cred.id === _Credentials.PROVIDER_LOCKED); - } + showingTooltip(input) { + this.intObs?.observe(input); + } + removeInputHighlight(input) { + if (!input.classList.contains("ddg-autofilled")) + return; + removeInlineStyles(input, getIconStylesAutofilled(input, this)); + removeInlineStyles(input, { "cursor": "pointer" }); + input.classList.remove("ddg-autofilled"); + this.addAutofillStyles(input); + } + resetIconStylesToInitial() { + const input = this.activeInput; + if (input) { + const initialStyles = getIconStylesBase(input, this); + addInlineStyles(input, initialStyles); } } - return []; - } - - /** - * @param {object} params - * @param {import("../Form/Form").Form} params.form - * @param {HTMLInputElement} params.input - * @param {{ x: number; y: number; } | null} params.click - * @param {import('../deviceApiCalls/__generated__/validators-ts').GetAutofillDataRequest['trigger']} params.trigger - * @param {import('../UI/controllers/UIController.js').AttachArgs["triggerMetaData"]} params.triggerMetaData - */ - attachTooltip(params) { - const { - form, - input, - click, - trigger - } = params; - // Avoid flashing tooltip from background tabs on macOS - if (document.visibilityState !== 'visible' && trigger !== 'postSignup') return; - // Only autoprompt on mobile devices - if (trigger === 'autoprompt' && !this.globalConfig.isMobileApp) return; - // Only fire autoprompt once - if (trigger === 'autoprompt' && this.autopromptFired) return; - form.activeInput = input; - this.activeForm = form; - const inputType = (0, _matching.getInputType)(input); - - /** @type {PosFn} */ - const getPosition = () => { - // In extensions, the tooltip is centered on the Dax icon - const alignLeft = this.globalConfig.isApp || this.globalConfig.isWindows; - return alignLeft ? input.getBoundingClientRect() : (0, _autofillUtils.getDaxBoundingBox)(input); - }; - - // todo: this will be migrated to use NativeUIController soon - if (this.globalConfig.isMobileApp && inputType === 'identities.emailAddress') { - this.getAlias().then(alias => { - if (alias) { - form.autofillEmail(alias); - /** - * We're on mobile here, so we just record the email received. - * Then later in the form submission we can compare the values - */ - this.emailProtection.storeReceived(alias); - } else { - form.activeInput?.focus(); + removeAllHighlights(e, dataType) { + if (e && !e.isTrusted) + return; + this.resetShouldPromptToStoreData(); + this.execOnInputs((input) => this.removeInputHighlight(input), dataType); + } + removeInputDecoration(input) { + removeInlineStyles(input, getIconStylesBase(input, this)); + removeInlineStyles(input, getIconStylesAlternate(input, this)); + input.removeAttribute(ATTR_AUTOFILL); + input.removeAttribute(ATTR_INPUT_TYPE2); + } + removeAllDecorations() { + this.execOnInputs((input) => this.removeInputDecoration(input)); + this.listeners.forEach(({ el, type, fn, opts }) => el.removeEventListener(type, fn, opts)); + } + redecorateAllInputs() { + this.removeAllDecorations(); + this.execOnInputs((input) => { + if (input instanceof HTMLInputElement) { + this.decorateInput(input); } - - // Update data from native-side in case the `getAlias` call - // has included a successful in-context signup - this.updateForStateChange(); - this.onFinishedAutofill(); }); - return; } - - /** @type {TopContextData} */ - const topContextData = { - inputType - }; - - // Allow features to append/change top context data - // for example, generated passwords may get appended here - const processedTopContext = this.preAttachTooltip(topContextData, input, form); - this.uiController?.attach({ - input, - form, - click, - getPosition, - topContextData: processedTopContext, - device: this, - trigger, - triggerMetaData: params.triggerMetaData - }); - if (trigger === 'autoprompt') { - this.autopromptFired = true; + /** + * Removes all scoring attributes from the inputs and deletes them from memory + */ + forgetAllInputs() { + this.execOnInputs((input) => { + input.removeAttribute(ATTR_AUTOFILL); + input.removeAttribute(ATTR_INPUT_TYPE2); + }); + Object.values(this.inputs).forEach((inputSet) => inputSet.clear()); } - } - - /** - * When an item was selected, we then call back to the device - * to fetch the full suite of data needed to complete the autofill - * - * @param {import('../Form/matching').SupportedTypes} inputType - * @param {(CreditCardObject|IdentityObject|CredentialsObject)[]} items - * @param {CreditCardObject['id']|IdentityObject['id']|CredentialsObject['id']} id - */ - onSelect(inputType, items, id) { - id = String(id); - const mainType = (0, _matching.getMainTypeFromType)(inputType); - const subtype = (0, _matching.getSubtypeFromType)(inputType); - if (id === _Credentials.PROVIDER_LOCKED) { - return this.thirdPartyProvider?.askToUnlockProvider(); - } - const matchingData = items.find(item => String(item.id) === id); - if (!matchingData) throw new Error('unreachable (fatal)'); - const dataPromise = (() => { - switch (mainType) { - case 'creditCards': - return this.getAutofillCreditCard(id); - case 'identities': - return this.getAutofillIdentity(id); - case 'credentials': - { - if (_Credentials.AUTOGENERATED_KEY in matchingData) { - const autogeneratedPayload = { - ...matchingData, - username: '' - }; - return Promise.resolve({ - success: autogeneratedPayload - }); - } - return this.getAutofillCredentials(id); - } - default: - throw new Error('unreachable!'); + /** + * Resets our input scoring and starts from scratch + */ + recategorizeAllInputs() { + if (this.rescanCount >= MAX_FORM_RESCANS) { + this.mutObs.disconnect(); + return; } - })(); - - // wait for the data back from the device - dataPromise.then(response => { - if (response) { - const data = response.success || response; - if (mainType === 'identities') { - this.firePixel({ - pixelName: 'autofill_identity', - params: { - fieldType: subtype - } - }); - switch (id) { - case 'personalAddress': - this.firePixel({ - pixelName: 'autofill_personal_address' - }); - break; - case 'privateAddress': - this.firePixel({ - pixelName: 'autofill_private_address' - }); - break; - default: - // Also fire pixel when filling an identity with the personal duck address from an email field - const checks = [subtype === 'emailAddress', this.hasLocalAddresses, data?.emailAddress === (0, _autofillUtils.formatDuckAddress)(this.#addresses.personalAddress)]; - if (checks.every(Boolean)) { - this.firePixel({ - pixelName: 'autofill_personal_address' - }); - } - break; - } + this.rescanCount++; + this.initialScanComplete = false; + this.removeAllDecorations(); + this.forgetAllInputs(); + this.initFormListeners(); + this.categorizeInputs(); + } + resetAllInputs() { + this.execOnInputs((input) => { + setValue(input, "", this.device.globalConfig); + this.removeInputHighlight(input); + }); + if (this.activeInput) + this.activeInput.focus(); + this.matching.clear(); + } + resetShouldPromptToStoreData() { + this.shouldPromptToStoreData = this.device.settings.featureToggles.credentials_saving; + } + dismissTooltip() { + this.removeTooltip(); + } + // This removes all listeners to avoid memory leaks and weird behaviours + destroy() { + this.mutObs.disconnect(); + this.removeAllDecorations(); + this.removeTooltip(); + this.forgetAllInputs(); + this.matching.clear(); + this.intObs = null; + this.device.scanner.forms.delete(this.form); + } + initFormListeners() { + this.addListener(this.form, "input", () => { + if (!this.isAutofilling) { + this.submitHandlerExecuted = false; + this.resetShouldPromptToStoreData(); } - // some platforms do not include a `success` object, why? - return this.selectedDetail(data, mainType); - } else { - return Promise.reject(new Error('none-success response')); + }); + if (this.form instanceof HTMLFormElement && this.form.getRootNode()) { + this.addListener(this.form, "submit", () => { + this.submitHandler("in-form submit handler"); + }, { capture: true }); } - }).catch(e => { - console.error(e); - return this.removeTooltip(); - }); - } - isTooltipActive() { - return this.uiController?.isActive?.() ?? false; - } - removeTooltip() { - return this.uiController?.removeTooltip?.('interface'); - } - onFinishedAutofill() { - // Let input handlers know we've stopped autofilling - this.activeForm?.activeInput?.dispatchEvent(new Event('mouseleave')); - } - async updateForStateChange() { - // Remove decorations before refreshing data to make sure we - // remove the currently set icons - this.activeForm?.removeAllDecorations(); - - // Update for any state that may have changed - await this.refreshData(); - - // Add correct icons and behaviour - this.activeForm?.recategorizeAllInputs(); - } - async refreshData() { - await this.inContextSignup?.refreshData(); - await this.settings.populateData(); - } - async setupSettingsPage() { - let { - shouldLog - } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { - shouldLog: false - }; - if (!this.globalConfig.isDDGDomain) { - return; } - (0, _autofillUtils.notifyWebApp)({ - isApp: this.globalConfig.isApp - }); - if (this.isDeviceSignedIn()) { - let userData; - try { - userData = await this.getUserData(); - } catch (e) {} - let capabilities; - try { - capabilities = await this.getEmailProtectionCapabilities(); - } catch (e) {} - - // Set up listener for web app actions - if (this.globalConfig.isDDGDomain) { - window.addEventListener('message', e => { - if (e.data.removeUserData) { - this.removeUserData(); - } - if (e.data.closeEmailProtection) { - this.closeEmailProtection(); - } - }); + categorizeInputs() { + const selector = this.matching.cssSelector("formInputsSelector"); + if (this.form.matches(selector)) { + this.addInput(this.form); + } else { + let foundInputs = []; + if (this.form instanceof HTMLFormElement) { + foundInputs = [...this.form.elements].filter((el) => el.matches(selector)); + } else { + foundInputs = this.form.querySelectorAll(selector); + } + if (foundInputs.length < MAX_INPUTS_PER_FORM) { + foundInputs.forEach((input) => this.addInput(input)); + } else { + this.device.scanner.stopScanner(`The form has too many inputs (${foundInputs.length}), bailing.`); + return; + } } - const hasUserData = userData && !userData.error && Object.entries(userData).length > 0; - (0, _autofillUtils.notifyWebApp)({ - deviceSignedIn: { - value: true, - shouldLog, - userData: hasUserData ? userData : undefined, - capabilities + this.initialScanComplete = true; + if (this.form !== document.body) { + this.mutObs.observe(this.form, this.mutObsConfig); + } + } + get submitButtons() { + const selector = this.matching.cssSelector("submitButtonSelector"); + const allButtons = ( + /** @type {HTMLElement[]} */ + [...this.form.querySelectorAll(selector)] + ); + return allButtons.filter( + (btn) => isPotentiallyViewable(btn) && isLikelyASubmitButton(btn, this.matching) && buttonMatchesFormType(btn, this) + ); + } + attemptSubmissionIfNeeded() { + if (!this.isLogin || // Only submit login forms + this.submitButtons.length > 1) + return; + let isThereAnEmptyVisibleField = false; + this.execOnInputs((input) => { + if (input.value === "" && isPotentiallyViewable(input)) + isThereAnEmptyVisibleField = true; + }, "all", false); + if (isThereAnEmptyVisibleField) + return; + this.submitButtons.forEach((button) => { + if (isPotentiallyViewable(button)) { + button.click(); } }); - } else { - this.trySigningIn(); } - } - async setupAutofill() {} - - /** @returns {Promise} */ - async getAddresses() { - throw new Error('unimplemented'); - } - - /** @returns {Promise>} */ - getUserData() { - return Promise.resolve(null); - } - - /** @returns {void} */ - removeUserData() {} - - /** @returns {void} */ - closeEmailProtection() {} - - /** @returns {Promise>} */ - getEmailProtectionCapabilities() { - throw new Error('unimplemented'); - } - refreshAlias() {} - async trySigningIn() { - if (this.globalConfig.isDDGDomain) { - if (this.attempts < 10) { - this.attempts++; - const data = await (0, _autofillUtils.sendAndWaitForAnswer)(_autofillUtils.SIGN_IN_MSG, 'addUserData'); - // This call doesn't send a response, so we can't know if it succeeded - this.storeUserData(data); - await this.setupAutofill(); - await this.settings.refresh(); - await this.setupSettingsPage({ - shouldLog: true - }); - await this.postInit(); - } else { - console.warn('max attempts reached, bailing'); + /** + * Executes a function on input elements. Can be limited to certain element types + * @param {(input: HTMLInputElement|HTMLSelectElement) => void} fn + * @param {'all' | SupportedMainTypes} inputType + * @param {boolean} shouldCheckForDecorate + */ + execOnInputs(fn, inputType = "all", shouldCheckForDecorate = true) { + const inputs = this.inputs[inputType]; + for (const input of inputs) { + let canExecute = true; + if (shouldCheckForDecorate) { + canExecute = isFieldDecorated(input); + } + if (canExecute) + fn(input); } } - } - storeUserData(_data) {} - addDeviceListeners() {} - - /** @param {() => void} _fn */ - addLogoutListener(_fn) {} - isDeviceSignedIn() { - return false; - } - /** - * @returns {Promise} - */ - async getAlias() { - return undefined; - } - // PM endpoints - getAccounts() {} - /** - * Gets credentials ready for autofill - * @param {CredentialsObject['id']} id - the credential id - * @returns {Promise} - */ - async getAutofillCredentials(id) { - return this.deviceApi.request(new _deviceApiCalls.GetAutofillCredentialsCall({ - id: String(id) - })); - } - /** @returns {APIResponse} */ - async getAutofillCreditCard(_id) { - throw new Error('getAutofillCreditCard unimplemented'); - } - /** @returns {Promise<{success: IdentityObject|undefined}>} */ - async getAutofillIdentity(_id) { - throw new Error('getAutofillIdentity unimplemented'); - } - openManagePasswords() {} - openManageCreditCards() {} - openManageIdentities() {} - - /** - * @param {StoreFormData} values - * @param {StoreFormData['trigger']} trigger - */ - storeFormData(values, trigger) { - this.deviceApi.notify(new _deviceApiCalls.StoreFormDataCall({ - ...values, - trigger - })); - } - - /** - * `preAttachTooltip` happens just before a tooltip is show - features may want to append some data - * at this point. - * - * For example, if password generation is enabled, this will generate - * a password and send it to the tooltip as though it were a stored credential. - * - * @param {TopContextData} topContextData - * @param {HTMLInputElement} input - * @param {import("../Form/Form").Form} form - */ - preAttachTooltip(topContextData, input, form) { - // A list of checks to determine if we need to generate a password - const checks = [topContextData.inputType === 'credentials.password.new', this.settings.featureToggles.password_generation]; - - // if all checks pass, generate and save a password - if (checks.every(Boolean)) { - const password = this.passwordGenerator.generate({ - input: input.getAttribute('passwordrules'), - domain: window.location.hostname - }); - const rawValues = form.getRawValues(); - const username = rawValues.credentials?.username || rawValues.identities?.emailAddress || ''; - - // append the new credential to the topContextData so that the top autofill can display it - topContextData.credentials = [(0, _Credentials.fromPassword)(password, username)]; + addInput(input) { + if (this.inputs.all.has(input)) + return this; + if (this.inputs.all.size > MAX_INPUTS_PER_FORM) { + this.device.scanner.stopScanner("The form has too many inputs, bailing."); + return this; + } + if (this.initialScanComplete && this.rescanCount < MAX_FORM_RESCANS) { + this.formAnalyzer = new FormAnalyzer_default(this.form, input, this.matching); + this.recategorizeAllInputs(); + return this; + } + if (input.maxLength === 1) + return this; + this.inputs.all.add(input); + const opts = { + isLogin: this.isLogin, + isHybrid: this.isHybrid, + isCCForm: this.isCCForm, + hasCredentials: Boolean(this.device.settings.availableInputTypes.credentials?.username), + supportsIdentitiesAutofill: this.device.settings.featureToggles.inputType_identities + }; + this.matching.setInputType(input, this.form, opts); + const mainInputType = getInputMainType(input); + this.inputs[mainInputType].add(input); + this.decorateInput(input); + return this; } - return topContextData; - } - - /** - * `postAutofill` gives features an opportunity to perform an action directly - * following an autofill. - * - * For example, if a generated password was used, we want to fire a save event. - * - * @param {IdentityObject|CreditCardObject|CredentialsObject} data - * @param {SupportedMainTypes} dataType - * @param {import("../Form/Form").Form} formObj - */ - postAutofill(data, dataType, formObj) { - // If there's an autogenerated password, prompt to save - if (_Credentials.AUTOGENERATED_KEY in data && 'password' in data && - // Don't send message on Android to avoid potential abuse. Data is saved on native confirmation instead. - !this.globalConfig.isAndroid) { - const formValues = formObj.getValuesReadyForStorage(); - if (formValues.credentials?.password === data.password) { - /** @type {StoreFormData} */ - const formData = (0, _Credentials.appendGeneratedKey)(formValues, { - password: data.password + /** + * Adds event listeners and keeps track of them for subsequent removal + * @param {HTMLElement} el + * @param {Event['type']} type + * @param {(Event) => void} fn + * @param {AddEventListenerOptions} [opts] + */ + addListener(el, type, fn, opts) { + el.addEventListener(type, fn, opts); + this.listeners.add({ el, type, fn, opts }); + } + addAutofillStyles(input) { + const initialStyles = getIconStylesBase(input, this); + const activeStyles = getIconStylesAlternate(input, this); + addInlineStyles(input, initialStyles); + return { + onMouseMove: activeStyles, + onMouseLeave: initialStyles + }; + } + /** + * Decorate here means adding listeners and an optional icon + * @param {HTMLInputElement} input + * @returns {Promise} + */ + async decorateInput(input) { + const config = getInputConfig(input); + const shouldDecorate = await config.shouldDecorate(input, this); + if (!shouldDecorate) + return this; + input.setAttribute(ATTR_AUTOFILL, "true"); + const hasIcon = !!config.getIconBase(input, this); + if (hasIcon) { + const { onMouseMove, onMouseLeave } = this.addAutofillStyles(input); + this.addListener(input, "mousemove", (e) => { + if (wasAutofilledByChrome(input)) + return; + if (isEventWithinDax(e, e.target)) { + addInlineStyles(e.target, { + "cursor": "pointer", + ...onMouseMove + }); + } else { + removeInlineStyles(e.target, { "cursor": "pointer" }); + if (!this.device.isTooltipActive()) { + addInlineStyles(e.target, { ...onMouseLeave }); + } + } + }); + this.addListener(input, "mouseleave", (e) => { + removeInlineStyles(e.target, { "cursor": "pointer" }); + if (!this.device.isTooltipActive()) { + addInlineStyles(e.target, { ...onMouseLeave }); + } }); - this.storeFormData(formData, 'passwordGeneration'); } + function getMainClickCoords(e) { + if (!e.isTrusted) + return; + const isMainMouseButton = e.button === 0; + if (!isMainMouseButton) + return; + return { + x: e.clientX, + y: e.clientY + }; + } + function getClickCoords(e, storedClickCoords2) { + if (e.type === "pointerdown") { + return getMainClickCoords( + /** @type {PointerEvent} */ + e + ) || null; + } + const click = storedClickCoords2.get(input); + storedClickCoords2.delete(input); + return click || null; + } + let storedClickCoords = /* @__PURE__ */ new WeakMap(); + let timeout = null; + const handlerLabel = (e) => { + const control = ( + /** @type HTMLElement */ + e.target?.closest("label")?.control + ); + if (!control) + return; + if (e.isTrusted) { + storedClickCoords.set(control, getMainClickCoords(e)); + } + clearTimeout(timeout); + timeout = setTimeout(() => { + storedClickCoords = /* @__PURE__ */ new WeakMap(); + }, 1e3); + }; + const handler = (e) => { + if (this.isAutofilling || this.device.isTooltipActive()) { + return; + } + const isLabel = e.target instanceof HTMLLabelElement; + const input2 = isLabel ? e.target.control : e.target; + if (!input2 || !this.inputs.all.has(input2)) + return; + if (wasAutofilledByChrome(input2)) + return; + if (!canBeInteractedWith(input2)) + return; + const clickCoords = getClickCoords(e, storedClickCoords); + if (e.type === "pointerdown") { + if (!e.isTrusted || !clickCoords) + return; + } + if (this.shouldOpenTooltip(e, input2)) { + const iconClicked = isEventWithinDax(e, input2); + if ((this.device.globalConfig.isMobileApp || this.device.globalConfig.isExtension) && // Avoid the icon capturing clicks on small fields making it impossible to focus + input2.offsetWidth > 50 && iconClicked) { + e.preventDefault(); + e.stopImmediatePropagation(); + input2.blur(); + } + this.touched.add(input2); + this.device.attachTooltip({ + form: this, + input: input2, + click: clickCoords, + trigger: "userInitiated", + triggerMetaData: { + // An 'icon' click is very different to a field click or focus. + // It indicates an explicit opt-in to the feature. + type: iconClicked ? "explicit-opt-in" : "implicit-opt-in" + } + }); + const activeStyles = getIconStylesAlternate(input2, this); + addInlineStyles(input2, activeStyles); + } + }; + if (!(input instanceof HTMLSelectElement)) { + const events = ["pointerdown"]; + if (!this.device.globalConfig.isMobileApp) + events.push("focus"); + input.labels?.forEach((label) => { + if (this.device.globalConfig.isMobileApp) { + this.addListener(label, "pointerdown", handler); + } else { + this.addListener(label, "pointerdown", handlerLabel); + } + }); + events.forEach((ev) => this.addListener(input, ev, handler)); + } + return this; } - if (dataType === 'credentials' && formObj.shouldAutoSubmit) { - formObj.attemptSubmissionIfNeeded(); - } - } - - /** - * `postSubmit` gives features a one-time-only opportunity to perform an - * action directly after a form submission was observed. - * - * Mostly this is about storing data from the form submission, but it can - * also be used like in the case of Password generation, to append additional - * data before it's sent to be saved. - * - * @param {DataStorageObject} values - * @param {import("../Form/Form").Form} form - */ - postSubmit(values, form) { - if (!form.form) return; - if (!form.hasValues(values)) return; - const checks = [form.shouldPromptToStoreData && !form.submitHandlerExecuted, this.passwordGenerator.generated]; - if (checks.some(Boolean)) { - const formData = (0, _Credentials.appendGeneratedKey)(values, { - password: this.passwordGenerator.password, - username: this.emailProtection.lastGenerated - }); - this.storeFormData(formData, 'formSubmission'); + shouldOpenTooltip(e, input) { + if (!isPotentiallyViewable(input)) + return false; + if (isEventWithinDax(e, input)) + return true; + if (this.device.globalConfig.isWindows) + return true; + const subtype = getInputSubtype(input); + const isIncontextSignupAvailable = this.device.inContextSignup?.isAvailable(subtype); + if (this.device.globalConfig.isApp) { + const mainType = getInputMainType(input); + const hasSavedDetails = this.device.settings.canAutofillType({ mainType, subtype }, null); + if (!hasSavedDetails && isIncontextSignupAvailable) + return false; + return true; + } + if (this.device.globalConfig.isExtension || this.device.globalConfig.isMobileApp) { + if (isIncontextSignupAvailable) + return false; + } + return !this.touched.has(input) && !input.classList.contains("ddg-autofilled"); } - } - - /** - * Sends a pixel to be fired on the client side - * @param {import('../deviceApiCalls/__generated__/validators-ts').SendJSPixelParams} pixelParams - */ - firePixel(pixelParams) { - this.deviceApi.notify(new _deviceApiCalls.SendJSPixelCall(pixelParams)); - } - - /** - * This serves as a single place to create a default instance - * of InterfacePrototype that can be useful in testing scenarios - * @param {Partial} [globalConfigOverrides] - * @returns {InterfacePrototype} - */ - static default(globalConfigOverrides) { - const globalConfig = (0, _config.createGlobalConfig)(globalConfigOverrides); - const transport = (0, _transports.createTransport)(globalConfig); - const deviceApi = new _index.DeviceApi(transport); - const settings = _Settings.Settings.default(globalConfig, deviceApi); - return new InterfacePrototype(globalConfig, deviceApi, settings); - } -} -var _default = exports.default = InterfacePrototype; - -},{"../../packages/device-api/index.js":2,"../EmailProtection.js":22,"../Form/formatters.js":26,"../Form/matching.js":33,"../InputTypes/Credentials.js":35,"../PasswordGenerator.js":38,"../Scanner.js":39,"../Settings.js":40,"../UI/controllers/NativeUIController.js":47,"../autofill-utils.js":52,"../config.js":54,"../deviceApiCalls/__generated__/deviceApiCalls.js":56,"../deviceApiCalls/transports/transports.js":62,"./initFormSubmissionsApi.js":20}],18:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.WindowsInterface = void 0; -var _InterfacePrototype = _interopRequireDefault(require("./InterfacePrototype.js")); -var _OverlayUIController = require("../UI/controllers/OverlayUIController.js"); -var _deviceApiCalls = require("../deviceApiCalls/__generated__/deviceApiCalls.js"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -/** - * @typedef {import('../deviceApiCalls/__generated__/validators-ts').GetAutofillDataRequest} GetAutofillDataRequest - */ - -const EMAIL_PROTECTION_LOGOUT_MESSAGE = 'EMAIL_PROTECTION_LOGOUT'; -class WindowsInterface extends _InterfacePrototype.default { - ready = false; - /** @type {AbortController|null} */ - _abortController = null; - async setupAutofill() { - const loggedIn = await this._getIsLoggedIn(); - if (loggedIn) { - await this.getAddresses(); + autofillInput(input, string, dataType) { + if (input instanceof HTMLInputElement && !isPotentiallyViewable(input)) + return; + if (!canBeInteractedWith(input)) + return; + if (dataType !== "creditCards" && // creditCards always override, the others only when we're focusing the input + input.nodeName !== "SELECT" && input.value !== "" && // if the input is not empty + this.activeInput !== input) + return; + if (input.value === string) + return; + const successful = setValue(input, string, this.device.globalConfig); + if (!successful) + return; + input.classList.add("ddg-autofilled"); + addInlineStyles(input, getIconStylesAutofilled(input, this)); + this.touched.add(input); + input.addEventListener("input", (e) => this.removeAllHighlights(e, dataType), { once: true }); } - } - postInit() { - super.postInit(); - this.ready = true; - } - createUIController() { /** - * If we get here, we're just a controller for an overlay + * Autofill method for email protection only + * @param {string} alias + * @param {'all' | SupportedMainTypes} dataType */ - return new _OverlayUIController.OverlayUIController({ - remove: async () => this._closeAutofillParent(), - show: async details => this._show(details) - }); - } - - /** - * @param {GetAutofillDataRequest} details - */ - async _show(details) { - const { - mainType - } = details; - // prevent overlapping listeners - if (this._abortController && !this._abortController.signal.aborted) { - this._abortController.abort(); - } - this._abortController = new AbortController(); - this.deviceApi.request(new _deviceApiCalls.GetAutofillDataCall(details), { - signal: this._abortController.signal - }).then(resp => { - if (!this.activeForm) { - throw new Error('this.currentAttached was absent'); - } - switch (resp.action) { - case 'fill': - { - if (mainType in resp) { - this.activeForm?.autofillData(resp[mainType], mainType); - } else { - throw new Error(`action: "fill" cannot occur because "${mainType}" was missing`); - } - break; + autofillEmail(alias, dataType = "identities") { + this.isAutofilling = true; + this.execOnInputs( + (input) => { + const inputSubtype = getInputSubtype(input); + if (inputSubtype === "emailAddress") { + this.autofillInput(input, alias, dataType); } - case 'focus': - { - this.activeForm?.activeInput?.focus(); - break; + }, + dataType + ); + this.isAutofilling = false; + this.removeTooltip(); + } + autofillData(data, dataType) { + this.isAutofilling = true; + this.execOnInputs((input) => { + const inputSubtype = getInputSubtype(input); + let autofillData = data[inputSubtype]; + if (inputSubtype === "expiration" && input instanceof HTMLInputElement) { + autofillData = getUnifiedExpiryDate(input, data.expirationMonth, data.expirationYear, this); + } + if (inputSubtype === "expirationYear" && input instanceof HTMLInputElement) { + autofillData = formatCCYear(input, autofillData, this); + } + if (inputSubtype === "addressCountryCode") { + autofillData = getCountryName(input, data); + } + if (autofillData) { + const variant = getInputVariant(input); + if (!variant) { + return this.autofillInput(input, autofillData, dataType); } - case 'none': - { - // do nothing - break; + if (variant === "new" && AUTOGENERATED_KEY in data) { + return this.autofillInput(input, autofillData, dataType); } - default: - { - if (this.globalConfig.isDDGTestMode) { - console.warn('unhandled response', resp); - } + if (variant === "current" && !(AUTOGENERATED_KEY in data)) { + return this.autofillInput(input, autofillData, dataType); } - } - return this._closeAutofillParent(); - }).catch(e => { - if (this.globalConfig.isDDGTestMode) { - if (e.name === 'AbortError') { - console.log('Promise Aborted'); - } else { - console.error('Promise Rejected', e); } + }, dataType); + this.isAutofilling = false; + const formValues = this.getValuesReadyForStorage(); + const areAllFormValuesKnown = Object.keys(formValues[dataType] || {}).every((subtype) => formValues[dataType][subtype] === data[subtype]); + if (areAllFormValuesKnown) { + this.shouldPromptToStoreData = false; + this.shouldAutoSubmit = this.device.globalConfig.isMobileApp; + } else { + this.shouldAutoSubmit = false; } - }); - } - - /** - * @returns {Promise} - */ - async _closeAutofillParent() { - return this.deviceApi.notify(new _deviceApiCalls.CloseAutofillParentCall(null)); - } - - /** - * Email Protection calls - */ - - /** - * @returns {Promise} - */ - getEmailProtectionCapabilities() { - return this.deviceApi.request(new _deviceApiCalls.EmailProtectionGetCapabilitiesCall({})); - } - async _getIsLoggedIn() { - const isLoggedIn = await this.deviceApi.request(new _deviceApiCalls.EmailProtectionGetIsLoggedInCall({})); - this.isDeviceSignedIn = () => isLoggedIn; - return isLoggedIn; - } - addLogoutListener(handler) { - // Only deal with logging out if we're in the email web app - if (!this.globalConfig.isDDGDomain) return; - windowsInteropAddEventListener('message', e => { - if (this.globalConfig.isDDGDomain && e.data === EMAIL_PROTECTION_LOGOUT_MESSAGE) { - handler(); - } - }); - } - - /** - * @returns {Promise} - */ - storeUserData(_ref) { - let { - addUserData - } = _ref; - return this.deviceApi.request(new _deviceApiCalls.EmailProtectionStoreUserDataCall(addUserData)); - } - /** - * @returns {Promise} - */ - removeUserData() { - return this.deviceApi.request(new _deviceApiCalls.EmailProtectionRemoveUserDataCall({})); - } - /** - * @returns {Promise} - */ - getUserData() { - return this.deviceApi.request(new _deviceApiCalls.EmailProtectionGetUserDataCall({})); - } - async refreshAlias() { - const addresses = await this.deviceApi.request(new _deviceApiCalls.EmailProtectionRefreshPrivateAddressCall({})); - this.storeLocalAddresses(addresses); - } - async getAddresses() { - const addresses = await this.deviceApi.request(new _deviceApiCalls.EmailProtectionGetAddressesCall({})); - this.storeLocalAddresses(addresses); - return addresses; - } -} -exports.WindowsInterface = WindowsInterface; - -},{"../UI/controllers/OverlayUIController.js":48,"../deviceApiCalls/__generated__/deviceApiCalls.js":56,"./InterfacePrototype.js":17}],19:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.WindowsOverlayDeviceInterface = void 0; -var _InterfacePrototype = _interopRequireDefault(require("./InterfacePrototype.js")); -var _HTMLTooltipUIController = require("../UI/controllers/HTMLTooltipUIController.js"); -var _deviceApiCalls = require("../deviceApiCalls/__generated__/deviceApiCalls.js"); -var _overlayApi = require("./overlayApi.js"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -/** - * This subclass is designed to separate code that *only* runs inside the - * Windows Overlay into a single place. - * - * It has some subtle differences to the macOS version, which is why - * this is another DeviceInterface - */ -class WindowsOverlayDeviceInterface extends _InterfacePrototype.default { - /** - * Mark top frame as not stripping credential data - * @type {boolean} - */ - stripCredentials = false; - - /** - * overlay API helpers - */ - overlay = (0, _overlayApi.overlayApi)(this); - previousScreenX = 0; - previousScreenY = 0; - - /** - * Because we're running inside the Overlay, we always create the HTML - * Tooltip controller. - * - * @override - * @returns {import("../UI/controllers/UIController.js").UIController} - */ - createUIController() { - return new _HTMLTooltipUIController.HTMLTooltipUIController({ - tooltipKind: /** @type {const} */'modern', - device: this - }, { - wrapperClass: 'top-autofill', - tooltipPositionClass: () => '.wrapper { transform: none; }', - setSize: details => this.deviceApi.notify(new _deviceApiCalls.SetSizeCall(details)), - remove: async () => this._closeAutofillParent(), - testMode: this.isTestMode(), - /** - * Note: This is needed because Mutation observer didn't support visibility checks on Windows - */ - checkVisibility: false - }); - } - addDeviceListeners() { + this.device.postAutofill?.(data, dataType, this); + this.removeTooltip(); + } /** - * On Windows (vs. MacOS) we can use the built-in `mousemove` - * event and screen-relative positioning. - * - * Note: There's no cleanup required here since the Overlay has a fresh - * page load every time it's opened. - */ - window.addEventListener('mousemove', event => { - // Don't set focus if the mouse hasn't moved ever - // This is to avoid clickjacking where an attacker puts the pulldown under the cursor - // and tricks the user into clicking - if (!this.previousScreenX && !this.previousScreenY || - // if no previous coords - this.previousScreenX === event.screenX && this.previousScreenY === event.screenY // or the mouse hasn't moved - ) { - this.previousScreenX = event.screenX; - this.previousScreenY = event.screenY; + * Set all inputs of the data type to "touched" + * @param {'all' | SupportedMainTypes} dataType + */ + touchAllInputs(dataType = "all") { + this.execOnInputs( + (input) => this.touched.add(input), + dataType + ); + } + getFirstViableCredentialsInput() { + return [...this.inputs.credentials].find((input) => canBeInteractedWith(input) && isPotentiallyViewable(input)); + } + async promptLoginIfNeeded() { + if (document.visibilityState !== "visible" || !this.isLogin) + return; + const firstCredentialInput = this.getFirstViableCredentialsInput(); + const input = this.activeInput || firstCredentialInput; + if (!input) return; + const mainType = getInputMainType(input); + const subtype = getInputSubtype(input); + await this.device.settings.populateDataIfNeeded({ mainType, subtype }); + if (this.device.settings.canAutofillType({ mainType, subtype }, this.device.inContextSignup)) { + setTimeout(() => { + safeExecute(this.form, () => { + const { x, y, width, height } = this.form.getBoundingClientRect(); + const elHCenter = x + width / 2; + const elVCenter = y + height / 2; + const topMostElementFromPoint = document.elementFromPoint(elHCenter, elVCenter); + if (this.form.contains(topMostElementFromPoint)) { + this.execOnInputs((input2) => { + if (isPotentiallyViewable(input2)) { + this.touched.add(input2); + } + }, "credentials"); + this.device.attachTooltip({ + form: this, + input, + click: null, + trigger: "autoprompt", + triggerMetaData: { + type: "implicit-opt-in" + } + }); + } + }); + }, 200); } - const activeTooltip = this.uiController?.getActiveTooltip?.(); - activeTooltip?.focus(event.x, event.y); - this.previousScreenX = event.screenX; - this.previousScreenY = event.screenY; - }); - return super.addDeviceListeners(); - } - - /** - * @returns {Promise} - */ - async _closeAutofillParent() { - return this.deviceApi.notify(new _deviceApiCalls.CloseAutofillParentCall(null)); - } - - /** - * @returns {Promise} - */ - openManagePasswords() { - return this.deviceApi.notify(new _deviceApiCalls.OpenManagePasswordsCall({})); - } - /** - * @returns {Promise} - */ - openManageCreditCards() { - return this.deviceApi.notify(new _deviceApiCalls.OpenManageCreditCardsCall({})); - } - /** - * @returns {Promise} - */ - openManageIdentities() { - return this.deviceApi.notify(new _deviceApiCalls.OpenManageIdentitiesCall({})); - } - - /** - * Since we're running inside the Overlay we can limit what happens here to - * be only things that are needed to power the HTML Tooltip - * - * @override - * @returns {Promise} - */ - async setupAutofill() { - const loggedIn = await this._getIsLoggedIn(); - if (loggedIn) { - await this.getAddresses(); - } - const response = await this.deviceApi.request(new _deviceApiCalls.GetAutofillInitDataCall(null)); - // @ts-ignore - this.storeLocalData(response); - } - async postInit() { - // setup overlay API pieces - this.overlay.showImmediately(); - super.postInit(); - } - - /** - * In the top-frame scenario, we send a message to the native - * side to indicate a selection. Once received, the native side will store that selection so that a - * subsequence call from main webpage can retrieve it - * - * @override - * @param {IdentityObject|CreditCardObject|CredentialsObject|{email:string, id: string}} data - * @param {string} type - */ - async selectedDetail(data, type) { - return this.overlay.selectedDetail(data, type); - } - - /** - * Email Protection calls - */ - - async _getIsLoggedIn() { - const isLoggedIn = await this.deviceApi.request(new _deviceApiCalls.EmailProtectionGetIsLoggedInCall({})); - this.isDeviceSignedIn = () => isLoggedIn; - return isLoggedIn; - } - async getAddresses() { - const addresses = await this.deviceApi.request(new _deviceApiCalls.EmailProtectionGetAddressesCall({})); - this.storeLocalAddresses(addresses); - return addresses; - } - - /** - * Gets a single identity obj once the user requests it - * @param {Number} id - * @returns {Promise<{success: IdentityObject|undefined}>} - */ - getAutofillIdentity(id) { - const identity = this.getLocalIdentities().find(_ref => { - let { - id: identityId - } = _ref; - return `${identityId}` === `${id}`; - }); - return Promise.resolve({ - success: identity - }); - } -} -exports.WindowsOverlayDeviceInterface = WindowsOverlayDeviceInterface; - -},{"../UI/controllers/HTMLTooltipUIController.js":46,"../deviceApiCalls/__generated__/deviceApiCalls.js":56,"./InterfacePrototype.js":17,"./overlayApi.js":21}],20:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.initFormSubmissionsApi = initFormSubmissionsApi; -var _autofillUtils = require("../autofill-utils.js"); -var _labelUtil = require("../Form/label-util.js"); -/** - * This is a single place to contain all functionality relating to form submission detection - * - * @param {Map} forms - * @param {import("../Form/matching").Matching} matching - */ -function initFormSubmissionsApi(forms, matching) { - /** - * Global submit events - */ - window.addEventListener('submit', e => { - // @ts-ignore - return forms.get(e.target)?.submitHandler('global submit event'); - }, true); - - /** - * Global keydown events - */ - window.addEventListener('keydown', e => { - if (e.key === 'Enter') { - const focusedForm = [...forms.values()].find(form => form.hasFocus(e)); - focusedForm?.submitHandler('global keydown + Enter'); } - }, true); - - /** - * Global pointer down events - * @param {PointerEvent} event - */ - window.addEventListener('pointerdown', event => { - const realTarget = (0, _autofillUtils.pierceShadowTree)(event); - const formsArray = [...forms.values()]; - const matchingForm = formsArray.find(form => { - const btns = [...form.submitButtons]; - // @ts-ignore - if (btns.includes(realTarget)) return true; - - // @ts-ignore - if (btns.find(btn => btn.contains(realTarget))) return true; - }); - matchingForm?.submitHandler('global pointerdown event + matching form'); - if (!matchingForm) { - const selector = matching.cssSelector('submitButtonSelector') + ', a[href="#"], a[href^=javascript], *[onclick], [class*=button i]'; - // check if the click happened on a button - const button = /** @type HTMLElement */realTarget?.closest(selector); - if (!button) return; - - // If the element we've found includes a form it can't be a button, it's a false match - const buttonIsAFalsePositive = formsArray.some(form => button?.contains(form.form)); - if (buttonIsAFalsePositive) return; - const text = (0, _autofillUtils.getTextShallow)(button) || (0, _labelUtil.extractElementStrings)(button).join(' '); - const hasRelevantText = (0, _autofillUtils.safeRegexTest)(matching.getDDGMatcherRegex('submitButtonRegex'), text); - if (hasRelevantText && text.length < 25) { - // check if there's a form with values - const filledForm = formsArray.find(form => form.hasValues()); - if (filledForm && (0, _autofillUtils.buttonMatchesFormType)( /** @type HTMLElement */button, filledForm)) { - filledForm?.submitHandler('global pointerdown event + filled form'); - } - } + }; - // TODO: Temporary hack to support Google signin in different languages - // https://app.asana.com/0/1198964220583541/1201650539303898/f - if ( /** @type HTMLElement */realTarget?.closest('#passwordNext button, #identifierNext button')) { - // check if there's a form with values - const filledForm = formsArray.find(form => form.hasValues()); - filledForm?.submitHandler('global pointerdown event + google escape hatch'); - } + // zod-replacers:./validators.zod.js + var sendJSPixelParamsSchema = null; + var addDebugFlagParamsSchema = null; + var getAutofillCredentialsParamsSchema = null; + var setSizeParamsSchema = null; + var selectedDetailParamsSchema = null; + var setIncontextSignupPermanentlyDismissedAtSchema = null; + var getIncontextSignupDismissedAtSchema = null; + var emailProtectionStoreUserDataParamsSchema = null; + var showInContextEmailProtectionSignupPromptSchema = null; + var getAutofillDataRequestSchema = null; + var getAutofillDataResponseSchema = null; + var storeFormDataSchema = null; + var getAvailableInputTypesResultSchema = null; + var getAutofillInitDataResponseSchema = null; + var getAutofillCredentialsResultSchema = null; + var emailProtectionGetIsLoggedInResultSchema = null; + var emailProtectionGetUserDataResultSchema = null; + var emailProtectionGetCapabilitiesResultSchema = null; + var emailProtectionGetAddressesResultSchema = null; + var emailProtectionRefreshPrivateAddressResultSchema = null; + var getRuntimeConfigurationResponseSchema = null; + var askToUnlockProviderResultSchema = null; + var checkCredentialsProviderStatusResultSchema = null; + + // packages/device-api/lib/device-api-call.js + var DeviceApiCall = class { + /** + * @param {import("zod").infer} data + */ + constructor(data) { + /** @type {string} */ + __publicField(this, "method", "unknown"); + /** + * An optional 'id' - used to indicate if a request requires a response. + * @type {string|null} + */ + __publicField(this, "id", null); + /** @type {Params | null | undefined} */ + __publicField(this, "paramsValidator", null); + /** @type {Result | null | undefined} */ + __publicField(this, "resultValidator", null); + /** @type {import("zod").infer} */ + __publicField(this, "params"); + /** + * This is a carve-out for legacy messages that are not typed yet. + * If you set this to 'true', then the response will not be checked to conform + * to any shape + * @deprecated this is here to aid migration, should be removed ASAP + * @type {boolean} + */ + __publicField(this, "throwOnResultKeysMissing", true); + /** + * New messages should be in a particular format, eg: { success: T }, + * but you can set this to false if you want to access the result as-is, + * without any unwrapping logic + * @deprecated this is here to aid migration, should be removed ASAP + * @type {boolean} + */ + __publicField(this, "unwrapResult", true); + this.params = data; } - }, true); - - /** - * @type {PerformanceObserver} - */ - const observer = new PerformanceObserver(list => { - const formsArray = [...forms.values()]; - const entries = list.getEntries().filter(entry => - // @ts-ignore why does TS not know about `entry.initiatorType`? - ['fetch', 'xmlhttprequest'].includes(entry.initiatorType) && (0, _autofillUtils.safeRegexTest)(/login|sign-in|signin/, entry.name)); - if (!entries.length) return; - const filledForm = formsArray.find(form => form.hasValues()); - const focusedForm = formsArray.find(form => form.hasFocus()); - // If a form is still focused the user is still typing: do nothing - if (focusedForm) return; - filledForm?.submitHandler('performance observer'); - }); - observer.observe({ - entryTypes: ['resource'] - }); -} - -},{"../Form/label-util.js":29,"../autofill-utils.js":52}],21:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.overlayApi = overlayApi; -var _deviceApiCalls = require("../deviceApiCalls/__generated__/deviceApiCalls.js"); -/** - * These are some re-usable parts for handling 'overlays' (like on macOS + Windows) - * - * @param {import("./InterfacePrototype").default} device - */ -function overlayApi(device) { - return { - /** - * When we are inside an 'overlay' - the HTML tooltip will be opened immediately - */ - showImmediately() { - const topContextData = device.getTopContextData(); - if (!topContextData) throw new Error('unreachable, topContextData should be available'); - - // Provide dummy values - const getPosition = () => { - return { - x: 0, - y: 0, - height: 50, - width: 50 - }; - }; - - // Create the tooltip, and set it as active - const tooltip = device.uiController?.createTooltip?.(getPosition, topContextData); - if (tooltip) { - device.uiController?.setActiveTooltip?.(tooltip); - } - }, /** - * @param {IdentityObject|CreditCardObject|CredentialsObject|{email:string, id: string}} data - * @param {string} type - * @returns {Promise} + * @returns {import("zod").infer|undefined} */ - async selectedDetail(data, type) { - let detailsEntries = Object.entries(data).map(_ref => { - let [key, value] = _ref; - return [key, String(value)]; - }); - const entries = Object.fromEntries(detailsEntries); - /** @link {import("../deviceApiCalls/schemas/getAutofillData.result.json")} */ - await device.deviceApi.notify(new _deviceApiCalls.SelectedDetailCall({ - data: entries, - configType: type - })); + validateParams() { + if (this.params === void 0) { + return void 0; + } + this._validate(this.params, this.paramsValidator); + return this.params; } - }; -} - -},{"../deviceApiCalls/__generated__/deviceApiCalls.js":56}],22:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.EmailProtection = void 0; -/** - * Use this as place to store any state or functionality related to Email Protection - */ -class EmailProtection { - /** @type {string|null} */ - #previous = null; - - /** @param {import("./DeviceInterface/InterfacePrototype").default} device */ - constructor(device) { - this.device = device; - } - - /** @returns {string|null} */ - get lastGenerated() { - return this.#previous; - } - - /** - * Store the last received email address - * @param {string} emailAddress - */ - storeReceived(emailAddress) { - this.#previous = emailAddress; - return emailAddress; - } -} -exports.EmailProtection = EmailProtection; - -},{}],23:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.Form = void 0; -var _FormAnalyzer = _interopRequireDefault(require("./FormAnalyzer.js")); -var _autofillUtils = require("../autofill-utils.js"); -var _matching = require("./matching.js"); -var _inputStyles = require("./inputStyles.js"); -var _inputTypeConfig = require("./inputTypeConfig.js"); -var _formatters = require("./formatters.js"); -var _constants = require("../constants.js"); -var _Credentials = require("../InputTypes/Credentials.js"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const { - ATTR_AUTOFILL, - ATTR_INPUT_TYPE, - MAX_INPUTS_PER_FORM, - MAX_FORM_RESCANS -} = _constants.constants; -class Form { - /** @type {import("../Form/matching").Matching} */ - matching; - /** @type {HTMLElement} */ - form; - /** @type {HTMLInputElement | null} */ - activeInput; - /** - * @param {HTMLElement} form - * @param {HTMLInputElement|HTMLSelectElement} input - * @param {import("../DeviceInterface/InterfacePrototype").default} deviceInterface - * @param {import("../Form/matching").Matching} [matching] - * @param {Boolean} [shouldAutoprompt] - */ - constructor(form, input, deviceInterface, matching) { - let shouldAutoprompt = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; - this.form = form; - this.matching = matching || (0, _matching.createMatching)(); - this.formAnalyzer = new _FormAnalyzer.default(form, input, matching); - this.device = deviceInterface; - - /** @type Record<'all' | SupportedMainTypes, Set> */ - this.inputs = { - all: new Set(), - credentials: new Set(), - creditCards: new Set(), - identities: new Set(), - unknown: new Set() - }; - this.touched = new Set(); - this.listeners = new Set(); - this.activeInput = null; - // We set this to true to skip event listeners while we're autofilling - this.isAutofilling = false; - this.submitHandlerExecuted = false; - this.shouldPromptToStoreData = deviceInterface.settings.featureToggles.credentials_saving; - this.shouldAutoSubmit = this.device.globalConfig.isMobileApp; - /** - * @type {IntersectionObserver | null} + * @param {any|null} incoming + * @returns {import("zod").infer} */ - this.intObs = new IntersectionObserver(entries => { - for (const entry of entries) { - if (!entry.isIntersecting) this.removeTooltip(); + validateResult(incoming) { + this._validate(incoming, this.resultValidator); + if (!incoming) { + return incoming; } - }); - this.rescanCount = 0; - this.mutObsConfig = { - childList: true, - subtree: true - }; - this.mutObs = new MutationObserver(records => { - const anythingRemoved = records.some(record => record.removedNodes.length > 0); - if (anythingRemoved) { - // Ensure we destroy the form if it's removed from the DOM - if (!this.form.isConnected) { - this.destroy(); - return; - } - // Must check for inputs because a parent may be removed and not show up in record.removedNodes - if ([...this.inputs.all].some(input => !input.isConnected)) { - // This is re-connected in recategorizeAllInputs, disconnecting here to avoid risk of re-work - this.mutObs.disconnect(); - // If any known input has been removed from the DOM, reanalyze the whole form - window.requestIdleCallback(() => { - this.formAnalyzer = new _FormAnalyzer.default(this.form, input, this.matching); - this.recategorizeAllInputs(); - }); - } + if (!this.unwrapResult) { + return incoming; } - }); - this.initFormListeners(); - this.categorizeInputs(); - this.logFormInfo(); - if (shouldAutoprompt) { - this.promptLoginIfNeeded(); - } - } - get isLogin() { - return this.formAnalyzer.isLogin; - } - get isSignup() { - return this.formAnalyzer.isSignup; - } - get isHybrid() { - return this.formAnalyzer.isHybrid; - } - get isCCForm() { - return this.formAnalyzer.isCCForm(); - } - logFormInfo() { - if (!(0, _autofillUtils.shouldLog)()) return; - console.log(`Form type: %c${this.getFormType()}`, 'font-weight: bold'); - console.log('Signals: ', this.formAnalyzer.signals); - console.log('Wrapping element: ', this.form); - console.log('Inputs: ', this.inputs); - console.log('Submit Buttons: ', this.submitButtons); - } - getFormType() { - if (this.isHybrid) return `hybrid (hybrid score: ${this.formAnalyzer.hybridSignal}, score: ${this.formAnalyzer.autofillSignal})`; - if (this.isLogin) return `login (score: ${this.formAnalyzer.autofillSignal}, hybrid score: ${this.formAnalyzer.hybridSignal})`; - if (this.isSignup) return `signup (score: ${this.formAnalyzer.autofillSignal}, hybrid score: ${this.formAnalyzer.hybridSignal})`; - return 'something went wrong'; - } - - /** - * Checks if the form element contains the activeElement or the event target - * @return {boolean} - * @param {KeyboardEvent | null} [e] - */ - hasFocus(e) { - return this.form.contains((0, _autofillUtils.getActiveElement)()) || this.form.contains( /** @type HTMLElement */e?.target); - } - submitHandler() { - let via = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'unknown'; - if (this.device.globalConfig.isDDGTestMode) { - console.log('Form.submitHandler via:', via, this); - } - if (this.submitHandlerExecuted) return; - const values = this.getValuesReadyForStorage(); - this.device.postSubmit?.(values, this); - - // mark this form as being handled - this.submitHandlerExecuted = true; - } - - /** - * Reads the values from the form without preparing to store them - * @return {InternalDataStorageObject} - */ - getRawValues() { - const formValues = [...this.inputs.credentials, ...this.inputs.identities, ...this.inputs.creditCards].reduce((output, inputEl) => { - const mainType = (0, _matching.getInputMainType)(inputEl); - const subtype = (0, _matching.getInputSubtype)(inputEl); - let value = inputEl.value || output[mainType]?.[subtype]; - if (subtype === 'addressCountryCode') { - value = (0, _formatters.inferCountryCodeFromElement)(inputEl); + if ("data" in incoming) { + console.warn("response had `data` property. Please migrate to `success`"); + return incoming.data; } - // Discard passwords that are shorter than 4 characters - if (subtype === 'password' && value?.length <= 3) { - value = undefined; + if ("success" in incoming) { + return incoming.success; } - if (value) { - output[mainType][subtype] = value; + if ("error" in incoming) { + if (typeof incoming.error.message === "string") { + throw new DeviceApiCallError(`${this.method}: ${incoming.error.message}`); + } } - return output; - }, { - credentials: {}, - creditCards: {}, - identities: {} - }); - if (!formValues.credentials.username && !formValues.identities.emailAddress) { - // If we could find no username, let's search further - const hiddenFields = /** @type [HTMLInputElement] */[...this.form.querySelectorAll('input[type=hidden]')]; - const probableField = hiddenFields.find(field => { - const regex = new RegExp('email|' + this.matching.getDDGMatcherRegex('username')?.source); - const attributeText = field.id + ' ' + field.name; - return (0, _autofillUtils.safeRegexTest)(regex, attributeText); - }); - if (probableField?.value) { - formValues.credentials.username = probableField.value; - } else if ( - // If a form has phone + password(s) fields, save the phone as username - formValues.identities.phone && this.inputs.all.size - this.inputs.unknown.size < 4) { - formValues.credentials.username = formValues.identities.phone; - } else { - // If we still don't have a username, try scanning the form's text for an email address - this.form.querySelectorAll(this.matching.cssSelector('safeUniversalSelector')).forEach(el => { - const elText = (0, _autofillUtils.getTextShallow)(el); - // Ignore long texts to avoid false positives - if (elText.length > 70) return; - const emailOrUsername = elText.match( - // https://www.emailregex.com/ - /[a-zA-Z\d.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z\d-]+(?:\.[a-zA-Z\d-]+)*/)?.[0]; - if (emailOrUsername) { - formValues.credentials.username = emailOrUsername; + if (this.throwOnResultKeysMissing) { + throw new Error("unreachable. Response did not contain `success` or `data`"); + } + return incoming; + } + /** + * @param {any} data + * @param {import("zod").ZodType|undefined|null} [validator] + * @private + */ + _validate(data, validator) { + if (!validator) + return data; + if (validator) { + const result = validator?.safeParse(data); + if (!result) { + throw new Error("unreachable, data failure", data); + } + if (!result.success) { + if ("error" in result) { + this.throwError(result.error.issues); + } else { + console.error("unknown error from validate"); } - }); + } } } - return formValues; - } - - /** - * Return form values ready for storage - * @returns {DataStorageObject} - */ - getValuesReadyForStorage() { - const formValues = this.getRawValues(); - return (0, _formatters.prepareFormValuesForStorage)(formValues); - } - - /** - * Determine if the form has values we want to store in the device - * @param {DataStorageObject} [values] - * @return {boolean} - */ - hasValues(values) { - const { - credentials, - creditCards, - identities - } = values || this.getValuesReadyForStorage(); - return Boolean(credentials || creditCards || identities); - } - async removeTooltip() { - const tooltip = this.device.isTooltipActive(); - if (this.isAutofilling || !tooltip) { - return; + /** + * @param {import('zod').ZodIssue[]} errors + */ + throwError(errors) { + const error = SchemaValidationError.fromZodErrors(errors, this.constructor.name); + throw error; } - await this.device.removeTooltip(); - this.intObs?.disconnect(); - } - showingTooltip(input) { - this.intObs?.observe(input); - } - removeInputHighlight(input) { - if (!input.classList.contains('ddg-autofilled')) return; - (0, _autofillUtils.removeInlineStyles)(input, (0, _inputStyles.getIconStylesAutofilled)(input, this)); - (0, _autofillUtils.removeInlineStyles)(input, { - 'cursor': 'pointer' - }); - input.classList.remove('ddg-autofilled'); - this.addAutofillStyles(input); - } - resetIconStylesToInitial() { - const input = this.activeInput; - if (input) { - const initialStyles = (0, _inputStyles.getIconStylesBase)(input, this); - (0, _autofillUtils.addInlineStyles)(input, initialStyles); + /** + * Use this helper for creating stand-in response messages that are typed correctly. + * + * @examples + * + * ```js + * const msg = new Message(); + * const response = msg.response({}) // <-- This argument will be typed correctly + * ``` + * + * @param {import("zod").infer} response + * @returns {import("zod").infer} + */ + result(response) { + return response; } - } - removeAllHighlights(e, dataType) { - // This ensures we are not removing the highlight ourselves when autofilling more than once - if (e && !e.isTrusted) return; - - // If the user has changed the value, reset shouldPromptToStoreData to initial value - this.resetShouldPromptToStoreData(); - this.execOnInputs(input => this.removeInputHighlight(input), dataType); - } - removeInputDecoration(input) { - (0, _autofillUtils.removeInlineStyles)(input, (0, _inputStyles.getIconStylesBase)(input, this)); - (0, _autofillUtils.removeInlineStyles)(input, (0, _inputStyles.getIconStylesAlternate)(input, this)); - input.removeAttribute(ATTR_AUTOFILL); - input.removeAttribute(ATTR_INPUT_TYPE); - } - removeAllDecorations() { - this.execOnInputs(input => this.removeInputDecoration(input)); - this.listeners.forEach(_ref => { - let { - el, - type, - fn, - opts - } = _ref; - return el.removeEventListener(type, fn, opts); - }); - } - redecorateAllInputs() { - this.removeAllDecorations(); - this.execOnInputs(input => { - if (input instanceof HTMLInputElement) { - this.decorateInput(input); - } - }); - } - - /** - * Removes all scoring attributes from the inputs and deletes them from memory - */ - forgetAllInputs() { - this.execOnInputs(input => { - input.removeAttribute(ATTR_AUTOFILL); - input.removeAttribute(ATTR_INPUT_TYPE); - }); - Object.values(this.inputs).forEach(inputSet => inputSet.clear()); - } - - /** - * Resets our input scoring and starts from scratch - */ - recategorizeAllInputs() { - // If the form mutates too much, disconnect to avoid performance issues - if (this.rescanCount >= MAX_FORM_RESCANS) { - this.mutObs.disconnect(); - return; + /** + * @returns {import("zod").infer} + */ + preResultValidation(response) { + return response; } - this.rescanCount++; - this.initialScanComplete = false; - this.removeAllDecorations(); - this.forgetAllInputs(); - this.initFormListeners(); - this.categorizeInputs(); - } - resetAllInputs() { - this.execOnInputs(input => { - (0, _autofillUtils.setValue)(input, '', this.device.globalConfig); - this.removeInputHighlight(input); - }); - if (this.activeInput) this.activeInput.focus(); - this.matching.clear(); - } - resetShouldPromptToStoreData() { - this.shouldPromptToStoreData = this.device.settings.featureToggles.credentials_saving; - } - dismissTooltip() { - this.removeTooltip(); - } - // This removes all listeners to avoid memory leaks and weird behaviours - destroy() { - this.mutObs.disconnect(); - this.removeAllDecorations(); - this.removeTooltip(); - this.forgetAllInputs(); - this.matching.clear(); - this.intObs = null; - this.device.scanner.forms.delete(this.form); - } - initFormListeners() { - // This ensures we fire the handler again if the form is changed - this.addListener(this.form, 'input', () => { - if (!this.isAutofilling) { - this.submitHandlerExecuted = false; - this.resetShouldPromptToStoreData(); - } - }); - - // If it's a form within a shadow tree, attach the submit listener, because it doesn't bubble outside - if (this.form instanceof HTMLFormElement && this.form.getRootNode()) { - this.addListener(this.form, 'submit', () => { - this.submitHandler('in-form submit handler'); - }, { - capture: true - }); + }; + var DeviceApiCallError = class extends Error { + }; + var SchemaValidationError = class extends Error { + constructor() { + super(...arguments); + /** @type {import("zod").ZodIssue[]} */ + __publicField(this, "validationErrors", []); } - } - categorizeInputs() { - const selector = this.matching.cssSelector('formInputsSelector'); - // If there's no form container and it's just a lonely input field (this.form is an input field) - if (this.form.matches(selector)) { - this.addInput(this.form); - } else { - /** @type {Element[] | NodeList} */ - let foundInputs = []; - if (this.form instanceof HTMLFormElement) { - // For form elements we use .elements to catch fields outside the form itself using the form attribute. - // It also catches all elements when the markup is broken. - // We use .filter to avoid fieldset, button, textarea etc. - foundInputs = [...this.form.elements].filter(el => el.matches(selector)); - } else { - foundInputs = this.form.querySelectorAll(selector); + /** + * @param {import("zod").ZodIssue[]} errors + * @param {string} name + * @returns {SchemaValidationError} + */ + static fromZodErrors(errors, name) { + const heading = `${errors.length} SchemaValidationError(s) errors for ` + name; + function log(issue) { + switch (issue.code) { + case "invalid_literal": + case "invalid_type": { + console.log(`${name}. Path: '${issue.path.join(".")}', Error: '${issue.message}'`); + break; + } + case "invalid_union": { + for (let unionError of issue.unionErrors) { + for (let issue1 of unionError.issues) { + log(issue1); + } + } + break; + } + default: { + console.log(name, "other issue:", issue); + } + } } - if (foundInputs.length < MAX_INPUTS_PER_FORM) { - foundInputs.forEach(input => this.addInput(input)); - } else { - // This is rather extreme, but better safe than sorry - this.device.scanner.stopScanner(`The form has too many inputs (${foundInputs.length}), bailing.`); - return; + for (let error2 of errors) { + log(error2); } + const message = [heading, "please see the details above"].join("\n "); + const error = new SchemaValidationError(message); + error.validationErrors = errors; + return error; } - this.initialScanComplete = true; - - // Observe only if the container isn't the body, to avoid performance overloads - if (this.form !== document.body) { - this.mutObs.observe(this.form, this.mutObsConfig); + }; + function createDeviceApiCall(method, params, paramsValidator = null, resultValidator = null) { + const deviceApiCall = new DeviceApiCall(params); + deviceApiCall.paramsValidator = paramsValidator; + deviceApiCall.resultValidator = resultValidator; + deviceApiCall.method = method; + deviceApiCall.throwOnResultKeysMissing = false; + deviceApiCall.unwrapResult = false; + return deviceApiCall; + } + function createRequest(method, params, id = "n/a", paramsValidator = null, resultValidator = null) { + const call = createDeviceApiCall(method, params, paramsValidator, resultValidator); + call.id = id; + return call; + } + var createNotification = createDeviceApiCall; + function validate(data, validator = null) { + if (validator) { + return validator.parse(data); } - } - get submitButtons() { - const selector = this.matching.cssSelector('submitButtonSelector'); - const allButtons = /** @type {HTMLElement[]} */[...this.form.querySelectorAll(selector)]; - return allButtons.filter(btn => (0, _autofillUtils.isPotentiallyViewable)(btn) && (0, _autofillUtils.isLikelyASubmitButton)(btn, this.matching) && (0, _autofillUtils.buttonMatchesFormType)(btn, this)); - } - attemptSubmissionIfNeeded() { - if (!this.isLogin || - // Only submit login forms - this.submitButtons.length > 1 // Do not submit if we're unsure about the submit button - ) return; - - // check for visible empty fields before attemtping submission - // this is to avoid loops where a captcha keeps failing for the user - let isThereAnEmptyVisibleField = false; - this.execOnInputs(input => { - if (input.value === '' && (0, _autofillUtils.isPotentiallyViewable)(input)) isThereAnEmptyVisibleField = true; - }, 'all', false); - if (isThereAnEmptyVisibleField) return; - - // We're not using .submit() to minimise breakage with client-side forms - this.submitButtons.forEach(button => { - if ((0, _autofillUtils.isPotentiallyViewable)(button)) { - button.click(); - } - }); + return data; } - /** - * Executes a function on input elements. Can be limited to certain element types - * @param {(input: HTMLInputElement|HTMLSelectElement) => void} fn - * @param {'all' | SupportedMainTypes} inputType - * @param {boolean} shouldCheckForDecorate - */ - execOnInputs(fn) { - let inputType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'all'; - let shouldCheckForDecorate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; - const inputs = this.inputs[inputType]; - for (const input of inputs) { - let canExecute = true; - // sometimes we want to execute even if we didn't decorate - if (shouldCheckForDecorate) { - canExecute = (0, _inputTypeConfig.isFieldDecorated)(input); - } - if (canExecute) fn(input); + // packages/device-api/lib/device-api.js + var DeviceApiTransport = class { + /** + * @param {import("./device-api-call.js").DeviceApiCall} _deviceApiCall + * @param {CallOptions} [_options] + * @returns {Promise} + */ + async send(_deviceApiCall, _options) { + return void 0; } - } - addInput(input) { - if (this.inputs.all.has(input)) return this; - - // If the form has too many inputs, destroy everything to avoid performance issues - if (this.inputs.all.size > MAX_INPUTS_PER_FORM) { - this.device.scanner.stopScanner('The form has too many inputs, bailing.'); - return this; + }; + var DeviceApi = class { + /** @param {DeviceApiTransport} transport */ + constructor(transport) { + /** @type {DeviceApiTransport} */ + __publicField(this, "transport"); + this.transport = transport; } - - // When new inputs are added after the initial scan, reanalyze the whole form - if (this.initialScanComplete && this.rescanCount < MAX_FORM_RESCANS) { - this.formAnalyzer = new _FormAnalyzer.default(this.form, input, this.matching); - this.recategorizeAllInputs(); - return this; + /** + * @template {import("./device-api-call").DeviceApiCall} D + * @param {D} deviceApiCall + * @param {CallOptions} [options] + * @returns {Promise['success']>>} + */ + async request(deviceApiCall, options) { + deviceApiCall.validateParams(); + let result = await this.transport.send(deviceApiCall, options); + let processed = deviceApiCall.preResultValidation(result); + return deviceApiCall.validateResult(processed); } + /** + * @template {import("./device-api-call").DeviceApiCall} P + * @param {P} deviceApiCall + * @param {CallOptions} [options] + * @returns {Promise} + */ + async notify(deviceApiCall, options) { + deviceApiCall.validateParams(); + return this.transport.send(deviceApiCall, options); + } + }; - // Nothing to do with 1-character fields - if (input.maxLength === 1) return this; - this.inputs.all.add(input); - const opts = { - isLogin: this.isLogin, - isHybrid: this.isHybrid, - isCCForm: this.isCCForm, - hasCredentials: Boolean(this.device.settings.availableInputTypes.credentials?.username), - supportsIdentitiesAutofill: this.device.settings.featureToggles.inputType_identities - }; - this.matching.setInputType(input, this.form, opts); - const mainInputType = (0, _matching.getInputMainType)(input); - this.inputs[mainInputType].add(input); - this.decorateInput(input); - return this; - } - - /** - * Adds event listeners and keeps track of them for subsequent removal - * @param {HTMLElement} el - * @param {Event['type']} type - * @param {(Event) => void} fn - * @param {AddEventListenerOptions} [opts] - */ - addListener(el, type, fn, opts) { - el.addEventListener(type, fn, opts); - this.listeners.add({ - el, - type, - fn, - opts - }); - } - addAutofillStyles(input) { - const initialStyles = (0, _inputStyles.getIconStylesBase)(input, this); - const activeStyles = (0, _inputStyles.getIconStylesAlternate)(input, this); - (0, _autofillUtils.addInlineStyles)(input, initialStyles); - return { - onMouseMove: activeStyles, - onMouseLeave: initialStyles - }; - } + // src/deviceApiCalls/__generated__/deviceApiCalls.js + var AddDebugFlagCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "addDebugFlag"); + __publicField(this, "paramsValidator", addDebugFlagParamsSchema); + } + }; + var GetAutofillDataCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "getAutofillData"); + __publicField(this, "id", "getAutofillDataResponse"); + __publicField(this, "paramsValidator", getAutofillDataRequestSchema); + __publicField(this, "resultValidator", getAutofillDataResponseSchema); + } + }; + var GetRuntimeConfigurationCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "getRuntimeConfiguration"); + __publicField(this, "id", "getRuntimeConfigurationResponse"); + __publicField(this, "resultValidator", getRuntimeConfigurationResponseSchema); + } + }; + var StoreFormDataCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "storeFormData"); + __publicField(this, "paramsValidator", storeFormDataSchema); + } + }; + var GetAvailableInputTypesCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "getAvailableInputTypes"); + __publicField(this, "id", "getAvailableInputTypesResponse"); + __publicField(this, "resultValidator", getAvailableInputTypesResultSchema); + } + }; + var GetAutofillInitDataCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "getAutofillInitData"); + __publicField(this, "id", "getAutofillInitDataResponse"); + __publicField(this, "resultValidator", getAutofillInitDataResponseSchema); + } + }; + var GetAutofillCredentialsCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "getAutofillCredentials"); + __publicField(this, "id", "getAutofillCredentialsResponse"); + __publicField(this, "paramsValidator", getAutofillCredentialsParamsSchema); + __publicField(this, "resultValidator", getAutofillCredentialsResultSchema); + } + }; + var SetSizeCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "setSize"); + __publicField(this, "paramsValidator", setSizeParamsSchema); + } + }; + var SelectedDetailCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "selectedDetail"); + __publicField(this, "paramsValidator", selectedDetailParamsSchema); + } + }; + var CloseAutofillParentCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "closeAutofillParent"); + } + }; + var AskToUnlockProviderCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "askToUnlockProvider"); + __publicField(this, "id", "askToUnlockProviderResponse"); + __publicField(this, "resultValidator", askToUnlockProviderResultSchema); + } + }; + var CheckCredentialsProviderStatusCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "checkCredentialsProviderStatus"); + __publicField(this, "id", "checkCredentialsProviderStatusResponse"); + __publicField(this, "resultValidator", checkCredentialsProviderStatusResultSchema); + } + }; + var SendJSPixelCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "sendJSPixel"); + __publicField(this, "paramsValidator", sendJSPixelParamsSchema); + } + }; + var SetIncontextSignupPermanentlyDismissedAtCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "setIncontextSignupPermanentlyDismissedAt"); + __publicField(this, "paramsValidator", setIncontextSignupPermanentlyDismissedAtSchema); + } + }; + var GetIncontextSignupDismissedAtCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "getIncontextSignupDismissedAt"); + __publicField(this, "id", "getIncontextSignupDismissedAt"); + __publicField(this, "resultValidator", getIncontextSignupDismissedAtSchema); + } + }; + var OpenManagePasswordsCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "openManagePasswords"); + } + }; + var OpenManageCreditCardsCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "openManageCreditCards"); + } + }; + var OpenManageIdentitiesCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "openManageIdentities"); + } + }; + var EmailProtectionStoreUserDataCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "emailProtectionStoreUserData"); + __publicField(this, "id", "emailProtectionStoreUserDataResponse"); + __publicField(this, "paramsValidator", emailProtectionStoreUserDataParamsSchema); + } + }; + var EmailProtectionRemoveUserDataCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "emailProtectionRemoveUserData"); + } + }; + var EmailProtectionGetIsLoggedInCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "emailProtectionGetIsLoggedIn"); + __publicField(this, "id", "emailProtectionGetIsLoggedInResponse"); + __publicField(this, "resultValidator", emailProtectionGetIsLoggedInResultSchema); + } + }; + var EmailProtectionGetUserDataCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "emailProtectionGetUserData"); + __publicField(this, "id", "emailProtectionGetUserDataResponse"); + __publicField(this, "resultValidator", emailProtectionGetUserDataResultSchema); + } + }; + var EmailProtectionGetCapabilitiesCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "emailProtectionGetCapabilities"); + __publicField(this, "id", "emailProtectionGetCapabilitiesResponse"); + __publicField(this, "resultValidator", emailProtectionGetCapabilitiesResultSchema); + } + }; + var EmailProtectionGetAddressesCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "emailProtectionGetAddresses"); + __publicField(this, "id", "emailProtectionGetAddressesResponse"); + __publicField(this, "resultValidator", emailProtectionGetAddressesResultSchema); + } + }; + var EmailProtectionRefreshPrivateAddressCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "emailProtectionRefreshPrivateAddress"); + __publicField(this, "id", "emailProtectionRefreshPrivateAddressResponse"); + __publicField(this, "resultValidator", emailProtectionRefreshPrivateAddressResultSchema); + } + }; + var StartEmailProtectionSignupCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "startEmailProtectionSignup"); + } + }; + var CloseEmailProtectionTabCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "closeEmailProtectionTab"); + } + }; + var ShowInContextEmailProtectionSignupPromptCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "ShowInContextEmailProtectionSignupPrompt"); + __publicField(this, "id", "ShowInContextEmailProtectionSignupPromptResponse"); + __publicField(this, "resultValidator", showInContextEmailProtectionSignupPromptSchema); + } + }; - /** - * Decorate here means adding listeners and an optional icon - * @param {HTMLInputElement} input - * @returns {Promise} - */ - async decorateInput(input) { - const config = (0, _inputTypeConfig.getInputConfig)(input); - const shouldDecorate = await config.shouldDecorate(input, this); - if (!shouldDecorate) return this; - input.setAttribute(ATTR_AUTOFILL, 'true'); - const hasIcon = !!config.getIconBase(input, this); - if (hasIcon) { - const { - onMouseMove, - onMouseLeave - } = this.addAutofillStyles(input); - this.addListener(input, 'mousemove', e => { - if ((0, _autofillUtils.wasAutofilledByChrome)(input)) return; - if ((0, _autofillUtils.isEventWithinDax)(e, e.target)) { - (0, _autofillUtils.addInlineStyles)(e.target, { - 'cursor': 'pointer', - ...onMouseMove - }); - } else { - (0, _autofillUtils.removeInlineStyles)(e.target, { - 'cursor': 'pointer' - }); - // Only overwrite active icon styles if tooltip is closed - if (!this.device.isTooltipActive()) { - (0, _autofillUtils.addInlineStyles)(e.target, { - ...onMouseLeave - }); - } + // src/Scanner.js + var { + MAX_INPUTS_PER_PAGE, + MAX_FORMS_PER_PAGE, + MAX_INPUTS_PER_FORM: MAX_INPUTS_PER_FORM2, + ATTR_INPUT_TYPE: ATTR_INPUT_TYPE3 + } = constants; + var defaultScannerOptions = { + // This buffer size is very large because it's an unexpected edge-case that + // a DOM will be continually modified over and over without ever stopping. If we do see 1000 unique + // new elements in the buffer however then this will prevent the algorithm from never ending. + bufferSize: 50, + // wait for a 500ms window of event silence before performing the scan + debounceTimePeriod: 500, + // how long to wait when performing the initial scan + initialDelay: 0, + // How many inputs is too many on the page. If we detect that there's above + // this maximum, then we don't scan the page. This will prevent slowdowns on + // large pages which are unlikely to require autofill anyway. + maxInputsPerPage: MAX_INPUTS_PER_PAGE, + maxFormsPerPage: MAX_FORMS_PER_PAGE, + maxInputsPerForm: MAX_INPUTS_PER_FORM2 + }; + var DefaultScanner = class { + /** + * @param {import("./DeviceInterface/InterfacePrototype").default} device + * @param {ScannerOptions} options + */ + constructor(device, options) { + /** @type Map */ + __publicField(this, "forms", /* @__PURE__ */ new Map()); + /** @type {any|undefined} the timer to reset */ + __publicField(this, "debounceTimer"); + /** @type {Set} stored changed elements until they can be processed */ + __publicField(this, "changedElements", /* @__PURE__ */ new Set()); + /** @type {ScannerOptions} */ + __publicField(this, "options"); + /** @type {HTMLInputElement | null} */ + __publicField(this, "activeInput", null); + /** @type {boolean} A flag to indicate the whole page will be re-scanned */ + __publicField(this, "rescanAll", false); + /** @type {boolean} Indicates whether we called stopScanning */ + __publicField(this, "stopped", false); + /** @type {import("./Form/matching").Matching} matching */ + __publicField(this, "matching"); + /** + * Watch for changes in the DOM, and enqueue elements to be scanned + * @type {MutationObserver} + */ + __publicField(this, "mutObs", new MutationObserver((mutationList) => { + if (this.rescanAll) { + this.enqueue([]); + return; } - }); - this.addListener(input, 'mouseleave', e => { - (0, _autofillUtils.removeInlineStyles)(e.target, { - 'cursor': 'pointer' - }); - // Only overwrite active icon styles if tooltip is closed - if (!this.device.isTooltipActive()) { - (0, _autofillUtils.addInlineStyles)(e.target, { - ...onMouseLeave - }); + const outgoing = []; + for (const mutationRecord of mutationList) { + if (mutationRecord.type === "childList") { + for (let addedNode of mutationRecord.addedNodes) { + if (!(addedNode instanceof HTMLElement)) + continue; + if (addedNode.nodeName === "DDG-AUTOFILL") + continue; + outgoing.push(addedNode); + } + } } - }); + this.enqueue(outgoing); + })); + this.device = device; + this.matching = createMatching(); + this.options = options; + this.initTimeStamp = Date.now(); } - /** - * @param {PointerEvent} e - * @returns {{ x: number; y: number; } | undefined} + * Determine whether we should fire the credentials autoprompt. This is needed because some sites are blank + * on page load and load scripts asynchronously, so our initial scan didn't set the autoprompt correctly + * @returns {boolean} */ - function getMainClickCoords(e) { - if (!e.isTrusted) return; - const isMainMouseButton = e.button === 0; - if (!isMainMouseButton) return; - return { - x: e.clientX, - y: e.clientY + get shouldAutoprompt() { + return Date.now() - this.initTimeStamp <= 1500; + } + /** + * Call this to scan once and then watch for changes. + * + * Call the returned function to remove listeners. + * @returns {(reason: string, ...rest) => void} + */ + init() { + if (this.device.globalConfig.isExtension) { + this.device.deviceApi.notify(new AddDebugFlagCall({ flag: "autofill" })); + } + window.addEventListener("pointerdown", this, true); + if (!this.device.globalConfig.isMobileApp) { + window.addEventListener("focus", this, true); + } + const delay = this.options.initialDelay; + if (delay === 0) { + window.requestIdleCallback(() => this.scanAndObserve()); + } else { + setTimeout(() => this.scanAndObserve(), delay); + } + return (reason, ...rest) => { + this.stopScanner(reason, ...rest); }; } - /** - * @param {Event} e - * @param {WeakMap} storedClickCoords - * @returns {{ x: number; y: number; } | null} + * Scan the page and begin observing changes + */ + scanAndObserve() { + window.performance?.mark?.("initial_scanner:init:start"); + this.findEligibleInputs(document); + window.performance?.mark?.("initial_scanner:init:end"); + logPerformance("initial_scanner"); + this.mutObs.observe(document.documentElement, { childList: true, subtree: true }); + } + /** + * @param context */ - function getClickCoords(e, storedClickCoords) { - // Get click co-ordinates for pointer events - // We need click coordinates to position the tooltip when the field is in an iframe - if (e.type === 'pointerdown') { - return getMainClickCoords( /** @type {PointerEvent} */e) || null; + findEligibleInputs(context) { + if (this.device.globalConfig.isDDGDomain) { + return this; } - - // Reuse a previous click co-ordinates if they exist for this element - const click = storedClickCoords.get(input); - storedClickCoords.delete(input); - return click || null; + if ("matches" in context && context.matches?.(this.matching.cssSelector("formInputsSelector"))) { + this.addInput(context); + } else { + const inputs = context.querySelectorAll(this.matching.cssSelector("formInputsSelector")); + if (inputs.length > this.options.maxInputsPerPage) { + this.stopScanner(`Too many input fields in the given context (${inputs.length}), stop scanning`, context); + return this; + } + inputs.forEach((input) => this.addInput(input)); + } + return this; } - - // Store the click to a label so we can use the click when the field is focused - // Needed to handle label clicks when the form is in an iframe - let storedClickCoords = new WeakMap(); - let timeout = null; - /** - * @param {PointerEvent} e + * Stops scanning, switches off the mutation observer and clears all forms + * @param {string} reason + * @param {any} rest */ - const handlerLabel = e => { - // Look for e.target OR it's closest parent to be a HTMLLabelElement - const control = /** @type HTMLElement */e.target?.closest('label')?.control; - if (!control) return; - if (e.isTrusted) { - storedClickCoords.set(control, getMainClickCoords(e)); + stopScanner(reason, ...rest) { + this.stopped = true; + if (shouldLog()) { + console.log(reason, ...rest); } - clearTimeout(timeout); - // Remove the stored click if the timer expires - timeout = setTimeout(() => { - storedClickCoords = new WeakMap(); - }, 1000); - }; - const handler = e => { - // Avoid firing multiple times - if (this.isAutofilling || this.device.isTooltipActive()) { + const activeInput = this.device.activeForm?.activeInput; + clearTimeout(this.debounceTimer); + this.changedElements.clear(); + this.mutObs.disconnect(); + window.removeEventListener("pointerdown", this, true); + window.removeEventListener("focus", this, true); + this.forms.forEach((form) => { + form.destroy(); + }); + this.forms.clear(); + activeInput?.focus(); + } + /** + * @param {HTMLElement|HTMLInputElement|HTMLSelectElement} input + * @returns {HTMLFormElement|HTMLElement} + */ + getParentForm(input) { + if (input instanceof HTMLInputElement || input instanceof HTMLSelectElement) { + if (input.form) { + if (this.forms.has(input.form) || // If we've added the form we've already checked that it's not a page wrapper + !isFormLikelyToBeUsedAsPageWrapper(input.form)) { + return input.form; + } + } + } + let traversalLayerCount = 0; + let element = input; + while (traversalLayerCount <= 5 && element.parentElement && element.parentElement !== document.documentElement) { + const siblingForm = element.parentElement?.querySelector("form"); + if (siblingForm && siblingForm !== element) { + return element; + } + element = element.parentElement; + const inputs = element.querySelectorAll(this.matching.cssSelector("formInputsSelector")); + const buttons = element.querySelectorAll(this.matching.cssSelector("submitButtonSelector")); + if (inputs.length > 1 || buttons.length) { + return element; + } + traversalLayerCount++; + } + return input; + } + /** + * @param {HTMLInputElement|HTMLSelectElement} input + */ + addInput(input) { + if (this.stopped) + return; + const parentForm = this.getParentForm(input); + if (parentForm instanceof HTMLFormElement && this.forms.has(parentForm)) { + const foundForm = this.forms.get(parentForm); + if (foundForm && foundForm.inputs.all.size < MAX_INPUTS_PER_FORM2) { + foundForm.addInput(input); + } else { + this.stopScanner("The form has too many inputs, destroying."); + } return; } - - // On mobile, we don't trigger on focus, so here we get the target control on label click - const isLabel = e.target instanceof HTMLLabelElement; - const input = isLabel ? e.target.control : e.target; - if (!input || !this.inputs.all.has(input)) return; - if ((0, _autofillUtils.wasAutofilledByChrome)(input)) return; - if (!(0, _inputTypeConfig.canBeInteractedWith)(input)) return; - const clickCoords = getClickCoords(e, storedClickCoords); - if (e.type === 'pointerdown') { - // Only allow real user clicks with co-ordinates through - if (!e.isTrusted || !clickCoords) return; - } - if (this.shouldOpenTooltip(e, input)) { - const iconClicked = (0, _autofillUtils.isEventWithinDax)(e, input); - // On mobile and extensions we don't trigger the focus event to avoid - // keyboard flashing and conflicts with browsers' own tooltips - if ((this.device.globalConfig.isMobileApp || this.device.globalConfig.isExtension) && - // Avoid the icon capturing clicks on small fields making it impossible to focus - input.offsetWidth > 50 && iconClicked) { - e.preventDefault(); - e.stopImmediatePropagation(); - input.blur(); - } - this.touched.add(input); - this.device.attachTooltip({ - form: this, - input: input, - click: clickCoords, - trigger: 'userInitiated', - triggerMetaData: { - // An 'icon' click is very different to a field click or focus. - // It indicates an explicit opt-in to the feature. - type: iconClicked ? 'explicit-opt-in' : 'implicit-opt-in' - } - }); - const activeStyles = (0, _inputStyles.getIconStylesAlternate)(input, this); - (0, _autofillUtils.addInlineStyles)(input, activeStyles); + if (parentForm.role === "search") + return; + let previouslyFoundParent, childForm; + for (const [formEl] of this.forms) { + if (!formEl.isConnected) { + this.forms.delete(formEl); + continue; + } + if (formEl.contains(parentForm)) { + previouslyFoundParent = formEl; + break; + } + if (parentForm.contains(formEl)) { + childForm = formEl; + break; + } } - }; - if (!(input instanceof HTMLSelectElement)) { - const events = ['pointerdown']; - if (!this.device.globalConfig.isMobileApp) events.push('focus'); - input.labels?.forEach(label => { - if (this.device.globalConfig.isMobileApp) { - // On mobile devices we don't trigger on focus, so we use the click handler here - this.addListener(label, 'pointerdown', handler); + if (previouslyFoundParent) { + if (parentForm instanceof HTMLFormElement && parentForm !== previouslyFoundParent) { + this.forms.delete(previouslyFoundParent); } else { - // Needed to handle label clicks when the form is in an iframe - this.addListener(label, 'pointerdown', handlerLabel); + this.forms.get(previouslyFoundParent)?.addInput(input); } - }); - events.forEach(ev => this.addListener(input, ev, handler)); - } - return this; - } - shouldOpenTooltip(e, input) { - if (!(0, _autofillUtils.isPotentiallyViewable)(input)) return false; - - // Always open if the user has clicked on the Dax icon - if ((0, _autofillUtils.isEventWithinDax)(e, input)) return true; - if (this.device.globalConfig.isWindows) return true; - const subtype = (0, _matching.getInputSubtype)(input); - const isIncontextSignupAvailable = this.device.inContextSignup?.isAvailable(subtype); - if (this.device.globalConfig.isApp) { - const mainType = (0, _matching.getInputMainType)(input); - // Check if, without in-context signup (passed as `null` below), - // we'd have any other items to show. This lets us know if we're - // just showing in-context signup, or with other autofill items. - const hasSavedDetails = this.device.settings.canAutofillType({ - mainType, - subtype - }, null); - - // Don't open the tooltip on input focus whenever it'll only show in-context signup - if (!hasSavedDetails && isIncontextSignupAvailable) return false; - return true; + } else { + if (childForm) { + this.forms.get(childForm)?.destroy(); + this.forms.delete(childForm); + } + if (this.forms.size < this.options.maxFormsPerPage) { + this.forms.set(parentForm, new Form(parentForm, input, this.device, this.matching, this.shouldAutoprompt)); + } else { + this.stopScanner("The page has too many forms, stop adding them."); + } + } } - if (this.device.globalConfig.isExtension || this.device.globalConfig.isMobileApp) { - // Don't open the tooltip on input focus whenever it's showing in-context signup - if (isIncontextSignupAvailable) return false; + /** + * enqueue elements to be re-scanned after the given + * amount of time has elapsed. + * + * @param {(HTMLElement|Document)[]} htmlElements + */ + enqueue(htmlElements) { + if (this.changedElements.size >= this.options.bufferSize) { + this.rescanAll = true; + this.changedElements.clear(); + } else if (!this.rescanAll) { + for (let element of htmlElements) { + this.changedElements.add(element); + } + } + clearTimeout(this.debounceTimer); + this.debounceTimer = setTimeout(() => { + window.performance?.mark?.("scanner:init:start"); + this.processChangedElements(); + this.changedElements.clear(); + this.rescanAll = false; + window.performance?.mark?.("scanner:init:end"); + logPerformance("scanner"); + }, this.options.debounceTimePeriod); } - return !this.touched.has(input) && !input.classList.contains('ddg-autofilled'); - } - autofillInput(input, string, dataType) { - // Do not autofill if it's invisible (select elements can be hidden because of custom implementations) - if (input instanceof HTMLInputElement && !(0, _autofillUtils.isPotentiallyViewable)(input)) return; - // Do not autofill if it's disabled or readonly to avoid potential breakage - if (!(0, _inputTypeConfig.canBeInteractedWith)(input)) return; - - // Don't override values the user provided, unless it's the focused input or we're autofilling creditCards - if (dataType !== 'creditCards' && - // creditCards always override, the others only when we're focusing the input - input.nodeName !== 'SELECT' && input.value !== '' && - // if the input is not empty - this.activeInput !== input // and this is not the active input - ) return; // do not overwrite the value - - // If the value is already there, just return - if (input.value === string) return; - const successful = (0, _autofillUtils.setValue)(input, string, this.device.globalConfig); - if (!successful) return; - input.classList.add('ddg-autofilled'); - (0, _autofillUtils.addInlineStyles)(input, (0, _inputStyles.getIconStylesAutofilled)(input, this)); - this.touched.add(input); - - // If the user changes the value, remove the decoration - input.addEventListener('input', e => this.removeAllHighlights(e, dataType), { - once: true - }); - } - - /** - * Autofill method for email protection only - * @param {string} alias - * @param {'all' | SupportedMainTypes} dataType - */ - autofillEmail(alias) { - let dataType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'identities'; - this.isAutofilling = true; - this.execOnInputs(input => { - const inputSubtype = (0, _matching.getInputSubtype)(input); - if (inputSubtype === 'emailAddress') { - this.autofillInput(input, alias, dataType); - } - }, dataType); - this.isAutofilling = false; - this.removeTooltip(); - } - autofillData(data, dataType) { - this.isAutofilling = true; - this.execOnInputs(input => { - const inputSubtype = (0, _matching.getInputSubtype)(input); - let autofillData = data[inputSubtype]; - if (inputSubtype === 'expiration' && input instanceof HTMLInputElement) { - autofillData = (0, _formatters.getUnifiedExpiryDate)(input, data.expirationMonth, data.expirationYear, this); + /** + * re-scan the changed elements, but only if they + * are still present in the DOM + */ + processChangedElements() { + if (this.rescanAll) { + this.findEligibleInputs(document); + return; } - if (inputSubtype === 'expirationYear' && input instanceof HTMLInputElement) { - autofillData = (0, _formatters.formatCCYear)(input, autofillData, this); + for (let element of this.changedElements) { + if (element.isConnected) { + this.findEligibleInputs(element); + } } - if (inputSubtype === 'addressCountryCode') { - autofillData = (0, _formatters.getCountryName)(input, data); + } + handleEvent(event) { + switch (event.type) { + case "pointerdown": + case "focus": + this.scanShadow(event); + break; } - if (autofillData) { - const variant = (0, _matching.getInputVariant)(input); - if (!variant) { - return this.autofillInput(input, autofillData, dataType); - } - - // Fields with a variant should only be filled when fill is initiated from the same variant. - // This ensures we don't overwrite the current password when filling a - // generated password in password update forms. - if (variant === 'new' && _Credentials.AUTOGENERATED_KEY in data) { - return this.autofillInput(input, autofillData, dataType); - } - if (variant === 'current' && !(_Credentials.AUTOGENERATED_KEY in data)) { - return this.autofillInput(input, autofillData, dataType); - } + } + /** + * Scan clicked input fields, even if they're within a shadow tree + * @param {FocusEvent | PointerEvent} event + */ + scanShadow(event) { + if (this.stopped || !(event.target instanceof Element) || !event.target?.shadowRoot) + return; + window.performance?.mark?.("scan_shadow:init:start"); + const realTarget = pierceShadowTree(event, HTMLInputElement); + if (realTarget instanceof HTMLInputElement && !realTarget.hasAttribute(ATTR_INPUT_TYPE3)) { + this.findEligibleInputs(realTarget.getRootNode()); } - }, dataType); - this.isAutofilling = false; - - // After autofill we check if form values match the data provided… - const formValues = this.getValuesReadyForStorage(); - const areAllFormValuesKnown = Object.keys(formValues[dataType] || {}).every(subtype => formValues[dataType][subtype] === data[subtype]); - if (areAllFormValuesKnown) { - // …if we know all the values do not prompt to store data - this.shouldPromptToStoreData = false; - // reset this to its initial value - this.shouldAutoSubmit = this.device.globalConfig.isMobileApp; - } else { - // …otherwise we will prompt and do not want to autosubmit because the experience is jarring - this.shouldAutoSubmit = false; + window.performance?.mark?.("scan_shadow:init:end"); + logPerformance("scan_shadow"); } - this.device.postAutofill?.(data, dataType, this); - this.removeTooltip(); - } - - /** - * Set all inputs of the data type to "touched" - * @param {'all' | SupportedMainTypes} dataType - */ - touchAllInputs() { - let dataType = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'all'; - this.execOnInputs(input => this.touched.add(input), dataType); - } - getFirstViableCredentialsInput() { - return [...this.inputs.credentials].find(input => (0, _inputTypeConfig.canBeInteractedWith)(input) && (0, _autofillUtils.isPotentiallyViewable)(input)); - } - async promptLoginIfNeeded() { - if (document.visibilityState !== 'visible' || !this.isLogin) return; - const firstCredentialInput = this.getFirstViableCredentialsInput(); - const input = this.activeInput || firstCredentialInput; - if (!input) return; - const mainType = (0, _matching.getInputMainType)(input); - const subtype = (0, _matching.getInputSubtype)(input); - await this.device.settings.populateDataIfNeeded({ - mainType, - subtype + }; + function createScanner(device, scannerOptions) { + return new DefaultScanner(device, { + ...defaultScannerOptions, + ...scannerOptions }); - if (this.device.settings.canAutofillType({ - mainType, - subtype - }, this.device.inContextSignup)) { - // The timeout is needed in case the page shows a cookie prompt with a slight delay - setTimeout(() => { - // safeExecute checks that the element is on screen according to IntersectionObserver - (0, _autofillUtils.safeExecute)(this.form, () => { - const { - x, - y, - width, - height - } = this.form.getBoundingClientRect(); - const elHCenter = x + width / 2; - const elVCenter = y + height / 2; - // This checks that the form is not covered by anything else - const topMostElementFromPoint = document.elementFromPoint(elHCenter, elVCenter); - if (this.form.contains(topMostElementFromPoint)) { - this.execOnInputs(input => { - if ((0, _autofillUtils.isPotentiallyViewable)(input)) { - this.touched.add(input); - } - }, 'credentials'); - this.device.attachTooltip({ - form: this, - input: input, - click: null, - trigger: 'autoprompt', - triggerMetaData: { - type: 'implicit-opt-in' - } - }); - } - }); - }, 200); - } } -} -exports.Form = Form; - -},{"../InputTypes/Credentials.js":35,"../autofill-utils.js":52,"../constants.js":55,"./FormAnalyzer.js":24,"./formatters.js":26,"./inputStyles.js":27,"./inputTypeConfig.js":28,"./matching.js":33}],24:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _matching = require("./matching.js"); -var _constants = require("../constants.js"); -var _compiledMatchingConfig = require("./matching-config/__generated__/compiled-matching-config.js"); -var _autofillUtils = require("../autofill-utils.js"); -class FormAnalyzer { - /** @type HTMLElement */ - form; - /** @type Matching */ - matching; - /** - * @param {HTMLElement} form - * @param {HTMLInputElement|HTMLSelectElement} input - * @param {Matching} [matching] - */ - constructor(form, input, matching) { - this.form = form; - this.matching = matching || new _matching.Matching(_compiledMatchingConfig.matchingConfiguration); + // src/UI/controllers/UIController.js + var UIController = class { /** - * The signal is a continuum where negative values imply login and positive imply signup - * @type {number} + * Implement this method to control what happen when Autofill + * has enough information to 'attach' a tooltip. + * + * @param {AttachArgs} _args + * @returns {void} */ - this.autofillSignal = 0; + attach(_args) { + throw new Error("must implement attach"); + } /** - * A hybrid form can be either a login or a signup, the site uses a single form for both - * @type {number} + * Implement this if your tooltip can be created from positioning + * + topContextData. + * + * For example, in an 'overlay' on macOS/Windows this is needed since + * there's no page information to call 'attach' above. + * + * @param {PosFn} _pos + * @param {TopContextData} _topContextData + * @returns {any | null} */ - this.hybridSignal = 0; - + createTooltip(_pos, _topContextData) { + } /** - * Collects the signals for debugging purposes - * @type {string[]} + * @param {string} _via */ - this.signals = []; - this.evaluateElAttributes(input, 1, true); - form ? this.evaluateForm() : this.evaluatePage(); - return this; - } - - /** - * Hybrid forms can be used for both login and signup - * @returns {boolean} - */ - get isHybrid() { - // When marking for hybrid we also want to ensure other signals are weak - const areOtherSignalsWeak = Math.abs(this.autofillSignal) < 10; - return this.hybridSignal > 0 && areOtherSignalsWeak; - } - get isLogin() { - if (this.isHybrid) return false; - return this.autofillSignal < 0; - } - get isSignup() { - if (this.isHybrid) return false; - return this.autofillSignal >= 0; - } - - /** - * Tilts the scoring towards Signup - * @param {number} strength - * @param {string} signal - * @returns {FormAnalyzer} - */ - increaseSignalBy(strength, signal) { - this.autofillSignal += strength; - this.signals.push(`${signal}: +${strength}`); - return this; - } - - /** - * Tilts the scoring towards Login - * @param {number} strength - * @param {string} signal - * @returns {FormAnalyzer} - */ - decreaseSignalBy(strength, signal) { - this.autofillSignal -= strength; - this.signals.push(`${signal}: -${strength}`); - return this; - } - - /** - * Increases the probability that this is a hybrid form (can be either login or signup) - * @param {number} strength - * @param {string} signal - * @returns {FormAnalyzer} - */ - increaseHybridSignal(strength, signal) { - this.hybridSignal += strength; - this.signals.push(`${signal} (hybrid): +${strength}`); - return this; - } - - /** - * Updates the Login<->Signup signal according to the provided parameters - * @param {object} p - * @param {string} p.string - The string to check - * @param {number} p.strength - Strength of the signal - * @param {string} [p.signalType] - For debugging purposes, we give a name to the signal - * @param {boolean} [p.shouldFlip] - Flips the signals, i.e. when a link points outside. See below - * @param {boolean} [p.shouldCheckUnifiedForm] - Should check for login/signup forms - * @param {boolean} [p.shouldBeConservative] - Should use the conservative signup regex - * @returns {FormAnalyzer} - */ - updateSignal(_ref) { - let { - string, - strength, - signalType = 'generic', - shouldFlip = false, - shouldCheckUnifiedForm = false, - shouldBeConservative = false - } = _ref; - // If the string is empty or too long (noisy) do nothing - if (!string || string.length > _constants.constants.TEXT_LENGTH_CUTOFF) return this; - const matchesLogin = (0, _autofillUtils.safeRegexTest)(/current.?password/i, string) || (0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('loginRegex'), string) || (0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('resetPasswordLink'), string); - - // Check explicitly for unified login/signup forms - if (shouldCheckUnifiedForm && matchesLogin && (0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('conservativeSignupRegex'), string)) { - this.increaseHybridSignal(strength, signalType); - return this; + removeTooltip(_via) { } - const signupRegexToUse = this.matching.getDDGMatcherRegex(shouldBeConservative ? 'conservativeSignupRegex' : 'signupRegex'); - const matchesSignup = (0, _autofillUtils.safeRegexTest)(/new.?password/i, string) || (0, _autofillUtils.safeRegexTest)(signupRegexToUse, string); - - // In some cases a login match means the login is somewhere else, i.e. when a link points outside - if (shouldFlip) { - if (matchesLogin) this.increaseSignalBy(strength, signalType); - if (matchesSignup) this.decreaseSignalBy(strength, signalType); - } else { - if (matchesLogin) this.decreaseSignalBy(strength, signalType); - if (matchesSignup) this.increaseSignalBy(strength, signalType); + /** + * Set the currently open HTMLTooltip instance + * + * @param {import("../HTMLTooltip.js").HTMLTooltip} _tooltip + */ + setActiveTooltip(_tooltip) { } - return this; - } - evaluateElAttributes(el) { - let signalStrength = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 3; - let isInput = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; - Array.from(el.attributes).forEach(attr => { - if (attr.name === 'style') return; - const attributeString = `${attr.name}=${attr.value}`; - this.updateSignal({ - string: attributeString, - strength: signalStrength, - signalType: `${el.name} attr: ${attributeString}`, - shouldCheckUnifiedForm: isInput - }); - }); - } - evaluateUrl() { - const path = window.location.pathname; - const matchesLogin = (0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('loginRegex'), path); - const matchesSignup = (0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('conservativeSignupRegex'), path); - - // If the url matches both, do nothing: the signal is probably confounding - if (matchesLogin && matchesSignup) return; - if (matchesLogin) { - this.decreaseSignalBy(1, 'url matches login'); + /** + * Get the currently open HTMLTooltip instance, if one exists + * + * @returns {import("../HTMLTooltip.js").HTMLTooltip | null} + */ + getActiveTooltip() { + return null; } - if (matchesSignup) { - this.increaseSignalBy(1, 'url matches signup'); + /** + * Indicate whether the controller deems itself 'active' + * + * @returns {boolean} + */ + isActive() { + return false; } - } - evaluatePageTitle() { - const pageTitle = document.title; - this.updateSignal({ - string: pageTitle, - strength: 2, - signalType: `page title: ${pageTitle}`, - shouldCheckUnifiedForm: true - }); - } - evaluatePageHeadings() { - const headings = document.querySelectorAll('h1, h2, h3'); - headings.forEach(heading => { - const textContent = (0, _matching.removeExcessWhitespace)(heading.textContent || ''); - this.updateSignal({ - string: textContent, - strength: 0.5, - signalType: `heading: ${textContent}`, - shouldCheckUnifiedForm: true, - shouldBeConservative: true - }); - }); - } - evaluatePage() { - this.evaluatePageTitle(); - this.evaluatePageHeadings(); - // Check for submit buttons - const buttons = document.querySelectorAll(this.matching.cssSelector('submitButtonSelector')); - buttons.forEach(button => { - // if the button has a form, it's not related to our input, because our input has no form here - if (button instanceof HTMLButtonElement) { - if (!button.form && !button.closest('form')) { - this.evaluateElement(button); - this.evaluateElAttributes(button, 0.5); - } - } - }); - } - evaluateElement(el) { - const string = (0, _autofillUtils.getTextShallow)(el); - if (el.matches(this.matching.cssSelector('password'))) { - // These are explicit signals by the web author, so we weigh them heavily - this.updateSignal({ - string: el.getAttribute('autocomplete') || el.getAttribute('name') || '', - strength: 5, - signalType: `explicit: ${el.getAttribute('autocomplete')}` - }); - return; + /** + * Updates the items in the tooltip based on new data. Currently only supporting credentials. + * @param {CredentialsObject[]} _data + */ + updateItems(_data7) { } + destroy() { + } + }; - // check button contents - if (el.matches(this.matching.cssSelector('submitButtonSelector') + ', *[class*=button]')) { - // If we're confident this is the submit button, it's a stronger signal - let likelyASubmit = (0, _autofillUtils.isLikelyASubmitButton)(el, this.matching); - let shouldFlip = false; - if (likelyASubmit) { - this.form.querySelectorAll('input[type=submit], button[type=submit]').forEach(submit => { - // If there is another element marked as submit and this is not, flip back to false - if (el.type !== 'submit' && el !== submit) { - likelyASubmit = false; - } + // src/UI/controllers/NativeUIController.js + var _passwordStatus; + var NativeUIController = class extends UIController { + constructor() { + super(...arguments); + /** + * Keep track of when passwords were suggested/rejected/accepted etc + * State is kept here because it's specific to the interactions on mobile (eg: NativeUIController) + * + * @type {"default" | "rejected"} + */ + __privateAdd(this, _passwordStatus, "default"); + } + /** + * @param {import('./UIController').AttachArgs} args + */ + attach(args) { + const { form, input, device, trigger, triggerMetaData, topContextData } = args; + const inputType = getInputType(input); + const mainType = getMainTypeFromType(inputType); + const subType = getSubtypeFromType(inputType); + if (mainType === "unknown") { + throw new Error('unreachable, should not be here if (mainType === "unknown")'); + } + if (trigger === "autoprompt") { + window.scrollTo({ + behavior: "smooth", + top: form.form.getBoundingClientRect().top - document.body.getBoundingClientRect().top - 50 }); - } else { - // Here we don't think this is a submit, so if there is another submit in the form, flip the score - const thereIsASubmitButton = Boolean(this.form.querySelector('input[type=submit], button[type=submit]')); - const isSocialButton = /facebook|twitter|google|apple/i.test(string); - shouldFlip = thereIsASubmitButton && !isSocialButton; - } - const strength = likelyASubmit ? 20 : 4; - this.updateSignal({ - string, - strength, - signalType: `button: ${string}`, - shouldFlip + } + let payload = { + inputType, + mainType, + subType, + trigger + }; + if (device.settings.featureToggles.password_generation) { + payload = this.appendGeneratedPassword(topContextData, payload, triggerMetaData); + } + device.deviceApi.request(new GetAutofillDataCall(payload)).then((resp) => { + switch (resp.action) { + case "fill": { + if (mainType in resp) { + form.autofillData(resp[mainType], mainType); + } else { + throw new Error(`action: "fill" cannot occur because "${mainType}" was missing`); + } + break; + } + case "focus": { + form.activeInput?.focus(); + break; + } + case "acceptGeneratedPassword": { + form.autofillData({ + password: topContextData.credentials?.[0].password, + [AUTOGENERATED_KEY]: true + }, mainType); + break; + } + case "rejectGeneratedPassword": { + __privateSet(this, _passwordStatus, "rejected"); + form.touchAllInputs("credentials"); + form.activeInput?.focus(); + break; + } + default: { + if (args.device.isTestMode()) { + console.warn("response not handled", resp); + } + } + } + }).catch((e) => { + console.error("NativeTooltip::device.getAutofillData(payload)"); + console.error(e); }); - return; } - // if an external link matches one of the regexes, we assume the match is not pertinent to the current form - if (el instanceof HTMLAnchorElement && el.href && el.getAttribute('href') !== '#' || (el.getAttribute('role') || '').toUpperCase() === 'LINK' || el.matches('button[class*=secondary]')) { - let shouldFlip = true; - let strength = 1; - // Don't flip forgotten password links - if ((0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('resetPasswordLink'), string)) { - shouldFlip = false; - strength = 3; - } else if ((0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('loginProvidersRegex'), string)) { - // Don't flip login providers links - shouldFlip = false; - } - this.updateSignal({ - string, - strength, - signalType: `external link: ${string}`, - shouldFlip - }); - } else { - // any other case - this.updateSignal({ - string, - strength: 1, - signalType: `generic: ${string}`, - shouldCheckUnifiedForm: true - }); + /** + * If a password exists in `topContextData`, we can append it to the outgoing data + * in a way that native platforms can easily understand. + * + * @param {TopContextData} topContextData + * @param {import('../../deviceApiCalls/__generated__/validators-ts.js').GetAutofillDataRequest} outgoingData + * @param {import('../../UI/controllers/UIController.js').AttachArgs['triggerMetaData']} triggerMetaData + * @return {import('../../deviceApiCalls/__generated__/validators-ts.js').GetAutofillDataRequest} + */ + appendGeneratedPassword(topContextData, outgoingData, triggerMetaData) { + const autoGeneratedCredential = topContextData.credentials?.find((credential) => credential.autogenerated); + if (!autoGeneratedCredential?.password) { + return outgoingData; + } + function suggestPassword() { + if (!autoGeneratedCredential?.password) + throw new Error("unreachable"); + return { + ...outgoingData, + generatedPassword: { + value: autoGeneratedCredential.password, + username: autoGeneratedCredential.username + } + }; + } + if (triggerMetaData.type === "explicit-opt-in") { + return suggestPassword(); + } + if (triggerMetaData.type === "implicit-opt-in" && __privateGet(this, _passwordStatus) !== "rejected") { + return suggestPassword(); + } + return outgoingData; } - } - evaluateForm() { - // Check page url - this.evaluateUrl(); - - // Check page title - this.evaluatePageTitle(); - - // Check form attributes - this.evaluateElAttributes(this.form); - - // Check form contents (noisy elements are skipped with the safeUniversalSelector) - this.form.querySelectorAll(this.matching.cssSelector('safeUniversalSelector')).forEach(el => { - // Check if element is not hidden. Note that we can't use offsetHeight - // nor intersectionObserver, because the element could be outside the - // viewport or its parent hidden - const displayValue = window.getComputedStyle(el, null).getPropertyValue('display'); - if (displayValue !== 'none') this.evaluateElement(el); - }); + }; + _passwordStatus = new WeakMap(); - // A form with many fields is unlikely to be a login form - const relevantFields = this.form.querySelectorAll(this.matching.cssSelector('genericTextField')); - if (relevantFields.length >= 4) { - this.increaseSignalBy(relevantFields.length * 1.5, 'many fields: it is probably not a login'); + // packages/messaging/webkit.js + var WebkitMessagingTransport = class { + /** + * @param {WebkitMessagingConfig} config + */ + constructor(config) { + /** @type {WebkitMessagingConfig} */ + __publicField(this, "config"); + __publicField(this, "globals"); + /** + * @type {{name: string, length: number}} + */ + __publicField(this, "algoObj", { name: "AES-GCM", length: 256 }); + this.config = config; + this.globals = captureGlobals(); + if (!this.config.hasModernWebkitAPI) { + this.captureWebkitHandlers(this.config.webkitMessageHandlerNames); + } } - - // If we can't decide at this point, try reading page headings - if (this.autofillSignal === 0) { - this.evaluatePageHeadings(); + /** + * Sends message to the webkit layer (fire and forget) + * @param {String} handler + * @param {*} data + * @internal + */ + wkSend(handler, data = {}) { + if (!(handler in this.globals.window.webkit.messageHandlers)) { + throw new MissingHandler(`Missing webkit handler: '${handler}'`, handler); + } + const outgoing = { + ...data, + messageHandling: { ...data.messageHandling, secret: this.config.secret } + }; + if (!this.config.hasModernWebkitAPI) { + if (!(handler in this.globals.capturedWebkitHandlers)) { + throw new MissingHandler(`cannot continue, method ${handler} not captured on macos < 11`, handler); + } else { + return this.globals.capturedWebkitHandlers[handler](outgoing); + } + } + return this.globals.window.webkit.messageHandlers[handler].postMessage?.(outgoing); } - return this; - } - - /** @type {undefined|boolean} */ - _isCCForm = undefined; - /** - * Tries to infer if it's a credit card form - * @returns {boolean} - */ - isCCForm() { - if (this._isCCForm !== undefined) return this._isCCForm; - const formEl = this.form; - const ccFieldSelector = this.matching.joinCssSelectors('cc'); - if (!ccFieldSelector) { - this._isCCForm = false; - return this._isCCForm; + /** + * Sends message to the webkit layer and waits for the specified response + * @param {String} handler + * @param {*} data + * @returns {Promise<*>} + * @internal + */ + async wkSendAndWait(handler, data = {}) { + if (this.config.hasModernWebkitAPI) { + const response = await this.wkSend(handler, data); + return this.globals.JSONparse(response || "{}"); + } + try { + const randMethodName = this.createRandMethodName(); + const key = await this.createRandKey(); + const iv = this.createRandIv(); + const { ciphertext, tag } = await new this.globals.Promise((resolve) => { + this.generateRandomMethod(randMethodName, resolve); + data.messageHandling = new SecureMessagingParams({ + methodName: randMethodName, + secret: this.config.secret, + key: this.globals.Arrayfrom(key), + iv: this.globals.Arrayfrom(iv) + }); + this.wkSend(handler, data); + }); + const cipher = new this.globals.Uint8Array([...ciphertext, ...tag]); + const decrypted = await this.decrypt(cipher, key, iv); + return this.globals.JSONparse(decrypted || "{}"); + } catch (e) { + if (e instanceof MissingHandler) { + throw e; + } else { + console.error("decryption failed", e); + console.error(e); + return { error: e }; + } + } } - const hasCCSelectorChild = formEl.matches(ccFieldSelector) || formEl.querySelector(ccFieldSelector); - // If the form contains one of the specific selectors, we have high confidence - if (hasCCSelectorChild) { - this._isCCForm = true; - return this._isCCForm; + /** + * @param {string} name + * @param {Record} [data] + */ + notify(name, data = {}) { + this.wkSend(name, data); } - - // Read form attributes to find a signal - const hasCCAttribute = [...formEl.attributes].some(_ref2 => { - let { - name, - value - } = _ref2; - return (0, _autofillUtils.safeRegexTest)(/(credit|payment).?card/i, `${name}=${value}`); - }); - if (hasCCAttribute) { - this._isCCForm = true; - return this._isCCForm; + /** + * @param {string} name + * @param {Record} [data] + */ + request(name, data = {}) { + return this.wkSendAndWait(name, data); } - - // Match form textContent against common cc fields (includes hidden labels) - const textMatches = formEl.textContent?.match(/(credit|payment).?card(.?number)?|ccv|security.?code|cvv|cvc|csc/ig); - // De-dupe matches to avoid counting the same element more than once - const deDupedMatches = new Set(textMatches?.map(match => match.toLowerCase())); - - // We check for more than one to minimise false positives - this._isCCForm = Boolean(textMatches && deDupedMatches.size > 1); - return this._isCCForm; - } -} -var _default = exports.default = FormAnalyzer; - -},{"../autofill-utils.js":52,"../constants.js":55,"./matching-config/__generated__/compiled-matching-config.js":31,"./matching.js":33}],25:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.COUNTRY_NAMES_TO_CODES = exports.COUNTRY_CODES_TO_NAMES = void 0; -/** - * Country names object using 2-letter country codes to reference country name - * Derived from the Intl.DisplayNames implementation - * @source https://stackoverflow.com/a/70517921/1948947 - */ -const COUNTRY_CODES_TO_NAMES = exports.COUNTRY_CODES_TO_NAMES = { - AC: 'Ascension Island', - AD: 'Andorra', - AE: 'United Arab Emirates', - AF: 'Afghanistan', - AG: 'Antigua & Barbuda', - AI: 'Anguilla', - AL: 'Albania', - AM: 'Armenia', - AN: 'Curaçao', - AO: 'Angola', - AQ: 'Antarctica', - AR: 'Argentina', - AS: 'American Samoa', - AT: 'Austria', - AU: 'Australia', - AW: 'Aruba', - AX: 'Åland Islands', - AZ: 'Azerbaijan', - BA: 'Bosnia & Herzegovina', - BB: 'Barbados', - BD: 'Bangladesh', - BE: 'Belgium', - BF: 'Burkina Faso', - BG: 'Bulgaria', - BH: 'Bahrain', - BI: 'Burundi', - BJ: 'Benin', - BL: 'St. Barthélemy', - BM: 'Bermuda', - BN: 'Brunei', - BO: 'Bolivia', - BQ: 'Caribbean Netherlands', - BR: 'Brazil', - BS: 'Bahamas', - BT: 'Bhutan', - BU: 'Myanmar (Burma)', - BV: 'Bouvet Island', - BW: 'Botswana', - BY: 'Belarus', - BZ: 'Belize', - CA: 'Canada', - CC: 'Cocos (Keeling) Islands', - CD: 'Congo - Kinshasa', - CF: 'Central African Republic', - CG: 'Congo - Brazzaville', - CH: 'Switzerland', - CI: 'Côte d’Ivoire', - CK: 'Cook Islands', - CL: 'Chile', - CM: 'Cameroon', - CN: 'China mainland', - CO: 'Colombia', - CP: 'Clipperton Island', - CR: 'Costa Rica', - CS: 'Serbia', - CU: 'Cuba', - CV: 'Cape Verde', - CW: 'Curaçao', - CX: 'Christmas Island', - CY: 'Cyprus', - CZ: 'Czechia', - DD: 'Germany', - DE: 'Germany', - DG: 'Diego Garcia', - DJ: 'Djibouti', - DK: 'Denmark', - DM: 'Dominica', - DO: 'Dominican Republic', - DY: 'Benin', - DZ: 'Algeria', - EA: 'Ceuta & Melilla', - EC: 'Ecuador', - EE: 'Estonia', - EG: 'Egypt', - EH: 'Western Sahara', - ER: 'Eritrea', - ES: 'Spain', - ET: 'Ethiopia', - EU: 'European Union', - EZ: 'Eurozone', - FI: 'Finland', - FJ: 'Fiji', - FK: 'Falkland Islands', - FM: 'Micronesia', - FO: 'Faroe Islands', - FR: 'France', - FX: 'France', - GA: 'Gabon', - GB: 'United Kingdom', - GD: 'Grenada', - GE: 'Georgia', - GF: 'French Guiana', - GG: 'Guernsey', - GH: 'Ghana', - GI: 'Gibraltar', - GL: 'Greenland', - GM: 'Gambia', - GN: 'Guinea', - GP: 'Guadeloupe', - GQ: 'Equatorial Guinea', - GR: 'Greece', - GS: 'So. Georgia & So. Sandwich Isl.', - GT: 'Guatemala', - GU: 'Guam', - GW: 'Guinea-Bissau', - GY: 'Guyana', - HK: 'Hong Kong', - HM: 'Heard & McDonald Islands', - HN: 'Honduras', - HR: 'Croatia', - HT: 'Haiti', - HU: 'Hungary', - HV: 'Burkina Faso', - IC: 'Canary Islands', - ID: 'Indonesia', - IE: 'Ireland', - IL: 'Israel', - IM: 'Isle of Man', - IN: 'India', - IO: 'Chagos Archipelago', - IQ: 'Iraq', - IR: 'Iran', - IS: 'Iceland', - IT: 'Italy', - JE: 'Jersey', - JM: 'Jamaica', - JO: 'Jordan', - JP: 'Japan', - KE: 'Kenya', - KG: 'Kyrgyzstan', - KH: 'Cambodia', - KI: 'Kiribati', - KM: 'Comoros', - KN: 'St. Kitts & Nevis', - KP: 'North Korea', - KR: 'South Korea', - KW: 'Kuwait', - KY: 'Cayman Islands', - KZ: 'Kazakhstan', - LA: 'Laos', - LB: 'Lebanon', - LC: 'St. Lucia', - LI: 'Liechtenstein', - LK: 'Sri Lanka', - LR: 'Liberia', - LS: 'Lesotho', - LT: 'Lithuania', - LU: 'Luxembourg', - LV: 'Latvia', - LY: 'Libya', - MA: 'Morocco', - MC: 'Monaco', - MD: 'Moldova', - ME: 'Montenegro', - MF: 'St. Martin', - MG: 'Madagascar', - MH: 'Marshall Islands', - MK: 'North Macedonia', - ML: 'Mali', - MM: 'Myanmar (Burma)', - MN: 'Mongolia', - MO: 'Macao', - MP: 'Northern Mariana Islands', - MQ: 'Martinique', - MR: 'Mauritania', - MS: 'Montserrat', - MT: 'Malta', - MU: 'Mauritius', - MV: 'Maldives', - MW: 'Malawi', - MX: 'Mexico', - MY: 'Malaysia', - MZ: 'Mozambique', - NA: 'Namibia', - NC: 'New Caledonia', - NE: 'Niger', - NF: 'Norfolk Island', - NG: 'Nigeria', - NH: 'Vanuatu', - NI: 'Nicaragua', - NL: 'Netherlands', - NO: 'Norway', - NP: 'Nepal', - NR: 'Nauru', - NU: 'Niue', - NZ: 'New Zealand', - OM: 'Oman', - PA: 'Panama', - PE: 'Peru', - PF: 'French Polynesia', - PG: 'Papua New Guinea', - PH: 'Philippines', - PK: 'Pakistan', - PL: 'Poland', - PM: 'St. Pierre & Miquelon', - PN: 'Pitcairn Islands', - PR: 'Puerto Rico', - PS: 'Palestinian Territories', - PT: 'Portugal', - PW: 'Palau', - PY: 'Paraguay', - QA: 'Qatar', - QO: 'Outlying Oceania', - RE: 'Réunion', - RH: 'Zimbabwe', - RO: 'Romania', - RS: 'Serbia', - RU: 'Russia', - RW: 'Rwanda', - SA: 'Saudi Arabia', - SB: 'Solomon Islands', - SC: 'Seychelles', - SD: 'Sudan', - SE: 'Sweden', - SG: 'Singapore', - SH: 'St. Helena', - SI: 'Slovenia', - SJ: 'Svalbard & Jan Mayen', - SK: 'Slovakia', - SL: 'Sierra Leone', - SM: 'San Marino', - SN: 'Senegal', - SO: 'Somalia', - SR: 'Suriname', - SS: 'South Sudan', - ST: 'São Tomé & Príncipe', - SU: 'Russia', - SV: 'El Salvador', - SX: 'Sint Maarten', - SY: 'Syria', - SZ: 'Eswatini', - TA: 'Tristan da Cunha', - TC: 'Turks & Caicos Islands', - TD: 'Chad', - TF: 'French Southern Territories', - TG: 'Togo', - TH: 'Thailand', - TJ: 'Tajikistan', - TK: 'Tokelau', - TL: 'Timor-Leste', - TM: 'Turkmenistan', - TN: 'Tunisia', - TO: 'Tonga', - TP: 'Timor-Leste', - TR: 'Turkey', - TT: 'Trinidad & Tobago', - TV: 'Tuvalu', - TW: 'Taiwan', - TZ: 'Tanzania', - UA: 'Ukraine', - UG: 'Uganda', - UK: 'United Kingdom', - UM: 'U.S. Outlying Islands', - UN: 'United Nations', - US: 'United States', - UY: 'Uruguay', - UZ: 'Uzbekistan', - VA: 'Vatican City', - VC: 'St. Vincent & Grenadines', - VD: 'Vietnam', - VE: 'Venezuela', - VG: 'British Virgin Islands', - VI: 'U.S. Virgin Islands', - VN: 'Vietnam', - VU: 'Vanuatu', - WF: 'Wallis & Futuna', - WS: 'Samoa', - XA: 'Pseudo-Accents', - XB: 'Pseudo-Bidi', - XK: 'Kosovo', - YD: 'Yemen', - YE: 'Yemen', - YT: 'Mayotte', - YU: 'Serbia', - ZA: 'South Africa', - ZM: 'Zambia', - ZR: 'Congo - Kinshasa', - ZW: 'Zimbabwe', - ZZ: 'Unknown Region' -}; - -/** - * Country names object using country name to reference 2-letter country codes - * Derived from the solution above with - * Object.fromEntries(Object.entries(COUNTRY_CODES_TO_NAMES).map(entry => [entry[1], entry[0]])) - */ -const COUNTRY_NAMES_TO_CODES = exports.COUNTRY_NAMES_TO_CODES = { - 'Ascension Island': 'AC', - Andorra: 'AD', - 'United Arab Emirates': 'AE', - Afghanistan: 'AF', - 'Antigua & Barbuda': 'AG', - Anguilla: 'AI', - Albania: 'AL', - Armenia: 'AM', - 'Curaçao': 'CW', - Angola: 'AO', - Antarctica: 'AQ', - Argentina: 'AR', - 'American Samoa': 'AS', - Austria: 'AT', - Australia: 'AU', - Aruba: 'AW', - 'Åland Islands': 'AX', - Azerbaijan: 'AZ', - 'Bosnia & Herzegovina': 'BA', - Barbados: 'BB', - Bangladesh: 'BD', - Belgium: 'BE', - 'Burkina Faso': 'HV', - Bulgaria: 'BG', - Bahrain: 'BH', - Burundi: 'BI', - Benin: 'DY', - 'St. Barthélemy': 'BL', - Bermuda: 'BM', - Brunei: 'BN', - Bolivia: 'BO', - 'Caribbean Netherlands': 'BQ', - Brazil: 'BR', - Bahamas: 'BS', - Bhutan: 'BT', - 'Myanmar (Burma)': 'MM', - 'Bouvet Island': 'BV', - Botswana: 'BW', - Belarus: 'BY', - Belize: 'BZ', - Canada: 'CA', - 'Cocos (Keeling) Islands': 'CC', - 'Congo - Kinshasa': 'ZR', - 'Central African Republic': 'CF', - 'Congo - Brazzaville': 'CG', - Switzerland: 'CH', - 'Côte d’Ivoire': 'CI', - 'Cook Islands': 'CK', - Chile: 'CL', - Cameroon: 'CM', - 'China mainland': 'CN', - Colombia: 'CO', - 'Clipperton Island': 'CP', - 'Costa Rica': 'CR', - Serbia: 'YU', - Cuba: 'CU', - 'Cape Verde': 'CV', - 'Christmas Island': 'CX', - Cyprus: 'CY', - Czechia: 'CZ', - Germany: 'DE', - 'Diego Garcia': 'DG', - Djibouti: 'DJ', - Denmark: 'DK', - Dominica: 'DM', - 'Dominican Republic': 'DO', - Algeria: 'DZ', - 'Ceuta & Melilla': 'EA', - Ecuador: 'EC', - Estonia: 'EE', - Egypt: 'EG', - 'Western Sahara': 'EH', - Eritrea: 'ER', - Spain: 'ES', - Ethiopia: 'ET', - 'European Union': 'EU', - Eurozone: 'EZ', - Finland: 'FI', - Fiji: 'FJ', - 'Falkland Islands': 'FK', - Micronesia: 'FM', - 'Faroe Islands': 'FO', - France: 'FX', - Gabon: 'GA', - 'United Kingdom': 'UK', - Grenada: 'GD', - Georgia: 'GE', - 'French Guiana': 'GF', - Guernsey: 'GG', - Ghana: 'GH', - Gibraltar: 'GI', - Greenland: 'GL', - Gambia: 'GM', - Guinea: 'GN', - Guadeloupe: 'GP', - 'Equatorial Guinea': 'GQ', - Greece: 'GR', - 'So. Georgia & So. Sandwich Isl.': 'GS', - Guatemala: 'GT', - Guam: 'GU', - 'Guinea-Bissau': 'GW', - Guyana: 'GY', - 'Hong Kong': 'HK', - 'Heard & McDonald Islands': 'HM', - Honduras: 'HN', - Croatia: 'HR', - Haiti: 'HT', - Hungary: 'HU', - 'Canary Islands': 'IC', - Indonesia: 'ID', - Ireland: 'IE', - Israel: 'IL', - 'Isle of Man': 'IM', - India: 'IN', - 'Chagos Archipelago': 'IO', - Iraq: 'IQ', - Iran: 'IR', - Iceland: 'IS', - Italy: 'IT', - Jersey: 'JE', - Jamaica: 'JM', - Jordan: 'JO', - Japan: 'JP', - Kenya: 'KE', - Kyrgyzstan: 'KG', - Cambodia: 'KH', - Kiribati: 'KI', - Comoros: 'KM', - 'St. Kitts & Nevis': 'KN', - 'North Korea': 'KP', - 'South Korea': 'KR', - Kuwait: 'KW', - 'Cayman Islands': 'KY', - Kazakhstan: 'KZ', - Laos: 'LA', - Lebanon: 'LB', - 'St. Lucia': 'LC', - Liechtenstein: 'LI', - 'Sri Lanka': 'LK', - Liberia: 'LR', - Lesotho: 'LS', - Lithuania: 'LT', - Luxembourg: 'LU', - Latvia: 'LV', - Libya: 'LY', - Morocco: 'MA', - Monaco: 'MC', - Moldova: 'MD', - Montenegro: 'ME', - 'St. Martin': 'MF', - Madagascar: 'MG', - 'Marshall Islands': 'MH', - 'North Macedonia': 'MK', - Mali: 'ML', - Mongolia: 'MN', - Macao: 'MO', - 'Northern Mariana Islands': 'MP', - Martinique: 'MQ', - Mauritania: 'MR', - Montserrat: 'MS', - Malta: 'MT', - Mauritius: 'MU', - Maldives: 'MV', - Malawi: 'MW', - Mexico: 'MX', - Malaysia: 'MY', - Mozambique: 'MZ', - Namibia: 'NA', - 'New Caledonia': 'NC', - Niger: 'NE', - 'Norfolk Island': 'NF', - Nigeria: 'NG', - Vanuatu: 'VU', - Nicaragua: 'NI', - Netherlands: 'NL', - Norway: 'NO', - Nepal: 'NP', - Nauru: 'NR', - Niue: 'NU', - 'New Zealand': 'NZ', - Oman: 'OM', - Panama: 'PA', - Peru: 'PE', - 'French Polynesia': 'PF', - 'Papua New Guinea': 'PG', - Philippines: 'PH', - Pakistan: 'PK', - Poland: 'PL', - 'St. Pierre & Miquelon': 'PM', - 'Pitcairn Islands': 'PN', - 'Puerto Rico': 'PR', - 'Palestinian Territories': 'PS', - Portugal: 'PT', - Palau: 'PW', - Paraguay: 'PY', - Qatar: 'QA', - 'Outlying Oceania': 'QO', - 'Réunion': 'RE', - Zimbabwe: 'ZW', - Romania: 'RO', - Russia: 'SU', - Rwanda: 'RW', - 'Saudi Arabia': 'SA', - 'Solomon Islands': 'SB', - Seychelles: 'SC', - Sudan: 'SD', - Sweden: 'SE', - Singapore: 'SG', - 'St. Helena': 'SH', - Slovenia: 'SI', - 'Svalbard & Jan Mayen': 'SJ', - Slovakia: 'SK', - 'Sierra Leone': 'SL', - 'San Marino': 'SM', - Senegal: 'SN', - Somalia: 'SO', - Suriname: 'SR', - 'South Sudan': 'SS', - 'São Tomé & Príncipe': 'ST', - 'El Salvador': 'SV', - 'Sint Maarten': 'SX', - Syria: 'SY', - Eswatini: 'SZ', - 'Tristan da Cunha': 'TA', - 'Turks & Caicos Islands': 'TC', - Chad: 'TD', - 'French Southern Territories': 'TF', - Togo: 'TG', - Thailand: 'TH', - Tajikistan: 'TJ', - Tokelau: 'TK', - 'Timor-Leste': 'TP', - Turkmenistan: 'TM', - Tunisia: 'TN', - Tonga: 'TO', - Turkey: 'TR', - 'Trinidad & Tobago': 'TT', - Tuvalu: 'TV', - Taiwan: 'TW', - Tanzania: 'TZ', - Ukraine: 'UA', - Uganda: 'UG', - 'U.S. Outlying Islands': 'UM', - 'United Nations': 'UN', - 'United States': 'US', - Uruguay: 'UY', - Uzbekistan: 'UZ', - 'Vatican City': 'VA', - 'St. Vincent & Grenadines': 'VC', - Vietnam: 'VN', - Venezuela: 'VE', - 'British Virgin Islands': 'VG', - 'U.S. Virgin Islands': 'VI', - 'Wallis & Futuna': 'WF', - Samoa: 'WS', - 'Pseudo-Accents': 'XA', - 'Pseudo-Bidi': 'XB', - Kosovo: 'XK', - Yemen: 'YE', - Mayotte: 'YT', - 'South Africa': 'ZA', - Zambia: 'ZM', - 'Unknown Region': 'ZZ' -}; - -},{}],26:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.prepareFormValuesForStorage = exports.inferCountryCodeFromElement = exports.getUnifiedExpiryDate = exports.getMMAndYYYYFromString = exports.getCountryName = exports.getCountryDisplayName = exports.formatPhoneNumber = exports.formatFullName = exports.formatCCYear = void 0; -var _matching = require("./matching.js"); -var _countryNames = require("./countryNames.js"); -// Matches strings like mm/yy, mm-yyyy, mm-aa, 12 / 2024 -const DATE_SEPARATOR_REGEX = /\b((.)\2{1,3}|\d+)(?\s?[/\s.\-_—–]\s?)((.)\5{1,3}|\d+)\b/i; -// Matches 4 non-digit repeated characters (YYYY or AAAA) or 4 digits (2022) -const FOUR_DIGIT_YEAR_REGEX = /(\D)\1{3}|\d{4}/i; - -/** - * Format the cc year to best adapt to the input requirements (YY vs YYYY) - * @param {HTMLInputElement} input - * @param {string} year - * @param {import("./Form").Form} form - * @returns {string} - */ -const formatCCYear = (input, year, form) => { - const selector = form.matching.cssSelector('formInputsSelector'); - if (input.maxLength === 4 || (0, _matching.checkPlaceholderAndLabels)(input, FOUR_DIGIT_YEAR_REGEX, form.form, selector)) return year; - return `${Number(year) - 2000}`; -}; - -/** - * Get a unified expiry date with separator - * @param {HTMLInputElement} input - * @param {string} month - * @param {string} year - * @param {import("./Form").Form} form - * @returns {string} - */ -exports.formatCCYear = formatCCYear; -const getUnifiedExpiryDate = (input, month, year, form) => { - const formattedYear = formatCCYear(input, year, form); - const paddedMonth = `${month}`.padStart(2, '0'); - const cssSelector = form.matching.cssSelector('formInputsSelector'); - const separator = (0, _matching.matchInPlaceholderAndLabels)(input, DATE_SEPARATOR_REGEX, form.form, cssSelector)?.groups?.separator || '/'; - return `${paddedMonth}${separator}${formattedYear}`; -}; -exports.getUnifiedExpiryDate = getUnifiedExpiryDate; -const formatFullName = _ref => { - let { - firstName = '', - middleName = '', - lastName = '' - } = _ref; - return `${firstName} ${middleName ? middleName + ' ' : ''}${lastName}`.trim(); -}; - -/** - * Tries to look up a human-readable country name from the country code - * @param {string} locale - * @param {string} addressCountryCode - * @return {string} - Returns the country code if we can't find a name - */ -exports.formatFullName = formatFullName; -const getCountryDisplayName = (locale, addressCountryCode) => { - try { - const regionNames = new Intl.DisplayNames([locale], { - type: 'region' - }); - // Adding this ts-ignore to prevent having to change this implementation. - // @ts-ignore - return regionNames.of(addressCountryCode); - } catch (e) { - return _countryNames.COUNTRY_CODES_TO_NAMES[addressCountryCode] || addressCountryCode; - } -}; - -/** - * Tries to infer the element locale or returns 'en' - * @param {HTMLInputElement | HTMLSelectElement} el - * @return {string | 'en'} - */ -exports.getCountryDisplayName = getCountryDisplayName; -const inferElementLocale = el => el.lang || el.form?.lang || document.body.lang || document.documentElement.lang || 'en'; - -/** - * Tries to format the country code into a localised country name - * @param {HTMLInputElement | HTMLSelectElement} el - * @param {{addressCountryCode?: string}} options - */ -const getCountryName = function (el) { - let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - const { - addressCountryCode - } = options; - if (!addressCountryCode) return ''; - - // Try to infer the field language or fallback to en - const elLocale = inferElementLocale(el); - const localisedCountryName = getCountryDisplayName(elLocale, addressCountryCode); - - // If it's a select el we try to find a suitable match to autofill - if (el.nodeName === 'SELECT') { - const englishCountryName = getCountryDisplayName('en', addressCountryCode); - // This regex matches both the localised and English country names - const countryNameRegex = new RegExp(String.raw`${localisedCountryName.replace(/ /g, '.?')}|${englishCountryName.replace(/ /g, '.?')}`, 'i'); - const countryCodeRegex = new RegExp(String.raw`\b${addressCountryCode}\b`, 'i'); - - // We check the country code first because it's more accurate - if (el instanceof HTMLSelectElement) { - for (const option of el.options) { - if (countryCodeRegex.test(option.value)) { - return option.value; + /** + * Generate a random method name and adds it to the global scope + * The native layer will use this method to send the response + * @param {string | number} randomMethodName + * @param {Function} callback + */ + generateRandomMethod(randomMethodName, callback) { + this.globals.ObjectDefineProperty(this.globals.window, randomMethodName, { + enumerable: false, + // configurable, To allow for deletion later + configurable: true, + writable: false, + /** + * @param {any[]} args + */ + value: (...args) => { + callback(...args); + delete this.globals.window[randomMethodName]; } - } - for (const option of el.options) { - if (countryNameRegex.test(option.value) || countryNameRegex.test(option.innerText)) return option.value; - } + }); } - } - return localisedCountryName; -}; - -/** - * Try to get a map of localised country names to code, or falls back to the English map - * @param {HTMLInputElement | HTMLSelectElement} el - */ -exports.getCountryName = getCountryName; -const getLocalisedCountryNamesToCodes = el => { - if (typeof Intl.DisplayNames !== 'function') return _countryNames.COUNTRY_NAMES_TO_CODES; - - // Try to infer the field language or fallback to en - const elLocale = inferElementLocale(el); - return Object.fromEntries(Object.entries(_countryNames.COUNTRY_CODES_TO_NAMES).map(_ref2 => { - let [code] = _ref2; - return [getCountryDisplayName(elLocale, code), code]; - })); -}; - -/** - * Try to infer a country code from an element we identified as identities.addressCountryCode - * @param {HTMLInputElement | HTMLSelectElement} el - * @return {string} - */ -const inferCountryCodeFromElement = el => { - if (_countryNames.COUNTRY_CODES_TO_NAMES[el.value]) return el.value; - if (_countryNames.COUNTRY_NAMES_TO_CODES[el.value]) return _countryNames.COUNTRY_NAMES_TO_CODES[el.value]; - const localisedCountryNamesToCodes = getLocalisedCountryNamesToCodes(el); - if (localisedCountryNamesToCodes[el.value]) return localisedCountryNamesToCodes[el.value]; - if (el instanceof HTMLSelectElement) { - const selectedText = el.selectedOptions[0]?.text; - if (_countryNames.COUNTRY_CODES_TO_NAMES[selectedText]) return selectedText; - if (_countryNames.COUNTRY_NAMES_TO_CODES[selectedText]) return localisedCountryNamesToCodes[selectedText]; - if (localisedCountryNamesToCodes[selectedText]) return localisedCountryNamesToCodes[selectedText]; - } - return ''; -}; - -/** - * Gets separate expiration month and year from a single string - * @param {string} expiration - * @return {{expirationYear: string, expirationMonth: string}} - */ -exports.inferCountryCodeFromElement = inferCountryCodeFromElement; -const getMMAndYYYYFromString = expiration => { - /** @type {string[]} */ - const values = expiration.match(/(\d+)/g) || []; - return values?.reduce((output, current) => { - if (Number(current) > 12) { - output.expirationYear = current.padStart(4, '20'); - } else { - output.expirationMonth = current.padStart(2, '0'); + randomString() { + return "" + this.globals.getRandomValues(new this.globals.Uint32Array(1))[0]; + } + createRandMethodName() { + return "_" + this.randomString(); + } + /** + * @returns {Promise} + */ + async createRandKey() { + const key = await this.globals.generateKey(this.algoObj, true, ["encrypt", "decrypt"]); + const exportedKey = await this.globals.exportKey("raw", key); + return new this.globals.Uint8Array(exportedKey); + } + /** + * @returns {Uint8Array} + */ + createRandIv() { + return this.globals.getRandomValues(new this.globals.Uint8Array(12)); } - return output; - }, { - expirationYear: '', - expirationMonth: '' - }); -}; - -/** - * @param {InternalDataStorageObject} credentials - * @return {boolean} - */ -exports.getMMAndYYYYFromString = getMMAndYYYYFromString; -const shouldStoreCredentials = _ref3 => { - let { - credentials - } = _ref3; - return Boolean(credentials.password); -}; - -/** - * @param {InternalDataStorageObject} credentials - * @return {boolean} - */ -const shouldStoreIdentities = _ref4 => { - let { - identities - } = _ref4; - return Boolean((identities.firstName || identities.fullName) && identities.addressStreet && identities.addressCity); -}; - -/** - * @param {InternalDataStorageObject} credentials - * @return {boolean} - */ -const shouldStoreCreditCards = _ref5 => { - let { - creditCards - } = _ref5; - if (!creditCards.cardNumber) return false; - if (creditCards.cardSecurityCode) return true; - // Some forms (Amazon) don't have the cvv, so we still save if there's the expiration - if (creditCards.expiration) return true; - // Expiration can also be two separate values - return Boolean(creditCards.expirationYear && creditCards.expirationMonth); -}; - -/** - * Removes formatting characters from phone numbers, only leaves digits and the + sign - * @param {String} phone - * @returns {String} - */ -const formatPhoneNumber = phone => phone.replaceAll(/[^0-9|+]/g, ''); - -/** - * Formats form data into an object to send to the device for storage - * If values are insufficient for a complete entry, they are discarded - * @param {InternalDataStorageObject} formValues - * @return {DataStorageObject} - */ -exports.formatPhoneNumber = formatPhoneNumber; -const prepareFormValuesForStorage = formValues => { - /** @type {Partial} */ - let { - credentials, - identities, - creditCards - } = formValues; - - // If we have an identity name but not a card name, copy it over there - if (!creditCards.cardName && (identities?.fullName || identities?.firstName)) { - creditCards.cardName = identities?.fullName || formatFullName(identities); - } - - /** Fixes for credentials **/ - // Don't store if there isn't enough data - if (shouldStoreCredentials(formValues)) { - // If we don't have a username to match a password, let's see if the email is available - if (credentials.password && !credentials.username && identities.emailAddress) { - credentials.username = identities.emailAddress; - } - } else { - credentials = undefined; - } - - /** Fixes for identities **/ - // Don't store if there isn't enough data - if (shouldStoreIdentities(formValues)) { - if (identities.fullName) { - // when forms have both first/last and fullName we keep the individual values and drop the fullName - if (!(identities.firstName && identities.lastName)) { - // If the fullname can be easily split into two, we'll store it as first and last - const nameParts = identities.fullName.trim().split(/\s+/); - if (nameParts.length === 2) { - identities.firstName = nameParts[0]; - identities.lastName = nameParts[1]; - } else { - // If we can't split it, just store it as first name - identities.firstName = identities.fullName; + /** + * @param {BufferSource} ciphertext + * @param {BufferSource} key + * @param {Uint8Array} iv + * @returns {Promise} + */ + async decrypt(ciphertext, key, iv) { + const cryptoKey = await this.globals.importKey("raw", key, "AES-GCM", false, ["decrypt"]); + const algo = { name: "AES-GCM", iv }; + let decrypted = await this.globals.decrypt(algo, cryptoKey, ciphertext); + let dec = new this.globals.TextDecoder(); + return dec.decode(decrypted); + } + /** + * When required (such as on macos 10.x), capture the `postMessage` method on + * each webkit messageHandler + * + * @param {string[]} handlerNames + */ + captureWebkitHandlers(handlerNames) { + const handlers = window.webkit.messageHandlers; + if (!handlers) + throw new MissingHandler("window.webkit.messageHandlers was absent", "all"); + for (let webkitMessageHandlerName of handlerNames) { + if (typeof handlers[webkitMessageHandlerName]?.postMessage === "function") { + const original = handlers[webkitMessageHandlerName]; + const bound = handlers[webkitMessageHandlerName].postMessage?.bind(original); + this.globals.capturedWebkitHandlers[webkitMessageHandlerName] = bound; + delete handlers[webkitMessageHandlerName].postMessage; } } - delete identities.fullName; } - if (identities.phone) { - identities.phone = formatPhoneNumber(identities.phone); + }; + var WebkitMessagingConfig = class { + /** + * @param {object} params + * @param {boolean} params.hasModernWebkitAPI + * @param {string[]} params.webkitMessageHandlerNames + * @param {string} params.secret + */ + constructor(params) { + this.hasModernWebkitAPI = params.hasModernWebkitAPI; + this.webkitMessageHandlerNames = params.webkitMessageHandlerNames; + this.secret = params.secret; } - } else { - identities = undefined; - } - - /** Fixes for credit cards **/ - // Don't store if there isn't enough data - if (shouldStoreCreditCards(formValues)) { - if (creditCards.expiration) { - const { - expirationMonth, - expirationYear - } = getMMAndYYYYFromString(creditCards.expiration); - creditCards.expirationMonth = expirationMonth; - creditCards.expirationYear = expirationYear; - delete creditCards.expiration; - } - creditCards.expirationYear = creditCards.expirationYear?.padStart(4, '20'); - if (creditCards.cardNumber) { - creditCards.cardNumber = creditCards.cardNumber.replace(/\D/g, ''); - } - } else { - creditCards = undefined; - } - return { - credentials, - identities, - creditCards }; -}; -exports.prepareFormValuesForStorage = prepareFormValuesForStorage; - -},{"./countryNames.js":25,"./matching.js":33}],27:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.getIconStylesBase = exports.getIconStylesAutofilled = exports.getIconStylesAlternate = void 0; -var _inputTypeConfig = require("./inputTypeConfig.js"); -/** - * Returns the css-ready base64 encoding of the icon for the given input - * @param {HTMLInputElement} input - * @param {import("./Form").Form} form - * @param {'base' | 'filled' | 'alternate'} type - * @return {string} - */ -const getIcon = function (input, form) { - let type = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'base'; - const config = (0, _inputTypeConfig.getInputConfig)(input); - if (type === 'base') { - return config.getIconBase(input, form); - } - if (type === 'filled') { - return config.getIconFilled(input, form); - } - if (type === 'alternate') { - return config.getIconAlternate(input, form); + var SecureMessagingParams = class { + /** + * @param {object} params + * @param {string} params.methodName + * @param {string} params.secret + * @param {number[]} params.key + * @param {number[]} params.iv + */ + constructor(params) { + this.methodName = params.methodName; + this.secret = params.secret; + this.key = params.key; + this.iv = params.iv; + } + }; + function captureGlobals() { + return { + window, + // Methods must be bound to their interface, otherwise they throw Illegal invocation + encrypt: window.crypto.subtle.encrypt.bind(window.crypto.subtle), + decrypt: window.crypto.subtle.decrypt.bind(window.crypto.subtle), + generateKey: window.crypto.subtle.generateKey.bind(window.crypto.subtle), + exportKey: window.crypto.subtle.exportKey.bind(window.crypto.subtle), + importKey: window.crypto.subtle.importKey.bind(window.crypto.subtle), + getRandomValues: window.crypto.getRandomValues.bind(window.crypto), + TextEncoder, + TextDecoder, + Uint8Array, + Uint16Array, + Uint32Array, + JSONstringify: window.JSON.stringify, + JSONparse: window.JSON.parse, + Arrayfrom: window.Array.from, + Promise: window.Promise, + ObjectDefineProperty: window.Object.defineProperty, + addEventListener: window.addEventListener.bind(window), + /** @type {Record} */ + capturedWebkitHandlers: {} + }; } - return ''; -}; - -/** - * Returns an object with styles to be applied inline - * @param {HTMLInputElement} input - * @param {String} icon - * @return {Object} - */ -const getBasicStyles = (input, icon) => ({ - // Height must be > 0 to account for fields initially hidden - 'background-size': `auto ${input.offsetHeight <= 30 && input.offsetHeight > 0 ? '100%' : '24px'}`, - 'background-position': 'center right', - 'background-repeat': 'no-repeat', - 'background-origin': 'content-box', - 'background-image': `url(${icon})`, - 'transition': 'background 0s' -}); - -/** - * Get inline styles for the injected icon, base state - * @param {HTMLInputElement} input - * @param {import("./Form").Form} form - * @return {Object} - */ -const getIconStylesBase = (input, form) => { - const icon = getIcon(input, form); - if (!icon) return {}; - return getBasicStyles(input, icon); -}; - -/** - * Get inline styles for the injected icon, alternate state - * @param {HTMLInputElement} input - * @param {import("./Form").Form} form - * @return {Object} - */ -exports.getIconStylesBase = getIconStylesBase; -const getIconStylesAlternate = (input, form) => { - const icon = getIcon(input, form, 'alternate'); - if (!icon) return {}; - return { - ...getBasicStyles(input, icon) - }; -}; -/** - * Get inline styles for the injected icon, autofilled state - * @param {HTMLInputElement} input - * @param {import("./Form").Form} form - * @return {Object} - */ -exports.getIconStylesAlternate = getIconStylesAlternate; -const getIconStylesAutofilled = (input, form) => { - const icon = getIcon(input, form, 'filled'); - const iconStyle = icon ? getBasicStyles(input, icon) : {}; - return { - ...iconStyle, - 'background-color': '#F8F498', - 'color': '#333333' - }; -}; -exports.getIconStylesAutofilled = getIconStylesAutofilled; - -},{"./inputTypeConfig.js":28}],28:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.isFieldDecorated = exports.getInputConfigFromType = exports.getInputConfig = exports.canBeInteractedWith = void 0; -var _logoSvg = require("./logo-svg.js"); -var ddgPasswordIcons = _interopRequireWildcard(require("../UI/img/ddgPasswordIcon.js")); -var _matching = require("./matching.js"); -var _Credentials = require("../InputTypes/Credentials.js"); -var _CreditCard = require("../InputTypes/CreditCard.js"); -var _Identity = require("../InputTypes/Identity.js"); -var _constants = require("../constants.js"); -function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } -function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } -/** - * Get the icon for the identities (currently only Dax for emails) - * @param {HTMLInputElement} input - * @param {import("./Form").Form} form - * @return {string} - */ -const getIdentitiesIcon = (input, _ref) => { - let { - device - } = _ref; - if (!canBeInteractedWith(input)) return ''; - - // In Firefox web_accessible_resources could leak a unique user identifier, so we avoid it here - const { - isDDGApp, - isFirefox, - isExtension - } = device.globalConfig; - const subtype = (0, _matching.getInputSubtype)(input); - if (device.inContextSignup?.isAvailable(subtype)) { - if (isDDGApp || isFirefox) { - return _logoSvg.daxGrayscaleBase64; - } else if (isExtension) { - return chrome.runtime.getURL('img/logo-small-grayscale.svg'); + // packages/messaging/messaging.js + var Messaging = class { + /** + * @param {WebkitMessagingConfig} config + */ + constructor(config) { + this.transport = getTransport(config); } - } - if (subtype === 'emailAddress' && device.isDeviceSignedIn()) { - if (isDDGApp || isFirefox) { - return _logoSvg.daxBase64; - } else if (isExtension) { - return chrome.runtime.getURL('img/logo-small.svg'); + /** + * Send a 'fire-and-forget' message. + * @throws {Error} + * {@link MissingHandler} + * + * @example + * + * ``` + * const messaging = new Messaging(config) + * messaging.notify("foo", {bar: "baz"}) + * ``` + * @param {string} name + * @param {Record} [data] + */ + notify(name, data = {}) { + this.transport.notify(name, data); } - } - return ''; -}; - -/** - * Get the alternate icon for the identities (currently only Dax for emails) - * @param {HTMLInputElement} input - * @param {import("./Form").Form} form - * @return {string} - */ -const getIdentitiesAlternateIcon = (input, _ref2) => { - let { - device - } = _ref2; - if (!canBeInteractedWith(input)) return ''; - - // In Firefox web_accessible_resources could leak a unique user identifier, so we avoid it here - const { - isDDGApp, - isFirefox, - isExtension - } = device.globalConfig; - const subtype = (0, _matching.getInputSubtype)(input); - const isIncontext = device.inContextSignup?.isAvailable(subtype); - const isEmailProtection = subtype === 'emailAddress' && device.isDeviceSignedIn(); - if (isIncontext || isEmailProtection) { - if (isDDGApp || isFirefox) { - return _logoSvg.daxBase64; - } else if (isExtension) { - return chrome.runtime.getURL('img/logo-small.svg'); + /** + * Send a request, and wait for a response + * @throws {Error} + * {@link MissingHandler} + * + * @example + * ``` + * const messaging = new Messaging(config) + * const response = await messaging.request("foo", {bar: "baz"}) + * ``` + * + * @param {string} name + * @param {Record} [data] + * @return {Promise} + */ + request(name, data = {}) { + return this.transport.request(name, data); } - } - return ''; -}; - -/** - * Checks whether a field is readonly or disabled - * @param {HTMLInputElement} input - * @return {boolean} - */ -const canBeInteractedWith = input => !input.readOnly && !input.disabled; - -/** - * Checks if the input can be decorated and we have the needed data - * @param {HTMLInputElement} input - * @param {import("../DeviceInterface/InterfacePrototype").default} device - * @returns {Promise} - */ -exports.canBeInteractedWith = canBeInteractedWith; -const canBeAutofilled = async (input, device) => { - if (!canBeInteractedWith(input)) return false; - const mainType = (0, _matching.getInputMainType)(input); - const subtype = (0, _matching.getInputSubtype)(input); - await device.settings.populateDataIfNeeded({ - mainType, - subtype - }); - const canAutofill = device.settings.canAutofillType({ - mainType, - subtype - }, device.inContextSignup); - return Boolean(canAutofill); -}; - -/** - * A map of config objects. These help by centralising here some complexity - * @type {InputTypeConfig} - */ -const inputTypeConfig = { - /** @type {CredentialsInputTypeConfig} */ - credentials: { - type: 'credentials', - displayName: 'passwords', - getIconBase: (input, _ref3) => { - let { - device - } = _ref3; - if (!canBeInteractedWith(input)) return ''; - if (device.settings.featureToggles.inlineIcon_credentials) { - const subtype = (0, _matching.getInputSubtype)(input); - const variant = (0, _matching.getInputVariant)(input); - if (subtype === 'password' && variant === 'new') { - return ddgPasswordIcons.ddgPasswordGenIconBase; - } - return ddgPasswordIcons.ddgPasswordIconBase; - } - return ''; - }, - getIconFilled: (input, _ref4) => { - let { - device - } = _ref4; - if (device.settings.featureToggles.inlineIcon_credentials) { - const subtype = (0, _matching.getInputSubtype)(input); - const variant = (0, _matching.getInputVariant)(input); - if (subtype === 'password' && variant === 'new') { - return ddgPasswordIcons.ddgPasswordGenIconFilled; - } - return ddgPasswordIcons.ddgPasswordIconFilled; - } - return ''; - }, - getIconAlternate: () => '', - shouldDecorate: async (input, _ref5) => { - let { - isLogin, - isHybrid, - device - } = _ref5; - const subtype = (0, _matching.getInputSubtype)(input); - const variant = (0, _matching.getInputVariant)(input); - - // Check first for password generation and the password.new scoring - if (device.settings.featureToggles.password_generation) { - if (subtype === 'password' && variant === 'new') { - return canBeInteractedWith(input); - } - } - - // if we are on a 'login' page, check if we have data to autofill the field - if (isLogin || isHybrid || variant === 'current') { - return canBeAutofilled(input, device); - } - return false; - }, - dataType: 'Credentials', - tooltipItem: data => (0, _Credentials.createCredentialsTooltipItem)(data) - }, - /** @type {CreditCardsInputTypeConfig} */ - creditCards: { - type: 'creditCards', - displayName: 'credit cards', - getIconBase: () => '', - getIconFilled: () => '', - getIconAlternate: () => '', - shouldDecorate: async (input, _ref6) => { - let { - device - } = _ref6; - return canBeAutofilled(input, device); - }, - dataType: 'CreditCards', - tooltipItem: data => new _CreditCard.CreditCardTooltipItem(data) - }, - /** @type {IdentitiesInputTypeConfig} */ - identities: { - type: 'identities', - displayName: 'identities', - getIconBase: getIdentitiesIcon, - getIconFilled: getIdentitiesIcon, - getIconAlternate: getIdentitiesAlternateIcon, - shouldDecorate: async (input, _ref7) => { - let { - device - } = _ref7; - return canBeAutofilled(input, device); - }, - dataType: 'Identities', - tooltipItem: data => new _Identity.IdentityTooltipItem(data) - }, - /** @type {UnknownInputTypeConfig} */ - unknown: { - type: 'unknown', - displayName: '', - getIconBase: () => '', - getIconFilled: () => '', - getIconAlternate: () => '', - shouldDecorate: async () => false, - dataType: '', - tooltipItem: _data => { - throw new Error('unreachable - setting tooltip to unknown field type'); + }; + function getTransport(config) { + if (config instanceof WebkitMessagingConfig) { + return new WebkitMessagingTransport(config); } + throw new Error("unreachable"); } -}; - -/** - * Retrieves configs from an input el - * @param {HTMLInputElement} input - * @returns {InputTypeConfigs} - */ -const getInputConfig = input => { - const inputType = (0, _matching.getInputType)(input); - return getInputConfigFromType(inputType); -}; - -/** - * Retrieves configs from an input type - * @param {import('./matching').SupportedTypes} inputType - * @returns {InputTypeConfigs} - */ -exports.getInputConfig = getInputConfig; -const getInputConfigFromType = inputType => { - const inputMainType = (0, _matching.getMainTypeFromType)(inputType); - return inputTypeConfig[inputMainType]; -}; - -/** - * Given an input field checks wheter it was previously decorated - * @param {HTMLInputElement} input - * @returns {Boolean} - */ -exports.getInputConfigFromType = getInputConfigFromType; -const isFieldDecorated = input => { - return input.hasAttribute(_constants.constants.ATTR_INPUT_TYPE); -}; -exports.isFieldDecorated = isFieldDecorated; - -},{"../InputTypes/Credentials.js":35,"../InputTypes/CreditCard.js":36,"../InputTypes/Identity.js":37,"../UI/img/ddgPasswordIcon.js":50,"../constants.js":55,"./logo-svg.js":30,"./matching.js":33}],29:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.extractElementStrings = exports.EXCLUDED_TAGS = void 0; -var _matching = require("./matching.js"); -const EXCLUDED_TAGS = exports.EXCLUDED_TAGS = ['BR', 'SCRIPT', 'NOSCRIPT', 'OPTION', 'STYLE']; - -/** - * Extract all strings of an element's children to an array. - * "element.textContent" is a string which is merged of all children nodes, - * which can cause issues with things like script tags etc. - * - * @param {Element} element - * A DOM element to be extracted. - * @returns {string[]} - * All strings in an element. - */ -const extractElementStrings = element => { - const strings = new Set(); - const _extractElementStrings = el => { - if (EXCLUDED_TAGS.includes(el.tagName)) { - return; + var MissingHandler = class extends Error { + /** + * @param {string} message + * @param {string} handlerName + */ + constructor(message, handlerName) { + super(message); + this.handlerName = handlerName; } + }; - // only take the string when it's an explicit text node - if (el.nodeType === el.TEXT_NODE || !el.childNodes.length) { - let trimmedText = (0, _matching.removeExcessWhitespace)(el.textContent); - if (trimmedText) { - strings.add(trimmedText); - } - return; + // src/deviceApiCalls/transports/apple.transport.js + var AppleTransport = class extends DeviceApiTransport { + /** @param {GlobalConfig} globalConfig */ + constructor(globalConfig) { + super(); + this.config = globalConfig; + const webkitConfig = new WebkitMessagingConfig({ + hasModernWebkitAPI: this.config.hasModernWebkitAPI, + webkitMessageHandlerNames: this.config.webkitMessageHandlerNames, + secret: this.config.secret + }); + this.messaging = new Messaging(webkitConfig); } - for (let node of el.childNodes) { - let nodeType = node.nodeType; - if (nodeType !== node.ELEMENT_NODE && nodeType !== node.TEXT_NODE) { - continue; + async send(deviceApiCall) { + try { + if (deviceApiCall.id) { + return await this.messaging.request(deviceApiCall.method, deviceApiCall.params || void 0); + } else { + return this.messaging.notify(deviceApiCall.method, deviceApiCall.params || void 0); + } + } catch (e) { + if (e instanceof MissingHandler) { + if (this.config.isDDGTestMode) { + console.log("MissingWebkitHandler error for:", deviceApiCall.method); + } + throw new Error("unimplemented handler: " + deviceApiCall.method); + } else { + throw e; + } } - _extractElementStrings(node); } }; - _extractElementStrings(element); - return [...strings]; -}; -exports.extractElementStrings = extractElementStrings; - -},{"./matching.js":33}],30:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.daxGrayscaleBase64 = exports.daxBase64 = void 0; -const daxSvg = ` - - - - - - - - - - - - - -`.trim(); -const daxBase64 = exports.daxBase64 = `data:image/svg+xml;base64,${window.btoa(daxSvg)}`; -const daxGrayscaleSvg = ` - - - - - - -`.trim(); -const daxGrayscaleBase64 = exports.daxGrayscaleBase64 = `data:image/svg+xml;base64,${window.btoa(daxGrayscaleSvg)}`; - -},{}],31:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.matchingConfiguration = void 0; -/* DO NOT EDIT, this file was generated by scripts/precompile-regexes.js */ -/** @type {MatchingConfiguration} */ -const matchingConfiguration = exports.matchingConfiguration = { - matchers: { - fields: { - unknown: { - type: 'unknown', - strategies: { - ddgMatcher: 'unknown' - } - }, - emailAddress: { - type: 'emailAddress', - strategies: { - cssSelector: 'emailAddress', - ddgMatcher: 'emailAddress', - vendorRegex: 'email' - } - }, - password: { - type: 'password', - strategies: { - cssSelector: 'password', - ddgMatcher: 'password' - } - }, - username: { - type: 'username', - strategies: { - cssSelector: 'username', - ddgMatcher: 'username' - } - }, - firstName: { - type: 'firstName', - strategies: { - cssSelector: 'firstName', - ddgMatcher: 'firstName', - vendorRegex: 'given-name' + // src/deviceApiCalls/transports/android.transport.js + var AndroidTransport = class extends DeviceApiTransport { + /** @param {GlobalConfig} globalConfig */ + constructor(globalConfig) { + super(); + /** @type {GlobalConfig} */ + __publicField(this, "config"); + this.config = globalConfig; + if (this.config.isDDGTestMode) { + if (typeof window.BrowserAutofill?.getAutofillData !== "function") { + console.warn("window.BrowserAutofill.getAutofillData missing"); } - }, - middleName: { - type: 'middleName', - strategies: { - cssSelector: 'middleName', - ddgMatcher: 'middleName', - vendorRegex: 'additional-name' - } - }, - lastName: { - type: 'lastName', - strategies: { - cssSelector: 'lastName', - ddgMatcher: 'lastName', - vendorRegex: 'family-name' - } - }, - fullName: { - type: 'fullName', - strategies: { - cssSelector: 'fullName', - ddgMatcher: 'fullName', - vendorRegex: 'name' - } - }, - phone: { - type: 'phone', - strategies: { - cssSelector: 'phone', - ddgMatcher: 'phone', - vendorRegex: 'tel' - } - }, - addressStreet: { - type: 'addressStreet', - strategies: { - cssSelector: 'addressStreet', - ddgMatcher: 'addressStreet', - vendorRegex: 'address-line1' - } - }, - addressStreet2: { - type: 'addressStreet2', - strategies: { - cssSelector: 'addressStreet2', - ddgMatcher: 'addressStreet2', - vendorRegex: 'address-line2' - } - }, - addressCity: { - type: 'addressCity', - strategies: { - cssSelector: 'addressCity', - ddgMatcher: 'addressCity', - vendorRegex: 'address-level2' + if (typeof window.BrowserAutofill?.storeFormData !== "function") { + console.warn("window.BrowserAutofill.storeFormData missing"); } - }, - addressProvince: { - type: 'addressProvince', - strategies: { - cssSelector: 'addressProvince', - ddgMatcher: 'addressProvince', - vendorRegex: 'address-level1' + } + } + /** + * @param {import("../../../packages/device-api").DeviceApiCall} deviceApiCall + * @returns {Promise} + */ + async send(deviceApiCall) { + if (deviceApiCall instanceof GetRuntimeConfigurationCall) { + return androidSpecificRuntimeConfiguration(this.config); + } + if (deviceApiCall instanceof GetAvailableInputTypesCall) { + return androidSpecificAvailableInputTypes(this.config); + } + if (deviceApiCall instanceof GetIncontextSignupDismissedAtCall) { + window.BrowserAutofill.getIncontextSignupDismissedAt(JSON.stringify(deviceApiCall.params)); + return waitForResponse(deviceApiCall.id, this.config); + } + if (deviceApiCall instanceof SetIncontextSignupPermanentlyDismissedAtCall) { + return window.BrowserAutofill.setIncontextSignupPermanentlyDismissedAt(JSON.stringify(deviceApiCall.params)); + } + if (deviceApiCall instanceof StartEmailProtectionSignupCall) { + return window.BrowserAutofill.startEmailProtectionSignup(JSON.stringify(deviceApiCall.params)); + } + if (deviceApiCall instanceof CloseEmailProtectionTabCall) { + return window.BrowserAutofill.closeEmailProtectionTab(JSON.stringify(deviceApiCall.params)); + } + if (deviceApiCall instanceof ShowInContextEmailProtectionSignupPromptCall) { + window.BrowserAutofill.showInContextEmailProtectionSignupPrompt(JSON.stringify(deviceApiCall.params)); + return waitForResponse(deviceApiCall.id, this.config); + } + if (deviceApiCall instanceof GetAutofillDataCall) { + window.BrowserAutofill.getAutofillData(JSON.stringify(deviceApiCall.params)); + return waitForResponse(deviceApiCall.id, this.config); + } + if (deviceApiCall instanceof StoreFormDataCall) { + return window.BrowserAutofill.storeFormData(JSON.stringify(deviceApiCall.params)); + } + throw new Error("android: not implemented: " + deviceApiCall.method); + } + }; + function waitForResponse(expectedResponse, config) { + return new Promise((resolve) => { + const handler = (e) => { + if (!config.isDDGTestMode) { + if (e.origin !== "") { + return; + } } - }, - addressPostalCode: { - type: 'addressPostalCode', - strategies: { - cssSelector: 'addressPostalCode', - ddgMatcher: 'addressPostalCode', - vendorRegex: 'postal-code' + if (!e.data) { + return; } - }, - addressCountryCode: { - type: 'addressCountryCode', - strategies: { - cssSelector: 'addressCountryCode', - ddgMatcher: 'addressCountryCode', - vendorRegex: 'country' + if (typeof e.data !== "string") { + if (config.isDDGTestMode) { + console.log("\u274C event.data was not a string. Expected a string so that it can be JSON parsed"); + } + return; } - }, - birthdayDay: { - type: 'birthdayDay', - strategies: { - cssSelector: 'birthdayDay', - ddgMatcher: 'birthdayDay' + try { + let data = JSON.parse(e.data); + if (data.type === expectedResponse) { + window.removeEventListener("message", handler); + return resolve(data); + } + if (config.isDDGTestMode) { + console.log(`\u274C event.data.type was '${data.type}', which didnt match '${expectedResponse}'`, JSON.stringify(data)); + } + } catch (e2) { + window.removeEventListener("message", handler); + if (config.isDDGTestMode) { + console.log("\u274C Could not JSON.parse the response"); + } } - }, - birthdayMonth: { - type: 'birthdayMonth', - strategies: { - cssSelector: 'birthdayMonth', - ddgMatcher: 'birthdayMonth' + }; + window.addEventListener("message", handler); + }); + } + function androidSpecificRuntimeConfiguration(globalConfig) { + if (!globalConfig.userPreferences) { + throw new Error("globalConfig.userPreferences not supported yet on Android"); + } + return { + success: { + // @ts-ignore + contentScope: globalConfig.contentScope, + // @ts-ignore + userPreferences: globalConfig.userPreferences, + // @ts-ignore + userUnprotectedDomains: globalConfig.userUnprotectedDomains, + // @ts-ignore + availableInputTypes: globalConfig.availableInputTypes + } + }; + } + function androidSpecificAvailableInputTypes(globalConfig) { + if (!globalConfig.availableInputTypes) { + throw new Error("globalConfig.availableInputTypes not supported yet on Android"); + } + return { + success: globalConfig.availableInputTypes + }; + } + + // zod-replacers:./deviceApiCalls/__generated__/validators.zod.js + var autofillSettingsSchema = null; + var providerStatusUpdatedSchema = null; + + // src/Settings.js + var _Settings = class { + /** + * @param {GlobalConfig} config + * @param {DeviceApi} deviceApi + */ + constructor(config, deviceApi) { + /** @type {GlobalConfig} */ + __publicField(this, "globalConfig"); + /** @type {DeviceApi} */ + __publicField(this, "deviceApi"); + /** @type {AutofillFeatureToggles | null} */ + __publicField(this, "_featureToggles", null); + /** @type {AvailableInputTypes | null} */ + __publicField(this, "_availableInputTypes", null); + /** @type {RuntimeConfiguration | null | undefined} */ + __publicField(this, "_runtimeConfiguration", null); + /** @type {boolean | null} */ + __publicField(this, "_enabled", null); + this.deviceApi = deviceApi; + this.globalConfig = config; + } + /** + * Feature toggles are delivered as part of the Runtime Configuration - a flexible design that + * allows data per user + remote config to be accessed together. + * + * Once we access the Runtime Configuration, we then extract the autofill-specific settings via + * `runtimeConfig.userPreferences.features.autofill.settings` and validate that separately. + * + * The 2-step validation occurs because RuntimeConfiguration will be coming from a shared library + * and does not know about the shape of Autofill specific settings. + * + * @returns {Promise} + */ + async getFeatureToggles() { + try { + const runtimeConfig = await this._getRuntimeConfiguration(); + const autofillSettings = validate(runtimeConfig.userPreferences?.features?.autofill?.settings, autofillSettingsSchema); + return autofillSettings.featureToggles; + } catch (e) { + if (this.globalConfig.isDDGTestMode) { + console.log("isDDGTestMode: getFeatureToggles: \u274C", e); } - }, - birthdayYear: { - type: 'birthdayYear', - strategies: { - cssSelector: 'birthdayYear', - ddgMatcher: 'birthdayYear' + return _Settings.defaults.featureToggles; + } + } + /** + * If the platform in question is happy to derive it's 'enabled' state from the RuntimeConfiguration, + * then they should use this. Currently only Windows supports this, but we aim to move all platforms to + * support this going forward. + * @returns {Promise} + */ + async getEnabled() { + try { + const runtimeConfig = await this._getRuntimeConfiguration(); + const enabled = autofillEnabled(runtimeConfig); + return enabled; + } catch (e) { + if (this.globalConfig.isDDGTestMode) { + console.log("isDDGTestMode: getEnabled: \u274C", e); } - }, - cardName: { - type: 'cardName', - strategies: { - cssSelector: 'cardName', - ddgMatcher: 'cardName', - vendorRegex: 'cc-name' + return null; + } + } + /** + * Get runtime configuration, but only once. + * + * Some platforms may be reading this directly from inlined variables, whilst others + * may make a DeviceApiCall. + * + * Currently, it's only read once - but we should be open to the idea that we may need + * this to be called multiple times in the future. + * + * @returns {Promise} + * @throws + * @private + */ + async _getRuntimeConfiguration() { + if (this._runtimeConfiguration) + return this._runtimeConfiguration; + const runtimeConfig = await this.deviceApi.request(new GetRuntimeConfigurationCall(null)); + this._runtimeConfiguration = runtimeConfig; + return this._runtimeConfiguration; + } + /** + * Available Input Types are boolean indicators to represent which input types the + * current **user** has data available for. + * + * @returns {Promise} + */ + async getAvailableInputTypes() { + try { + if (this.globalConfig.isTopFrame) { + return _Settings.defaults.availableInputTypes; } - }, - cardNumber: { - type: 'cardNumber', - strategies: { - cssSelector: 'cardNumber', - ddgMatcher: 'cardNumber', - vendorRegex: 'cc-number' + return await this.deviceApi.request(new GetAvailableInputTypesCall(null)); + } catch (e) { + if (this.globalConfig.isDDGTestMode) { + console.log("isDDGTestMode: getAvailableInputTypes: \u274C", e); } - }, - cardSecurityCode: { - type: 'cardSecurityCode', - strategies: { - cssSelector: 'cardSecurityCode', - ddgMatcher: 'cardSecurityCode' + return _Settings.defaults.availableInputTypes; + } + } + /** + * To 'refresh' settings means to re-call APIs to determine new state. This may + * only occur once per page, but it must be done before any page scanning/decorating can happen + * + * @returns {Promise<{ + * availableInputTypes: AvailableInputTypes, + * featureToggles: AutofillFeatureToggles, + * enabled: boolean | null + * }>} + */ + async refresh() { + this.setEnabled(await this.getEnabled()); + this.setFeatureToggles(await this.getFeatureToggles()); + this.setAvailableInputTypes(await this.getAvailableInputTypes()); + if (typeof this.enabled === "boolean") { + if (!this.enabled) { + return _Settings.defaults; } + } + return { + featureToggles: this.featureToggles, + availableInputTypes: this.availableInputTypes, + enabled: this.enabled + }; + } + /** + * Checks if input type is one which we can't autofill + * @param {{ + * mainType: SupportedMainTypes + * subtype: import('./Form/matching.js').SupportedSubTypes | "unknown" + * }} types + * @returns {boolean} + */ + isTypeUnavailable({ mainType, subtype }) { + if (mainType === "unknown") + return true; + if (!this.featureToggles[`inputType_${mainType}`] && subtype !== "emailAddress") { + return true; + } + return false; + } + /** + * Requests data from remote + * @returns {Promise<>} + */ + async populateData() { + const availableInputTypesFromRemote = await this.getAvailableInputTypes(); + this.setAvailableInputTypes(availableInputTypesFromRemote); + } + /** + * Requests data from remote if not available + * @param {{ + * mainType: SupportedMainTypes + * subtype: import('./Form/matching.js').SupportedSubTypes | "unknown" + * }} types + * @returns {Promise} + */ + async populateDataIfNeeded({ mainType, subtype }) { + if (this.isTypeUnavailable({ mainType, subtype })) + return false; + if (this.availableInputTypes?.[mainType] === void 0) { + await this.populateData(); + return true; + } + return false; + } + /** + * Checks if items will show in the autofill menu, including in-context signup. + * Triggers side-effect if input types is not already available. + * @param {{ + * mainType: SupportedMainTypes + * subtype: import('./Form/matching.js').SupportedSubTypes | "unknown" + * }} types + * @param {import("./InContextSignup.js").InContextSignup?} inContextSignup + * @returns {boolean} + */ + canAutofillType({ mainType, subtype }, inContextSignup) { + if (this.isTypeUnavailable({ mainType, subtype })) + return false; + const isEmailProtectionEnabled = this.featureToggles.emailProtection && this.availableInputTypes.email; + if (subtype === "emailAddress" && isEmailProtectionEnabled) { + return true; + } + if (inContextSignup?.isAvailable(subtype)) { + return true; + } + if (subtype === "fullName") { + return Boolean(this.availableInputTypes.identities?.firstName || this.availableInputTypes.identities?.lastName); + } + if (subtype === "expiration") { + return Boolean(this.availableInputTypes.creditCards?.expirationMonth || this.availableInputTypes.creditCards?.expirationYear); + } + return Boolean(this.availableInputTypes[mainType]?.[subtype]); + } + /** @returns {AutofillFeatureToggles} */ + get featureToggles() { + if (this._featureToggles === null) + throw new Error("feature toggles accessed before being set"); + return this._featureToggles; + } + /** @param {AutofillFeatureToggles} input */ + setFeatureToggles(input) { + this._featureToggles = input; + } + /** @returns {AvailableInputTypes} */ + get availableInputTypes() { + if (this._availableInputTypes === null) + throw new Error("available input types accessed before being set"); + return this._availableInputTypes; + } + /** @param {AvailableInputTypes} value */ + setAvailableInputTypes(value) { + this._availableInputTypes = { ...this._availableInputTypes, ...value }; + } + static default(globalConfig, deviceApi) { + const settings = new _Settings(globalConfig, deviceApi); + settings.setFeatureToggles(_Settings.defaults.featureToggles); + settings.setAvailableInputTypes(_Settings.defaults.availableInputTypes); + return settings; + } + /** @returns {boolean|null} */ + get enabled() { + return this._enabled; + } + /** + * @param {boolean|null} enabled + */ + setEnabled(enabled) { + this._enabled = enabled; + } + }; + var Settings = _Settings; + __publicField(Settings, "defaults", { + /** @type {AutofillFeatureToggles} */ + featureToggles: { + credentials_saving: false, + password_generation: false, + emailProtection: false, + emailProtection_incontext_signup: false, + inputType_identities: false, + inputType_credentials: false, + inputType_creditCards: false, + inlineIcon_credentials: false + }, + /** @type {AvailableInputTypes} */ + availableInputTypes: { + credentials: { + username: false, + password: false }, - expirationMonth: { - type: 'expirationMonth', - strategies: { - cssSelector: 'expirationMonth', - ddgMatcher: 'expirationMonth', - vendorRegex: 'cc-exp-month' - } + identities: { + firstName: false, + middleName: false, + lastName: false, + birthdayDay: false, + birthdayMonth: false, + birthdayYear: false, + addressStreet: false, + addressStreet2: false, + addressCity: false, + addressProvince: false, + addressPostalCode: false, + addressCountryCode: false, + phone: false, + emailAddress: false }, - expirationYear: { - type: 'expirationYear', - strategies: { - cssSelector: 'expirationYear', - ddgMatcher: 'expirationYear', - vendorRegex: 'cc-exp-year' - } + creditCards: { + cardName: false, + cardSecurityCode: false, + expirationMonth: false, + expirationYear: false, + cardNumber: false }, - expiration: { - type: 'expiration', - strategies: { - cssSelector: 'expiration', - ddgMatcher: 'expiration', - vendorRegex: 'cc-exp' - } - } - }, - lists: { - unknown: ['unknown'], - emailAddress: ['emailAddress'], - password: ['password'], - username: ['username'], - cc: ['cardName', 'cardNumber', 'cardSecurityCode', 'expirationMonth', 'expirationYear', 'expiration'], - id: ['firstName', 'middleName', 'lastName', 'fullName', 'phone', 'addressStreet', 'addressStreet2', 'addressCity', 'addressProvince', 'addressPostalCode', 'addressCountryCode', 'birthdayDay', 'birthdayMonth', 'birthdayYear'] - } - }, - strategies: { - cssSelector: { - selectors: { - genericTextField: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month])', - submitButtonSelector: 'input[type=submit], input[type=button], input[type=image], button:not([role=switch]):not([role=link]), [role=button], a[href="#"][id*=button i], a[href="#"][id*=btn i]', - formInputsSelector: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month]),[autocomplete=username],select', - safeUniversalSelector: '*:not(select):not(option):not(script):not(noscript):not(style):not(br)', - emailAddress: 'input:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]), input[type=""][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([type=tel]), input[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]):not([name*=code i]), input:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]), input[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=""][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=email], input[type=text][aria-label*=email i]:not([aria-label*=search i]), input:not([type])[aria-label*=email i]:not([aria-label*=search i]), input[name=username][type=email], input[autocomplete=username][type=email], input[autocomplete=username][placeholder*=email i], input[autocomplete=email],input[name="mail_tel" i],input[value=email i]', - username: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month])[autocomplete^=user i],input[name=username i],input[name="loginId" i],input[name="userid" i],input[id="userid" i],input[name="user_id" i],input[name="user-id" i],input[id="login-id" i],input[id="login_id" i],input[id="loginid" i],input[name="login" i],input[name=accountname i],input[autocomplete=username i],input[name*=accountid i],input[name="j_username" i],input[id="j_username" i],input[name="uwinid" i],input[name="livedoor_id" i],input[name="ssousername" i],input[name="j_userlogin_pwd" i],input[name="user[login]" i],input[name="user" i],input[name$="_username" i],input[id="lmSsoinput" i],input[name="account_subdomain" i],input[name="masterid" i],input[name="tridField" i],input[id="signInName" i],input[id="w3c_accountsbundle_accountrequeststep1_login" i],input[id="username" i],input[name="_user" i],input[name="login_username" i],input[name^="login-user-account" i],input[id="loginusuario" i],input[name="usuario" i],input[id="UserLoginFormUsername" i],input[id="nw_username" i],input[can-field="accountName"],input[placeholder^="username" i]', - password: 'input[type=password]:not([autocomplete*=cc]):not([autocomplete=one-time-code]):not([name*=answer i]):not([name*=mfa i]):not([name*=tin i]):not([name*=card i]):not([name*=cvv i]),input.js-cloudsave-phrase', - cardName: 'input[autocomplete="cc-name" i], input[autocomplete="ccname" i], input[name="ccname" i], input[name="cc-name" i], input[name="ppw-accountHolderName" i], input[id*=cardname i], input[id*=card-name i], input[id*=card_name i]', - cardNumber: 'input[autocomplete="cc-number" i], input[autocomplete="ccnumber" i], input[autocomplete="cardnumber" i], input[autocomplete="card-number" i], input[name="ccnumber" i], input[name="cc-number" i], input[name*=card i][name*=number i], input[name*=cardnumber i], input[id*=cardnumber i], input[id*=card-number i], input[id*=card_number i]', - cardSecurityCode: 'input[autocomplete="cc-csc" i], input[autocomplete="csc" i], input[autocomplete="cc-cvc" i], input[autocomplete="cvc" i], input[name="cvc" i], input[name="cc-cvc" i], input[name="cc-csc" i], input[name="csc" i], input[name*=security i][name*=code i]', - expirationMonth: '[autocomplete="cc-exp-month" i], [autocomplete="cc_exp_month" i], [name="ccmonth" i], [name="ppw-expirationDate_month" i], [name=cardExpiryMonth i], [name*=ExpDate_Month i], [name*=expiration i][name*=month i], [id*=expiration i][id*=month i], [name*=cc-exp-month i], [name*="card_exp-month" i], [name*=cc_exp_month i], [id*=cc-month i]', - expirationYear: '[autocomplete="cc-exp-year" i], [autocomplete="cc_exp_year" i], [name="ccyear" i], [name="ppw-expirationDate_year" i], [name=cardExpiryYear i], [name*=ExpDate_Year i], [name*=expiration i][name*=year i], [id*=expiration i][id*=year i], [name*="cc-exp-year" i], [name*="card_exp-year" i], [name*=cc_exp_year i], [id*=cc-year i]', - expiration: '[autocomplete="cc-exp" i], [name="cc-exp" i], [name="exp-date" i], [name="expirationDate" i], input[id*=expiration i]', - firstName: '[name*=fname i], [autocomplete*=given-name i], [name*=firstname i], [autocomplete*=firstname i], [name*=first-name i], [autocomplete*=first-name i], [name*=first_name i], [autocomplete*=first_name i], [name*=givenname i], [autocomplete*=givenname i], [name*=given-name i], [name*=given_name i], [autocomplete*=given_name i], [name*=forename i], [autocomplete*=forename i]', - middleName: '[name*=mname i], [autocomplete*=additional-name i], [name*=middlename i], [autocomplete*=middlename i], [name*=middle-name i], [autocomplete*=middle-name i], [name*=middle_name i], [autocomplete*=middle_name i], [name*=additionalname i], [autocomplete*=additionalname i], [name*=additional-name i], [name*=additional_name i], [autocomplete*=additional_name i]', - lastName: '[name=lname], [autocomplete*=family-name i], [name*=lastname i], [autocomplete*=lastname i], [name*=last-name i], [autocomplete*=last-name i], [name*=last_name i], [autocomplete*=last_name i], [name*=familyname i], [autocomplete*=familyname i], [name*=family-name i], [name*=family_name i], [autocomplete*=family_name i], [name*=surname i], [autocomplete*=surname i]', - fullName: '[autocomplete=name], [name*=fullname i], [autocomplete*=fullname i], [name*=full-name i], [autocomplete*=full-name i], [name*=full_name i], [autocomplete*=full_name i], [name*=your-name i], [autocomplete*=your-name i]', - phone: '[name*=phone i]:not([name*=extension i]):not([name*=type i]):not([name*=country i]), [name*=mobile i]:not([name*=type i]), [autocomplete=tel], [autocomplete="tel-national"], [placeholder*="phone number" i]', - addressStreet: '[name=address i], [autocomplete=street-address i], [autocomplete=address-line1 i], [name=street i], [name=ppw-line1 i], [name*=addressLine1 i]', - addressStreet2: '[name=address2 i], [autocomplete=address-line2 i], [name=ppw-line2 i], [name*=addressLine2 i]', - addressCity: '[name=city i], [autocomplete=address-level2 i], [name=ppw-city i], [name*=addressCity i]', - addressProvince: '[name=province i], [name=state i], [autocomplete=address-level1 i]', - addressPostalCode: '[name=zip i], [name=zip2 i], [name=postal i], [autocomplete=postal-code i], [autocomplete=zip-code i], [name*=postalCode i], [name*=zipcode i]', - addressCountryCode: '[name=country i], [autocomplete=country i], [name*=countryCode i], [name*=country-code i], [name*=countryName i], [name*=country-name i],select.idms-address-country', - birthdayDay: '[name=bday-day i], [name*=birthday_day i], [name*=birthday-day i], [name=date_of_birth_day i], [name=date-of-birth-day i], [name^=birthdate_d i], [name^=birthdate-d i], [aria-label="birthday" i][placeholder="day" i]', - birthdayMonth: '[name=bday-month i], [name*=birthday_month i], [name*=birthday-month i], [name=date_of_birth_month i], [name=date-of-birth-month i], [name^=birthdate_m i], [name^=birthdate-m i], select[name="mm" i]', - birthdayYear: '[name=bday-year i], [name*=birthday_year i], [name*=birthday-year i], [name=date_of_birth_year i], [name=date-of-birth-year i], [name^=birthdate_y i], [name^=birthdate-y i], [aria-label="birthday" i][placeholder="year" i]' - } - }, - ddgMatcher: { - matchers: { - unknown: { - match: /search|filter|subject|title|captcha|mfa|2fa|(two|2).?factor|one-time|otp|cerca|filtr|oggetto|titolo|(due|2|più).?fattori|suche|filtern|betreff|zoeken|filter|onderwerp|titel|chercher|filtrer|objet|titre|authentification multifacteur|double authentification|à usage unique|busca|busqueda|filtra|dos pasos|un solo uso|sök|filter|ämne|multifaktorsautentisering|tvåfaktorsautentisering|två.?faktor|engångs/iu, - skip: /phone|mobile|email|password/iu - }, - emailAddress: { - match: /.mail\b|apple.?id|posta elettronica|e.?mailadres|correo electr|correo-e|^correo$|\be.?post|e.?postadress/iu, - skip: /phone|(first.?|last.?)name|number|code/iu, - forceUnknown: /search|filter|subject|title|\btab\b|otp/iu - }, - password: { - match: /password|passwort|kennwort|wachtwoord|mot de passe|clave|contraseña|lösenord/iu, - skip: /email|one-time|error|hint/iu, - forceUnknown: /captcha|mfa|2fa|two factor|otp|pin/iu - }, - newPassword: { - match: /new|re.?(enter|type)|repeat|update|reset\b/iu - }, - currentPassword: { - match: /current|old|previous|expired|existing/iu - }, - username: { - match: /(user|account|online.?id|log(i|o)n|net)((.)?(name|i.?d.?|log(i|o)n).?)?(.?((or|\/).+|\*|:)( required)?)?$|(nome|id|login).?utente|(nome|id) (dell.)?account|codice cliente|nutzername|anmeldename|gebruikersnaam|nom d.utilisateur|identifiant|pseudo|usuari|cuenta|identificador|apodo|\bdni\b|\bnie\b| del? documento|documento de identidad|användarnamn|kontonamn|användar-id/iu, - skip: /phone/iu, - forceUnknown: /search|policy|choose a user\b/iu - }, - cardName: { - match: /(card.*name|name.*card)|(card.*holder|holder.*card)|(card.*owner|owner.*card)/iu - }, - cardNumber: { - match: /card.*number|number.*card/iu, - skip: /phone/iu, - forceUnknown: /plus/iu - }, - cardSecurityCode: { - match: /security.?code|card.?verif|cvv|csc|cvc|cv2|card id/iu - }, - expirationMonth: { - match: /(card|\bcc\b)?.?(exp(iry|iration)?)?.?(month|\bmm\b(?![.\s/-]yy))/iu, - skip: /mm[/\s.\-_—–]|check/iu - }, - expirationYear: { - match: /(card|\bcc\b)?.?(exp(iry|iration)?)?.?(year|yy)/iu, - skip: /mm[/\s.\-_—–]|check/iu - }, - expiration: { - match: /(\bmm\b|\b\d\d\b)[/\s.\-_—–](\byy|\bjj|\baa|\b\d\d)|\bexp|\bvalid(idity| through| until)/iu, - skip: /invalid|^dd\/|check/iu - }, - firstName: { - match: /(first|given|fore).?name|\bnome/iu, - skip: /last|cognome|completo/iu - }, - middleName: { - match: /(middle|additional).?name/iu - }, - lastName: { - match: /(last|family|sur)[^i]?name|cognome/iu, - skip: /first|\bnome/iu - }, - fullName: { - match: /^(full.?|whole\s|first.*last\s|real\s|contact.?)?name\b|\bnome/iu, - forceUnknown: /company|org|item/iu - }, - phone: { - match: /phone|mobile|telefono|cellulare/iu, - skip: /code|pass|country/iu, - forceUnknown: /ext|type|otp/iu - }, - addressStreet: { - match: /address/iu, - forceUnknown: /\bip\b|duck|web|url/iu, - skip: /address.*(2|two|3|three)|email|log.?in|sign.?in|civico/iu - }, - addressStreet2: { - match: /address.*(2|two)|apartment|\bapt\b|\bflat\b|\bline.*(2|two)/iu, - forceUnknown: /\bip\b|duck/iu, - skip: /email|log.?in|sign.?in/iu - }, - addressCity: { - match: /city|town|città|comune/iu, - skip: /\bzip\b|\bcap\b/iu, - forceUnknown: /vatican/iu - }, - addressProvince: { - match: /state|province|region|county|provincia|regione/iu, - forceUnknown: /united/iu, - skip: /country/iu - }, - addressPostalCode: { - match: /\bzip\b|postal\b|post.?code|\bcap\b|codice postale/iu - }, - addressCountryCode: { - match: /country|\bnation\b|nazione|paese/iu - }, - birthdayDay: { - match: /(birth.*day|day.*birth)/iu, - skip: /month|year/iu - }, - birthdayMonth: { - match: /(birth.*month|month.*birth)/iu, - skip: /year/iu - }, - birthdayYear: { - match: /(birth.*year|year.*birth)/iu - }, - loginRegex: { - match: /sign(ing)?.?[io]n(?!g)|log.?[io]n|log.?out|unsubscri|(forgot(ten)?|reset) (your )?password|password (forgotten|lost)|mfa-submit-form|unlock|logged in as|entra|accedi|accesso|resetta password|password dimenticata|dimenticato la password|recuper[ao] password|(ein|aus)loggen|anmeld(eformular|ung|efeld)|abmelden|passwort (vergessen|verloren)|zugang| zugangsformular|einwahl|inloggen|se (dé)?connecter|(dé)?connexion|récupérer ((mon|ton|votre|le) )?mot de passe|mot de passe (oublié|perdu)|clave(?! su)|olvidó su (clave|contraseña)|.*sesión|conect(arse|ado)|conéctate|acce(de|so)|entrar|logga (in|ut)|avprenumerera|avregistrera|glömt lösenord|återställ lösenord/iu - }, - signupRegex: { - match: /sign(ing)?.?up|join|\bregist(er|ration)|newsletter|\bsubscri(be|ption)|contact|create|start|enroll|settings|preferences|profile|update|checkout|purchase|buy|order|schedule|estimate|request|new.?customer|(confirm|re.?(type|enter)|repeat) password|password confirm|iscri(viti|zione)|registra(ti|zione)|(?:nuovo|crea(?:zione)?) account|contatt(?:ac)i|sottoscriv|sottoscrizione|compra|acquist(a|o)|ordin[aeio]|richie(?:di|sta)|(?:conferma|ripeti) password|inizia|nuovo cliente|impostazioni|preferenze|profilo|aggiorna|paga|registrier(ung|en)|profil (anlegen|erstellen)| nachrichten|verteiler|neukunde|neuer (kunde|benutzer|nutzer)|passwort wiederholen|anmeldeseite|nieuwsbrief|aanmaken|profiel|s.inscrire|inscription|s.abonner|créer|préférences|profil|mise à jour|payer|ach(eter|at)| nouvel utilisateur|(confirmer|réessayer) ((mon|ton|votre|le) )?mot de passe|regis(trarse|tro)|regístrate|inscr(ibirse|ipción|íbete)|solicitar|crea(r cuenta)?|nueva cuenta|nuevo (cliente|usuario)|preferencias|perfil|lista de correo|registrer(a|ing)|(nytt|öppna) konto|nyhetsbrev|prenumer(era|ation)|kontakt|skapa|starta|inställningar|min (sida|kundvagn)|uppdatera|till kassan|gäst|köp|beställ|schemalägg|ny kund|(repetera|bekräfta) lösenord/iu - }, - conservativeSignupRegex: { - match: /sign.?up|join|register|enroll|(create|new).+account|newsletter|subscri(be|ption)|settings|preferences|profile|update|iscri(viti|zione)|registra(ti|zione)|(?:nuovo|crea(?:zione)?) account|contatt(?:ac)?i|sottoscriv|sottoscrizione|impostazioni|preferenze|aggiorna|anmeld(en|ung)|registrier(en|ung)|neukunde|neuer (kunde|benutzer|nutzer)|registreren|eigenschappen|profiel|bijwerken|s.inscrire|inscription|s.abonner|abonnement|préférences|profil|créer un compte|regis(trarse|tro)|regístrate|inscr(ibirse|ipción|íbete)|crea(r cuenta)?|nueva cuenta|nuevo (cliente|usuario)|preferencias|perfil|lista de correo|registrer(a|ing)|(nytt|öppna) konto|nyhetsbrev|prenumer(era|ation)|kontakt|skapa|starta|inställningar|min (sida|kundvagn)|uppdatera/iu + email: false + }, + /** @type {boolean | null} */ + enabled: null + }); + + // src/deviceApiCalls/transports/extension.transport.js + var ExtensionTransport = class extends DeviceApiTransport { + /** @param {GlobalConfig} globalConfig */ + constructor(globalConfig) { + super(); + this.config = globalConfig; + } + async send(deviceApiCall) { + if (deviceApiCall instanceof GetRuntimeConfigurationCall) { + return deviceApiCall.result(await extensionSpecificRuntimeConfiguration(this)); + } + if (deviceApiCall instanceof GetAvailableInputTypesCall) { + return deviceApiCall.result(await extensionSpecificGetAvailableInputTypes()); + } + if (deviceApiCall instanceof SetIncontextSignupPermanentlyDismissedAtCall) { + return deviceApiCall.result(await extensionSpecificSetIncontextSignupPermanentlyDismissedAtCall(deviceApiCall.params)); + } + if (deviceApiCall instanceof GetIncontextSignupDismissedAtCall) { + return deviceApiCall.result(await extensionSpecificGetIncontextSignupDismissedAt()); + } + if (deviceApiCall instanceof SendJSPixelCall) { + return deviceApiCall.result(await extensionSpecificSendPixel(deviceApiCall.params)); + } + if (deviceApiCall instanceof AddDebugFlagCall) { + return deviceApiCall.result(await extensionSpecificAddDebugFlag(deviceApiCall.params)); + } + if (deviceApiCall instanceof CloseAutofillParentCall || deviceApiCall instanceof StartEmailProtectionSignupCall) { + return; + } + console.error("Send not implemented for " + deviceApiCall.method); + } + }; + async function extensionSpecificRuntimeConfiguration(deviceApi) { + const contentScope = await getContentScopeConfig(); + const emailProtectionEnabled = isAutofillEnabledFromProcessedConfig(contentScope); + const incontextSignupEnabled = isIncontextSignupEnabledFromProcessedConfig(contentScope); + return { + success: { + // @ts-ignore + contentScope, + // @ts-ignore + userPreferences: { + features: { + autofill: { + settings: { + featureToggles: { + ...Settings.defaults.featureToggles, + emailProtection: emailProtectionEnabled, + emailProtection_incontext_signup: incontextSignupEnabled + } + } + } + } }, - resetPasswordLink: { - match: /(forgot(ten)?|reset|don't remember) (your )?password|password forgotten|password dimenticata|reset(?:ta) password|recuper[ao] password|(vergessen|verloren|verlegt|wiederherstellen) passwort|wachtwoord (vergeten|reset)|(oublié|récupérer) ((mon|ton|votre|le) )?mot de passe|mot de passe (oublié|perdu)|re(iniciar|cuperar) (contraseña|clave)|olvid(ó su|aste tu|é mi) (contraseña|clave)|recordar( su)? (contraseña|clave)|glömt lösenord|återställ lösenord/iu + // @ts-ignore + userUnprotectedDomains: deviceApi.config?.userUnprotectedDomains || [] + } + }; + } + async function extensionSpecificGetAvailableInputTypes() { + const contentScope = await getContentScopeConfig(); + const emailProtectionEnabled = isAutofillEnabledFromProcessedConfig(contentScope); + return { + success: { + ...Settings.defaults.availableInputTypes, + email: emailProtectionEnabled + } + }; + } + async function getContentScopeConfig() { + return new Promise((resolve) => { + chrome.runtime.sendMessage( + { + registeredTempAutofillContentScript: true, + documentUrl: window.location.href }, - loginProvidersRegex: { - match: / with | con | mit | met | avec /iu + (response) => { + if (response && "site" in response) { + resolve(response); + } + } + ); + }); + } + async function extensionSpecificSendPixel(params) { + return new Promise((resolve) => { + chrome.runtime.sendMessage( + { + messageType: "sendJSPixel", + options: params }, - submitButtonRegex: { - match: /submit|send|confirm|save|continue|next|sign|log.?([io])n|buy|purchase|check.?out|subscribe|donate|update|\bset\b|invia|conferma|salva|continua|entra|acced|accesso|compra|paga|sottoscriv|registra|dona|senden|\bja\b|bestätigen|weiter|nächste|kaufen|bezahlen|spenden|versturen|verzenden|opslaan|volgende|koop|kopen|voeg toe|aanmelden|envoyer|confirmer|sauvegarder|continuer|suivant|signer|connexion|acheter|payer|s.abonner|donner|enviar|confirmar|registrarse|continuar|siguiente|comprar|donar|skicka|bekräfta|spara|fortsätt|nästa|logga in|köp|handla|till kassan|registrera|donera/iu + () => { + resolve(true); + } + ); + }); + } + async function extensionSpecificAddDebugFlag(params) { + return new Promise((resolve) => { + chrome.runtime.sendMessage( + { + messageType: "addDebugFlag", + options: params }, - submitButtonUnlikelyRegex: { - match: /facebook|twitter|google|apple|cancel|show|toggle|reveal|hide|print|back|already|annulla|mostra|nascondi|stampa|indietro|già|abbrechen|passwort|zeigen|verbergen|drucken|zurück|annuleer|wachtwoord|toon|vorige|annuler|mot de passe|montrer|cacher|imprimer|retour|déjà|anular|cancelar|imprimir|cerrar|avbryt|lösenord|visa|dölj|skirv ut|tillbaka|redan/iu - } - } - }, - vendorRegex: { - rules: { - email: /((^e-?mail$)|(^email-?address$))|(e.?mail|courriel|correo.*electr(o|ó)nico|メールアドレス|электронной.?почты|邮件|邮箱|電郵地址|ഇ-മെയില്‍|ഇലക്ട്രോണിക്.?മെയിൽ|ایمیل|پست.*الکترونیک|ईमेल|इलॅक्ट्रॉनिक.?मेल|(\b|_)eposta(\b|_)|(?:이메일|전자.?우편|[ee]-?mail)(.?주소)?)/iu, - tel: /((^phone$)|(^mobile$)|(^mobile-?phone$)|(^tel$)|(^telephone$)|(^phone-?number$))|(phone|mobile|contact.?number|telefonnummer|telefono|teléfono|telfixe|電話|telefone|telemovel|телефон|मोबाइल|(\b|_|\*)telefon(\b|_|\*)|电话|മൊബൈല്‍|(?:전화|핸드폰|휴대폰|휴대전화)(?:.?번호)?)/iu, - organization: /((^company$)|(^company-?name$)|(^organization$)|(^organization-?name$))|(company|business|organization|organisation|empresa|societe|société|ragione.?sociale|会社|название.?компании|单位|公司|شرکت|회사|직장)/iu, - 'street-address': /((^address$)|(^street-?address$)|(^addr$)|(^street$)|(^mailing-?addr(ess)?$)|(^billing-?addr(ess)?$)|(^mail-?addr(ess)?$)|(^bill-?addr(ess)?$))|(streetaddress|street-address)/iu, - 'address-line1': /(addrline1|address_1)|((^address-?1$)|(^address-?line-?1$)|(^addr-?1$)|(^street-?1$))|(^address$|address[_-]?line[_-]?(1|one)|address1|addr1|street|(?:shipping|billing)address$|strasse|straße|hausnummer|housenumber|house.?name|direccion|dirección|adresse|indirizzo|^住所$|住所1|адрес|地址|(\b|_)adres(?! (başlığı(nız)?|tarifi))(\b|_)|^주소.?$|주소.?1)/iu, - 'address-line2': /(addrline2|address_2)|((^address-?2$)|(^address-?line-?2$)|(^addr-?2$)|(^street-?2$))|(address[_-]?line(2|two)|address2|addr2|street|suite|unit(?!e)|adresszusatz|ergänzende.?angaben|direccion2|colonia|adicional|addresssuppl|complementnom|appartement|indirizzo2|住所2|complemento|addrcomplement|улица|地址2|주소.?2)/iu, - 'address-line3': /(addrline3|address_3)|((^address-?3$)|(^address-?line-?3$)|(^addr-?3$)|(^street-?3$))|(address[_-]?line(3|three)|address3|addr3|street|suite|unit(?!e)|adresszusatz|ergänzende.?angaben|direccion3|colonia|adicional|addresssuppl|complementnom|appartement|indirizzo3|住所3|complemento|addrcomplement|улица|地址3|주소.?3)/iu, - 'address-level2': /((^city$)|(^town$)|(^address-?level-?2$)|(^address-?city$)|(^address-?town$))|(city|town|\bort\b|stadt|suburb|ciudad|provincia|localidad|poblacion|ville|commune|localit(a|à)|citt(a|à)|市区町村|cidade|город|市|分區|شهر|शहर|ग्राम|गाँव|നഗരം|ഗ്രാമം|((\b|_|\*)([i̇ii̇]l[cç]e(miz|niz)?)(\b|_|\*))|^시[^도·・]|시[·・]?군[·・]?구)/iu, - 'address-level1': /(land)|((^state$)|(^province$)|(^provence$)|(^address-?level-?1$)|(^address-?state$)|(^address-?province$))|(county|region|province|county|principality|都道府県|estado|provincia|область|省|地區|സംസ്ഥാനം|استان|राज्य|((\b|_|\*)(eyalet|[şs]ehir|[i̇ii̇]limiz|kent)(\b|_|\*))|^시[·・]?도)/iu, - 'postal-code': /((^postal$)|(^zip$)|(^zip2$)|(^zip-?code$)|(^postal-?code$)|(^post-?code$)|(^address-?zip$)|(^address-?postal$)|(^address-?code$)|(^address-?postal-?code$)|(^address-?zip-?code$))|(zip|postal|post.*code|pcode|pin.?code|postleitzahl|\bcp\b|\bcdp\b|\bcap\b|郵便番号|codigo|codpos|\bcep\b|почтовый.?индекс|पिन.?कोड|പിന്‍കോഡ്|邮政编码|邮编|郵遞區號|(\b|_)posta kodu(\b|_)|우편.?번호)/iu, - country: /((^country$)|(^country-?code$)|(^country-?name$)|(^address-?country$)|(^address-?country-?name$)|(^address-?country-?code$))|(country|countries|país|pais|(\b|_)land(\b|_)(?!.*(mark.*))|国家|국가|나라|(\b|_)(ülke|ulce|ulke)(\b|_)|کشور)/iu, - 'cc-name': /(accountholdername|titulaire)|(cc-?name|card-?name|cardholder-?name|cardholder|(^nom$))|(card.?(?:holder|owner)|name.*(\b)?on(\b)?.*card|(?:card|cc).?name|cc.?full.?name|karteninhaber|nombre.*tarjeta|nom.*carte|nome.*cart|名前|имя.*карты|信用卡开户名|开户名|持卡人姓名|持卡人姓名)/iu, - name: /((^name$)|full-?name|your-?name)|(^name|full.?name|your.?name|customer.?name|bill.?name|ship.?name|name.*first.*last|firstandlastname|nombre.*y.*apellidos|^nom(?!bre)\b|お名前|氏名|^nome|نام.*نام.*خانوادگی|姓名|(\b|_|\*)ad[ı]? soyad[ı]?(\b|_|\*)|성명)/iu, - 'given-name': /((^f-?name$)|(^first-?name$)|(^given-?name$)|(^first-?n$))|(first.*name|initials|fname|first$|given.*name|vorname|nombre|forename|prénom|prenom|名|\bnome|имя|نام|이름|പേര്|(\b|_|\*)(isim|ad|ad(i|ı|iniz|ınız)?)(\b|_|\*)|नाम)/iu, - 'additional-name': /(apellido.?materno|lastlastname)|((^m-?name$)|(^middle-?name$)|(^additional-?name$)|(^middle-?initial$)|(^middle-?n$)|(^middle-?i$))|(middle.*name|mname|middle$|middle.*initial|m\.i\.|mi$|\bmi\b)/iu, - 'family-name': /((^l-?name$)|(^last-?name$)|(^s-?name$)|(^surname$)|(^family-?name$)|(^family-?n$)|(^last-?n$))|(last.*name|lname|surname|last$|secondname|family.*name|nachname|apellidos?|famille|^nom(?!bre)|cognome|姓|apelidos|surename|sobrenome|фамилия|نام.*خانوادگی|उपनाम|മറുപേര്|(\b|_|\*)(soyisim|soyad(i|ı|iniz|ınız)?)(\b|_|\*)|\b성(?:[^명]|\b))/iu, - 'cc-number': /((cc|kk)nr)|(cc-?number|cc-?num|card-?number|card-?num|(^number$)|(^cc$)|cc-?no|card-?no|(^credit-?card$)|numero-?carte|(^carte$)|(^carte-?credit$)|num-?carte|cb-?num)|((add)?(?:card|cc|acct).?(?:number|#|no|num|field)|カード番号|номер.*карты|信用卡号|信用卡号码|信用卡卡號|카드|(numero|número|numéro)(?!.*(document|fono|phone|réservation)))/iu, - 'cc-exp-month': /((cc|kk)month)|((^exp-?month$)|(^cc-?exp-?month$)|(^cc-?month$)|(^card-?month$)|(^cc-?mo$)|(^card-?mo$)|(^exp-?mo$)|(^card-?exp-?mo$)|(^cc-?exp-?mo$)|(^card-?expiration-?month$)|(^expiration-?month$)|(^cc-?mm$)|(^cc-?m$)|(^card-?mm$)|(^card-?m$)|(^card-?exp-?mm$)|(^cc-?exp-?mm$)|(^exp-?mm$)|(^exp-?m$)|(^expire-?month$)|(^expire-?mo$)|(^expiry-?month$)|(^expiry-?mo$)|(^card-?expire-?month$)|(^card-?expire-?mo$)|(^card-?expiry-?month$)|(^card-?expiry-?mo$)|(^mois-?validite$)|(^mois-?expiration$)|(^m-?validite$)|(^m-?expiration$)|(^expiry-?date-?field-?month$)|(^expiration-?date-?month$)|(^expiration-?date-?mm$)|(^exp-?mon$)|(^validity-?mo$)|(^exp-?date-?mo$)|(^cb-?date-?mois$)|(^date-?m$))|(gueltig|gültig|monat|fecha|date.*exp|scadenza|有効期限|validade|срок действия карты|月)/iu, - 'cc-exp-year': /((cc|kk)year)|((^exp-?year$)|(^cc-?exp-?year$)|(^cc-?year$)|(^card-?year$)|(^cc-?yr$)|(^card-?yr$)|(^exp-?yr$)|(^card-?exp-?yr$)|(^cc-?exp-?yr$)|(^card-?expiration-?year$)|(^expiration-?year$)|(^cc-?yy$)|(^cc-?y$)|(^card-?yy$)|(^card-?y$)|(^card-?exp-?yy$)|(^cc-?exp-?yy$)|(^exp-?yy$)|(^exp-?y$)|(^cc-?yyyy$)|(^card-?yyyy$)|(^card-?exp-?yyyy$)|(^cc-?exp-?yyyy$)|(^expire-?year$)|(^expire-?yr$)|(^expiry-?year$)|(^expiry-?yr$)|(^card-?expire-?year$)|(^card-?expire-?yr$)|(^card-?expiry-?year$)|(^card-?expiry-?yr$)|(^an-?validite$)|(^an-?expiration$)|(^annee-?validite$)|(^annee-?expiration$)|(^expiry-?date-?field-?year$)|(^expiration-?date-?year$)|(^cb-?date-?ann$)|(^expiration-?date-?yy$)|(^expiration-?date-?yyyy$)|(^validity-?year$)|(^exp-?date-?year$)|(^date-?y$))|(ablaufdatum|gueltig|gültig|jahr|fecha|scadenza|有効期限|validade|срок действия карты|年|有效期)/iu, - 'cc-exp': /((^cc-?exp$)|(^card-?exp$)|(^cc-?expiration$)|(^card-?expiration$)|(^cc-?ex$)|(^card-?ex$)|(^card-?expire$)|(^card-?expiry$)|(^validite$)|(^expiration$)|(^expiry$)|mm-?yy|mm-?yyyy|yy-?mm|yyyy-?mm|expiration-?date|payment-?card-?expiration|(^payment-?cc-?date$))|(expir|exp.*date|^expfield$|gueltig|gültig|fecha|date.*exp|scadenza|有効期限|validade|срок действия карты)/iu, - 'cc-type': /(type|kartenmarke)|((^cc-?type$)|(^card-?type$)|(^card-?brand$)|(^cc-?brand$)|(^cb-?type$))/iu - }, - ruleSets: [{ - 'address-line1': 'addrline1|address_1', - 'address-line2': 'addrline2|address_2', - 'address-line3': 'addrline3|address_3', - 'address-level1': 'land', - 'additional-name': 'apellido.?materno|lastlastname', - 'cc-name': 'accountholdername|titulaire', - 'cc-number': '(cc|kk)nr', - 'cc-exp-month': '(cc|kk)month', - 'cc-exp-year': '(cc|kk)year', - 'cc-type': 'type|kartenmarke' - }, { - email: '(^e-?mail$)|(^email-?address$)', - tel: '(^phone$)|(^mobile$)|(^mobile-?phone$)|(^tel$)|(^telephone$)|(^phone-?number$)', - organization: '(^company$)|(^company-?name$)|(^organization$)|(^organization-?name$)', - 'street-address': '(^address$)|(^street-?address$)|(^addr$)|(^street$)|(^mailing-?addr(ess)?$)|(^billing-?addr(ess)?$)|(^mail-?addr(ess)?$)|(^bill-?addr(ess)?$)', - 'address-line1': '(^address-?1$)|(^address-?line-?1$)|(^addr-?1$)|(^street-?1$)', - 'address-line2': '(^address-?2$)|(^address-?line-?2$)|(^addr-?2$)|(^street-?2$)', - 'address-line3': '(^address-?3$)|(^address-?line-?3$)|(^addr-?3$)|(^street-?3$)', - 'address-level2': '(^city$)|(^town$)|(^address-?level-?2$)|(^address-?city$)|(^address-?town$)', - 'address-level1': '(^state$)|(^province$)|(^provence$)|(^address-?level-?1$)|(^address-?state$)|(^address-?province$)', - 'postal-code': '(^postal$)|(^zip$)|(^zip2$)|(^zip-?code$)|(^postal-?code$)|(^post-?code$)|(^address-?zip$)|(^address-?postal$)|(^address-?code$)|(^address-?postal-?code$)|(^address-?zip-?code$)', - country: '(^country$)|(^country-?code$)|(^country-?name$)|(^address-?country$)|(^address-?country-?name$)|(^address-?country-?code$)', - name: '(^name$)|full-?name|your-?name', - 'given-name': '(^f-?name$)|(^first-?name$)|(^given-?name$)|(^first-?n$)', - 'additional-name': '(^m-?name$)|(^middle-?name$)|(^additional-?name$)|(^middle-?initial$)|(^middle-?n$)|(^middle-?i$)', - 'family-name': '(^l-?name$)|(^last-?name$)|(^s-?name$)|(^surname$)|(^family-?name$)|(^family-?n$)|(^last-?n$)', - 'cc-name': 'cc-?name|card-?name|cardholder-?name|cardholder|(^nom$)', - 'cc-number': 'cc-?number|cc-?num|card-?number|card-?num|(^number$)|(^cc$)|cc-?no|card-?no|(^credit-?card$)|numero-?carte|(^carte$)|(^carte-?credit$)|num-?carte|cb-?num', - 'cc-exp': '(^cc-?exp$)|(^card-?exp$)|(^cc-?expiration$)|(^card-?expiration$)|(^cc-?ex$)|(^card-?ex$)|(^card-?expire$)|(^card-?expiry$)|(^validite$)|(^expiration$)|(^expiry$)|mm-?yy|mm-?yyyy|yy-?mm|yyyy-?mm|expiration-?date|payment-?card-?expiration|(^payment-?cc-?date$)', - 'cc-exp-month': '(^exp-?month$)|(^cc-?exp-?month$)|(^cc-?month$)|(^card-?month$)|(^cc-?mo$)|(^card-?mo$)|(^exp-?mo$)|(^card-?exp-?mo$)|(^cc-?exp-?mo$)|(^card-?expiration-?month$)|(^expiration-?month$)|(^cc-?mm$)|(^cc-?m$)|(^card-?mm$)|(^card-?m$)|(^card-?exp-?mm$)|(^cc-?exp-?mm$)|(^exp-?mm$)|(^exp-?m$)|(^expire-?month$)|(^expire-?mo$)|(^expiry-?month$)|(^expiry-?mo$)|(^card-?expire-?month$)|(^card-?expire-?mo$)|(^card-?expiry-?month$)|(^card-?expiry-?mo$)|(^mois-?validite$)|(^mois-?expiration$)|(^m-?validite$)|(^m-?expiration$)|(^expiry-?date-?field-?month$)|(^expiration-?date-?month$)|(^expiration-?date-?mm$)|(^exp-?mon$)|(^validity-?mo$)|(^exp-?date-?mo$)|(^cb-?date-?mois$)|(^date-?m$)', - 'cc-exp-year': '(^exp-?year$)|(^cc-?exp-?year$)|(^cc-?year$)|(^card-?year$)|(^cc-?yr$)|(^card-?yr$)|(^exp-?yr$)|(^card-?exp-?yr$)|(^cc-?exp-?yr$)|(^card-?expiration-?year$)|(^expiration-?year$)|(^cc-?yy$)|(^cc-?y$)|(^card-?yy$)|(^card-?y$)|(^card-?exp-?yy$)|(^cc-?exp-?yy$)|(^exp-?yy$)|(^exp-?y$)|(^cc-?yyyy$)|(^card-?yyyy$)|(^card-?exp-?yyyy$)|(^cc-?exp-?yyyy$)|(^expire-?year$)|(^expire-?yr$)|(^expiry-?year$)|(^expiry-?yr$)|(^card-?expire-?year$)|(^card-?expire-?yr$)|(^card-?expiry-?year$)|(^card-?expiry-?yr$)|(^an-?validite$)|(^an-?expiration$)|(^annee-?validite$)|(^annee-?expiration$)|(^expiry-?date-?field-?year$)|(^expiration-?date-?year$)|(^cb-?date-?ann$)|(^expiration-?date-?yy$)|(^expiration-?date-?yyyy$)|(^validity-?year$)|(^exp-?date-?year$)|(^date-?y$)', - 'cc-type': '(^cc-?type$)|(^card-?type$)|(^card-?brand$)|(^cc-?brand$)|(^cb-?type$)' - }, { - email: 'e.?mail|courriel|correo.*electr(o|ó)nico|メールアドレス|Электронной.?Почты|邮件|邮箱|電郵地址|ഇ-മെയില്‍|ഇലക്ട്രോണിക്.?മെയിൽ|ایمیل|پست.*الکترونیک|ईमेल|इलॅक्ट्रॉनिक.?मेल|(\\b|_)eposta(\\b|_)|(?:이메일|전자.?우편|[Ee]-?mail)(.?주소)?', - tel: 'phone|mobile|contact.?number|telefonnummer|telefono|teléfono|telfixe|電話|telefone|telemovel|телефон|मोबाइल|(\\b|_|\\*)telefon(\\b|_|\\*)|电话|മൊബൈല്‍|(?:전화|핸드폰|휴대폰|휴대전화)(?:.?번호)?', - organization: 'company|business|organization|organisation|empresa|societe|société|ragione.?sociale|会社|название.?компании|单位|公司|شرکت|회사|직장', - 'street-address': 'streetaddress|street-address', - 'address-line1': '^address$|address[_-]?line[_-]?(1|one)|address1|addr1|street|(?:shipping|billing)address$|strasse|straße|hausnummer|housenumber|house.?name|direccion|dirección|adresse|indirizzo|^住所$|住所1|Адрес|地址|(\\b|_)adres(?! (başlığı(nız)?|tarifi))(\\b|_)|^주소.?$|주소.?1', - 'address-line2': 'address[_-]?line(2|two)|address2|addr2|street|suite|unit(?!e)|adresszusatz|ergänzende.?angaben|direccion2|colonia|adicional|addresssuppl|complementnom|appartement|indirizzo2|住所2|complemento|addrcomplement|Улица|地址2|주소.?2', - 'address-line3': 'address[_-]?line(3|three)|address3|addr3|street|suite|unit(?!e)|adresszusatz|ergänzende.?angaben|direccion3|colonia|adicional|addresssuppl|complementnom|appartement|indirizzo3|住所3|complemento|addrcomplement|Улица|地址3|주소.?3', - 'address-level2': 'city|town|\\bort\\b|stadt|suburb|ciudad|provincia|localidad|poblacion|ville|commune|localit(a|à)|citt(a|à)|市区町村|cidade|Город|市|分區|شهر|शहर|ग्राम|गाँव|നഗരം|ഗ്രാമം|((\\b|_|\\*)([İii̇]l[cç]e(miz|niz)?)(\\b|_|\\*))|^시[^도·・]|시[·・]?군[·・]?구', - 'address-level1': 'county|region|province|county|principality|都道府県|estado|provincia|область|省|地區|സംസ്ഥാനം|استان|राज्य|((\\b|_|\\*)(eyalet|[şs]ehir|[İii̇]limiz|kent)(\\b|_|\\*))|^시[·・]?도', - 'postal-code': 'zip|postal|post.*code|pcode|pin.?code|postleitzahl|\\bcp\\b|\\bcdp\\b|\\bcap\\b|郵便番号|codigo|codpos|\\bcep\\b|Почтовый.?Индекс|पिन.?कोड|പിന്‍കോഡ്|邮政编码|邮编|郵遞區號|(\\b|_)posta kodu(\\b|_)|우편.?번호', - country: 'country|countries|país|pais|(\\b|_)land(\\b|_)(?!.*(mark.*))|国家|국가|나라|(\\b|_)(ülke|ulce|ulke)(\\b|_)|کشور', - 'cc-name': 'card.?(?:holder|owner)|name.*(\\b)?on(\\b)?.*card|(?:card|cc).?name|cc.?full.?name|karteninhaber|nombre.*tarjeta|nom.*carte|nome.*cart|名前|Имя.*карты|信用卡开户名|开户名|持卡人姓名|持卡人姓名', - name: '^name|full.?name|your.?name|customer.?name|bill.?name|ship.?name|name.*first.*last|firstandlastname|nombre.*y.*apellidos|^nom(?!bre)\\b|お名前|氏名|^nome|نام.*نام.*خانوادگی|姓名|(\\b|_|\\*)ad[ı]? soyad[ı]?(\\b|_|\\*)|성명', - 'given-name': 'first.*name|initials|fname|first$|given.*name|vorname|nombre|forename|prénom|prenom|名|\\bnome|Имя|نام|이름|പേര്|(\\b|_|\\*)(isim|ad|ad(i|ı|iniz|ınız)?)(\\b|_|\\*)|नाम', - 'additional-name': 'middle.*name|mname|middle$|middle.*initial|m\\.i\\.|mi$|\\bmi\\b', - 'family-name': 'last.*name|lname|surname|last$|secondname|family.*name|nachname|apellidos?|famille|^nom(?!bre)|cognome|姓|apelidos|surename|sobrenome|Фамилия|نام.*خانوادگی|उपनाम|മറുപേര്|(\\b|_|\\*)(soyisim|soyad(i|ı|iniz|ınız)?)(\\b|_|\\*)|\\b성(?:[^명]|\\b)', - 'cc-number': '(add)?(?:card|cc|acct).?(?:number|#|no|num|field)|カード番号|Номер.*карты|信用卡号|信用卡号码|信用卡卡號|카드|(numero|número|numéro)(?!.*(document|fono|phone|réservation))', - 'cc-exp-month': 'gueltig|gültig|monat|fecha|date.*exp|scadenza|有効期限|validade|Срок действия карты|月', - 'cc-exp-year': 'ablaufdatum|gueltig|gültig|jahr|fecha|scadenza|有効期限|validade|Срок действия карты|年|有效期', - 'cc-exp': 'expir|exp.*date|^expfield$|gueltig|gültig|fecha|date.*exp|scadenza|有効期限|validade|Срок действия карты' - }] - } + () => { + resolve(true); + } + ); + }); } -}; - -},{}],32:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.logMatching = logMatching; -exports.logUnmatched = logUnmatched; -var _autofillUtils = require("../autofill-utils.js"); -var _matching = require("./matching.js"); -/** - * Logs out matching details when debug flag is active - * @param {HTMLInputElement | HTMLSelectElement} el - * @param {MatchingResult} matchingResult - */ -function logMatching(el, matchingResult) { - if (!(0, _autofillUtils.shouldLog)()) return; - const fieldIdentifier = getInputIdentifier(el); - console.group(fieldIdentifier); - console.log(el); - const { - strategyName, - matchedString, - matchedFrom, - matcherType - } = matchingResult; - const verb = getVerb(matchingResult); - let stringToLog = `${verb} for "${matcherType}" with "${strategyName}"`; - if (matchedString && matchedFrom) { - stringToLog += `\nString: "${matchedString}"\nSource: "${matchedFrom}"`; + async function extensionSpecificGetIncontextSignupDismissedAt() { + return new Promise((resolve) => { + chrome.runtime.sendMessage( + { + messageType: "getIncontextSignupDismissedAt" + }, + (response) => { + resolve(response); + } + ); + }); } - console.log(stringToLog); - console.groupEnd(); -} - -/** - * Helper to form the correct string based on matching result type - * @param {MatchingResult} matchingResult - * @return {string} - */ -function getVerb(matchingResult) { - if (matchingResult.matched) return 'Matched'; - if (matchingResult.proceed === false) return 'Matched forceUnknown'; - if (matchingResult.skip) return 'Skipped'; - return ''; -} - -/** - * Returns a human-friendly name to identify a single input field - * @param {HTMLInputElement | HTMLSelectElement} el - * @returns {string} - */ -function getInputIdentifier(el) { - const label = (0, _matching.getExplicitLabelsText)(el); - const placeholder = el instanceof HTMLInputElement && el.placeholder ? `${el.placeholder}` : ''; - const name = el.name ? `${el.name}` : ''; - const id = el.id ? `#${el.id}` : ''; - return 'Field: ' + (label || placeholder || name || id); -} - -/** - * Logs info when a field was not matched by the algo - * @param el - * @param allStrings - */ -function logUnmatched(el, allStrings) { - if (!(0, _autofillUtils.shouldLog)()) return; - const fieldIdentifier = getInputIdentifier(el); - console.group(fieldIdentifier); - console.log(el); - const stringToLog = 'Field not matched.'; - console.log(stringToLog, allStrings); - console.groupEnd(); -} - -},{"../autofill-utils.js":52,"./matching.js":33}],33:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.checkPlaceholderAndLabels = exports.Matching = void 0; -exports.createMatching = createMatching; -exports.getInputMainType = exports.getExplicitLabelsText = void 0; -exports.getInputSubtype = getInputSubtype; -exports.getInputType = getInputType; -exports.getInputVariant = getInputVariant; -exports.getMainTypeFromType = getMainTypeFromType; -exports.getRelatedText = void 0; -exports.getSubtypeFromType = getSubtypeFromType; -exports.getVariantFromType = getVariantFromType; -exports.removeExcessWhitespace = exports.matchInPlaceholderAndLabels = void 0; -var _constants = require("../constants.js"); -var _labelUtil = require("./label-util.js"); -var _compiledMatchingConfig = require("./matching-config/__generated__/compiled-matching-config.js"); -var _matchingUtils = require("./matching-utils.js"); -var _autofillUtils = require("../autofill-utils.js"); -const { - TEXT_LENGTH_CUTOFF, - ATTR_INPUT_TYPE -} = _constants.constants; - -/** @type {{[K in keyof MatcherLists]?: { minWidth: number }} } */ -const dimensionBounds = { - emailAddress: { - minWidth: 35 + async function extensionSpecificSetIncontextSignupPermanentlyDismissedAtCall(params) { + return new Promise((resolve) => { + chrome.runtime.sendMessage( + { + messageType: "setIncontextSignupPermanentlyDismissedAt", + options: params + }, + () => { + resolve(true); + } + ); + }); } -}; - -/** - * An abstraction around the concept of classifying input fields. - * - * The only state this class keeps is derived from the passed-in MatchingConfiguration. - */ -class Matching { - /** @type {MatchingConfiguration} */ - #config; - - /** @type {CssSelectorConfiguration['selectors']} */ - #cssSelectors; - - /** @type {Record} */ - #ddgMatchers; - - /** - * This acts as an internal cache for the larger vendorRegexes - * @type {VendorRegexConfiguration['rules']} - */ - #vendorRegexRules; - - /** @type {MatcherLists} */ - #matcherLists; - /** @type {Array} */ - #defaultStrategyOrder = ['cssSelector', 'ddgMatcher', 'vendorRegex']; - - /** @type {Record} */ - activeElementStrings = { - nameAttr: '', - labelText: '', - placeholderAttr: '', - relatedText: '', - id: '' + // src/deviceApiCalls/transports/windows.transport.js + var WindowsTransport = class extends DeviceApiTransport { + async send(deviceApiCall, options) { + if (deviceApiCall.id) { + return windowsTransport(deviceApiCall, options).withResponse(deviceApiCall.id); + } + return windowsTransport(deviceApiCall, options); + } }; - - /** - * @param {MatchingConfiguration} config - */ - constructor(config) { - this.#config = config; - this.#vendorRegexRules = this.#config.strategies.vendorRegex.rules; - this.#cssSelectors = this.#config.strategies.cssSelector.selectors; - this.#ddgMatchers = this.#config.strategies.ddgMatcher.matchers; - this.#matcherLists = { - unknown: [], - cc: [], - id: [], - password: [], - username: [], - emailAddress: [] + function windowsTransport(deviceApiCall, options) { + windowsInteropPostMessage({ + Feature: "Autofill", + Name: deviceApiCall.method, + Data: deviceApiCall.params + }); + return { + /** + * Sends a message and returns a Promise that resolves with the response + * @param responseId + * @returns {Promise<*>} + */ + withResponse(responseId) { + return waitForWindowsResponse(responseId, options); + } }; - - /** - * Convert the raw config data into actual references. - * - * For example this takes `email: ["email"]` and creates - * - * `email: [{type: "email", strategies: {cssSelector: "email", ... etc}]` - */ - for (let [listName, matcherNames] of Object.entries(this.#config.matchers.lists)) { - for (let fieldName of matcherNames) { - if (!this.#matcherLists[listName]) { - this.#matcherLists[listName] = []; - } - this.#matcherLists[listName].push(this.#config.matchers.fields[fieldName]); + } + function waitForWindowsResponse(responseId, options) { + return new Promise((resolve, reject) => { + if (options?.signal?.aborted) { + return reject(new DOMException("Aborted", "AbortError")); } - } + let teardown; + const handler = (event) => { + if (!event.data) { + console.warn("data absent from message"); + return; + } + if (event.data.type === responseId) { + teardown(); + resolve(event.data); + } + }; + const abortHandler = () => { + teardown(); + reject(new DOMException("Aborted", "AbortError")); + }; + windowsInteropAddEventListener("message", handler); + options?.signal?.addEventListener("abort", abortHandler); + teardown = () => { + windowsInteropRemoveEventListener("message", handler); + options?.signal?.removeEventListener("abort", abortHandler); + }; + }); } - /** - * @param {HTMLInputElement|HTMLSelectElement} input - * @param {HTMLElement} formEl - */ - setActiveElementStrings(input, formEl) { - this.activeElementStrings = this.getElementStrings(input, formEl); + // src/deviceApiCalls/transports/transports.js + function createTransport(globalConfig) { + if (typeof globalConfig.userPreferences?.platform?.name === "string") { + switch (globalConfig.userPreferences?.platform?.name) { + case "ios": + case "macos": + return new AppleTransport(globalConfig); + case "android": + return new AndroidTransport(globalConfig); + default: + throw new Error("selectSender unimplemented!"); + } + } + if (globalConfig.isWindows) { + return new WindowsTransport(); + } + if (globalConfig.isDDGApp) { + if (globalConfig.isAndroid) { + return new AndroidTransport(globalConfig); + } + throw new Error("unreachable, createTransport"); + } + return new ExtensionTransport(globalConfig); + } + + // src/DeviceInterface/initFormSubmissionsApi.js + function initFormSubmissionsApi(forms, matching) { + window.addEventListener("submit", (e) => { + return forms.get(e.target)?.submitHandler("global submit event"); + }, true); + window.addEventListener("keydown", (e) => { + if (e.key === "Enter") { + const focusedForm = [...forms.values()].find((form) => form.hasFocus(e)); + focusedForm?.submitHandler("global keydown + Enter"); + } + }, true); + window.addEventListener("pointerdown", (event) => { + const realTarget = pierceShadowTree(event); + const formsArray = [...forms.values()]; + const matchingForm = formsArray.find( + (form) => { + const btns = [...form.submitButtons]; + if (btns.includes(realTarget)) + return true; + if (btns.find((btn) => btn.contains(realTarget))) + return true; + } + ); + matchingForm?.submitHandler("global pointerdown event + matching form"); + if (!matchingForm) { + const selector = matching.cssSelector("submitButtonSelector") + ', a[href="#"], a[href^=javascript], *[onclick], [class*=button i]'; + const button = ( + /** @type HTMLElement */ + realTarget?.closest(selector) + ); + if (!button) + return; + const buttonIsAFalsePositive = formsArray.some((form) => button?.contains(form.form)); + if (buttonIsAFalsePositive) + return; + const text = getTextShallow(button) || extractElementStrings(button).join(" "); + const hasRelevantText = safeRegexTest(matching.getDDGMatcherRegex("submitButtonRegex"), text); + if (hasRelevantText && text.length < 25) { + const filledForm = formsArray.find((form) => form.hasValues()); + if (filledForm && buttonMatchesFormType( + /** @type HTMLElement */ + button, + filledForm + )) { + filledForm?.submitHandler("global pointerdown event + filled form"); + } + } + if ( + /** @type HTMLElement */ + realTarget?.closest("#passwordNext button, #identifierNext button") + ) { + const filledForm = formsArray.find((form) => form.hasValues()); + filledForm?.submitHandler("global pointerdown event + google escape hatch"); + } + } + }, true); + const observer = new PerformanceObserver((list) => { + const formsArray = [...forms.values()]; + const entries = list.getEntries().filter( + (entry) => ( + // @ts-ignore why does TS not know about `entry.initiatorType`? + ["fetch", "xmlhttprequest"].includes(entry.initiatorType) && safeRegexTest(/login|sign-in|signin/, entry.name) + ) + ); + if (!entries.length) + return; + const filledForm = formsArray.find((form) => form.hasValues()); + const focusedForm = formsArray.find((form) => form.hasFocus()); + if (focusedForm) + return; + filledForm?.submitHandler("performance observer"); + }); + observer.observe({ entryTypes: ["resource"] }); } - /** - * Try to access a 'vendor regex' by name - * @param {string} regexName - * @returns {RegExp | undefined} - */ - vendorRegex(regexName) { - const match = this.#vendorRegexRules[regexName]; - if (!match) { - console.warn('Vendor Regex not found for', regexName); - return undefined; + // src/EmailProtection.js + var _previous2; + var EmailProtection = class { + /** @param {import("./DeviceInterface/InterfacePrototype").default} device */ + constructor(device) { + /** @type {string|null} */ + __privateAdd(this, _previous2, null); + this.device = device; } - return match; - } - - /** - * Strategies can have different lookup names. This returns the correct one - * @param {MatcherTypeNames} matcherName - * @param {StrategyNames} vendorRegex - * @returns {MatcherTypeNames} - */ - getStrategyLookupByType(matcherName, vendorRegex) { - return this.#config.matchers.fields[matcherName]?.strategies[vendorRegex]; - } - - /** - * Try to access a 'css selector' by name from configuration - * @param {RequiredCssSelectors | string} selectorName - * @returns {string}; - */ - cssSelector(selectorName) { - const match = this.#cssSelectors[selectorName]; - if (!match) { - console.warn('CSS selector not found for %s, using a default value', selectorName); - return ''; + /** @returns {string|null} */ + get lastGenerated() { + return __privateGet(this, _previous2); } - return match; - } - - /** - * Try to access a 'ddg matcher' by name from configuration - * @param {MatcherTypeNames | string} matcherName - * @returns {DDGMatcher | undefined} - */ - ddgMatcher(matcherName) { - const match = this.#ddgMatchers[matcherName]; - if (!match) { - console.warn('DDG matcher not found for', matcherName); - return undefined; + /** + * Store the last received email address + * @param {string} emailAddress + */ + storeReceived(emailAddress) { + __privateSet(this, _previous2, emailAddress); + return emailAddress; } - return match; - } + }; + _previous2 = new WeakMap(); - /** - * Returns the RegExp for the given matcherName, with proper flags - * @param {AllDDGMatcherNames} matcherName - * @returns {RegExp|undefined} - */ - getDDGMatcherRegex(matcherName) { - const matcher = this.ddgMatcher(matcherName); - if (!matcher || !matcher.match) { - console.warn('DDG matcher has unexpected format'); - return undefined; + // src/DeviceInterface/InterfacePrototype.js + var _addresses, _data6; + var _InterfacePrototype = class { + /** + * @param {GlobalConfig} config + * @param {import("../../packages/device-api").DeviceApi} deviceApi + * @param {Settings} settings + */ + constructor(config, deviceApi, settings) { + __publicField(this, "attempts", 0); + /** @type {import("../Form/Form").Form | null} */ + __publicField(this, "activeForm", null); + /** @type {import("../UI/HTMLTooltip.js").default | null} */ + __publicField(this, "currentTooltip", null); + /** @type {number} */ + __publicField(this, "initialSetupDelayMs", 0); + __publicField(this, "autopromptFired", false); + /** @type {PasswordGenerator} */ + __publicField(this, "passwordGenerator", new PasswordGenerator()); + __publicField(this, "emailProtection", new EmailProtection(this)); + /** @type {import("../InContextSignup.js").InContextSignup | null} */ + __publicField(this, "inContextSignup", null); + /** @type {import("../ThirdPartyProvider.js").ThirdPartyProvider | null} */ + __publicField(this, "thirdPartyProvider", null); + /** @type {{privateAddress: string, personalAddress: string}} */ + __privateAdd(this, _addresses, { + privateAddress: "", + personalAddress: "" + }); + /** @type {GlobalConfig} */ + __publicField(this, "globalConfig"); + /** @type {import('../Scanner').Scanner} */ + __publicField(this, "scanner"); + /** @type {import("../UI/controllers/UIController.js").UIController | null} */ + __publicField(this, "uiController"); + /** @type {import("../../packages/device-api").DeviceApi} */ + __publicField(this, "deviceApi"); + /** @type {boolean} */ + __publicField(this, "isInitializationStarted"); + /** @type {((reason, ...rest) => void) | null} */ + __publicField(this, "_scannerCleanup", null); + /** @type { PMData } */ + __privateAdd(this, _data6, { + credentials: [], + creditCards: [], + identities: [], + topContextData: void 0 + }); + this.globalConfig = config; + this.deviceApi = deviceApi; + this.settings = settings; + this.uiController = null; + this.scanner = createScanner(this, { + initialDelay: this.initialSetupDelayMs + }); + this.isInitializationStarted = false; } - return matcher?.match; - } - - /** - * Try to access a list of matchers by name - these are the ones collected in the constructor - * @param {keyof MatcherLists} listName - * @return {Matcher[]} - */ - matcherList(listName) { - const matcherList = this.#matcherLists[listName]; - if (!matcherList) { - console.warn('MatcherList not found for ', listName); - return []; + /** + * Implementors should override this with a UI controller that suits + * their platform. + * + * @returns {import("../UI/controllers/UIController.js").UIController} + */ + createUIController() { + return new NativeUIController(); } - return matcherList; - } - - /** - * Convert a list of matchers into a single CSS selector. - * - * This will consider all matchers in the list and if it - * contains a CSS Selector it will be added to the final output - * - * @param {keyof MatcherLists} listName - * @returns {string | undefined} - */ - joinCssSelectors(listName) { - const matcherList = this.matcherList(listName); - if (!matcherList) { - console.warn('Matcher list not found for', listName); - return undefined; + /** + * @param {string} reason + */ + removeAutofillUIFromPage(reason) { + this.uiController?.destroy(); + this._scannerCleanup?.(reason); + } + get hasLocalAddresses() { + return !!(__privateGet(this, _addresses)?.privateAddress && __privateGet(this, _addresses)?.personalAddress); + } + getLocalAddresses() { + return __privateGet(this, _addresses); + } + storeLocalAddresses(addresses) { + __privateSet(this, _addresses, addresses); + const identities = this.getLocalIdentities(); + const privateAddressIdentity = identities.find(({ id }) => id === "privateAddress"); + if (privateAddressIdentity) { + privateAddressIdentity.emailAddress = formatDuckAddress(addresses.privateAddress); + } else { + __privateGet(this, _data6).identities = this.addDuckAddressesToIdentities(identities); + } + } + /** + * @returns {import('../Form/matching').SupportedTypes} + */ + getCurrentInputType() { + throw new Error("Not implemented"); + } + addDuckAddressesToIdentities(identities) { + if (!this.hasLocalAddresses) + return identities; + const newIdentities = []; + let { privateAddress, personalAddress } = this.getLocalAddresses(); + privateAddress = formatDuckAddress(privateAddress); + personalAddress = formatDuckAddress(personalAddress); + const duckEmailsInIdentities = identities.reduce( + (duckEmails, { emailAddress: email }) => email?.includes(ADDRESS_DOMAIN) ? duckEmails.concat(email) : duckEmails, + [] + ); + if (!duckEmailsInIdentities.includes(personalAddress)) { + newIdentities.push({ + id: "personalAddress", + emailAddress: personalAddress, + title: "Block email trackers" + }); + } + newIdentities.push({ + id: "privateAddress", + emailAddress: privateAddress, + title: "Block email trackers & hide address" + }); + return [...identities, ...newIdentities]; } - /** - * @type {string[]} + * Stores init data coming from the tooltipHandler + * @param { InboundPMData } data */ - const selectors = []; - for (let matcher of matcherList) { - if (matcher.strategies.cssSelector) { - const css = this.cssSelector(matcher.strategies.cssSelector); - if (css) { - selectors.push(css); + storeLocalData(data) { + this.storeLocalCredentials(data.credentials); + data.creditCards.forEach((cc) => delete cc.cardNumber && delete cc.cardSecurityCode); + const updatedIdentities = data.identities.map((identity) => ({ + ...identity, + fullName: formatFullName(identity) + })); + __privateGet(this, _data6).identities = this.addDuckAddressesToIdentities(updatedIdentities); + __privateGet(this, _data6).creditCards = data.creditCards; + if (data.serializedInputContext) { + try { + __privateGet(this, _data6).topContextData = JSON.parse(data.serializedInputContext); + } catch (e) { + console.error(e); + this.removeTooltip(); } } } - return selectors.join(', '); - } - - /** - * Returns true if the field is visible and large enough - * @param {keyof MatcherLists} matchedType - * @param {HTMLInputElement} input - * @returns {boolean} - */ - isInputLargeEnough(matchedType, input) { - const expectedDimensionBounds = dimensionBounds[matchedType]; - if (!expectedDimensionBounds) return true; - const width = input.offsetWidth; - const height = input.offsetHeight; - - // Ignore hidden elements as we can't determine their dimensions - const isHidden = height === 0 && width === 0; - if (isHidden) return true; - return width >= expectedDimensionBounds.minWidth; - } - - /** - * Tries to infer the input type for an input - * - * @param {HTMLInputElement|HTMLSelectElement} input - * @param {HTMLElement} formEl - * @param {SetInputTypeOpts} [opts] - * @returns {SupportedTypes} - */ - inferInputType(input, formEl) { - let opts = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - const presetType = getInputType(input); - if (presetType !== 'unknown') { - return presetType; - } - this.setActiveElementStrings(input, formEl); - if (this.subtypeFromMatchers('unknown', input)) return 'unknown'; - - // For CC forms we run aggressive matches, so we want to make sure we only - // run them on actual CC forms to avoid false positives and expensive loops - if (opts.isCCForm) { - const subtype = this.subtypeFromMatchers('cc', input); - if (subtype && isValidCreditCardSubtype(subtype)) { - return `creditCards.${subtype}`; - } - } - if (input instanceof HTMLInputElement) { - if (this.subtypeFromMatchers('password', input)) { - // Any other input type is likely a false match - // Arguably "text" should be as well, but it can be used for password reveal fields - if (['password', 'text'].includes(input.type) && input.name !== 'email' && - // pcsretirement.com, improper use of the for attribute - input.name !== 'Username') { - return this.inferPasswordVariant(input, opts); - } - } - if (this.subtypeFromMatchers('emailAddress', input)) { - if (!this.isInputLargeEnough('emailAddress', input)) { - if ((0, _autofillUtils.shouldLog)()) { - console.log('Field matched for Email Address, but discarded because too small when scanned'); - } - return 'unknown'; - } - if (opts.isLogin || opts.isHybrid) { - // TODO: Bring this support back in the future - // https://app.asana.com/0/1198964220583541/1204686960531034/f - // Show identities when supported and there are no credentials - // if (opts.supportsIdentitiesAutofill && !opts.hasCredentials) { - // return 'identities.emailAddress' - // } - - return 'credentials.username'; - } - - // TODO: Temporary hack to support Google signin in different languages - // https://app.asana.com/0/1198964220583541/1201650539303898/f - if (window.location.href.includes('https://accounts.google.com/v3/signin/identifier') && input.matches('[type=email][autocomplete=username]')) { - return 'credentials.username'; - } - return 'identities.emailAddress'; - } - if (this.subtypeFromMatchers('username', input)) { - return 'credentials.username'; - } + /** + * Stores credentials locally + * @param {CredentialsObject[]} credentials + */ + storeLocalCredentials(credentials) { + credentials.forEach((cred) => delete cred.password); + __privateGet(this, _data6).credentials = credentials; } - const idSubtype = this.subtypeFromMatchers('id', input); - if (idSubtype && isValidIdentitiesSubtype(idSubtype)) { - return `identities.${idSubtype}`; + getTopContextData() { + return __privateGet(this, _data6).topContextData; } - (0, _matchingUtils.logUnmatched)(input, this.activeElementStrings); - return 'unknown'; - } - - /** - * @typedef {{ - * isLogin?: boolean, - * isHybrid?: boolean, - * isCCForm?: boolean, - * hasCredentials?: boolean, - * supportsIdentitiesAutofill?: boolean - * }} SetInputTypeOpts - */ - - /** - * Sets the input type as a data attribute to the element and returns it - * @param {HTMLInputElement} input - * @param {HTMLElement} formEl - * @param {SetInputTypeOpts} [opts] - * @returns {SupportedSubTypes | string} - */ - setInputType(input, formEl) { - let opts = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - const type = this.inferInputType(input, formEl, opts); - input.setAttribute(ATTR_INPUT_TYPE, type); - return type; - } - - /** - * Tries to infer input subtype, with checks in decreasing order of reliability - * @param {keyof MatcherLists} listName - * @param {HTMLInputElement|HTMLSelectElement} el - * @return {MatcherTypeNames|undefined} - */ - subtypeFromMatchers(listName, el) { - const matchers = this.matcherList(listName); - /** - * Loop through each strategy in order + * @deprecated use `availableInputTypes.credentials` directly instead + * @returns {boolean} */ - for (let strategyName of this.#defaultStrategyOrder) { - let result; - /** - * Now loop through each matcher in the list. - */ - for (let matcher of matchers) { - /** - * for each `strategyName` (such as cssSelector), check - * if the current matcher implements it. - */ - const lookup = matcher.strategies[strategyName]; - /** - * Sometimes a matcher may not implement the current strategy, - * so we skip it - */ - if (!lookup) continue; - - /** - * Now perform the matching - */ - if (strategyName === 'cssSelector') { - result = this.execCssSelector(lookup, el); - } - if (strategyName === 'ddgMatcher') { - result = this.execDDGMatcher(lookup); - } - if (strategyName === 'vendorRegex') { - result = this.execVendorRegex(lookup); - } - - /** - * If there's a match, return the matcher type. - * - * So, for example if 'username' had a `cssSelector` implemented, and - * it matched the current element, then we'd return 'username' - */ - if (result?.matched) { - (0, _matchingUtils.logMatching)(el, result); - return matcher.type; - } - - /** - * If a matcher wants to prevent all future matching on this element, - * it would return { matched: false, proceed: false } - */ - if (!result?.matched && result?.proceed === false) { - (0, _matchingUtils.logMatching)(el, result); - // If we get here, do not allow subsequent strategies to continue - return undefined; - } - } - if (result?.skip) { - (0, _matchingUtils.logMatching)(el, result); - break; - } - } - return undefined; - } - - /** - * Returns the password type string including the variant - * @param {HTMLInputElement} input - * @param opts - * @returns {'credentials.password.new'|'credentials.password.current'} - */ - inferPasswordVariant(input, opts) { - // Check attributes first - // This is done mainly to ensure coverage for all languages, since attributes are usually in English - const attrsToCheck = [input.autocomplete, input.name, input.id]; - if (opts.isSignup && attrsToCheck.some(str => (0, _autofillUtils.safeRegexTest)(/new.?password|password.?new/i, str))) { - return 'credentials.password.new'; + get hasLocalCredentials() { + return __privateGet(this, _data6).credentials.length > 0; } - if ((opts.isLogin || opts.isHybrid) && attrsToCheck.some(str => (0, _autofillUtils.safeRegexTest)(/(current|old|previous).?password|password.?(current|old|previous)/i, str))) { - return 'credentials.password.current'; + getLocalCredentials() { + return __privateGet(this, _data6).credentials.map((cred) => { + const { password, ...rest } = cred; + return rest; + }); } - - // Check strings using the usual DDG matcher - const newPasswordMatch = this.execDDGMatcher('newPassword'); - if (newPasswordMatch.matched) { - return 'credentials.password.new'; + /** + * @deprecated use `availableInputTypes.identities` directly instead + * @returns {boolean} + */ + get hasLocalIdentities() { + return __privateGet(this, _data6).identities.length > 0; } - const currentPasswordMatch = this.execDDGMatcher('currentPassword'); - if (currentPasswordMatch.matched) { - return 'credentials.password.current'; + getLocalIdentities() { + return __privateGet(this, _data6).identities; } - - // Otherwise, rely on the passed form type - if (opts.isLogin || opts.isHybrid) { - return 'credentials.password.current'; + /** + * @deprecated use `availableInputTypes.creditCards` directly instead + * @returns {boolean} + */ + get hasLocalCreditCards() { + return __privateGet(this, _data6).creditCards.length > 0; } - return 'credentials.password.new'; - } - - /** - * CSS selector matching just leverages the `.matches` method on elements - * - * @param {MatcherTypeNames} lookup - * @param {HTMLInputElement|HTMLSelectElement} el - * @returns {MatchingResult} - */ - execCssSelector(lookup, el) { - const selector = this.cssSelector(lookup); - return { - matched: el.matches(selector), - strategyName: 'cssSelector', - matcherType: lookup - }; - } - - /** - * A DDG Matcher can have a `match` regex along with a `not` regex. This is done - * to allow it to be driven by configuration as it avoids needing to invoke custom functions. - * - * todo: maxDigits was added as an edge-case when converting this over to be declarative, but I'm - * unsure if it's actually needed. It's not urgent, but we should consider removing it if that's the case - * - * @param {MatcherTypeNames} lookup - * @returns {MatchingResult} - */ - execDDGMatcher(lookup) { - /** @type {MatchingResult} */ - const defaultResult = { - matched: false, - strategyName: 'ddgMatcher', - matcherType: lookup - }; - const ddgMatcher = this.ddgMatcher(lookup); - if (!ddgMatcher || !ddgMatcher.match) { - return defaultResult; + /** @return {CreditCardObject[]} */ + getLocalCreditCards() { + return __privateGet(this, _data6).creditCards; } - let matchRexExp = this.getDDGMatcherRegex(lookup); - if (!matchRexExp) { - return defaultResult; + async startInit() { + if (this.isInitializationStarted) + return; + this.alreadyInitialized = true; + await this.settings.refresh(); + this.addDeviceListeners(); + await this.setupAutofill(); + this.uiController = this.createUIController(); + if (!this.settings.enabled) { + return; + } + await this.setupSettingsPage(); + await this.postInit(); + if (this.settings.featureToggles.credentials_saving) { + initFormSubmissionsApi(this.scanner.forms, this.scanner.matching); + } } - let requiredScore = ['match', 'forceUnknown', 'maxDigits'].filter(ddgMatcherProp => ddgMatcherProp in ddgMatcher).length; - - /** @type {MatchableStrings[]} */ - const matchableStrings = ddgMatcher.matchableStrings || ['labelText', 'placeholderAttr', 'relatedText']; - for (let stringName of matchableStrings) { - let elementString = this.activeElementStrings[stringName]; - if (!elementString) continue; - - // Scoring to ensure all DDG tests are valid - let score = 0; - - /** @type {MatchingResult} */ - const result = { - ...defaultResult, - matchedString: elementString, - matchedFrom: stringName - }; - - // If a negated regex was provided, ensure it does not match - // If it DOES match - then we need to prevent any future strategies from continuing - if (ddgMatcher.forceUnknown) { - let notRegex = ddgMatcher.forceUnknown; - if (!notRegex) { - return { - ...result, - matched: false - }; + async init() { + const isEnabledInitiallyViaConfig = autofillEnabled(this.globalConfig); + if (!isEnabledInitiallyViaConfig) { + return; + } + const handler = async () => { + if (document.readyState === "complete") { + window.removeEventListener("load", handler); + document.removeEventListener("readystatechange", handler); + await this.startInit(); } - if ((0, _autofillUtils.safeRegexTest)(notRegex, elementString)) { - return { - ...result, - matched: false, - proceed: false - }; - } else { - // All good here, increment the score - score++; + }; + if (document.readyState === "complete") { + await this.startInit(); + } else { + window.addEventListener("load", handler); + document.addEventListener("readystatechange", handler); + } + } + postInit() { + const cleanup = this.scanner.init(); + this.addLogoutListener(() => { + cleanup("Logged out"); + if (this.globalConfig.isDDGDomain) { + notifyWebApp({ deviceSignedIn: { value: false } }); } + }); + } + /** + * @deprecated This was a port from the macOS implementation so the API may not be suitable for all + * @returns {Promise} + */ + async getSelectedCredentials() { + throw new Error("`getSelectedCredentials` not implemented"); + } + isTestMode() { + return this.globalConfig.isDDGTestMode; + } + /** + * This indicates an item was selected on Desktop, and we should try to autofill + * + * Note: When we're in a top-frame scenario, like on like macOS & Windows in the webview, + * this method gets overridden {@see WindowsOverlayDeviceInterface} {@see AppleOverlayDeviceInterface} + * + * @param {IdentityObject|CreditCardObject|CredentialsObject|{email:string, id: string}} data + * @param {string} type + */ + async selectedDetail(data, type) { + const form = this.activeForm; + if (!form) { + return; } - if (ddgMatcher.skip) { - let skipRegex = ddgMatcher.skip; - if (!skipRegex) { - return { - ...result, - matched: false + if (type === "email" && "email" in data) { + form.autofillEmail(data.email); + } else { + form.autofillData(data, type); + } + const isPrivateAddress = data.id === "privateAddress"; + if (isPrivateAddress) { + this.refreshAlias(); + if ("emailAddress" in data && data.emailAddress) { + this.emailProtection.storeReceived(data.emailAddress); + const formValues = { + credentials: { + username: data.emailAddress, + autogenerated: true + } }; + this.storeFormData(formValues, "emailProtection"); } - if ((0, _autofillUtils.safeRegexTest)(skipRegex, elementString)) { - return { - ...result, - matched: false, - skip: true - }; + } + await this.removeTooltip(); + } + /** + * Before the DataWebTooltip opens, we collect the data based on the config.type + * @param {InputTypeConfigs} config + * @param {import('../Form/matching').SupportedTypes} inputType + * @param {TopContextData} [data] + * @returns {(CredentialsObject|CreditCardObject|IdentityObject)[]} + */ + dataForAutofill(config, inputType, data) { + const subtype = getSubtypeFromType(inputType); + if (config.type === "identities") { + return this.getLocalIdentities().filter((identity) => !!identity[subtype]); + } + if (config.type === "creditCards") { + return this.getLocalCreditCards(); + } + if (config.type === "credentials") { + if (data) { + if (Array.isArray(data.credentials) && data.credentials.length > 0) { + return data.credentials; + } else { + return this.getLocalCredentials().filter((cred) => !!cred[subtype] || subtype === "password" || cred.id === PROVIDER_LOCKED); + } } } - - // if the `match` regex fails, moves onto the next string - if (!(0, _autofillUtils.safeRegexTest)(matchRexExp, elementString)) { - continue; + return []; + } + /** + * @param {object} params + * @param {import("../Form/Form").Form} params.form + * @param {HTMLInputElement} params.input + * @param {{ x: number; y: number; } | null} params.click + * @param {import('../deviceApiCalls/__generated__/validators-ts').GetAutofillDataRequest['trigger']} params.trigger + * @param {import('../UI/controllers/UIController.js').AttachArgs["triggerMetaData"]} params.triggerMetaData + */ + attachTooltip(params) { + const { + form, + input, + click, + trigger + } = params; + if (document.visibilityState !== "visible" && trigger !== "postSignup") + return; + if (trigger === "autoprompt" && !this.globalConfig.isMobileApp) + return; + if (trigger === "autoprompt" && this.autopromptFired) + return; + form.activeInput = input; + this.activeForm = form; + const inputType = getInputType(input); + const getPosition = () => { + const alignLeft = this.globalConfig.isApp || this.globalConfig.isWindows; + return alignLeft ? input.getBoundingClientRect() : getDaxBoundingBox(input); + }; + if (this.globalConfig.isMobileApp && inputType === "identities.emailAddress") { + this.getAlias().then((alias) => { + if (alias) { + form.autofillEmail(alias); + this.emailProtection.storeReceived(alias); + } else { + form.activeInput?.focus(); + } + this.updateForStateChange(); + this.onFinishedAutofill(); + }); + return; } - - // Otherwise, increment the score - score++; - - // If a 'maxDigits' rule was provided, validate it - if (ddgMatcher.maxDigits) { - const digitLength = elementString.replace(/[^0-9]/g, '').length; - if (digitLength > ddgMatcher.maxDigits) { - return { - ...result, - matched: false - }; + const topContextData = { + inputType + }; + const processedTopContext = this.preAttachTooltip(topContextData, input, form); + this.uiController?.attach({ + input, + form, + click, + getPosition, + topContextData: processedTopContext, + device: this, + trigger, + triggerMetaData: params.triggerMetaData + }); + if (trigger === "autoprompt") { + this.autopromptFired = true; + } + } + /** + * When an item was selected, we then call back to the device + * to fetch the full suite of data needed to complete the autofill + * + * @param {import('../Form/matching').SupportedTypes} inputType + * @param {(CreditCardObject|IdentityObject|CredentialsObject)[]} items + * @param {CreditCardObject['id']|IdentityObject['id']|CredentialsObject['id']} id + */ + onSelect(inputType, items, id) { + id = String(id); + const mainType = getMainTypeFromType(inputType); + const subtype = getSubtypeFromType(inputType); + if (id === PROVIDER_LOCKED) { + return this.thirdPartyProvider?.askToUnlockProvider(); + } + const matchingData = items.find((item) => String(item.id) === id); + if (!matchingData) + throw new Error("unreachable (fatal)"); + const dataPromise = (() => { + switch (mainType) { + case "creditCards": + return this.getAutofillCreditCard(id); + case "identities": + return this.getAutofillIdentity(id); + case "credentials": { + if (AUTOGENERATED_KEY in matchingData) { + const autogeneratedPayload = { ...matchingData, username: "" }; + return Promise.resolve({ success: autogeneratedPayload }); + } + return this.getAutofillCredentials(id); + } + default: + throw new Error("unreachable!"); + } + })(); + dataPromise.then((response) => { + if (response) { + const data = response.success || response; + if (mainType === "identities") { + this.firePixel({ pixelName: "autofill_identity", params: { fieldType: subtype } }); + switch (id) { + case "personalAddress": + this.firePixel({ pixelName: "autofill_personal_address" }); + break; + case "privateAddress": + this.firePixel({ pixelName: "autofill_private_address" }); + break; + default: + const checks = [ + subtype === "emailAddress", + this.hasLocalAddresses, + data?.emailAddress === formatDuckAddress(__privateGet(this, _addresses).personalAddress) + ]; + if (checks.every(Boolean)) { + this.firePixel({ pixelName: "autofill_personal_address" }); + } + break; + } + } + return this.selectedDetail(data, mainType); } else { - score++; + return Promise.reject(new Error("none-success response")); } - } - if (score === requiredScore) { - return { - ...result, - matched: true - }; - } + }).catch((e) => { + console.error(e); + return this.removeTooltip(); + }); } - return defaultResult; - } - - /** - * If we get here, a firefox/vendor regex was given and we can execute it on the element - * strings - * @param {MatcherTypeNames} lookup - * @return {MatchingResult} - */ - execVendorRegex(lookup) { - /** @type {MatchingResult} */ - const defaultResult = { - matched: false, - strategyName: 'vendorRegex', - matcherType: lookup - }; - const regex = this.vendorRegex(lookup); - if (!regex) { - return defaultResult; + isTooltipActive() { + return this.uiController?.isActive?.() ?? false; } - /** @type {MatchableStrings[]} */ - const stringsToMatch = ['placeholderAttr', 'nameAttr', 'labelText', 'id', 'relatedText']; - for (let stringName of stringsToMatch) { - let elementString = this.activeElementStrings[stringName]; - if (!elementString) continue; - if ((0, _autofillUtils.safeRegexTest)(regex, elementString)) { - return { - ...defaultResult, - matched: true, - matchedString: elementString, - matchedFrom: stringName - }; - } + removeTooltip() { + return this.uiController?.removeTooltip?.("interface"); } - return defaultResult; - } - - /** - * Yield strings in the order in which they should be checked against. - * - * Note: some strategies may not want to accept all strings, which is - * where `matchableStrings` helps. It defaults to when you see below but can - * be overridden. - * - * For example, `nameAttr` is first, since this has the highest chance of matching - * and then the rest are in decreasing order of value vs cost - * - * A generator function is used here to prevent any potentially expensive - * lookups occurring if they are rare. For example if 90% of all matching never needs - * to look at the output from `relatedText`, then the cost of computing it will be avoided. - * - * @param {HTMLInputElement|HTMLSelectElement} el - * @param {HTMLElement} form - * @returns {Record} - */ - _elementStringCache = new WeakMap(); - getElementStrings(el, form) { - if (this._elementStringCache.has(el)) { - return this._elementStringCache.get(el); + onFinishedAutofill() { + this.activeForm?.activeInput?.dispatchEvent(new Event("mouseleave")); } - const explicitLabelsText = getExplicitLabelsText(el); - - /** @type {Record} */ - const next = { - nameAttr: el.name, - labelText: explicitLabelsText, - placeholderAttr: el.placeholder || '', - id: el.id, - relatedText: explicitLabelsText ? '' : getRelatedText(el, form, this.cssSelector('formInputsSelector')) - }; - this._elementStringCache.set(el, next); - return next; - } - clear() { - this._elementStringCache = new WeakMap(); - } - - /** - * Only used for testing - * @param {HTMLInputElement|HTMLSelectElement} input - * @param {HTMLElement} form - * @returns {Matching} - */ - forInput(input, form) { - this.setActiveElementStrings(input, form); - return this; - } - - /** - * @type {MatchingConfiguration} - */ - static emptyConfig = { - matchers: { - lists: {}, - fields: {} - }, - strategies: { - 'vendorRegex': { - rules: {}, - ruleSets: [] - }, - 'ddgMatcher': { - matchers: {} - }, - 'cssSelector': { - selectors: {} - } + async updateForStateChange() { + this.activeForm?.removeAllDecorations(); + await this.refreshData(); + this.activeForm?.recategorizeAllInputs(); } - }; -} - -/** - * @returns {SupportedTypes} - */ -exports.Matching = Matching; -function getInputType(input) { - const attr = input?.getAttribute(ATTR_INPUT_TYPE); - if (isValidSupportedType(attr)) { - return attr; - } - return 'unknown'; -} - -/** - * Retrieves the main type - * @param {SupportedTypes | string} type - * @returns {SupportedMainTypes} - */ -function getMainTypeFromType(type) { - const mainType = type.split('.')[0]; - switch (mainType) { - case 'credentials': - case 'creditCards': - case 'identities': - return mainType; - } - return 'unknown'; -} - -/** - * Retrieves the input main type - * @param {HTMLInputElement} input - * @returns {SupportedMainTypes} - */ -const getInputMainType = input => getMainTypeFromType(getInputType(input)); - -/** @typedef {supportedIdentitiesSubtypes[number]} SupportedIdentitiesSubTypes */ -exports.getInputMainType = getInputMainType; -const supportedIdentitiesSubtypes = /** @type {const} */['emailAddress', 'firstName', 'middleName', 'lastName', 'fullName', 'phone', 'addressStreet', 'addressStreet2', 'addressCity', 'addressProvince', 'addressPostalCode', 'addressCountryCode', 'birthdayDay', 'birthdayMonth', 'birthdayYear']; - -/** - * @param {SupportedTypes | any} supportedType - * @returns {supportedType is SupportedIdentitiesSubTypes} - */ -function isValidIdentitiesSubtype(supportedType) { - return supportedIdentitiesSubtypes.includes(supportedType); -} - -/** @typedef {supportedCreditCardSubtypes[number]} SupportedCreditCardSubTypes */ -const supportedCreditCardSubtypes = /** @type {const} */['cardName', 'cardNumber', 'cardSecurityCode', 'expirationMonth', 'expirationYear', 'expiration']; - -/** - * @param {SupportedTypes | any} supportedType - * @returns {supportedType is SupportedCreditCardSubTypes} - */ -function isValidCreditCardSubtype(supportedType) { - return supportedCreditCardSubtypes.includes(supportedType); -} - -/** @typedef {supportedCredentialsSubtypes[number]} SupportedCredentialsSubTypes */ -const supportedCredentialsSubtypes = /** @type {const} */['password', 'password.new', 'password.current', 'username']; - -/** @typedef {supportedVariants[number]} SupportedVariants */ -const supportedVariants = /** @type {const} */['new', 'current']; - -/** - * @param {SupportedTypes | any} supportedType - * @returns {supportedType is SupportedCredentialsSubTypes} - */ -function isValidCredentialsSubtype(supportedType) { - return supportedCredentialsSubtypes.includes(supportedType); -} - -/** @typedef {SupportedIdentitiesSubTypes | SupportedCreditCardSubTypes | SupportedCredentialsSubTypes} SupportedSubTypes */ - -/** @typedef {`identities.${SupportedIdentitiesSubTypes}` | `creditCards.${SupportedCreditCardSubTypes}` | `credentials.${SupportedCredentialsSubTypes}` | 'unknown'} SupportedTypes */ -const supportedTypes = [...supportedIdentitiesSubtypes.map(type => `identities.${type}`), ...supportedCreditCardSubtypes.map(type => `creditCards.${type}`), ...supportedCredentialsSubtypes.map(type => `credentials.${type}`)]; - -/** - * Retrieves the subtype - * @param {SupportedTypes | string} type - * @returns {SupportedSubTypes | 'unknown'} - */ -function getSubtypeFromType(type) { - const subType = type?.split('.')[1]; - const validType = isValidSubtype(subType); - return validType ? subType : 'unknown'; -} - -/** - * Retrieves the variant - * @param {SupportedTypes | string} type - * @returns {SupportedVariants | ''} - */ -function getVariantFromType(type) { - const variant = type?.split('.')[2]; - const validVariant = isValidVariant(variant); - return validVariant ? variant : ''; -} - -/** - * @param {SupportedSubTypes | any} supportedSubType - * @returns {supportedSubType is SupportedSubTypes} - */ -function isValidSubtype(supportedSubType) { - return isValidIdentitiesSubtype(supportedSubType) || isValidCreditCardSubtype(supportedSubType) || isValidCredentialsSubtype(supportedSubType); -} - -/** - * @param {SupportedTypes | any} supportedType - * @returns {supportedType is SupportedTypes} - */ -function isValidSupportedType(supportedType) { - return supportedTypes.includes(supportedType); -} - -/** - * @param {SupportedVariants | any} supportedVariant - * @returns {supportedVariant is SupportedVariants} - */ -function isValidVariant(supportedVariant) { - return supportedVariants.includes(supportedVariant); -} - -/** - * Retrieves the input subtype - * @param {HTMLInputElement|Element} input - * @returns {SupportedSubTypes | 'unknown'} - */ -function getInputSubtype(input) { - const type = getInputType(input); - return getSubtypeFromType(type); -} - -/** - * Retrieves the input variant - * @param {HTMLInputElement|Element} input - * @returns {SupportedVariants | ''} - */ -function getInputVariant(input) { - const type = getInputType(input); - return getVariantFromType(type); -} - -/** - * Remove whitespace of more than 2 in a row and trim the string - * @param {string | null} string - * @return {string} - */ -const removeExcessWhitespace = function () { - let string = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; - string = string?.trim() || ''; - // The length check is extra safety to avoid trimming strings that would be discarded anyway - if (!string || string.length > TEXT_LENGTH_CUTOFF + 50) return ''; - return string.replace(/\n/g, ' ').replace(/\s{2,}/g, ' '); -}; - -/** - * Get text from all explicit labels - * @param {HTMLInputElement|HTMLSelectElement} el - * @return {string} - */ -exports.removeExcessWhitespace = removeExcessWhitespace; -const getExplicitLabelsText = el => { - const labelTextCandidates = []; - for (let label of el.labels || []) { - labelTextCandidates.push(...(0, _labelUtil.extractElementStrings)(label)); - } - if (el.hasAttribute('aria-label')) { - labelTextCandidates.push(removeExcessWhitespace(el.getAttribute('aria-label'))); - } - - // Try to access another element if it was marked as the label for this input/select - const ariaLabelAttr = removeExcessWhitespace(el.getAttribute('aria-labelled') || el.getAttribute('aria-labelledby')); - if (ariaLabelAttr) { - const labelledByElement = document.getElementById(ariaLabelAttr); - if (labelledByElement) { - labelTextCandidates.push(...(0, _labelUtil.extractElementStrings)(labelledByElement)); + async refreshData() { + await this.inContextSignup?.refreshData(); + await this.settings.populateData(); + } + async setupSettingsPage({ shouldLog: shouldLog2 } = { shouldLog: false }) { + if (!this.globalConfig.isDDGDomain) { + return; + } + notifyWebApp({ isApp: this.globalConfig.isApp }); + if (this.isDeviceSignedIn()) { + let userData; + try { + userData = await this.getUserData(); + } catch (e) { + } + let capabilities; + try { + capabilities = await this.getEmailProtectionCapabilities(); + } catch (e) { + } + if (this.globalConfig.isDDGDomain) { + window.addEventListener("message", (e) => { + if (e.data.removeUserData) { + this.removeUserData(); + } + if (e.data.closeEmailProtection) { + this.closeEmailProtection(); + } + }); + } + const hasUserData = userData && !userData.error && Object.entries(userData).length > 0; + notifyWebApp({ + deviceSignedIn: { + value: true, + shouldLog: shouldLog2, + userData: hasUserData ? userData : void 0, + capabilities + } + }); + } else { + this.trySigningIn(); + } } - } - - // Labels with long text are likely to be noisy and lead to false positives - const filteredLabels = labelTextCandidates.filter(string => string.length < 65); - if (filteredLabels.length > 0) { - return filteredLabels.join(' '); - } - return ''; -}; - -/** - * Tries to get a relevant previous Element sibling, excluding certain tags - * @param {Element} el - * @returns {Element|null} - */ -exports.getExplicitLabelsText = getExplicitLabelsText; -const recursiveGetPreviousElSibling = el => { - const previousEl = el.previousElementSibling; - if (!previousEl) return null; - - // Skip elements with no childNodes - if (_labelUtil.EXCLUDED_TAGS.includes(previousEl.tagName)) { - return recursiveGetPreviousElSibling(previousEl); - } - return previousEl; -}; - -/** - * Get all text close to the input (useful when no labels are defined) - * @param {HTMLInputElement|HTMLSelectElement} el - * @param {HTMLElement} form - * @param {string} cssSelector - * @return {string} - */ -const getRelatedText = (el, form, cssSelector) => { - let scope = getLargestMeaningfulContainer(el, form, cssSelector); - - // If we didn't find a container, try looking for an adjacent label - if (scope === el) { - let previousEl = recursiveGetPreviousElSibling(el); - if (previousEl instanceof HTMLElement) { - scope = previousEl; + async setupAutofill() { } - // If there is still no meaningful container return empty string - if (scope === el || scope instanceof HTMLSelectElement) { - if (el.previousSibling instanceof Text) { - return removeExcessWhitespace(el.previousSibling.textContent); - } - return ''; + /** @returns {Promise} */ + async getAddresses() { + throw new Error("unimplemented"); } - } - - // If there is still no meaningful container return empty string - if (scope === el || scope instanceof HTMLSelectElement) { - if (el.previousSibling instanceof Text) { - return removeExcessWhitespace(el.previousSibling.textContent); + /** @returns {Promise>} */ + getUserData() { + return Promise.resolve(null); } - return ''; - } - let trimmedText = ''; - const label = scope.querySelector('label'); - if (label) { - // Try searching for a label first - trimmedText = (0, _autofillUtils.getTextShallow)(label); - } else { - // If the container has a select element, remove its contents to avoid noise - trimmedText = (0, _labelUtil.extractElementStrings)(scope).join(' '); - } - - // If the text is longer than n chars it's too noisy and likely to yield false positives, so return '' - if (trimmedText.length < TEXT_LENGTH_CUTOFF) return trimmedText; - return ''; -}; - -/** - * Find a container for the input field that won't contain other inputs (useful to get elements related to the field) - * @param {HTMLElement} el - * @param {HTMLElement} form - * @param {string} cssSelector - * @return {HTMLElement} - */ -exports.getRelatedText = getRelatedText; -const getLargestMeaningfulContainer = (el, form, cssSelector) => { - /* TODO: there could be more than one select el for the same label, in that case we should - change how we compute the container */ - const parentElement = el.parentElement; - if (!parentElement || el === form || !cssSelector) return el; - const inputsInParentsScope = parentElement.querySelectorAll(cssSelector); - // To avoid noise, ensure that our input is the only in scope - if (inputsInParentsScope.length === 1) { - return getLargestMeaningfulContainer(parentElement, form, cssSelector); - } - return el; -}; - -/** - * Find a regex match for a given input - * @param {HTMLInputElement} input - * @param {RegExp} regex - * @param {HTMLElement} form - * @param {string} cssSelector - * @returns {RegExpMatchArray|null} - */ -const matchInPlaceholderAndLabels = (input, regex, form, cssSelector) => { - return input.placeholder?.match(regex) || getExplicitLabelsText(input).match(regex) || getRelatedText(input, form, cssSelector).match(regex); -}; - -/** - * Check if a given input matches a regex - * @param {HTMLInputElement} input - * @param {RegExp} regex - * @param {HTMLElement} form - * @param {string} cssSelector - * @returns {boolean} - */ -exports.matchInPlaceholderAndLabels = matchInPlaceholderAndLabels; -const checkPlaceholderAndLabels = (input, regex, form, cssSelector) => { - return !!matchInPlaceholderAndLabels(input, regex, form, cssSelector); -}; - -/** - * Factory for instances of Matching - * - * @return {Matching} - */ -exports.checkPlaceholderAndLabels = checkPlaceholderAndLabels; -function createMatching() { - return new Matching(_compiledMatchingConfig.matchingConfiguration); -} - -},{"../autofill-utils.js":52,"../constants.js":55,"./label-util.js":29,"./matching-config/__generated__/compiled-matching-config.js":31,"./matching-utils.js":32}],34:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.InContextSignup = void 0; -var _deviceApiCalls = require("./deviceApiCalls/__generated__/deviceApiCalls.js"); -var _autofillUtils = require("./autofill-utils.js"); -class InContextSignup { - /** - * @param {import("./DeviceInterface/InterfacePrototype").default} device - */ - constructor(device) { - this.device = device; - } - async init() { - await this.refreshData(); - this.addNativeAccessibleGlobalFunctions(); - } - addNativeAccessibleGlobalFunctions() { - if (!this.device.globalConfig.hasModernWebkitAPI) return; - try { - // Set up a function which can be called from the native layer after completed sign-up or sign-in. - Object.defineProperty(window, 'openAutofillAfterClosingEmailProtectionTab', { - enumerable: false, - configurable: false, - writable: false, - value: () => { - this.openAutofillTooltip(); + /** @returns {void} */ + removeUserData() { + } + /** @returns {void} */ + closeEmailProtection() { + } + /** @returns {Promise>} */ + getEmailProtectionCapabilities() { + throw new Error("unimplemented"); + } + refreshAlias() { + } + async trySigningIn() { + if (this.globalConfig.isDDGDomain) { + if (this.attempts < 10) { + this.attempts++; + const data = await sendAndWaitForAnswer(SIGN_IN_MSG, "addUserData"); + this.storeUserData(data); + await this.setupAutofill(); + await this.settings.refresh(); + await this.setupSettingsPage({ shouldLog: true }); + await this.postInit(); + } else { + console.warn("max attempts reached, bailing"); } - }); - } catch (e) { - // Ignore if function can't be set up, it's a UX enhancement not a critical flow + } } - } - async refreshData() { - const incontextSignupDismissedAt = await this.device.deviceApi.request(new _deviceApiCalls.GetIncontextSignupDismissedAtCall(null)); - this.permanentlyDismissedAt = incontextSignupDismissedAt.permanentlyDismissedAt; - this.isInstalledRecently = incontextSignupDismissedAt.isInstalledRecently; - } - async openAutofillTooltip() { - // Make sure we're working with the latest data - await this.device.refreshData(); - - // Make sure the tooltip is closed before we try to open it - await this.device.uiController?.removeTooltip('stateChange'); - - // Make sure the input doesn't have focus so we can focus on it again - const activeInput = this.device.activeForm?.activeInput; - activeInput?.blur(); - - // Select the active input to open the tooltip - const selectActiveInput = () => { - activeInput?.focus(); - }; - if (document.hasFocus()) { - selectActiveInput(); - } else { - document.addEventListener('visibilitychange', () => { - selectActiveInput(); - }, { - once: true - }); + storeUserData(_data7) { } - } - isPermanentlyDismissed() { - return Boolean(this.permanentlyDismissedAt); - } - isOnValidDomain() { - // Only show in-context signup if we've high confidence that the page is - // not internally hosted or an intranet - return (0, _autofillUtils.isValidTLD)() && !(0, _autofillUtils.isLocalNetwork)(); - } - isAllowedByDevice() { - if (typeof this.isInstalledRecently === 'boolean') { - return this.isInstalledRecently; - } else { - // Don't restrict in-context signup based on recent installation - // if the device hasn't provided a clear indication - return true; + addDeviceListeners() { } - } - - /** - * @param {import('./Form/matching.js').SupportedSubTypes | "unknown"} [inputType] - * @returns {boolean} - */ - isAvailable() { - let inputType = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'emailAddress'; - const isEmailInput = inputType === 'emailAddress'; - const isEmailProtectionEnabled = !!this.device.settings?.featureToggles.emailProtection; - const isIncontextSignupEnabled = !!this.device.settings?.featureToggles.emailProtection_incontext_signup; - const isNotAlreadyLoggedIn = !this.device.isDeviceSignedIn(); - const isNotDismissed = !this.isPermanentlyDismissed(); - const isOnExpectedPage = this.device.globalConfig.isTopFrame || this.isOnValidDomain(); - const isAllowedByDevice = this.isAllowedByDevice(); - return isEmailInput && isEmailProtectionEnabled && isIncontextSignupEnabled && isNotAlreadyLoggedIn && isNotDismissed && isOnExpectedPage && isAllowedByDevice; - } - onIncontextSignup() { - this.device.deviceApi.notify(new _deviceApiCalls.StartEmailProtectionSignupCall({})); - this.device.firePixel({ - pixelName: 'incontext_primary_cta' - }); - } - onIncontextSignupDismissed() { - let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { - shouldHideTooltip: true - }; - if (options.shouldHideTooltip) { - this.device.removeAutofillUIFromPage('Email Protection in-context signup dismissed.'); - this.device.deviceApi.notify(new _deviceApiCalls.CloseAutofillParentCall(null)); - } - this.permanentlyDismissedAt = new Date().getTime(); - this.device.deviceApi.notify(new _deviceApiCalls.SetIncontextSignupPermanentlyDismissedAtCall({ - value: this.permanentlyDismissedAt - })); - this.device.firePixel({ - pixelName: 'incontext_dismiss_persisted' - }); - } - - // In-context signup can be closed when displayed as a stand-alone tooltip, e.g. extension - onIncontextSignupClosed() { - this.device.activeForm?.dismissTooltip(); - this.device.firePixel({ - pixelName: 'incontext_close_x' - }); - } -} -exports.InContextSignup = InContextSignup; - -},{"./autofill-utils.js":52,"./deviceApiCalls/__generated__/deviceApiCalls.js":56}],35:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.PROVIDER_LOCKED = exports.AUTOGENERATED_KEY = void 0; -exports.appendGeneratedKey = appendGeneratedKey; -exports.createCredentialsTooltipItem = createCredentialsTooltipItem; -exports.fromPassword = fromPassword; -var _autofillUtils = require("../autofill-utils.js"); -const AUTOGENERATED_KEY = exports.AUTOGENERATED_KEY = 'autogenerated'; -const PROVIDER_LOCKED = exports.PROVIDER_LOCKED = 'provider_locked'; - -/** - * @implements {TooltipItemRenderer} - */ -class CredentialsTooltipItem { - /** @type {CredentialsObject} */ - #data; - /** @param {CredentialsObject} data */ - constructor(data) { - this.#data = data; - } - id = () => String(this.#data.id); - labelMedium = _subtype => { - if (this.#data.username) { - return this.#data.username; + /** @param {() => void} _fn */ + addLogoutListener(_fn) { } - if (this.#data.origin?.url) { - return `Password for ${(0, _autofillUtils.truncateFromMiddle)(this.#data.origin.url)}`; + isDeviceSignedIn() { + return false; } - return ''; - }; - labelSmall = _subtype => { - if (this.#data.origin?.url) { - return (0, _autofillUtils.truncateFromMiddle)(this.#data.origin.url); + /** + * @returns {Promise} + */ + async getAlias() { + return void 0; } - return '•••••••••••••••'; - }; - credentialsProvider = () => this.#data.credentialsProvider; -} - -/** - * @implements {TooltipItemRenderer} - */ -class AutoGeneratedCredential { - /** @type {CredentialsObject} */ - #data; - /** @param {CredentialsObject} data */ - constructor(data) { - this.#data = data; - } - id = () => String(this.#data.id); - label = _subtype => this.#data.password; - labelMedium = _subtype => 'Generated password'; - labelSmall = _subtype => 'Password will be saved for this website'; -} - -/** - * Generate a stand-in 'CredentialsObject' from a - * given (generated) password. - * - * @param {string} password - * @param {string} username - * @returns {CredentialsObject} - */ -function fromPassword(password, username) { - return { - [AUTOGENERATED_KEY]: true, - password, - username - }; -} - -/** - * @implements TooltipItemRenderer - */ -class ProviderLockedItem { - /** @type {CredentialsObject} */ - #data; - /** @param {CredentialsObject} data */ - constructor(data) { - this.#data = data; - } - id = () => String(this.#data.id); - labelMedium = _subtype => 'Bitwarden is locked'; - labelSmall = _subtype => 'Unlock your vault to access credentials or generate passwords'; - credentialsProvider = () => this.#data.credentialsProvider; -} - -/** - * If the locally generated/stored password or username ends up being the same - * as submitted in a subsequent form submission - then we mark the - * credentials as 'autogenerated' so that the native layer can decide - * how to process it - * - * @param {DataStorageObject} data - * @param {object} [autofilledFields] - * @param {string|null|undefined} [autofilledFields.username] - if present, it's the last username generated by something like email Protection - * @param {string|null|undefined} [autofilledFields.password] - if present, it's the last generated password - * - */ -function appendGeneratedKey(data) { - let autofilledFields = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - let autogenerated = false; - - // does the current password match the most recently generated one? - if (autofilledFields.password && data.credentials?.password === autofilledFields.password) { - autogenerated = true; - } - - // does the current username match a recently generated one? (eg: email protection) - if (autofilledFields.username && data.credentials?.username === autofilledFields.username) { - autogenerated = true; - } - - // if neither username nor password were generated, don't alter the outgoing data - if (!autogenerated) return data; - - // if we get here, we're confident that something was generated + filled - // so we mark the credential as 'autogenerated' for the benefit of native implementations - return { - ...data, - credentials: { - ...data.credentials, - [AUTOGENERATED_KEY]: true + // PM endpoints + getAccounts() { } - }; -} - -/** - * Factory for creating a TooltipItemRenderer - * - * @param {CredentialsObject} data - * @returns {TooltipItemRenderer} - */ -function createCredentialsTooltipItem(data) { - if (data.id === PROVIDER_LOCKED) { - return new ProviderLockedItem(data); - } - if (AUTOGENERATED_KEY in data && data.password) { - return new AutoGeneratedCredential(data); - } - return new CredentialsTooltipItem(data); -} - -},{"../autofill-utils.js":52}],36:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.CreditCardTooltipItem = void 0; -/** - * @implements {TooltipItemRenderer} - */ -class CreditCardTooltipItem { - /** @type {CreditCardObject} */ - #data; - /** @param {CreditCardObject} data */ - constructor(data) { - this.#data = data; - } - id = () => String(this.#data.id); - labelMedium = _ => this.#data.title; - labelSmall = _ => this.#data.displayNumber; -} -exports.CreditCardTooltipItem = CreditCardTooltipItem; - -},{}],37:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.IdentityTooltipItem = void 0; -var _formatters = require("../Form/formatters.js"); -/** - * @implements {TooltipItemRenderer} - */ -class IdentityTooltipItem { - /** @type {IdentityObject} */ - #data; - /** @param {IdentityObject} data */ - constructor(data) { - this.#data = data; - } - id = () => String(this.#data.id); - labelMedium = subtype => { - if (subtype === 'addressCountryCode') { - return (0, _formatters.getCountryDisplayName)('en', this.#data.addressCountryCode || ''); + /** + * Gets credentials ready for autofill + * @param {CredentialsObject['id']} id - the credential id + * @returns {Promise} + */ + async getAutofillCredentials(id) { + return this.deviceApi.request(new GetAutofillCredentialsCall({ id: String(id) })); } - if (this.#data.id === 'privateAddress') { - return 'Generate Private Duck Address'; + /** @returns {APIResponse} */ + async getAutofillCreditCard(_id) { + throw new Error("getAutofillCreditCard unimplemented"); } - return this.#data[subtype]; - }; - label(subtype) { - if (this.#data.id === 'privateAddress') { - return this.#data[subtype]; + /** @returns {Promise<{success: IdentityObject|undefined}>} */ + async getAutofillIdentity(_id) { + throw new Error("getAutofillIdentity unimplemented"); } - return null; - } - labelSmall = _ => { - return this.#data.title; - }; -} -exports.IdentityTooltipItem = IdentityTooltipItem; - -},{"../Form/formatters.js":26}],38:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.PasswordGenerator = void 0; -var _index = require("../packages/password/index.js"); -var _rules = _interopRequireDefault(require("../packages/password/rules.json")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -/** - * Create a password once and reuse it. - */ -class PasswordGenerator { - /** @type {string|null} */ - #previous = null; - - /** @returns {boolean} */ - get generated() { - return this.#previous !== null; - } - - /** @returns {string|null} */ - get password() { - return this.#previous; - } - - /** @param {import('../packages/password').GenerateOptions} [params] */ - generate() { - let params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - if (this.#previous) { - return this.#previous; - } - this.#previous = (0, _index.generate)({ - ...params, - rules: _rules.default - }); - return this.#previous; - } -} -exports.PasswordGenerator = PasswordGenerator; - -},{"../packages/password/index.js":7,"../packages/password/rules.json":11}],39:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.createScanner = createScanner; -var _Form = require("./Form/Form.js"); -var _constants = require("./constants.js"); -var _matching = require("./Form/matching.js"); -var _autofillUtils = require("./autofill-utils.js"); -var _deviceApiCalls = require("./deviceApiCalls/__generated__/deviceApiCalls.js"); -const { - MAX_INPUTS_PER_PAGE, - MAX_FORMS_PER_PAGE, - MAX_INPUTS_PER_FORM, - ATTR_INPUT_TYPE -} = _constants.constants; - -/** - * @typedef {{ - * forms: Map; - * init(): (reason, ...rest)=> void; - * enqueue(elements: (HTMLElement|Document)[]): void; - * findEligibleInputs(context): Scanner; - * matching: import("./Form/matching").Matching; - * options: ScannerOptions; - * stopScanner: (reason: string, ...rest: any) => void; - * }} Scanner - * - * @typedef {{ - * initialDelay: number, - * bufferSize: number, - * debounceTimePeriod: number, - * maxInputsPerPage: number, - * maxFormsPerPage: number, - * maxInputsPerForm: number - * }} ScannerOptions - */ - -/** @type {ScannerOptions} */ -const defaultScannerOptions = { - // This buffer size is very large because it's an unexpected edge-case that - // a DOM will be continually modified over and over without ever stopping. If we do see 1000 unique - // new elements in the buffer however then this will prevent the algorithm from never ending. - bufferSize: 50, - // wait for a 500ms window of event silence before performing the scan - debounceTimePeriod: 500, - // how long to wait when performing the initial scan - initialDelay: 0, - // How many inputs is too many on the page. If we detect that there's above - // this maximum, then we don't scan the page. This will prevent slowdowns on - // large pages which are unlikely to require autofill anyway. - maxInputsPerPage: MAX_INPUTS_PER_PAGE, - maxFormsPerPage: MAX_FORMS_PER_PAGE, - maxInputsPerForm: MAX_INPUTS_PER_FORM -}; - -/** - * This allows: - * 1) synchronous DOM scanning + mutations - via `createScanner(device).findEligibleInputs(document)` - * 2) or, as above + a debounced mutation observer to re-run the scan after the given time - */ -class DefaultScanner { - /** @type Map */ - forms = new Map(); - /** @type {any|undefined} the timer to reset */ - debounceTimer; - /** @type {Set} stored changed elements until they can be processed */ - changedElements = new Set(); - /** @type {ScannerOptions} */ - options; - /** @type {HTMLInputElement | null} */ - activeInput = null; - /** @type {boolean} A flag to indicate the whole page will be re-scanned */ - rescanAll = false; - /** @type {boolean} Indicates whether we called stopScanning */ - stopped = false; - /** @type {import("./Form/matching").Matching} matching */ - matching; - - /** - * @param {import("./DeviceInterface/InterfacePrototype").default} device - * @param {ScannerOptions} options - */ - constructor(device, options) { - this.device = device; - this.matching = (0, _matching.createMatching)(); - this.options = options; - /** @type {number} A timestamp of the */ - this.initTimeStamp = Date.now(); - } - - /** - * Determine whether we should fire the credentials autoprompt. This is needed because some sites are blank - * on page load and load scripts asynchronously, so our initial scan didn't set the autoprompt correctly - * @returns {boolean} - */ - get shouldAutoprompt() { - return Date.now() - this.initTimeStamp <= 1500; - } - - /** - * Call this to scan once and then watch for changes. - * - * Call the returned function to remove listeners. - * @returns {(reason: string, ...rest) => void} - */ - init() { - var _this = this; - if (this.device.globalConfig.isExtension) { - this.device.deviceApi.notify(new _deviceApiCalls.AddDebugFlagCall({ - flag: 'autofill' - })); + openManagePasswords() { } - - // Add the shadow DOM listener. Handlers in handleEvent - window.addEventListener('pointerdown', this, true); - // We don't listen for focus events on mobile, they can cause keyboard flashing - if (!this.device.globalConfig.isMobileApp) { - window.addEventListener('focus', this, true); - } - const delay = this.options.initialDelay; - // if the delay is zero, (chrome/firefox etc) then use `requestIdleCallback` - if (delay === 0) { - window.requestIdleCallback(() => this.scanAndObserve()); - } else { - // otherwise, use the delay time to defer the initial scan - setTimeout(() => this.scanAndObserve(), delay); + openManageCreditCards() { } - return function (reason) { - for (var _len = arguments.length, rest = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - rest[_key - 1] = arguments[_key]; - } - _this.stopScanner(reason, ...rest); - }; - } - - /** - * Scan the page and begin observing changes - */ - scanAndObserve() { - window.performance?.mark?.('initial_scanner:init:start'); - this.findEligibleInputs(document); - window.performance?.mark?.('initial_scanner:init:end'); - (0, _autofillUtils.logPerformance)('initial_scanner'); - this.mutObs.observe(document.documentElement, { - childList: true, - subtree: true - }); - } - - /** - * @param context - */ - findEligibleInputs(context) { - // Avoid autofill on Email Protection web app - if (this.device.globalConfig.isDDGDomain) { - return this; + openManageIdentities() { } - if ('matches' in context && context.matches?.(this.matching.cssSelector('formInputsSelector'))) { - this.addInput(context); - } else { - const inputs = context.querySelectorAll(this.matching.cssSelector('formInputsSelector')); - if (inputs.length > this.options.maxInputsPerPage) { - this.stopScanner(`Too many input fields in the given context (${inputs.length}), stop scanning`, context); - return this; - } - inputs.forEach(input => this.addInput(input)); + /** + * @param {StoreFormData} values + * @param {StoreFormData['trigger']} trigger + */ + storeFormData(values, trigger) { + this.deviceApi.notify(new StoreFormDataCall({ ...values, trigger })); } - return this; - } - - /** - * Stops scanning, switches off the mutation observer and clears all forms - * @param {string} reason - * @param {any} rest - */ - stopScanner(reason) { - this.stopped = true; - if ((0, _autofillUtils.shouldLog)()) { - for (var _len2 = arguments.length, rest = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { - rest[_key2 - 1] = arguments[_key2]; + /** + * `preAttachTooltip` happens just before a tooltip is show - features may want to append some data + * at this point. + * + * For example, if password generation is enabled, this will generate + * a password and send it to the tooltip as though it were a stored credential. + * + * @param {TopContextData} topContextData + * @param {HTMLInputElement} input + * @param {import("../Form/Form").Form} form + */ + preAttachTooltip(topContextData, input, form) { + const checks = [ + topContextData.inputType === "credentials.password.new", + this.settings.featureToggles.password_generation + ]; + if (checks.every(Boolean)) { + const password = this.passwordGenerator.generate({ + input: input.getAttribute("passwordrules"), + domain: window.location.hostname + }); + const rawValues = form.getRawValues(); + const username = rawValues.credentials?.username || rawValues.identities?.emailAddress || ""; + topContextData.credentials = [fromPassword(password, username)]; } - console.log(reason, ...rest); + return topContextData; } - const activeInput = this.device.activeForm?.activeInput; - - // remove Dax, listeners, timers, and observers - clearTimeout(this.debounceTimer); - this.changedElements.clear(); - this.mutObs.disconnect(); - window.removeEventListener('pointerdown', this, true); - window.removeEventListener('focus', this, true); - this.forms.forEach(form => { - form.destroy(); - }); - this.forms.clear(); - - // Bring the user back to the input they were interacting with - activeInput?.focus(); - } - - /** - * @param {HTMLElement|HTMLInputElement|HTMLSelectElement} input - * @returns {HTMLFormElement|HTMLElement} - */ - getParentForm(input) { - if (input instanceof HTMLInputElement || input instanceof HTMLSelectElement) { - if (input.form) { - // Use input.form unless it encloses most of the DOM - // In that case we proceed to identify more precise wrappers - if (this.forms.has(input.form) || - // If we've added the form we've already checked that it's not a page wrapper - !(0, _autofillUtils.isFormLikelyToBeUsedAsPageWrapper)(input.form)) { - return input.form; + /** + * `postAutofill` gives features an opportunity to perform an action directly + * following an autofill. + * + * For example, if a generated password was used, we want to fire a save event. + * + * @param {IdentityObject|CreditCardObject|CredentialsObject} data + * @param {SupportedMainTypes} dataType + * @param {import("../Form/Form").Form} formObj + */ + postAutofill(data, dataType, formObj) { + if (AUTOGENERATED_KEY in data && "password" in data && // Don't send message on Android to avoid potential abuse. Data is saved on native confirmation instead. + !this.globalConfig.isAndroid) { + const formValues = formObj.getValuesReadyForStorage(); + if (formValues.credentials?.password === data.password) { + const formData = appendGeneratedKey(formValues, { password: data.password }); + this.storeFormData(formData, "passwordGeneration"); } } + if (dataType === "credentials" && formObj.shouldAutoSubmit) { + formObj.attemptSubmissionIfNeeded(); + } } - /** - * Max number of nodes we want to traverse upwards, critical to avoid enclosing large portions of the DOM - * @type {number} - */ - let traversalLayerCount = 0; - let element = input; - // traverse the DOM to search for related inputs - while (traversalLayerCount <= 5 && element.parentElement && element.parentElement !== document.documentElement) { - // Avoid overlapping containers or forms - const siblingForm = element.parentElement?.querySelector('form'); - if (siblingForm && siblingForm !== element) { - return element; - } - element = element.parentElement; - const inputs = element.querySelectorAll(this.matching.cssSelector('formInputsSelector')); - const buttons = element.querySelectorAll(this.matching.cssSelector('submitButtonSelector')); - // If we find a button or another input, we assume that's our form - if (inputs.length > 1 || buttons.length) { - // found related input, return common ancestor - return element; - } - traversalLayerCount++; - } - return input; - } - - /** - * @param {HTMLInputElement|HTMLSelectElement} input - */ - addInput(input) { - if (this.stopped) return; - const parentForm = this.getParentForm(input); - if (parentForm instanceof HTMLFormElement && this.forms.has(parentForm)) { - const foundForm = this.forms.get(parentForm); - // We've met the form, add the input provided it's below the max input limit - if (foundForm && foundForm.inputs.all.size < MAX_INPUTS_PER_FORM) { - foundForm.addInput(input); - } else { - this.stopScanner('The form has too many inputs, destroying.'); + * `postSubmit` gives features a one-time-only opportunity to perform an + * action directly after a form submission was observed. + * + * Mostly this is about storing data from the form submission, but it can + * also be used like in the case of Password generation, to append additional + * data before it's sent to be saved. + * + * @param {DataStorageObject} values + * @param {import("../Form/Form").Form} form + */ + postSubmit(values, form) { + if (!form.form) + return; + if (!form.hasValues(values)) + return; + const checks = [ + form.shouldPromptToStoreData && !form.submitHandlerExecuted, + this.passwordGenerator.generated + ]; + if (checks.some(Boolean)) { + const formData = appendGeneratedKey(values, { + password: this.passwordGenerator.password, + username: this.emailProtection.lastGenerated + }); + this.storeFormData(formData, "formSubmission"); } - return; } + /** + * Sends a pixel to be fired on the client side + * @param {import('../deviceApiCalls/__generated__/validators-ts').SendJSPixelParams} pixelParams + */ + firePixel(pixelParams) { + this.deviceApi.notify(new SendJSPixelCall(pixelParams)); + } + /** + * This serves as a single place to create a default instance + * of InterfacePrototype that can be useful in testing scenarios + * @param {Partial} [globalConfigOverrides] + * @returns {InterfacePrototype} + */ + static default(globalConfigOverrides) { + const globalConfig = createGlobalConfig(globalConfigOverrides); + const transport = createTransport(globalConfig); + const deviceApi = new DeviceApi(transport); + const settings = Settings.default(globalConfig, deviceApi); + return new _InterfacePrototype(globalConfig, deviceApi, settings); + } + }; + var InterfacePrototype = _InterfacePrototype; + _addresses = new WeakMap(); + _data6 = new WeakMap(); + var InterfacePrototype_default = InterfacePrototype; - // Do not add explicitly search forms - if (parentForm.role === 'search') return; - - // Check if the forms we've seen are either disconnected, - // or are parent/child of the currently-found form - let previouslyFoundParent, childForm; - for (const [formEl] of this.forms) { - // Remove disconnected forms to avoid leaks - if (!formEl.isConnected) { - this.forms.delete(formEl); - continue; - } - if (formEl.contains(parentForm)) { - previouslyFoundParent = formEl; - break; - } - if (parentForm.contains(formEl)) { - childForm = formEl; - break; + // src/InContextSignup.js + var InContextSignup = class { + /** + * @param {import("./DeviceInterface/InterfacePrototype").default} device + */ + constructor(device) { + this.device = device; + } + async init() { + await this.refreshData(); + this.addNativeAccessibleGlobalFunctions(); + } + addNativeAccessibleGlobalFunctions() { + if (!this.device.globalConfig.hasModernWebkitAPI) + return; + try { + Object.defineProperty(window, "openAutofillAfterClosingEmailProtectionTab", { + enumerable: false, + configurable: false, + writable: false, + value: () => { + this.openAutofillTooltip(); + } + }); + } catch (e) { } } - if (previouslyFoundParent) { - if (parentForm instanceof HTMLFormElement && parentForm !== previouslyFoundParent) { - // If we had a prior parent but this is an explicit form, the previous was a false positive - this.forms.delete(previouslyFoundParent); + async refreshData() { + const incontextSignupDismissedAt = await this.device.deviceApi.request(new GetIncontextSignupDismissedAtCall(null)); + this.permanentlyDismissedAt = incontextSignupDismissedAt.permanentlyDismissedAt; + this.isInstalledRecently = incontextSignupDismissedAt.isInstalledRecently; + } + async openAutofillTooltip() { + await this.device.refreshData(); + await this.device.uiController?.removeTooltip("stateChange"); + const activeInput = this.device.activeForm?.activeInput; + activeInput?.blur(); + const selectActiveInput = () => { + activeInput?.focus(); + }; + if (document.hasFocus()) { + selectActiveInput(); } else { - // If we've already met the form or a descendant, add the input - this.forms.get(previouslyFoundParent)?.addInput(input); + document.addEventListener("visibilitychange", () => { + selectActiveInput(); + }, { once: true }); } - } else { - // if this form is an ancestor of an existing form, remove that before adding this - if (childForm) { - this.forms.get(childForm)?.destroy(); - this.forms.delete(childForm); - } - - // Only add the form if below the limit of forms per page - if (this.forms.size < this.options.maxFormsPerPage) { - this.forms.set(parentForm, new _Form.Form(parentForm, input, this.device, this.matching, this.shouldAutoprompt)); + } + isPermanentlyDismissed() { + return Boolean(this.permanentlyDismissedAt); + } + isOnValidDomain() { + return isValidTLD() && !isLocalNetwork(); + } + isAllowedByDevice() { + if (typeof this.isInstalledRecently === "boolean") { + return this.isInstalledRecently; } else { - this.stopScanner('The page has too many forms, stop adding them.'); + return true; } } - } - - /** - * enqueue elements to be re-scanned after the given - * amount of time has elapsed. - * - * @param {(HTMLElement|Document)[]} htmlElements - */ - enqueue(htmlElements) { - // if the buffer limit is reached, stop trying to track elements and process body instead. - if (this.changedElements.size >= this.options.bufferSize) { - this.rescanAll = true; - this.changedElements.clear(); - } else if (!this.rescanAll) { - // otherwise keep adding each element to the queue - for (let element of htmlElements) { - this.changedElements.add(element); - } + /** + * @param {import('./Form/matching.js').SupportedSubTypes | "unknown"} [inputType] + * @returns {boolean} + */ + isAvailable(inputType = "emailAddress") { + const isEmailInput = inputType === "emailAddress"; + const isEmailProtectionEnabled = !!this.device.settings?.featureToggles.emailProtection; + const isIncontextSignupEnabled = !!this.device.settings?.featureToggles.emailProtection_incontext_signup; + const isNotAlreadyLoggedIn = !this.device.isDeviceSignedIn(); + const isNotDismissed = !this.isPermanentlyDismissed(); + const isOnExpectedPage = this.device.globalConfig.isTopFrame || this.isOnValidDomain(); + const isAllowedByDevice = this.isAllowedByDevice(); + return isEmailInput && isEmailProtectionEnabled && isIncontextSignupEnabled && isNotAlreadyLoggedIn && isNotDismissed && isOnExpectedPage && isAllowedByDevice; + } + onIncontextSignup() { + this.device.deviceApi.notify(new StartEmailProtectionSignupCall({})); + this.device.firePixel({ pixelName: "incontext_primary_cta" }); + } + onIncontextSignupDismissed(options = { shouldHideTooltip: true }) { + if (options.shouldHideTooltip) { + this.device.removeAutofillUIFromPage("Email Protection in-context signup dismissed."); + this.device.deviceApi.notify(new CloseAutofillParentCall(null)); + } + this.permanentlyDismissedAt = (/* @__PURE__ */ new Date()).getTime(); + this.device.deviceApi.notify(new SetIncontextSignupPermanentlyDismissedAtCall({ value: this.permanentlyDismissedAt })); + this.device.firePixel({ pixelName: "incontext_dismiss_persisted" }); + } + // In-context signup can be closed when displayed as a stand-alone tooltip, e.g. extension + onIncontextSignupClosed() { + this.device.activeForm?.dismissTooltip(); + this.device.firePixel({ pixelName: "incontext_close_x" }); } - clearTimeout(this.debounceTimer); - this.debounceTimer = setTimeout(() => { - window.performance?.mark?.('scanner:init:start'); - this.processChangedElements(); - this.changedElements.clear(); - this.rescanAll = false; - window.performance?.mark?.('scanner:init:end'); - (0, _autofillUtils.logPerformance)('scanner'); - }, this.options.debounceTimePeriod); - } + }; - /** - * re-scan the changed elements, but only if they - * are still present in the DOM - */ - processChangedElements() { - if (this.rescanAll) { - this.findEligibleInputs(document); - return; + // src/DeviceInterface/AndroidInterface.js + var AndroidInterface = class extends InterfacePrototype_default { + constructor() { + super(...arguments); + __publicField(this, "inContextSignup", new InContextSignup(this)); + } + /** + * @returns {Promise} + */ + async getAlias() { + const { alias } = await sendAndWaitForAnswer(async () => { + if (this.inContextSignup.isAvailable()) { + const { isSignedIn } = await this.deviceApi.request(new ShowInContextEmailProtectionSignupPromptCall(null)); + if (this.globalConfig.availableInputTypes) { + this.globalConfig.availableInputTypes.email = isSignedIn; + } + this.updateForStateChange(); + this.onFinishedAutofill(); + } + return window.EmailInterface.showTooltip(); + }, "getAliasResponse"); + return alias; } - for (let element of this.changedElements) { - if (element.isConnected) { - this.findEligibleInputs(element); + /** + * @override + */ + createUIController() { + return new NativeUIController(); + } + /** + * @deprecated use `this.settings.availableInputTypes.email` in the future + * @returns {boolean} + */ + isDeviceSignedIn() { + if (this.globalConfig.isDDGDomain) { + return window.EmailInterface.isSignedIn() === "true"; } + if (typeof this.globalConfig.availableInputTypes?.email === "boolean") { + return this.globalConfig.availableInputTypes.email; + } + return true; } - } - - /** - * Watch for changes in the DOM, and enqueue elements to be scanned - * @type {MutationObserver} - */ - mutObs = new MutationObserver(mutationList => { - /** @type {HTMLElement[]} */ - if (this.rescanAll) { - // quick version if buffer full - this.enqueue([]); - return; + async setupAutofill() { + await this.inContextSignup.init(); } - const outgoing = []; - for (const mutationRecord of mutationList) { - if (mutationRecord.type === 'childList') { - for (let addedNode of mutationRecord.addedNodes) { - if (!(addedNode instanceof HTMLElement)) continue; - if (addedNode.nodeName === 'DDG-AUTOFILL') continue; - outgoing.push(addedNode); + /** + * Used by the email web app + * Settings page displays data of the logged in user data + */ + getUserData() { + let userData = null; + try { + userData = JSON.parse(window.EmailInterface.getUserData()); + } catch (e) { + if (this.globalConfig.isDDGTestMode) { + console.error(e); } } + return Promise.resolve(userData); } - this.enqueue(outgoing); - }); - handleEvent(event) { - switch (event.type) { - case 'pointerdown': - case 'focus': - this.scanShadow(event); - break; + /** + * Used by the email web app + * Device capabilities determine which functionality is available to the user + */ + getEmailProtectionCapabilities() { + let deviceCapabilities = null; + try { + deviceCapabilities = JSON.parse(window.EmailInterface.getDeviceCapabilities()); + } catch (e) { + if (this.globalConfig.isDDGTestMode) { + console.error(e); + } + } + return Promise.resolve(deviceCapabilities); } - } - - /** - * Scan clicked input fields, even if they're within a shadow tree - * @param {FocusEvent | PointerEvent} event - */ - scanShadow(event) { - // If the scanner is stopped or there's no shadow root, just return - if (this.stopped || !(event.target instanceof Element) || !event.target?.shadowRoot) return; - window.performance?.mark?.('scan_shadow:init:start'); - const realTarget = (0, _autofillUtils.pierceShadowTree)(event, HTMLInputElement); - - // If it's an input we haven't already scanned, scan the whole shadow tree - if (realTarget instanceof HTMLInputElement && !realTarget.hasAttribute(ATTR_INPUT_TYPE)) { - this.findEligibleInputs(realTarget.getRootNode()); + storeUserData({ addUserData: { token, userName, cohort } }) { + return window.EmailInterface.storeCredentials(token, userName, cohort); } - window.performance?.mark?.('scan_shadow:init:end'); - (0, _autofillUtils.logPerformance)('scan_shadow'); - } -} - -/** - * @param {import("./DeviceInterface/InterfacePrototype").default} device - * @param {Partial} [scannerOptions] - * @returns {Scanner} - */ -function createScanner(device, scannerOptions) { - return new DefaultScanner(device, { - ...defaultScannerOptions, - ...scannerOptions - }); -} - -},{"./Form/Form.js":23,"./Form/matching.js":33,"./autofill-utils.js":52,"./constants.js":55,"./deviceApiCalls/__generated__/deviceApiCalls.js":56}],40:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.Settings = void 0; -var _index = require("../packages/device-api/index.js"); -var _deviceApiCalls = require("./deviceApiCalls/__generated__/deviceApiCalls.js"); -var _validatorsZod = require("./deviceApiCalls/__generated__/validators.zod.js"); -var _autofillUtils = require("./autofill-utils.js"); -/** - * Some Type helpers to prevent duplication - * @typedef {import("./deviceApiCalls/__generated__/validators-ts").AutofillFeatureToggles} AutofillFeatureToggles - * @typedef {import("./deviceApiCalls/__generated__/validators-ts").AvailableInputTypes} AvailableInputTypes - * @typedef {import("./deviceApiCalls/__generated__/validators-ts").RuntimeConfiguration} RuntimeConfiguration - * @typedef {import("../packages/device-api").DeviceApi} DeviceApi - */ - -/** - * The Settings class encapsulates the concept of 1) feature toggles + 2) available input types. - * - * 1) Feature toggles are boolean flags that can represent a device's capabilities. That may be user-toggled - * or not, we don't make that distinction. - * - * 2) Available Input Types are indicators to whether the given platform can provide data for a given type. - * For example, a user might have credentials saved for https://example.com, so when the page loads, but **before** - * we can decorate any fields, we determine this first. - */ -class Settings { - /** @type {GlobalConfig} */ - globalConfig; - /** @type {DeviceApi} */ - deviceApi; - /** @type {AutofillFeatureToggles | null} */ - _featureToggles = null; - /** @type {AvailableInputTypes | null} */ - _availableInputTypes = null; - /** @type {RuntimeConfiguration | null | undefined} */ - _runtimeConfiguration = null; - /** @type {boolean | null} */ - _enabled = null; - - /** - * @param {GlobalConfig} config - * @param {DeviceApi} deviceApi - */ - constructor(config, deviceApi) { - this.deviceApi = deviceApi; - this.globalConfig = config; - } - - /** - * Feature toggles are delivered as part of the Runtime Configuration - a flexible design that - * allows data per user + remote config to be accessed together. - * - * Once we access the Runtime Configuration, we then extract the autofill-specific settings via - * `runtimeConfig.userPreferences.features.autofill.settings` and validate that separately. - * - * The 2-step validation occurs because RuntimeConfiguration will be coming from a shared library - * and does not know about the shape of Autofill specific settings. - * - * @returns {Promise} - */ - async getFeatureToggles() { - try { - const runtimeConfig = await this._getRuntimeConfiguration(); - const autofillSettings = (0, _index.validate)(runtimeConfig.userPreferences?.features?.autofill?.settings, _validatorsZod.autofillSettingsSchema); - return autofillSettings.featureToggles; - } catch (e) { - // these are the fallbacks for when a platform hasn't implemented the calls above. - if (this.globalConfig.isDDGTestMode) { - console.log('isDDGTestMode: getFeatureToggles: ❌', e); + /** + * Used by the email web app + * Provides functionality to log the user out + */ + removeUserData() { + try { + return window.EmailInterface.removeCredentials(); + } catch (e) { + if (this.globalConfig.isDDGTestMode) { + console.error(e); + } } - return Settings.defaults.featureToggles; } - } - - /** - * If the platform in question is happy to derive it's 'enabled' state from the RuntimeConfiguration, - * then they should use this. Currently only Windows supports this, but we aim to move all platforms to - * support this going forward. - * @returns {Promise} - */ - async getEnabled() { - try { - const runtimeConfig = await this._getRuntimeConfiguration(); - const enabled = (0, _autofillUtils.autofillEnabled)(runtimeConfig); - return enabled; - } catch (e) { - // these are the fallbacks for when a platform hasn't implemented the calls above. (like on android) - if (this.globalConfig.isDDGTestMode) { - console.log('isDDGTestMode: getEnabled: ❌', e); - } - return null; + /** + * Used by the email web app + * Provides functionality to close the window after in-context sign-up or sign-in + */ + closeEmailProtection() { + this.deviceApi.request(new CloseEmailProtectionTabCall(null)); } - } - - /** - * Get runtime configuration, but only once. - * - * Some platforms may be reading this directly from inlined variables, whilst others - * may make a DeviceApiCall. - * - * Currently, it's only read once - but we should be open to the idea that we may need - * this to be called multiple times in the future. - * - * @returns {Promise} - * @throws - * @private - */ - async _getRuntimeConfiguration() { - if (this._runtimeConfiguration) return this._runtimeConfiguration; - const runtimeConfig = await this.deviceApi.request(new _deviceApiCalls.GetRuntimeConfigurationCall(null)); - this._runtimeConfiguration = runtimeConfig; - return this._runtimeConfiguration; - } - - /** - * Available Input Types are boolean indicators to represent which input types the - * current **user** has data available for. - * - * @returns {Promise} - */ - async getAvailableInputTypes() { - try { - // This info is not needed in the topFrame, so we avoid calling the native app - if (this.globalConfig.isTopFrame) { - return Settings.defaults.availableInputTypes; - } - return await this.deviceApi.request(new _deviceApiCalls.GetAvailableInputTypesCall(null)); - } catch (e) { - if (this.globalConfig.isDDGTestMode) { - console.log('isDDGTestMode: getAvailableInputTypes: ❌', e); - } - return Settings.defaults.availableInputTypes; + addLogoutListener(handler) { + if (!this.globalConfig.isDDGDomain) + return; + window.addEventListener("message", (e) => { + if (this.globalConfig.isDDGDomain && e.data.emailProtectionSignedOut) { + handler(); + } + }); } - } + /** Noop */ + firePixel(_pixelParam) { + } + }; - /** - * To 'refresh' settings means to re-call APIs to determine new state. This may - * only occur once per page, but it must be done before any page scanning/decorating can happen - * - * @returns {Promise<{ - * availableInputTypes: AvailableInputTypes, - * featureToggles: AutofillFeatureToggles, - * enabled: boolean | null - * }>} - */ - async refresh() { - this.setEnabled(await this.getEnabled()); - this.setFeatureToggles(await this.getFeatureToggles()); - this.setAvailableInputTypes(await this.getAvailableInputTypes()); + // src/UI/styles/autofill-tooltip-styles.css + var autofill_tooltip_styles_default = ":root {\n color-scheme: light dark;\n}\n\n.wrapper *, .wrapper *::before, .wrapper *::after {\n box-sizing: border-box;\n}\n.wrapper {\n position: fixed;\n top: 0;\n left: 0;\n padding: 0;\n font-family: 'DDG_ProximaNova', 'Proxima Nova', system-ui, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',\n 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;\n -webkit-font-smoothing: antialiased;\n z-index: 2147483647;\n}\n.wrapper--data {\n font-family: 'SF Pro Text', system-ui, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',\n 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;\n}\n:not(.top-autofill) .tooltip {\n position: absolute;\n width: 300px;\n max-width: calc(100vw - 25px);\n transform: translate(-1000px, -1000px);\n z-index: 2147483647;\n}\n.tooltip--data, #topAutofill {\n background-color: rgba(242, 240, 240, 1);\n -webkit-backdrop-filter: blur(40px);\n backdrop-filter: blur(40px);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip--data, #topAutofill {\n background: rgb(100, 98, 102, .9);\n }\n}\n.tooltip--data {\n padding: 6px;\n font-size: 13px;\n line-height: 14px;\n width: 315px;\n max-height: 290px;\n overflow-y: auto;\n}\n.top-autofill .tooltip--data {\n min-height: 100vh;\n}\n.tooltip--data.tooltip--incontext-signup {\n width: 360px;\n}\n:not(.top-autofill) .tooltip--data {\n top: 100%;\n left: 100%;\n border: 0.5px solid rgba(255, 255, 255, 0.2);\n border-radius: 6px;\n box-shadow: 0 10px 20px rgba(0, 0, 0, 0.32);\n}\n@media (prefers-color-scheme: dark) {\n :not(.top-autofill) .tooltip--data {\n border: 1px solid rgba(255, 255, 255, 0.2);\n }\n}\n:not(.top-autofill) .tooltip--email {\n top: calc(100% + 6px);\n right: calc(100% - 48px);\n padding: 8px;\n border: 1px solid #D0D0D0;\n border-radius: 10px;\n background-color: #FFFFFF;\n font-size: 14px;\n line-height: 1.3;\n color: #333333;\n box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);\n}\n.tooltip--email__caret {\n position: absolute;\n transform: translate(-1000px, -1000px);\n z-index: 2147483647;\n}\n.tooltip--email__caret::before,\n.tooltip--email__caret::after {\n content: \"\";\n width: 0;\n height: 0;\n border-left: 10px solid transparent;\n border-right: 10px solid transparent;\n display: block;\n border-bottom: 8px solid #D0D0D0;\n position: absolute;\n right: -28px;\n}\n.tooltip--email__caret::before {\n border-bottom-color: #D0D0D0;\n top: -1px;\n}\n.tooltip--email__caret::after {\n border-bottom-color: #FFFFFF;\n top: 0px;\n}\n\n/* Buttons */\n.tooltip__button {\n display: flex;\n width: 100%;\n padding: 8px 8px 8px 0px;\n font-family: inherit;\n color: inherit;\n background: transparent;\n border: none;\n border-radius: 6px;\n}\n.tooltip__button.currentFocus,\n.wrapper:not(.top-autofill) .tooltip__button:hover {\n background-color: #3969EF;\n color: #FFFFFF;\n}\n\n/* Data autofill tooltip specific */\n.tooltip__button--data {\n position: relative;\n min-height: 48px;\n flex-direction: row;\n justify-content: flex-start;\n font-size: inherit;\n font-weight: 500;\n line-height: 16px;\n text-align: left;\n border-radius: 3px;\n}\n.tooltip--data__item-container {\n max-height: 220px;\n overflow: auto;\n}\n.tooltip__button--data:first-child {\n margin-top: 0;\n}\n.tooltip__button--data:last-child {\n margin-bottom: 0;\n}\n.tooltip__button--data::before {\n content: '';\n flex-shrink: 0;\n display: block;\n width: 32px;\n height: 32px;\n margin: 0 8px;\n background-size: 20px 20px;\n background-repeat: no-repeat;\n background-position: center center;\n}\n#provider_locked::after {\n position: absolute;\n content: '';\n flex-shrink: 0;\n display: block;\n width: 32px;\n height: 32px;\n margin: 0 8px;\n background-size: 11px 13px;\n background-repeat: no-repeat;\n background-position: right bottom;\n}\n.tooltip__button--data.currentFocus:not(.tooltip__button--data--bitwarden)::before,\n.wrapper:not(.top-autofill) .tooltip__button--data:not(.tooltip__button--data--bitwarden):hover::before {\n filter: invert(100%);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip__button--data:not(.tooltip__button--data--bitwarden)::before,\n .tooltip__button--data:not(.tooltip__button--data--bitwarden)::before {\n filter: invert(100%);\n opacity: .9;\n }\n}\n.tooltip__button__text-container {\n margin: auto 0;\n}\n.label {\n display: block;\n font-weight: 400;\n letter-spacing: -0.25px;\n color: rgba(0,0,0,.8);\n font-size: 13px;\n line-height: 1;\n}\n.label + .label {\n margin-top: 2px;\n}\n.label.label--medium {\n font-weight: 500;\n letter-spacing: -0.25px;\n color: rgba(0,0,0,.9);\n}\n.label.label--small {\n font-size: 11px;\n font-weight: 400;\n letter-spacing: 0.06px;\n color: rgba(0,0,0,0.6);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip--data .label {\n color: #ffffff;\n }\n .tooltip--data .label--medium {\n color: #ffffff;\n }\n .tooltip--data .label--small {\n color: #cdcdcd;\n }\n}\n.tooltip__button.currentFocus .label,\n.wrapper:not(.top-autofill) .tooltip__button:hover .label {\n color: #FFFFFF;\n}\n\n.tooltip__button--manage {\n font-size: 13px;\n padding: 5px 9px;\n border-radius: 3px;\n margin: 0;\n}\n\n/* Icons */\n.tooltip__button--data--credentials::before,\n.tooltip__button--data--credentials__current::before {\n background-size: 28px 28px;\n background-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNS4zMzQgNi42NjdhMiAyIDAgMSAwIDAgNCAyIDIgMCAwIDAgMC00Wm0tLjY2NyAyYS42NjcuNjY3IDAgMSAxIDEuMzMzIDAgLjY2Ny42NjcgMCAwIDEtMS4zMzMgMFoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTQuNjY3IDRhNS4zMzMgNS4zMzMgMCAwIDAtNS4xODggNi41NzhsLTUuMjg0IDUuMjg0YS42NjcuNjY3IDAgMCAwLS4xOTUuNDcxdjNjMCAuMzY5LjI5OC42NjcuNjY3LjY2N2gyLjY2NmMuNzM3IDAgMS4zMzQtLjU5NyAxLjMzNC0xLjMzM1YxOGguNjY2Yy43MzcgMCAxLjMzNC0uNTk3IDEuMzM0LTEuMzMzdi0xLjMzNEgxMmMuMTc3IDAgLjM0Ni0uMDcuNDcxLS4xOTVsLjY4OC0uNjg4QTUuMzMzIDUuMzMzIDAgMSAwIDE0LjY2NyA0Wm0tNCA1LjMzM2E0IDQgMCAxIDEgMi41NTUgMy43MzIuNjY3LjY2NyAwIDAgMC0uNzEzLjE1bC0uNzg1Ljc4NUgxMGEuNjY3LjY2NyAwIDAgMC0uNjY3LjY2N3YySDhhLjY2Ny42NjcgMCAwIDAtLjY2Ny42NjZ2MS4zMzRoLTJ2LTIuMDU4bDUuMzY1LTUuMzY0YS42NjcuNjY3IDAgMCAwIC4xNjMtLjY3NyAzLjk5NiAzLjk5NiAwIDAgMS0uMTk0LTEuMjM1WiIgY2xpcC1ydWxlPSJldmVub2RkIi8+Cjwvc3ZnPgo=');\n}\n.tooltip__button--data--credentials__new::before {\n background-size: 28px 28px;\n background-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjMDAwIiBkPSJNOC4wNDcgNC42MjVDNy45MzcgNC4xMjUgNy44NjIgNCA3LjUgNGMtLjM2MiAwLS40MzguMTI1LS41NDcuNjI1LS4wNjguMzEtLjE3NyAxLjMzOC0uMjUxIDIuMDc3LS43MzguMDc0LTEuNzY3LjE4My0yLjA3Ny4yNTEtLjUuMTEtLjYyNS4xODQtLjYyNS41NDcgMCAuMzYyLjEyNS40MzcuNjI1LjU0Ny4zMS4wNjcgMS4zMzYuMTc3IDIuMDc0LjI1LjA3My43NjcuMTg1IDEuODQyLjI1NCAyLjA3OC4xMS4zNzUuMTg1LjYyNS41NDcuNjI1LjM2MiAwIC40MzgtLjEyNS41NDctLjYyNS4wNjgtLjMxLjE3Ny0xLjMzNi4yNS0yLjA3NC43NjctLjA3MyAxLjg0Mi0uMTg1IDIuMDc4LS4yNTQuMzc1LS4xMS42MjUtLjE4NS42MjUtLjU0NyAwLS4zNjMtLjEyNS0uNDM4LS42MjUtLjU0Ny0uMzEtLjA2OC0xLjMzOS0uMTc3LTIuMDc3LS4yNTEtLjA3NC0uNzM5LS4xODMtMS43NjctLjI1MS0yLjA3N1oiLz4KICAgIDxwYXRoIGZpbGw9IiMwMDAiIGQ9Ik0xNC42ODEgNS4xOTljLS43NjYgMC0xLjQ4Mi4yMS0yLjA5My41NzhhLjYzNi42MzYgMCAwIDEtLjY1NS0xLjA5IDUuMzQgNS4zNCAwIDEgMSAxLjMwMiA5LjcyMmwtLjc3NS43NzZhLjYzNi42MzYgMCAwIDEtLjQ1LjE4NmgtMS4zOTh2MS42NWMwIC40OTMtLjQuODkzLS44OTMuODkzSDguNTc4djEuMTQxYzAgLjQ5NC0uNC44OTMtLjg5NC44OTNINC42MzZBLjYzNi42MzYgMCAwIDEgNCAxOS4zMTNWMTYuMjZjMC0uMTY5LjA2Ny0uMzMuMTg2LS40NWw1LjU2Mi01LjU2MmEuNjM2LjYzNiAwIDEgMSAuOS45bC01LjM3NiA1LjM3NXYyLjE1M2gyLjAzNHYtMS4zOTljMC0uMzUuMjg1LS42MzYuNjM2LS42MzZIOS4zNHYtMS45MDdjMC0uMzUxLjI4NC0uNjM2LjYzNS0uNjM2aDEuNzcxbC44NjQtLjg2M2EuNjM2LjYzNiAwIDAgMSAuNjY4LS4xNDcgNC4wNjkgNC4wNjkgMCAxIDAgMS40MDItNy44OVoiLz4KICAgIDxwYXRoIGZpbGw9IiMwMDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTEzLjYyNSA4LjQ5OWExLjg3NSAxLjg3NSAwIDEgMSAzLjc1IDAgMS44NzUgMS44NzUgMCAwIDEtMy43NSAwWm0xLjg3NS0uNjI1YS42MjUuNjI1IDAgMSAwIDAgMS4yNS42MjUuNjI1IDAgMCAwIDAtMS4yNVoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZmlsbD0iIzAwMCIgZD0iTTQuNjI1IDEyLjEyNWEuNjI1LjYyNSAwIDEgMCAwLTEuMjUuNjI1LjYyNSAwIDAgMCAwIDEuMjVaIi8+Cjwvc3ZnPgo=');\n}\n.tooltip__button--data--creditCards::before {\n background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBkPSJNNSA5Yy0uNTUyIDAtMSAuNDQ4LTEgMXYyYzAgLjU1Mi40NDggMSAxIDFoM2MuNTUyIDAgMS0uNDQ4IDEtMXYtMmMwLS41NTItLjQ0OC0xLTEtMUg1eiIgZmlsbD0iIzAwMCIvPgogICAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xIDZjMC0yLjIxIDEuNzktNCA0LTRoMTRjMi4yMSAwIDQgMS43OSA0IDR2MTJjMCAyLjIxLTEuNzkgNC00IDRINWMtMi4yMSAwLTQtMS43OS00LTRWNnptNC0yYy0xLjEwNSAwLTIgLjg5NS0yIDJ2OWgxOFY2YzAtMS4xMDUtLjg5NS0yLTItMkg1em0wIDE2Yy0xLjEwNSAwLTItLjg5NS0yLTJoMThjMCAxLjEwNS0uODk1IDItMiAySDV6IiBmaWxsPSIjMDAwIi8+Cjwvc3ZnPgo=');\n}\n.tooltip__button--data--identities::before {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTEyIDIxYzIuMTQzIDAgNC4xMTEtLjc1IDUuNjU3LTItLjYyNi0uNTA2LTEuMzE4LS45MjctMi4wNi0xLjI1LTEuMS0uNDgtMi4yODUtLjczNS0zLjQ4Ni0uNzUtMS4yLS4wMTQtMi4zOTIuMjExLTMuNTA0LjY2NC0uODE3LjMzMy0xLjU4Ljc4My0yLjI2NCAxLjMzNiAxLjU0NiAxLjI1IDMuNTE0IDIgNS42NTcgMnptNC4zOTctNS4wODNjLjk2Ny40MjIgMS44NjYuOTggMi42NzIgMS42NTVDMjAuMjc5IDE2LjAzOSAyMSAxNC4xMDQgMjEgMTJjMC00Ljk3LTQuMDMtOS05LTlzLTkgNC4wMy05IDljMCAyLjEwNC43MjIgNC4wNCAxLjkzMiA1LjU3Mi44NzQtLjczNCAxLjg2LTEuMzI4IDIuOTIxLTEuNzYgMS4zNi0uNTU0IDIuODE2LS44MyA0LjI4My0uODExIDEuNDY3LjAxOCAyLjkxNi4zMyA0LjI2LjkxNnpNMTIgMjNjNi4wNzUgMCAxMS00LjkyNSAxMS0xMVMxOC4wNzUgMSAxMiAxIDEgNS45MjUgMSAxMnM0LjkyNSAxMSAxMSAxMXptMy0xM2MwIDEuNjU3LTEuMzQzIDMtMyAzcy0zLTEuMzQzLTMtMyAxLjM0My0zIDMtMyAzIDEuMzQzIDMgM3ptMiAwYzAgMi43NjEtMi4yMzkgNS01IDVzLTUtMi4yMzktNS01IDIuMjM5LTUgNS01IDUgMi4yMzkgNSA1eiIgZmlsbD0iIzAwMCIvPgo8L3N2Zz4=');\n}\n.tooltip__button--data--credentials.tooltip__button--data--bitwarden::before,\n.tooltip__button--data--credentials__current.tooltip__button--data--bitwarden::before {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiByeD0iOCIgZmlsbD0iIzE3NUREQyIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTE4LjU2OTYgNS40MzM1NUMxOC41MDg0IDUuMzc0NDIgMTguNDM0NyA1LjMyNzYzIDE4LjM1MzEgNS4yOTYxMUMxOC4yNzE1IDUuMjY0NiAxOC4xODM3IDUuMjQ5MDQgMTguMDk1MyA1LjI1MDQxSDUuOTIxOTFDNS44MzMyNiA1LjI0NzI5IDUuNzQ0OTMgNS4yNjIwNSA1LjY2MzA0IDUuMjkzNjdDNS41ODExNSA1LjMyNTI5IDUuNTA3NjUgNS4zNzMwMiA1LjQ0NzYyIDUuNDMzNTVDNS4zMjE3IDUuNTUwMTMgNS4yNTA2NSA1LjcwODE1IDUuMjUgNS44NzMxVjEzLjM4MjFDNS4yNTMzNiAxMy45NTM1IDUuMzc0MDggMTQuNTE5MSA1LjYwNTcyIDE1LjA0ODdDNS44MTkzMSAxNS41NzI4IDYuMTEyMDcgMTYuMDY2MSA2LjQ3NTI0IDE2LjUxMzlDNi44NDIgMTYuOTY4MyA3LjI1OTI5IDE3LjM4NTcgNy43MjAyNSAxNy43NTkzQzguMTQwNTMgMTguMTI1NiA4LjU4OTcxIDE4LjQ2MjMgOS4wNjQwNyAxOC43NjY2QzkuNDU5MzEgMTkuMDIzIDkuOTEzODMgMTkuMjc5NCAxMC4zNDg2IDE5LjUxNzVDMTAuNzgzNCAxOS43NTU2IDExLjA5OTYgMTkuOTIwNCAxMS4yNzc0IDE5Ljk5MzdDMTEuNDU1MyAyMC4wNjY5IDExLjYxMzQgMjAuMTQwMiAxMS43MTIyIDIwLjE5NTFDMTEuNzk5MiAyMC4yMzEzIDExLjg5MzUgMjAuMjUgMTEuOTg4OCAyMC4yNUMxMi4wODQyIDIwLjI1IDEyLjE3ODUgMjAuMjMxMyAxMi4yNjU1IDIwLjE5NTFDMTIuNDIxMiAyMC4xMzYzIDEyLjU3MjkgMjAuMDY5IDEyLjcyIDE5Ljk5MzdDMTIuNzcxMSAxOS45Njc0IDEyLjgzMzUgMTkuOTM2NiAxMi45MDY5IDE5LjkwMDRDMTMuMDg5MSAxOS44MTA1IDEzLjMzODggMTkuNjg3MiAxMy42NDg5IDE5LjUxNzVDMTQuMDgzNiAxOS4yNzk0IDE0LjUxODQgMTkuMDIzIDE0LjkzMzQgMTguNzY2NkMxNS40MDQgMTguNDU3NyAxNS44NTI4IDE4LjEyMTIgMTYuMjc3MiAxNy43NTkzQzE2LjczMzEgMTcuMzgwOSAxNy4xNDk5IDE2Ljk2NCAxNy41MjIyIDE2LjUxMzlDMTcuODc4IDE2LjA2MTcgMTguMTcwMiAxNS41NjkzIDE4LjM5MTcgMTUuMDQ4N0MxOC42MjM0IDE0LjUxOTEgMTguNzQ0MSAxMy45NTM1IDE4Ljc0NzQgMTMuMzgyMVY1Ljg3MzFDMTguNzU1NyA1Ljc5MjE0IDE4Ljc0MzkgNS43MTA1IDE4LjcxMzEgNS42MzQzNUMxOC42ODIzIDUuNTU4MiAxOC42MzMyIDUuNDg5NTQgMTguNTY5NiA1LjQzMzU1Wk0xNy4wMDg0IDEzLjQ1NTNDMTcuMDA4NCAxNi4xODQyIDEyLjAwODYgMTguNTI4NSAxMi4wMDg2IDE4LjUyODVWNi44NjIwOUgxNy4wMDg0VjEzLjQ1NTNaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K');\n}\n#provider_locked:after {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTEiIGhlaWdodD0iMTMiIHZpZXdCb3g9IjAgMCAxMSAxMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEgNy42MDA1N1Y3LjYwMjVWOS41MjI1QzEgMTAuMDgwMSAxLjIyMTUxIDEwLjYxNDkgMS42MTU4MSAxMS4wMDkyQzIuMDEwMSAxMS40MDM1IDIuNTQ0ODggMTEuNjI1IDMuMTAyNSAxMS42MjVINy4yNzI1QzcuNTQ4NjEgMTEuNjI1IDcuODIyMDEgMTEuNTcwNiA4LjA3NzA5IDExLjQ2NUM4LjMzMjE4IDExLjM1OTMgOC41NjM5NiAxMS4yMDQ0IDguNzU5MTkgMTEuMDA5MkM4Ljk1NDQzIDEwLjgxNCA5LjEwOTMgMTAuNTgyMiA5LjIxNDk2IDEwLjMyNzFDOS4zMjA2MiAxMC4wNzIgOS4zNzUgOS43OTg2MSA5LjM3NSA5LjUyMjVMOS4zNzUgNy42MDI1TDkuMzc1IDcuNjAwNTdDOS4zNzQxNSA3LjE2MTMxIDkuMjM1NzQgNi43MzMzNSA4Ljk3OTIyIDYuMzc2NzhDOC44NzY4MyA2LjIzNDQ2IDguNzU3NjggNi4xMDYzNyA4LjYyNSA1Ljk5NDg5VjUuMTg3NUM4LjYyNSA0LjI3NTgyIDguMjYyODQgMy40MDE0OCA3LjYxODE4IDIuNzU2ODJDNi45NzM1MiAyLjExMjE2IDYuMDk5MTggMS43NSA1LjE4NzUgMS43NUM0LjI3NTgyIDEuNzUgMy40MDE0OCAyLjExMjE2IDIuNzU2ODIgMi43NTY4MkMyLjExMjE2IDMuNDAxNDggMS43NSA0LjI3NTgyIDEuNzUgNS4xODc1VjUuOTk0ODlDMS42MTczMiA2LjEwNjM3IDEuNDk4MTcgNi4yMzQ0NiAxLjM5NTc4IDYuMzc2NzhDMS4xMzkyNiA2LjczMzM1IDEuMDAwODUgNy4xNjEzMSAxIDcuNjAwNTdaTTQuOTY4NyA0Ljk2ODdDNS4wMjY5NCA0LjkxMDQ3IDUuMTA1MzIgNC44NzY5OSA1LjE4NzUgNC44NzUwN0M1LjI2OTY4IDQuODc2OTkgNS4zNDgwNiA0LjkxMDQ3IDUuNDA2MyA0Ljk2ODdDNS40NjU0MiA1LjAyNzgzIDUuNDk5MDQgNS4xMDc3NCA1LjUgNS4xOTEzVjUuNUg0Ljg3NVY1LjE5MTNDNC44NzU5NiA1LjEwNzc0IDQuOTA5NTggNS4wMjc4MyA0Ljk2ODcgNC45Njg3WiIgZmlsbD0iIzIyMjIyMiIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIyIi8+Cjwvc3ZnPgo=');\n}\n\nhr {\n display: block;\n margin: 5px 9px;\n border: none; /* reset the border */\n border-top: 1px solid rgba(0,0,0,.1);\n}\n\nhr:first-child {\n display: none;\n}\n\n@media (prefers-color-scheme: dark) {\n hr {\n border-top: 1px solid rgba(255,255,255,.2);\n }\n}\n\n#privateAddress {\n align-items: flex-start;\n}\n#personalAddress::before,\n#privateAddress::before,\n#incontextSignup::before,\n#personalAddress.currentFocus::before,\n#personalAddress:hover::before,\n#privateAddress.currentFocus::before,\n#privateAddress:hover::before {\n filter: none;\n /* This is the same icon as `daxBase64` in `src/Form/logo-svg.js` */\n background-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMTI4IDEyOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0ibTY0IDEyOGMzNS4zNDYgMCA2NC0yOC42NTQgNjQtNjRzLTI4LjY1NC02NC02NC02NC02NCAyOC42NTQtNjQgNjQgMjguNjU0IDY0IDY0IDY0eiIgZmlsbD0iI2RlNTgzMyIgZmlsbC1ydWxlPSJldmVub2RkIi8+CiAgICA8cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Im03MyAxMTEuNzVjMC0uNS4xMjMtLjYxNC0xLjQ2Ni0zLjc4Mi00LjIyNC04LjQ1OS04LjQ3LTIwLjM4NC02LjU0LTI4LjA3NS4zNTMtMS4zOTctMy45NzgtNTEuNzQ0LTcuMDQtNTMuMzY1LTMuNDAyLTEuODEzLTcuNTg4LTQuNjktMTEuNDE4LTUuMzMtMS45NDMtLjMxLTQuNDktLjE2NC02LjQ4Mi4xMDUtLjM1My4wNDctLjM2OC42ODMtLjAzLjc5OCAxLjMwOC40NDMgMi44OTUgMS4yMTIgMy44MyAyLjM3NS4xNzguMjItLjA2LjU2Ni0uMzQyLjU3Ny0uODgyLjAzMi0yLjQ4Mi40MDItNC41OTMgMi4xOTUtLjI0NC4yMDctLjA0MS41OTIuMjczLjUzIDQuNTM2LS44OTcgOS4xNy0uNDU1IDExLjkgMi4wMjcuMTc3LjE2LjA4NC40NS0uMTQ3LjUxMi0yMy42OTQgNi40NC0xOS4wMDMgMjcuMDUtMTIuNjk2IDUyLjM0NCA1LjYxOSAyMi41MyA3LjczMyAyOS43OTIgOC40IDMyLjAwNGEuNzE4LjcxOCAwIDAgMCAuNDIzLjQ2N2M4LjE1NiAzLjI0OCAyNS45MjggMy4zOTIgMjUuOTI4LTIuMTMyeiIgZmlsbD0iI2RkZCIgZmlsbC1ydWxlPSJldmVub2RkIi8+CiAgICA8cGF0aCBkPSJtNzYuMjUgMTE2LjVjLTIuODc1IDEuMTI1LTguNSAxLjYyNS0xMS43NSAxLjYyNS00Ljc2NCAwLTExLjYyNS0uNzUtMTQuMTI1LTEuODc1LTEuNTQ0LTQuNzUxLTYuMTY0LTE5LjQ4LTEwLjcyNy0zOC4xODVsLS40NDctMS44MjctLjAwNC0uMDE1Yy01LjQyNC0yMi4xNTctOS44NTUtNDAuMjUzIDE0LjQyNy00NS45MzguMjIyLS4wNTIuMzMtLjMxNy4xODQtLjQ5Mi0yLjc4Ni0zLjMwNS04LjAwNS00LjM4OC0xNC42MDUtMi4xMTEtLjI3LjA5My0uNTA2LS4xOC0uMzM3LS40MTIgMS4yOTQtMS43ODMgMy44MjMtMy4xNTUgNS4wNzEtMy43NTYuMjU4LS4xMjQuMjQyLS41MDItLjAzLS41ODhhMjcuODc3IDI3Ljg3NyAwIDAgMCAtMy43NzItLjljLS4zNy0uMDU5LS40MDMtLjY5My0uMDMyLS43NDMgOS4zNTYtMS4yNTkgMTkuMTI1IDEuNTUgMjQuMDI4IDcuNzI2YS4zMjYuMzI2IDAgMCAwIC4xODYuMTE0YzE3Ljk1MiAzLjg1NiAxOS4yMzggMzIuMjM1IDE3LjE3IDMzLjUyOC0uNDA4LjI1NS0xLjcxNS4xMDgtMy40MzgtLjA4NS02Ljk4Ni0uNzgxLTIwLjgxOC0yLjMyOS05LjQwMiAxOC45NDguMTEzLjIxLS4wMzYuNDg4LS4yNzIuNTI1LTYuNDM4IDEgMS44MTIgMjEuMTczIDcuODc1IDM0LjQ2MXoiIGZpbGw9IiNmZmYiLz4KICAgIDxwYXRoIGQ9Im04NC4yOCA5MC42OThjLTEuMzY3LS42MzMtNi42MjEgMy4xMzUtMTAuMTEgNi4wMjgtLjcyOC0xLjAzMS0yLjEwMy0xLjc4LTUuMjAzLTEuMjQyLTIuNzEzLjQ3Mi00LjIxMSAxLjEyNi00Ljg4IDIuMjU0LTQuMjgzLTEuNjIzLTExLjQ4OC00LjEzLTEzLjIyOS0xLjcxLTEuOTAyIDIuNjQ2LjQ3NiAxNS4xNjEgMy4wMDMgMTYuNzg2IDEuMzIuODQ5IDcuNjMtMy4yMDggMTAuOTI2LTYuMDA1LjUzMi43NDkgMS4zODggMS4xNzggMy4xNDggMS4xMzcgMi42NjItLjA2MiA2Ljk3OS0uNjgxIDcuNjQ5LTEuOTIxLjA0LS4wNzUuMDc1LS4xNjQuMTA1LS4yNjYgMy4zODggMS4yNjYgOS4zNSAyLjYwNiAxMC42ODIgMi40MDYgMy40Ny0uNTIxLS40ODQtMTYuNzIzLTIuMDktMTcuNDY3eiIgZmlsbD0iIzNjYTgyYiIvPgogICAgPHBhdGggZD0ibTc0LjQ5IDk3LjA5N2MuMTQ0LjI1Ni4yNi41MjYuMzU4LjguNDgzIDEuMzUyIDEuMjcgNS42NDguNjc0IDYuNzA5LS41OTUgMS4wNjItNC40NTkgMS41NzQtNi44NDMgMS42MTVzLTIuOTItLjgzMS0zLjQwMy0yLjE4MWMtLjM4Ny0xLjA4MS0uNTc3LTMuNjIxLS41NzItNS4wNzUtLjA5OC0yLjE1OC42OS0yLjkxNiA0LjMzNC0zLjUwNiAyLjY5Ni0uNDM2IDQuMTIxLjA3MSA0Ljk0NC45NCAzLjgyOC0yLjg1NyAxMC4yMTUtNi44ODkgMTAuODM4LTYuMTUyIDMuMTA2IDMuNjc0IDMuNDk5IDEyLjQyIDIuODI2IDE1LjkzOS0uMjIgMS4xNTEtMTAuNTA1LTEuMTM5LTEwLjUwNS0yLjM4IDAtNS4xNTItMS4zMzctNi41NjUtMi42NS02Ljcxem0tMjIuNTMtMS42MDljLjg0My0xLjMzMyA3LjY3NC4zMjUgMTEuNDI0IDEuOTkzIDAgMC0uNzcgMy40OTEuNDU2IDcuNjA0LjM1OSAxLjIwMy04LjYyNyA2LjU1OC05LjggNS42MzctMS4zNTUtMS4wNjUtMy44NS0xMi40MzItMi4wOC0xNS4yMzR6IiBmaWxsPSIjNGNiYTNjIi8+CiAgICA8cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Im01NS4yNjkgNjguNDA2Yy41NTMtMi40MDMgMy4xMjctNi45MzIgMTIuMzIxLTYuODIyIDQuNjQ4LS4wMTkgMTAuNDIyLS4wMDIgMTQuMjUtLjQzNmE1MS4zMTIgNTEuMzEyIDAgMCAwIDEyLjcyNi0zLjA5NWMzLjk4LTEuNTE5IDUuMzkyLTEuMTggNS44ODctLjI3Mi41NDQuOTk5LS4wOTcgMi43MjItMS40ODggNC4zMDktMi42NTYgMy4wMy03LjQzMSA1LjM4LTE1Ljg2NSA2LjA3Ni04LjQzMy42OTgtMTQuMDItMS41NjUtMTYuNDI1IDIuMTE4LTEuMDM4IDEuNTg5LS4yMzYgNS4zMzMgNy45MiA2LjUxMiAxMS4wMiAxLjU5IDIwLjA3Mi0xLjkxNyAyMS4xOS4yMDEgMS4xMTkgMi4xMTgtNS4zMjMgNi40MjgtMTYuMzYyIDYuNTE4cy0xNy45MzQtMy44NjUtMjAuMzc5LTUuODNjLTMuMTAyLTIuNDk1LTQuNDktNi4xMzMtMy43NzUtOS4yNzl6IiBmaWxsPSIjZmMzIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz4KICAgIDxnIGZpbGw9IiMxNDMwN2UiIG9wYWNpdHk9Ii44Ij4KICAgICAgPHBhdGggZD0ibTY5LjMyNyA0Mi4xMjdjLjYxNi0xLjAwOCAxLjk4MS0xLjc4NiA0LjIxNi0xLjc4NiAyLjIzNCAwIDMuMjg1Ljg4OSA0LjAxMyAxLjg4LjE0OC4yMDItLjA3Ni40NC0uMzA2LjM0YTU5Ljg2OSA1OS44NjkgMCAwIDEgLS4xNjgtLjA3M2MtLjgxNy0uMzU3LTEuODItLjc5NS0zLjU0LS44Mi0xLjgzOC0uMDI2LTIuOTk3LjQzNS0zLjcyNy44MzEtLjI0Ni4xMzQtLjYzNC0uMTMzLS40ODgtLjM3MnptLTI1LjE1NyAxLjI5YzIuMTctLjkwNyAzLjg3Ni0uNzkgNS4wODEtLjUwNC4yNTQuMDYuNDMtLjIxMy4yMjctLjM3Ny0uOTM1LS43NTUtMy4wMy0xLjY5Mi01Ljc2LS42NzQtMi40MzcuOTA5LTMuNTg1IDIuNzk2LTMuNTkyIDQuMDM4LS4wMDIuMjkyLjYuMzE3Ljc1Ni4wNy40Mi0uNjcgMS4xMi0xLjY0NiAzLjI4OS0yLjU1M3oiLz4KICAgICAgPHBhdGggY2xpcC1ydWxlPSJldmVub2RkIiBkPSJtNzUuNDQgNTUuOTJhMy40NyAzLjQ3IDAgMCAxIC0zLjQ3NC0zLjQ2MiAzLjQ3IDMuNDcgMCAwIDEgMy40NzUtMy40NiAzLjQ3IDMuNDcgMCAwIDEgMy40NzQgMy40NiAzLjQ3IDMuNDcgMCAwIDEgLTMuNDc1IDMuNDYyem0yLjQ0Ny00LjYwOGEuODk5Ljg5OSAwIDAgMCAtMS43OTkgMGMwIC40OTQuNDA1Ljg5NS45Ljg5NS40OTkgMCAuOS0uNC45LS44OTV6bS0yNS40NjQgMy41NDJhNC4wNDIgNC4wNDIgMCAwIDEgLTQuMDQ5IDQuMDM3IDQuMDQ1IDQuMDQ1IDAgMCAxIC00LjA1LTQuMDM3IDQuMDQ1IDQuMDQ1IDAgMCAxIDQuMDUtNC4wMzcgNC4wNDUgNC4wNDUgMCAwIDEgNC4wNSA0LjAzN3ptLTEuMTkzLTEuMzM4YTEuMDUgMS4wNSAwIDAgMCAtMi4wOTcgMCAxLjA0OCAxLjA0OCAwIDAgMCAyLjA5NyAweiIgZmlsbC1ydWxlPSJldmVub2RkIi8+CiAgICA8L2c+CiAgICA8cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Im02NCAxMTcuNzVjMjkuNjg1IDAgNTMuNzUtMjQuMDY1IDUzLjc1LTUzLjc1cy0yNC4wNjUtNTMuNzUtNTMuNzUtNTMuNzUtNTMuNzUgMjQuMDY1LTUzLjc1IDUzLjc1IDI0LjA2NSA1My43NSA1My43NSA1My43NXptMCA1YzMyLjQ0NyAwIDU4Ljc1LTI2LjMwMyA1OC43NS01OC43NXMtMjYuMzAzLTU4Ljc1LTU4Ljc1LTU4Ljc1LTU4Ljc1IDI2LjMwMy01OC43NSA1OC43NSAyNi4zMDMgNTguNzUgNTguNzUgNTguNzV6IiBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz4KPC9zdmc+');\n}\n\n/* Email tooltip specific */\n.tooltip__button--email {\n flex-direction: column;\n justify-content: center;\n align-items: flex-start;\n font-size: 14px;\n padding: 4px 8px;\n}\n.tooltip__button--email__primary-text {\n font-weight: bold;\n}\n.tooltip__button--email__secondary-text {\n font-size: 12px;\n}\n\n/* Email Protection signup notice */\n:not(.top-autofill) .tooltip--email-signup {\n text-align: left;\n color: #222222;\n padding: 16px 20px;\n width: 380px;\n}\n\n.tooltip--email-signup h1 {\n font-weight: 700;\n font-size: 16px;\n line-height: 1.5;\n margin: 0;\n}\n\n.tooltip--email-signup p {\n font-weight: 400;\n font-size: 14px;\n line-height: 1.4;\n}\n\n.notice-controls {\n display: flex;\n}\n\n.tooltip--email-signup .notice-controls > * {\n border-radius: 8px;\n border: 0;\n cursor: pointer;\n display: inline-block;\n font-family: inherit;\n font-style: normal;\n font-weight: bold;\n padding: 8px 12px;\n text-decoration: none;\n}\n\n.notice-controls .ghost {\n margin-left: 1rem;\n}\n\n.tooltip--email-signup a.primary {\n background: #3969EF;\n color: #fff;\n}\n\n.tooltip--email-signup a.primary:hover,\n.tooltip--email-signup a.primary:focus {\n background: #2b55ca;\n}\n\n.tooltip--email-signup a.primary:active {\n background: #1e42a4;\n}\n\n.tooltip--email-signup button.ghost {\n background: transparent;\n color: #3969EF;\n}\n\n.tooltip--email-signup button.ghost:hover,\n.tooltip--email-signup button.ghost:focus {\n background-color: rgba(0, 0, 0, 0.06);\n color: #2b55ca;\n}\n\n.tooltip--email-signup button.ghost:active {\n background-color: rgba(0, 0, 0, 0.12);\n color: #1e42a4;\n}\n\n.tooltip--email-signup button.close-tooltip {\n background-color: transparent;\n background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iMTMiIHZpZXdCb3g9IjAgMCAxMiAxMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0wLjI5Mjg5NCAwLjY1NjkwN0MwLjY4MzQxOCAwLjI2NjM4MyAxLjMxNjU4IDAuMjY2MzgzIDEuNzA3MTEgMC42NTY5MDdMNiA0Ljk0OThMMTAuMjkyOSAwLjY1NjkwN0MxMC42ODM0IDAuMjY2MzgzIDExLjMxNjYgMC4yNjYzODMgMTEuNzA3MSAwLjY1NjkwN0MxMi4wOTc2IDEuMDQ3NDMgMTIuMDk3NiAxLjY4MDYgMTEuNzA3MSAyLjA3MTEyTDcuNDE0MjEgNi4zNjQwMUwxMS43MDcxIDEwLjY1NjlDMTIuMDk3NiAxMS4wNDc0IDEyLjA5NzYgMTEuNjgwNiAxMS43MDcxIDEyLjA3MTFDMTEuMzE2NiAxMi40NjE2IDEwLjY4MzQgMTIuNDYxNiAxMC4yOTI5IDEyLjA3MTFMNiA3Ljc3ODIzTDEuNzA3MTEgMTIuMDcxMUMxLjMxNjU4IDEyLjQ2MTYgMC42ODM0MTcgMTIuNDYxNiAwLjI5Mjg5MyAxMi4wNzExQy0wLjA5NzYzMTEgMTEuNjgwNiAtMC4wOTc2MzExIDExLjA0NzQgMC4yOTI4OTMgMTAuNjU2OUw0LjU4NTc5IDYuMzY0MDFMMC4yOTI4OTQgMi4wNzExMkMtMC4wOTc2MzA2IDEuNjgwNiAtMC4wOTc2MzA2IDEuMDQ3NDMgMC4yOTI4OTQgMC42NTY5MDdaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjg0Ii8+Cjwvc3ZnPgo=);\n background-position: center center;\n background-repeat: no-repeat;\n border: 0;\n cursor: pointer;\n padding: 16px;\n position: absolute;\n right: 12px;\n top: 12px;\n}\n"; - // If 'this.enabled' is a boolean it means we were able to set it correctly and therefor respect its value - if (typeof this.enabled === 'boolean') { - if (!this.enabled) { - return Settings.defaults; - } + // src/UI/HTMLTooltip.js + var defaultOptions = { + wrapperClass: "", + tooltipPositionClass: (top, left) => ` + .tooltip { + transform: translate(${Math.floor(left)}px, ${Math.floor(top)}px) !important; + } + `, + caretPositionClass: (top, left, isAboveInput) => ` + .tooltip--email__caret { + ${isAboveInput ? `transform: translate(${Math.floor(left)}px, ${Math.floor(top)}px) rotate(180deg); transform-origin: 18px !important;` : `transform: translate(${Math.floor(left)}px, ${Math.floor(top)}px) !important;`} + }`, + css: ``, + setSize: void 0, + remove: () => { + }, + testMode: false, + checkVisibility: true, + hasCaret: false, + isIncontextSignupAvailable: () => false + }; + var HTMLTooltip = class { + /** + * @param config + * @param inputType + * @param getPosition + * @param {HTMLTooltipOptions} options + */ + constructor(config, inputType, getPosition, options) { + __publicField(this, "isAboveInput", false); + /** @type {HTMLTooltipOptions} */ + __publicField(this, "options"); + __publicField(this, "resObs", new ResizeObserver((entries) => entries.forEach(() => this.checkPosition()))); + __publicField(this, "mutObsCheckPositionWhenIdle", whenIdle.call(this, this.checkPosition)); + __publicField(this, "mutObs", new MutationObserver((mutationList) => { + for (const mutationRecord of mutationList) { + if (mutationRecord.type === "childList") { + mutationRecord.addedNodes.forEach((el) => { + if (el.nodeName === "DDG-AUTOFILL") + return; + this.ensureIsLastInDOM(); + }); + } + } + this.mutObsCheckPositionWhenIdle(); + })); + __publicField(this, "clickableButtons", /* @__PURE__ */ new Map()); + this.options = options; + this.shadow = document.createElement("ddg-autofill").attachShadow({ + mode: options.testMode ? "open" : "closed" + }); + this.host = this.shadow.host; + this.config = config; + this.subtype = getSubtypeFromType(inputType); + this.variant = getVariantFromType(inputType); + this.tooltip = null; + this.getPosition = getPosition; + const forcedVisibilityStyles = { + "display": "block", + "visibility": "visible", + "opacity": "1" + }; + addInlineStyles(this.host, forcedVisibilityStyles); + this.count = 0; + this.device = null; + this.transformRules = { + caret: { + getRuleString: this.options.caretPositionClass, + index: null + }, + tooltip: { + getRuleString: this.options.tooltipPositionClass, + index: null + } + }; } - return { - featureToggles: this.featureToggles, - availableInputTypes: this.availableInputTypes, - enabled: this.enabled - }; - } - - /** - * Checks if input type is one which we can't autofill - * @param {{ - * mainType: SupportedMainTypes - * subtype: import('./Form/matching.js').SupportedSubTypes | "unknown" - * }} types - * @returns {boolean} - */ - isTypeUnavailable(_ref) { - let { - mainType, - subtype - } = _ref; - if (mainType === 'unknown') return true; - if (!this.featureToggles[`inputType_${mainType}`] && subtype !== 'emailAddress') { - return true; + get isHidden() { + return this.tooltip.parentNode.hidden; } - return false; - } - - /** - * Requests data from remote - * @returns {Promise<>} - */ - async populateData() { - const availableInputTypesFromRemote = await this.getAvailableInputTypes(); - this.setAvailableInputTypes(availableInputTypesFromRemote); - } - - /** - * Requests data from remote if not available - * @param {{ - * mainType: SupportedMainTypes - * subtype: import('./Form/matching.js').SupportedSubTypes | "unknown" - * }} types - * @returns {Promise} - */ - async populateDataIfNeeded(_ref2) { - let { - mainType, - subtype - } = _ref2; - if (this.isTypeUnavailable({ - mainType, - subtype - })) return false; - if (this.availableInputTypes?.[mainType] === undefined) { - await this.populateData(); - return true; + append() { + document.body.appendChild(this.host); } - return false; - } - - /** - * Checks if items will show in the autofill menu, including in-context signup. - * Triggers side-effect if input types is not already available. - * @param {{ - * mainType: SupportedMainTypes - * subtype: import('./Form/matching.js').SupportedSubTypes | "unknown" - * }} types - * @param {import("./InContextSignup.js").InContextSignup?} inContextSignup - * @returns {boolean} - */ - canAutofillType(_ref3, inContextSignup) { - let { - mainType, - subtype - } = _ref3; - if (this.isTypeUnavailable({ - mainType, - subtype - })) return false; - - // If it's an email field and Email Protection is enabled, return true regardless of other options - const isEmailProtectionEnabled = this.featureToggles.emailProtection && this.availableInputTypes.email; - if (subtype === 'emailAddress' && isEmailProtectionEnabled) { - return true; + remove() { + this.device?.activeForm.resetIconStylesToInitial(); + window.removeEventListener("scroll", this, { capture: true }); + this.resObs.disconnect(); + this.mutObs.disconnect(); + this.lift(); } - if (inContextSignup?.isAvailable(subtype)) { - return true; + lift() { + this.left = null; + this.top = null; + document.body.removeChild(this.host); } - if (subtype === 'fullName') { - return Boolean(this.availableInputTypes.identities?.firstName || this.availableInputTypes.identities?.lastName); + handleEvent(event) { + switch (event.type) { + case "scroll": + this.checkPosition(); + break; + } } - if (subtype === 'expiration') { - return Boolean(this.availableInputTypes.creditCards?.expirationMonth || this.availableInputTypes.creditCards?.expirationYear); + focus(x, y) { + const focusableElements = "button"; + const currentFocusClassName = "currentFocus"; + const currentFocused = this.shadow.querySelectorAll(`.${currentFocusClassName}`); + [...currentFocused].forEach((el) => { + el.classList.remove(currentFocusClassName); + }); + this.shadow.elementFromPoint(x, y)?.closest(focusableElements)?.classList.add(currentFocusClassName); } - return Boolean(this.availableInputTypes[mainType]?.[subtype]); - } - - /** @returns {AutofillFeatureToggles} */ - get featureToggles() { - if (this._featureToggles === null) throw new Error('feature toggles accessed before being set'); - return this._featureToggles; - } - - /** @param {AutofillFeatureToggles} input */ - setFeatureToggles(input) { - this._featureToggles = input; - } - - /** @returns {AvailableInputTypes} */ - get availableInputTypes() { - if (this._availableInputTypes === null) throw new Error('available input types accessed before being set'); - return this._availableInputTypes; - } - - /** @param {AvailableInputTypes} value */ - setAvailableInputTypes(value) { - this._availableInputTypes = { - ...this._availableInputTypes, - ...value - }; - } - static defaults = { - /** @type {AutofillFeatureToggles} */ - featureToggles: { - credentials_saving: false, - password_generation: false, - emailProtection: false, - emailProtection_incontext_signup: false, - inputType_identities: false, - inputType_credentials: false, - inputType_creditCards: false, - inlineIcon_credentials: false - }, - /** @type {AvailableInputTypes} */ - availableInputTypes: { - credentials: { - username: false, - password: false - }, - identities: { - firstName: false, - middleName: false, - lastName: false, - birthdayDay: false, - birthdayMonth: false, - birthdayYear: false, - addressStreet: false, - addressStreet2: false, - addressCity: false, - addressProvince: false, - addressPostalCode: false, - addressCountryCode: false, - phone: false, - emailAddress: false - }, - creditCards: { - cardName: false, - cardSecurityCode: false, - expirationMonth: false, - expirationYear: false, - cardNumber: false - }, - email: false - }, - /** @type {boolean | null} */ - enabled: null - }; - static default(globalConfig, deviceApi) { - const settings = new Settings(globalConfig, deviceApi); - settings.setFeatureToggles(Settings.defaults.featureToggles); - settings.setAvailableInputTypes(Settings.defaults.availableInputTypes); - return settings; - } - - /** @returns {boolean|null} */ - get enabled() { - return this._enabled; - } - - /** - * @param {boolean|null} enabled - */ - setEnabled(enabled) { - this._enabled = enabled; - } -} -exports.Settings = Settings; - -},{"../packages/device-api/index.js":2,"./autofill-utils.js":52,"./deviceApiCalls/__generated__/deviceApiCalls.js":56,"./deviceApiCalls/__generated__/validators.zod.js":57}],41:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.ThirdPartyProvider = void 0; -var _deviceApiCalls = require("./deviceApiCalls/__generated__/deviceApiCalls.js"); -var _validatorsZod = require("./deviceApiCalls/__generated__/validators.zod.js"); -var _matching = require("./Form/matching.js"); -var _index = require("../packages/device-api/index.js"); -class ThirdPartyProvider { - /** - * @param {import("./DeviceInterface/InterfacePrototype").default} device - */ - constructor(device) { - this.device = device; - } - init() { - if (this.device.settings.featureToggles.third_party_credentials_provider) { - if (this.device.globalConfig.hasModernWebkitAPI) { - Object.defineProperty(window, 'providerStatusUpdated', { - enumerable: false, - configurable: false, - writable: false, - value: data => { - this.providerStatusUpdated(data); + checkPosition() { + if (this.animationFrame) { + window.cancelAnimationFrame(this.animationFrame); + } + this.animationFrame = window.requestAnimationFrame(() => { + if (this.isHidden) + return; + const { left, bottom, height, top } = this.getPosition(); + if (left !== this.left || bottom !== this.top) { + const coords = { left, top: bottom }; + this.updatePosition("tooltip", coords); + if (this.options.hasCaret) { + const { top: tooltipTop } = this.tooltip.getBoundingClientRect(); + this.isAboveInput = top > tooltipTop; + const borderWidth = 2; + const caretTop = this.isAboveInput ? coords.top - height - borderWidth : coords.top; + this.updatePosition("caret", { ...coords, top: caretTop }); } - }); - } else { - // On Catalina we poll the native layer - setTimeout(() => this._pollForUpdatesToCredentialsProvider(), 2000); + } + this.animationFrame = null; + }); + } + getOverridePosition({ left, top }) { + const tooltipBoundingBox = this.tooltip.getBoundingClientRect(); + const smallScreenWidth = tooltipBoundingBox.width * 2; + const spacing = 5; + if (tooltipBoundingBox.bottom > window.innerHeight) { + const inputPosition = this.getPosition(); + const caretHeight = 14; + const overriddenTopPosition = top - tooltipBoundingBox.height - inputPosition.height - caretHeight; + if (overriddenTopPosition >= 0) + return { left, top: overriddenTopPosition }; + } + if (tooltipBoundingBox.left < 0 && window.innerWidth <= smallScreenWidth) { + const leftOverflow = Math.abs(tooltipBoundingBox.left); + const leftPosWhenCentered = (window.innerWidth - tooltipBoundingBox.width) / 2; + const overriddenLeftPosition = left + leftOverflow + leftPosWhenCentered; + return { left: overriddenLeftPosition, top }; + } + if (tooltipBoundingBox.left < 0 && window.innerWidth > smallScreenWidth) { + const leftOverflow = Math.abs(tooltipBoundingBox.left); + const overriddenLeftPosition = left + leftOverflow + spacing; + return { left: overriddenLeftPosition, top }; + } + if (tooltipBoundingBox.right > window.innerWidth) { + const rightOverflow = tooltipBoundingBox.right - window.innerWidth; + const overriddenLeftPosition = left - rightOverflow - spacing; + return { left: overriddenLeftPosition, top }; } } - } - async askToUnlockProvider() { - const response = await this.device.deviceApi.request(new _deviceApiCalls.AskToUnlockProviderCall(null)); - this.providerStatusUpdated(response); - } - - /** - * Called by the native layer on all tabs when the provider status is updated - * @param {import("./deviceApiCalls/__generated__/validators-ts").ProviderStatusUpdated} data - */ - providerStatusUpdated(data) { - try { - const { - credentials, - availableInputTypes - } = (0, _index.validate)(data, _validatorsZod.providerStatusUpdatedSchema); - - // Update local settings and data - this.device.settings.setAvailableInputTypes(availableInputTypes); - this.device.storeLocalCredentials(credentials); - - // rerender the tooltip - this.device.uiController?.updateItems(credentials); - if (!this.device.globalConfig.isTopFrame) { - // If the tooltip is open on an autofill type that's not available, close it - const currentInputSubtype = (0, _matching.getSubtypeFromType)(this.device.getCurrentInputType()); - if (!availableInputTypes.credentials?.[currentInputSubtype]) { - this.device.removeTooltip(); + /** + * @param {'tooltip' | 'caret'} element + * @param {{ + * left: number, + * top: number + * }} coords + */ + applyPositionalStyles(element, { left, top }) { + const shadow = this.shadow; + const ruleObj = this.transformRules[element]; + if (ruleObj.index) { + if (shadow.styleSheets[0].rules[ruleObj.index]) { + shadow.styleSheets[0].deleteRule(ruleObj.index); } - // Redecorate fields according to the new types - this.device.scanner.forms.forEach(form => form.recategorizeAllInputs()); + } else { + ruleObj.index = shadow.styleSheets[0].rules.length; } - } catch (e) { - if (this.device.globalConfig.isDDGTestMode) { - console.log('isDDGTestMode: providerStatusUpdated error: ❌', e); + const cssRule = ruleObj.getRuleString?.(top, left, this.isAboveInput); + if (typeof cssRule === "string") { + shadow.styleSheets[0].insertRule(cssRule, ruleObj.index); } } - } - - // Only used on Catalina - async _pollForUpdatesToCredentialsProvider() { - try { - const response = await this.device.deviceApi.request(new _deviceApiCalls.CheckCredentialsProviderStatusCall(null)); - if (response.availableInputTypes.credentialsProviderStatus !== this.device.settings.availableInputTypes.credentialsProviderStatus) { - this.providerStatusUpdated(response); + /** + * @param {'tooltip' | 'caret'} element + * @param {{ + * left: number, + * top: number + * }} coords + */ + updatePosition(element, { left, top }) { + if (!this.shadow.styleSheets.length) { + this.stylesheet?.addEventListener("load", () => this.checkPosition()); + return; } - setTimeout(() => this._pollForUpdatesToCredentialsProvider(), 2000); - } catch (e) { - if (this.device.globalConfig.isDDGTestMode) { - console.log('isDDGTestMode: _pollForUpdatesToCredentialsProvider: ❌', e); + this.left = left; + this.top = top; + this.applyPositionalStyles(element, { left, top }); + if (this.options.hasCaret) { + const overridePosition = this.getOverridePosition({ left, top }); + if (overridePosition) + this.updatePosition(element, overridePosition); + } + } + ensureIsLastInDOM() { + this.count = this.count || 0; + if (document.body.lastElementChild !== this.host) { + if (this.count < 15) { + this.lift(); + this.append(); + this.checkPosition(); + this.count++; + } else { + this.options.remove(); + console.info(`DDG autofill bailing out`); + } + } + } + setActiveButton(e) { + this.activeButton = e.target; + } + unsetActiveButton() { + this.activeButton = null; + } + registerClickableButton(btn, handler) { + this.clickableButtons.set(btn, handler); + btn.addEventListener("mouseenter", (e) => this.setActiveButton(e)); + btn.addEventListener("mouseleave", () => this.unsetActiveButton()); + } + dispatchClick() { + const handler = this.clickableButtons.get(this.activeButton); + if (handler) { + if (this.activeButton.matches(".wrapper:not(.top-autofill) button:hover, .wrapper:not(.top-autofill) a:hover, .currentFocus")) { + safeExecute(this.activeButton, handler, { + checkVisibility: this.options.checkVisibility + }); + } else { + console.warn("The button doesn't seem to be hovered. Please check."); + } + } + } + setupSizeListener() { + const observer = new PerformanceObserver(() => { + this.setSize(); + }); + observer.observe({ entryTypes: ["layout-shift", "paint"] }); + } + setSize() { + const innerNode = this.shadow.querySelector(".wrapper--data"); + if (!innerNode) + return; + const details = { height: innerNode.clientHeight, width: innerNode.clientWidth }; + this.options.setSize?.(details); + } + init() { + this.animationFrame = null; + this.top = 0; + this.left = 0; + this.transformRuleIndex = null; + this.stylesheet = this.shadow.querySelector("link, style"); + this.stylesheet?.addEventListener("load", () => { + Promise.allSettled([ + document.fonts.load("normal 13px 'DDG_ProximaNova'"), + document.fonts.load("bold 13px 'DDG_ProximaNova'") + ]).then(() => { + this.tooltip.parentNode.removeAttribute("hidden"); + this.checkPosition(); + }); + }); + this.append(); + this.resObs.observe(document.body); + this.mutObs.observe(document.body, { childList: true, subtree: true, attributes: true }); + window.addEventListener("scroll", this, { capture: true }); + this.setSize(); + if (typeof this.options.setSize === "function") { + this.setupSizeListener(); } } - } -} -exports.ThirdPartyProvider = ThirdPartyProvider; - -},{"../packages/device-api/index.js":2,"./Form/matching.js":33,"./deviceApiCalls/__generated__/deviceApiCalls.js":56,"./deviceApiCalls/__generated__/validators.zod.js":57}],42:[function(require,module,exports){ -"use strict"; + }; + var HTMLTooltip_default = HTMLTooltip; -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _autofillUtils = require("../autofill-utils.js"); -var _HTMLTooltip = _interopRequireDefault(require("./HTMLTooltip.js")); -var _Credentials = require("../InputTypes/Credentials.js"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -class DataHTMLTooltip extends _HTMLTooltip.default { - renderEmailProtectionIncontextSignup(isOtherItems) { - const dataTypeClass = `tooltip__button--data--identities`; - const providerIconClass = 'tooltip__button--data--duckduckgo'; - return ` - ${isOtherItems ? '
' : ''} + // src/UI/DataHTMLTooltip.js + var DataHTMLTooltip = class extends HTMLTooltip_default { + renderEmailProtectionIncontextSignup(isOtherItems) { + const dataTypeClass = `tooltip__button--data--identities`; + const providerIconClass = "tooltip__button--data--duckduckgo"; + return ` + ${isOtherItems ? "
" : ""} `; - } - - /** - * @param {InputTypeConfigs} config - * @param {TooltipItemRenderer[]} items - * @param {{ - * onSelect(id:string): void - * onManage(type:InputTypeConfigs['type']): void - * onIncontextSignupDismissed?(data: { - * hasOtherOptions: Boolean - * }): void - * onIncontextSignup?(): void - * }} callbacks - */ - render(config, items, callbacks) { - const { - wrapperClass, - css - } = this.options; - const isTopAutofill = wrapperClass?.includes('top-autofill'); - let hasAddedSeparator = false; - // Only show an hr above the first duck address button, but it can be either personal or private - const shouldShowSeparator = (dataId, index) => { - const shouldShow = ['personalAddress', 'privateAddress'].includes(dataId) && !hasAddedSeparator; - if (shouldShow) hasAddedSeparator = true; - - // Don't show the separator if we want to show it, but it's unnecessary as the first item in the menu - const isFirst = index === 0; - return shouldShow && !isFirst; - }; - - // Only show manage Manage… when it's topAutofill, the provider is unlocked, and it's not just EmailProtection - const shouldShowManageButton = isTopAutofill && items.some(item => !['personalAddress', 'privateAddress', _Credentials.PROVIDER_LOCKED].includes(item.id())); - const topClass = wrapperClass || ''; - const dataTypeClass = `tooltip__button--data--${config.type}${this.variant ? '__' + this.variant : ''}`; - this.shadow.innerHTML = ` + } + /** + * @param {InputTypeConfigs} config + * @param {TooltipItemRenderer[]} items + * @param {{ + * onSelect(id:string): void + * onManage(type:InputTypeConfigs['type']): void + * onIncontextSignupDismissed?(data: { + * hasOtherOptions: Boolean + * }): void + * onIncontextSignup?(): void + * }} callbacks + */ + render(config, items, callbacks) { + const { wrapperClass, css } = this.options; + const isTopAutofill = wrapperClass?.includes("top-autofill"); + let hasAddedSeparator = false; + const shouldShowSeparator = (dataId, index) => { + const shouldShow = ["personalAddress", "privateAddress"].includes(dataId) && !hasAddedSeparator; + if (shouldShow) + hasAddedSeparator = true; + const isFirst = index === 0; + return shouldShow && !isFirst; + }; + const shouldShowManageButton = isTopAutofill && items.some((item) => !["personalAddress", "privateAddress", PROVIDER_LOCKED].includes(item.id())); + const topClass = wrapperClass || ""; + const dataTypeClass = `tooltip__button--data--${config.type}${this.variant ? "__" + this.variant : ""}`; + this.shadow.innerHTML = ` ${css} `; + this.wrapper = this.shadow.querySelector(".wrapper"); + this.tooltip = this.shadow.querySelector(".tooltip"); + this.usePersonalButton = this.shadow.querySelector(".js-use-personal"); + this.usePrivateButton = this.shadow.querySelector(".js-use-private"); + this.addressEl = this.shadow.querySelector(".js-address"); + this.updateAddresses = (addresses) => { + if (addresses && this.addressEl) { + this.addresses = addresses; + this.addressEl.textContent = formatDuckAddress(addresses.personalAddress); + } + }; + const firePixel = this.device.firePixel.bind(this.device); + this.registerClickableButton(this.usePersonalButton, () => { + this.fillForm("personalAddress"); + firePixel({ pixelName: "autofill_personal_address" }); + }); + this.registerClickableButton(this.usePrivateButton, () => { + this.fillForm("privateAddress"); + firePixel({ pixelName: "autofill_private_address" }); + }); + this.device.getAddresses().then(this.updateAddresses); + this.init(); + return this; + } + /** + * @param {'personalAddress' | 'privateAddress'} id + */ + async fillForm(id) { + const address = this.addresses[id]; + const formattedAddress = formatDuckAddress(address); + this.device?.selectedDetail({ email: formattedAddress, id }, "email"); + } + }; + var EmailHTMLTooltip_default = EmailHTMLTooltip; -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _HTMLTooltip = _interopRequireDefault(require("./HTMLTooltip.js")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -class EmailSignupHTMLTooltip extends _HTMLTooltip.default { - /** - * @param {import("../DeviceInterface/InterfacePrototype").default} device - */ - render(device) { - this.device = device; - this.shadow.innerHTML = ` + // src/UI/EmailSignupHTMLTooltip.js + var EmailSignupHTMLTooltip = class extends HTMLTooltip_default { + /** + * @param {import("../DeviceInterface/InterfacePrototype").default} device + */ + render(device) { + this.device = device; + this.shadow.innerHTML = ` ${this.options.css} `; - this.tooltip = this.shadow.querySelector('.tooltip'); - this.closeEmailSignup = this.shadow.querySelector('.js-close-email-signup'); - this.registerClickableButton(this.closeEmailSignup, () => { - device.inContextSignup?.onIncontextSignupClosed(); - }); - this.dismissEmailSignup = this.shadow.querySelector('.js-dismiss-email-signup'); - this.registerClickableButton(this.dismissEmailSignup, () => { - device.inContextSignup?.onIncontextSignupDismissed(); - }); - this.getEmailSignup = this.shadow.querySelector('.js-get-email-signup'); - this.registerClickableButton(this.getEmailSignup, () => { - device.inContextSignup?.onIncontextSignup(); - }); - this.init(); - return this; - } -} -var _default = exports.default = EmailSignupHTMLTooltip; - -},{"./HTMLTooltip.js":55}],55:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.defaultOptions = exports.default = exports.HTMLTooltip = void 0; -var _autofillUtils = require("../autofill-utils.js"); -var _matching = require("../Form/matching.js"); -var _styles = require("./styles/styles.js"); -/** - * @typedef {object} HTMLTooltipOptions - * @property {boolean} testMode - * @property {string | null} [wrapperClass] - * @property {(top: number, left: number) => string} [tooltipPositionClass] - * @property {(top: number, left: number, isAboveInput: boolean) => string} [caretPositionClass] - * @property {(details: {height: number, width: number}) => void} [setSize] - if this is set, it will be called initially once + every times the size changes - * @property {() => void} remove - * @property {string} css - * @property {boolean} checkVisibility - * @property {boolean} hasCaret - * @property {() => boolean} isIncontextSignupAvailable - */ - -/** - * @typedef {object} TransformRuleObj - * @property {HTMLTooltipOptions['caretPositionClass']} getRuleString - * @property {number | null} index - */ + this.tooltip = this.shadow.querySelector(".tooltip"); + this.closeEmailSignup = this.shadow.querySelector(".js-close-email-signup"); + this.registerClickableButton(this.closeEmailSignup, () => { + device.inContextSignup?.onIncontextSignupClosed(); + }); + this.dismissEmailSignup = this.shadow.querySelector(".js-dismiss-email-signup"); + this.registerClickableButton(this.dismissEmailSignup, () => { + device.inContextSignup?.onIncontextSignupDismissed(); + }); + this.getEmailSignup = this.shadow.querySelector(".js-get-email-signup"); + this.registerClickableButton(this.getEmailSignup, () => { + device.inContextSignup?.onIncontextSignup(); + }); + this.init(); + return this; + } + }; + var EmailSignupHTMLTooltip_default = EmailSignupHTMLTooltip; -/** @type {HTMLTooltipOptions} */ -const defaultOptions = exports.defaultOptions = { - wrapperClass: '', - tooltipPositionClass: (top, left) => ` - .tooltip { - transform: translate(${Math.floor(left)}px, ${Math.floor(top)}px) !important; - } - `, - caretPositionClass: (top, left, isAboveInput) => ` - .tooltip--email__caret { - ${isAboveInput ? `transform: translate(${Math.floor(left)}px, ${Math.floor(top)}px) rotate(180deg); transform-origin: 18px !important;` : `transform: translate(${Math.floor(left)}px, ${Math.floor(top)}px) !important;`} - }`, - css: ``, - setSize: undefined, - remove: () => {/** noop */}, - testMode: false, - checkVisibility: true, - hasCaret: false, - isIncontextSignupAvailable: () => false -}; -class HTMLTooltip { - isAboveInput = false; - /** @type {HTMLTooltipOptions} */ - options; - /** - * @param config - * @param inputType - * @param getPosition - * @param {HTMLTooltipOptions} options - */ - constructor(config, inputType, getPosition, options) { - this.options = options; - this.shadow = document.createElement('ddg-autofill').attachShadow({ - mode: options.testMode ? 'open' : 'closed' - }); - this.host = this.shadow.host; - this.config = config; - this.subtype = (0, _matching.getSubtypeFromType)(inputType); - this.variant = (0, _matching.getVariantFromType)(inputType); - this.tooltip = null; - this.getPosition = getPosition; - const forcedVisibilityStyles = { - 'display': 'block', - 'visibility': 'visible', - 'opacity': '1' - }; - // @ts-ignore how to narrow this.host to HTMLElement? - (0, _autofillUtils.addInlineStyles)(this.host, forcedVisibilityStyles); - this.count = 0; - this.device = null; + // src/UI/controllers/HTMLTooltipUIController.js + var HTMLTooltipUIController = class extends UIController { /** - * @type {{ - * 'tooltip': TransformRuleObj, - * 'caret': TransformRuleObj - * }} + * @param {HTMLTooltipControllerOptions} options + * @param {Partial} htmlTooltipOptions */ - this.transformRules = { - caret: { - getRuleString: this.options.caretPositionClass, - index: null - }, - tooltip: { - getRuleString: this.options.tooltipPositionClass, - index: null + constructor(options, htmlTooltipOptions = defaultOptions) { + super(); + /** @type {import("../HTMLTooltip.js").HTMLTooltip | null} */ + __publicField(this, "_activeTooltip", null); + /** @type {HTMLTooltipControllerOptions} */ + __publicField(this, "_options"); + /** @type {import('../HTMLTooltip.js').HTMLTooltipOptions} */ + __publicField(this, "_htmlTooltipOptions"); + /** + * Overwritten when calling createTooltip + * @type {import('../../Form/matching').SupportedTypes} + */ + __publicField(this, "_activeInputType", "unknown"); + __publicField(this, "_activeInput"); + __publicField(this, "_activeInputOriginalAutocomplete"); + this._options = options; + this._htmlTooltipOptions = Object.assign({}, defaultOptions, htmlTooltipOptions); + if (options.device.globalConfig.isTopFrame) { + window.addEventListener("pointerup", this, true); + } else { + window.addEventListener("pointerdown", this, true); } - }; - } - get isHidden() { - return this.tooltip.parentNode.hidden; - } - append() { - document.body.appendChild(this.host); - } - remove() { - this.device?.activeForm.resetIconStylesToInitial(); - window.removeEventListener('scroll', this, { - capture: true - }); - this.resObs.disconnect(); - this.mutObs.disconnect(); - this.lift(); - } - lift() { - this.left = null; - this.top = null; - document.body.removeChild(this.host); - } - handleEvent(event) { - switch (event.type) { - case 'scroll': - this.checkPosition(); - break; } - } - focus(x, y) { - const focusableElements = 'button'; - const currentFocusClassName = 'currentFocus'; - const currentFocused = this.shadow.querySelectorAll(`.${currentFocusClassName}`); - [...currentFocused].forEach(el => { - el.classList.remove(currentFocusClassName); - }); - this.shadow.elementFromPoint(x, y)?.closest(focusableElements)?.classList.add(currentFocusClassName); - } - checkPosition() { - if (this.animationFrame) { - window.cancelAnimationFrame(this.animationFrame); + /** + * Cleans up after this UI controller by removing the tooltip and all + * listeners. + */ + destroy() { + this.removeTooltip(); + window.removeEventListener("pointerdown", this, true); + window.removeEventListener("pointerup", this, true); } - this.animationFrame = window.requestAnimationFrame(() => { - if (this.isHidden) return; - const { - left, - bottom, - height, - top - } = this.getPosition(); - if (left !== this.left || bottom !== this.top) { - const coords = { - left, - top: bottom - }; - this.updatePosition('tooltip', coords); - if (this.options.hasCaret) { - // Recalculate tooltip top as it may have changed after update potition above - const { - top: tooltipTop - } = this.tooltip.getBoundingClientRect(); - this.isAboveInput = top > tooltipTop; - const borderWidth = 2; - const caretTop = this.isAboveInput ? coords.top - height - borderWidth : coords.top; - this.updatePosition('caret', { - ...coords, - top: caretTop - }); - } + /** + * @param {import('./UIController').AttachArgs} args + */ + attach(args) { + if (this.getActiveTooltip()) { + return; } - this.animationFrame = null; - }); - } - getOverridePosition(_ref) { - let { - left, - top - } = _ref; - const tooltipBoundingBox = this.tooltip.getBoundingClientRect(); - const smallScreenWidth = tooltipBoundingBox.width * 2; - const spacing = 5; - - // If overflowing from the bottom, move to above the input - if (tooltipBoundingBox.bottom > window.innerHeight) { - const inputPosition = this.getPosition(); - const caretHeight = 14; - const overriddenTopPosition = top - tooltipBoundingBox.height - inputPosition.height - caretHeight; - if (overriddenTopPosition >= 0) return { - left, - top: overriddenTopPosition - }; + const { topContextData, getPosition, input, form } = args; + const tooltip = this.createTooltip(getPosition, topContextData); + this.setActiveTooltip(tooltip); + form.showingTooltip(input); + this._activeInput = input; + this._activeInputOriginalAutocomplete = input.getAttribute("autocomplete"); + input.setAttribute("autocomplete", "off"); } - - // If overflowing from the left on smaller screen, center in the window - if (tooltipBoundingBox.left < 0 && window.innerWidth <= smallScreenWidth) { - const leftOverflow = Math.abs(tooltipBoundingBox.left); - const leftPosWhenCentered = (window.innerWidth - tooltipBoundingBox.width) / 2; - const overriddenLeftPosition = left + leftOverflow + leftPosWhenCentered; - return { - left: overriddenLeftPosition, - top + /** + * Actually create the HTML Tooltip + * @param {PosFn} getPosition + * @param {TopContextData} topContextData + * @return {import("../HTMLTooltip").HTMLTooltip} + */ + createTooltip(getPosition, topContextData) { + this._attachListeners(); + const config = getInputConfigFromType(topContextData.inputType); + this._activeInputType = topContextData.inputType; + const tooltipOptions = { + ...this._htmlTooltipOptions, + remove: () => this.removeTooltip(), + isIncontextSignupAvailable: () => { + const subtype = getSubtypeFromType(topContextData.inputType); + return !!this._options.device.inContextSignup?.isAvailable(subtype); + } }; + if (this._options.tooltipKind === "legacy") { + this._options.device.firePixel({ pixelName: "autofill_show" }); + return new EmailHTMLTooltip_default(config, topContextData.inputType, getPosition, tooltipOptions).render(this._options.device); + } + if (this._options.tooltipKind === "emailsignup") { + this._options.device.firePixel({ pixelName: "incontext_show" }); + return new EmailSignupHTMLTooltip_default(config, topContextData.inputType, getPosition, tooltipOptions).render(this._options.device); + } + const data = this._dataForAutofill(config, topContextData.inputType, topContextData); + const asRenderers = data.map((d) => config.tooltipItem(d)); + return new DataHTMLTooltip_default(config, topContextData.inputType, getPosition, tooltipOptions).render(config, asRenderers, { + onSelect: (id) => { + this._onSelect(topContextData.inputType, data, id); + }, + onManage: (type) => { + this._onManage(type); + }, + onIncontextSignupDismissed: (flags) => { + this._onIncontextSignupDismissed(flags); + }, + onIncontextSignup: () => { + this._onIncontextSignup(); + } + }); } - - // If overflowing from the left on larger screen, move so it's just on screen on the left - if (tooltipBoundingBox.left < 0 && window.innerWidth > smallScreenWidth) { - const leftOverflow = Math.abs(tooltipBoundingBox.left); - const overriddenLeftPosition = left + leftOverflow + spacing; - return { - left: overriddenLeftPosition, - top - }; + updateItems(data) { + if (this._activeInputType === "unknown") + return; + const config = getInputConfigFromType(this._activeInputType); + const asRenderers = data.map((d) => config.tooltipItem(d)); + const activeTooltip = this.getActiveTooltip(); + if (activeTooltip instanceof DataHTMLTooltip_default) { + activeTooltip?.render(config, asRenderers, { + onSelect: (id) => { + this._onSelect(this._activeInputType, data, id); + }, + onManage: (type) => { + this._onManage(type); + }, + onIncontextSignupDismissed: (flags) => { + this._onIncontextSignupDismissed(flags); + }, + onIncontextSignup: () => { + this._onIncontextSignup(); + } + }); + } + setTimeout(() => { + this.getActiveTooltip()?.setSize(); + }, 10); + } + _attachListeners() { + window.addEventListener("input", this); + window.addEventListener("keydown", this, true); + } + _removeListeners() { + window.removeEventListener("input", this); + window.removeEventListener("keydown", this, true); + } + handleEvent(event) { + switch (event.type) { + case "keydown": + if (["Escape", "Tab", "Enter"].includes(event.code)) { + if (event.code === "Escape") { + event.preventDefault(); + event.stopImmediatePropagation(); + } + this.removeTooltip(); + } + break; + case "input": + this.removeTooltip(); + break; + case "pointerdown": { + this._pointerDownListener(event); + break; + } + case "pointerup": { + this._pointerUpListener(event); + break; + } + } } - - // If overflowing from the right, move so it's just on screen on the right - if (tooltipBoundingBox.right > window.innerWidth) { - const rightOverflow = tooltipBoundingBox.right - window.innerWidth; - const overriddenLeftPosition = left - rightOverflow - spacing; - return { - left: overriddenLeftPosition, - top - }; + // Global listener for event delegation + _pointerDownListener(e) { + if (!e.isTrusted) + return; + if (isEventWithinDax(e, e.target)) + return; + if (e.target.nodeName === "DDG-AUTOFILL") { + this._handleClickInTooltip(e); + } else { + this.removeTooltip().catch((e2) => { + console.error("error removing tooltip", e2); + }); + } } - } - - /** - * @param {'tooltip' | 'caret'} element - * @param {{ - * left: number, - * top: number - * }} coords - */ - applyPositionalStyles(element, _ref2) { - let { - left, - top - } = _ref2; - const shadow = this.shadow; - const ruleObj = this.transformRules[element]; - if (ruleObj.index) { - if (shadow.styleSheets[0].rules[ruleObj.index]) { - // If we have already set the rule, remove it… - shadow.styleSheets[0].deleteRule(ruleObj.index); + // Global listener for event delegation + _pointerUpListener(e) { + if (!e.isTrusted) + return; + if (isEventWithinDax(e, e.target)) + return; + if (e.target.nodeName === "DDG-AUTOFILL") { + this._handleClickInTooltip(e); } - } else { - // …otherwise, set the index as the very last rule - ruleObj.index = shadow.styleSheets[0].rules.length; } - const cssRule = ruleObj.getRuleString?.(top, left, this.isAboveInput); - if (typeof cssRule === 'string') { - shadow.styleSheets[0].insertRule(cssRule, ruleObj.index); + _handleClickInTooltip(e) { + e.preventDefault(); + e.stopImmediatePropagation(); + const isMainMouseButton = e.button === 0; + if (!isMainMouseButton) + return; + const activeTooltip = this.getActiveTooltip(); + activeTooltip?.dispatchClick(); + } + async removeTooltip(_via) { + this._htmlTooltipOptions.remove(); + if (this._activeTooltip) { + this._removeListeners(); + this._activeTooltip.remove(); + this._activeTooltip = null; + } + if (this._activeInput) { + if (this._activeInputOriginalAutocomplete) { + this._activeInput.setAttribute("autocomplete", this._activeInputOriginalAutocomplete); + } else { + this._activeInput.removeAttribute("autocomplete"); + } + this._activeInput = null; + this._activeInputOriginalAutocomplete = null; + } + } + /** + * @returns {import("../HTMLTooltip.js").HTMLTooltip|null} + */ + getActiveTooltip() { + return this._activeTooltip; + } + /** + * @param {import("../HTMLTooltip.js").HTMLTooltip} value + */ + setActiveTooltip(value) { + this._activeTooltip = value; } - } + /** + * Collect the data that's needed to populate the Autofill UI. + * + * Note: ideally we'd pass this data instead, so that we didn't have a circular dependency + * + * @param {InputTypeConfigs} config - This is the selected `InputTypeConfig` based on the type of field + * @param {import('../../Form/matching').SupportedTypes} inputType - The input type for the current field + * @param {TopContextData} topContextData + */ + _dataForAutofill(config, inputType, topContextData) { + return this._options.device.dataForAutofill(config, inputType, topContextData); + } + /** + * When a field is selected, call the `onSelect` method from the device. + * + * Note: ideally we'd pass this data instead, so that we didn't have a circular dependency + * + * @param {import('../../Form/matching').SupportedTypes} inputType + * @param {(CreditCardObject | IdentityObject | CredentialsObject)[]} data + * @param {CreditCardObject['id']|IdentityObject['id']|CredentialsObject['id']} id + */ + _onSelect(inputType, data, id) { + return this._options.device.onSelect(inputType, data, id); + } + /** + * Called when clicking on the Manage… button in the html tooltip + * + * @param {SupportedMainTypes} type + * @returns {*} + * @private + */ + _onManage(type) { + this.removeTooltip(); + switch (type) { + case "credentials": + return this._options.device.openManagePasswords(); + case "creditCards": + return this._options.device.openManageCreditCards(); + case "identities": + return this._options.device.openManageIdentities(); + default: + } + } + _onIncontextSignupDismissed({ hasOtherOptions }) { + this._options.device.inContextSignup?.onIncontextSignupDismissed({ shouldHideTooltip: !hasOtherOptions }); + if (hasOtherOptions) { + const topContextData = this._options.device.getTopContextData(); + if (!topContextData) + return; + const config = getInputConfigFromType(topContextData.inputType); + const data = this._dataForAutofill(config, topContextData.inputType, topContextData); + this.updateItems(data); + } + } + _onIncontextSignup() { + this._options.device.inContextSignup?.onIncontextSignup(); + } + isActive() { + return Boolean(this.getActiveTooltip()); + } + }; - /** - * @param {'tooltip' | 'caret'} element - * @param {{ - * left: number, - * top: number - * }} coords - */ - updatePosition(element, _ref3) { - let { - left, - top - } = _ref3; - // If the stylesheet is not loaded wait for load (Chrome bug) - if (!this.shadow.styleSheets.length) { - this.stylesheet?.addEventListener('load', () => this.checkPosition()); - return; + // src/DeviceInterface/ExtensionInterface.js + var TOOLTIP_TYPES = { + EmailProtection: "EmailProtection", + EmailSignup: "EmailSignup" + }; + var ExtensionInterface = class extends InterfacePrototype_default { + constructor() { + super(...arguments); + /** + * Adding this here since only the extension currently supports this + */ + __publicField(this, "inContextSignup", new InContextSignup(this)); } - this.left = left; - this.top = top; - this.applyPositionalStyles(element, { - left, - top - }); - if (this.options.hasCaret) { - const overridePosition = this.getOverridePosition({ - left, - top - }); - if (overridePosition) this.updatePosition(element, overridePosition); + /** + * @override + */ + createUIController() { + const htmlTooltipOptions = { + ...defaultOptions, + css: ``, + testMode: this.isTestMode(), + hasCaret: true + }; + const tooltipKinds = { + [TOOLTIP_TYPES.EmailProtection]: "legacy", + [TOOLTIP_TYPES.EmailSignup]: "emailsignup" + }; + const tooltipKind = tooltipKinds[this.getActiveTooltipType()] || tooltipKinds[TOOLTIP_TYPES.EmailProtection]; + return new HTMLTooltipUIController({ tooltipKind, device: this }, htmlTooltipOptions); } - } - ensureIsLastInDOM() { - this.count = this.count || 0; - // If DDG el is not the last in the doc, move it there - if (document.body.lastElementChild !== this.host) { - // Try up to 15 times to avoid infinite loop in case someone is doing the same - if (this.count < 15) { - this.lift(); - this.append(); - this.checkPosition(); - this.count++; - } else { - // Remove the tooltip from the form to cleanup listeners and observers - this.options.remove(); - console.info(`DDG autofill bailing out`); + getActiveTooltipType() { + if (this.hasLocalAddresses) { + return TOOLTIP_TYPES.EmailProtection; } + const inputType = this.activeForm?.activeInput ? getInputSubtype(this.activeForm.activeInput) : void 0; + if (this.inContextSignup?.isAvailable(inputType)) { + return TOOLTIP_TYPES.EmailSignup; + } + return null; } - } - resObs = new ResizeObserver(entries => entries.forEach(() => this.checkPosition())); - mutObsCheckPositionWhenIdle = _autofillUtils.whenIdle.call(this, this.checkPosition); - mutObs = new MutationObserver(mutationList => { - for (const mutationRecord of mutationList) { - if (mutationRecord.type === 'childList') { - // Only check added nodes - mutationRecord.addedNodes.forEach(el => { - if (el.nodeName === 'DDG-AUTOFILL') return; - this.ensureIsLastInDOM(); - }); + async resetAutofillUI(callback) { + this.removeAutofillUIFromPage("Resetting autofill."); + await this.setupAutofill(); + if (callback) + await callback(); + this.uiController = this.createUIController(); + await this.postInit(); + } + isDeviceSignedIn() { + return this.hasLocalAddresses; + } + async setupAutofill() { + await this.inContextSignup.init(); + return this.getAddresses(); + } + postInit() { + switch (this.getActiveTooltipType()) { + case TOOLTIP_TYPES.EmailProtection: { + this._scannerCleanup = this.scanner.init(); + this.addLogoutListener(() => { + this.resetAutofillUI(); + if (this.globalConfig.isDDGDomain) { + notifyWebApp({ deviceSignedIn: { value: false } }); + } + }); + if (this.activeForm?.activeInput) { + this.attachTooltip({ + form: this.activeForm, + input: this.activeForm?.activeInput, + click: null, + trigger: "postSignup", + triggerMetaData: { + type: "transactional" + } + }); + } + break; + } + case TOOLTIP_TYPES.EmailSignup: { + this._scannerCleanup = this.scanner.init(); + break; + } + default: { + break; + } } } - this.mutObsCheckPositionWhenIdle(); - }); - setActiveButton(e) { - this.activeButton = e.target; - } - unsetActiveButton() { - this.activeButton = null; - } - clickableButtons = new Map(); - registerClickableButton(btn, handler) { - this.clickableButtons.set(btn, handler); - // Needed because clicks within the shadow dom don't provide this info to the outside - btn.addEventListener('mouseenter', e => this.setActiveButton(e)); - btn.addEventListener('mouseleave', () => this.unsetActiveButton()); - } - dispatchClick() { - const handler = this.clickableButtons.get(this.activeButton); - if (handler) { - if (this.activeButton.matches('.wrapper:not(.top-autofill) button:hover, .wrapper:not(.top-autofill) a:hover, .currentFocus')) { - (0, _autofillUtils.safeExecute)(this.activeButton, handler, { - checkVisibility: this.options.checkVisibility - }); - } else { - console.warn('The button doesn\'t seem to be hovered. Please check.'); + getAddresses() { + return new Promise((resolve) => chrome.runtime.sendMessage( + { getAddresses: true }, + (data) => { + this.storeLocalAddresses(data); + return resolve(data); + } + )); + } + /** + * Used by the email web app + * Settings page displays data of the logged in user data + */ + getUserData() { + return new Promise((resolve) => chrome.runtime.sendMessage( + { getUserData: true }, + (data) => resolve(data) + )); + } + /** + * Used by the email web app + * Device capabilities determine which functionality is available to the user + */ + getEmailProtectionCapabilities() { + return new Promise((resolve) => chrome.runtime.sendMessage( + { getEmailProtectionCapabilities: true }, + (data) => resolve(data) + )); + } + refreshAlias() { + return chrome.runtime.sendMessage( + { refreshAlias: true }, + (addresses) => this.storeLocalAddresses(addresses) + ); + } + async trySigningIn() { + if (this.globalConfig.isDDGDomain) { + const data = await sendAndWaitForAnswer(SIGN_IN_MSG, "addUserData"); + this.storeUserData(data); } } - } - setupSizeListener() { - // Listen to layout and paint changes to register the size - const observer = new PerformanceObserver(() => { - this.setSize(); - }); - observer.observe({ - entryTypes: ['layout-shift', 'paint'] - }); - } - setSize() { - const innerNode = this.shadow.querySelector('.wrapper--data'); - // Shouldn't be possible - if (!innerNode) return; - const details = { - height: innerNode.clientHeight, - width: innerNode.clientWidth - }; - this.options.setSize?.(details); - } - init() { - this.animationFrame = null; - this.top = 0; - this.left = 0; - this.transformRuleIndex = null; - this.stylesheet = this.shadow.querySelector('link, style'); - // Un-hide once the style and web fonts have loaded, to avoid flashing - // unstyled content and layout shifts - this.stylesheet?.addEventListener('load', () => { - Promise.allSettled([document.fonts.load("normal 13px 'DDG_ProximaNova'"), document.fonts.load("bold 13px 'DDG_ProximaNova'")]).then(() => { - this.tooltip.parentNode.removeAttribute('hidden'); - this.checkPosition(); - }); - }); - this.append(); - this.resObs.observe(document.body); - this.mutObs.observe(document.body, { - childList: true, - subtree: true, - attributes: true - }); - window.addEventListener('scroll', this, { - capture: true - }); - this.setSize(); - if (typeof this.options.setSize === 'function') { - this.setupSizeListener(); + /** + * @param {object} message + * @param {object} message.addUserData + * @param {string} message.addUserData.token + * @param {string} message.addUserData.userName + * @param {string} message.addUserData.cohort + */ + storeUserData(message) { + return chrome.runtime.sendMessage(message); } - } -} -exports.HTMLTooltip = HTMLTooltip; -var _default = exports.default = HTMLTooltip; - -},{"../Form/matching.js":43,"../autofill-utils.js":62,"./styles/styles.js":61}],56:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.HTMLTooltipUIController = void 0; -var _autofillUtils = require("../../autofill-utils.js"); -var _inputTypeConfig = require("../../Form/inputTypeConfig.js"); -var _matching = require("../../Form/matching.js"); -var _DataHTMLTooltip = _interopRequireDefault(require("../DataHTMLTooltip.js")); -var _EmailHTMLTooltip = _interopRequireDefault(require("../EmailHTMLTooltip.js")); -var _EmailSignupHTMLTooltip = _interopRequireDefault(require("../EmailSignupHTMLTooltip.js")); -var _HTMLTooltip = require("../HTMLTooltip.js"); -var _UIController = require("./UIController.js"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -/** - * @typedef HTMLTooltipControllerOptions - * @property {"modern" | "legacy" | "emailsignup"} tooltipKind - A choice between the newer Autofill UI vs the older ones used in the extension - * @property {import("../../DeviceInterface/InterfacePrototype").default} device - The device interface that's currently running - * regardless of whether this Controller has an open tooltip, or not - */ - -/** - * This encapsulates all the logic relating to showing/hiding the HTML Tooltip - * - * Note: This could be displayed in the current webpage (for example, in the extension) - * or within a webview overlay (like on macOS & upcoming in windows) - */ -class HTMLTooltipUIController extends _UIController.UIController { - /** @type {import("../HTMLTooltip.js").HTMLTooltip | null} */ - _activeTooltip = null; - - /** @type {HTMLTooltipControllerOptions} */ - _options; - - /** @type {import('../HTMLTooltip.js').HTMLTooltipOptions} */ - _htmlTooltipOptions; - - /** - * Overwritten when calling createTooltip - * @type {import('../../Form/matching').SupportedTypes} - */ - _activeInputType = 'unknown'; - - /** - * @param {HTMLTooltipControllerOptions} options - * @param {Partial} htmlTooltipOptions - */ - constructor(options) { - let htmlTooltipOptions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _HTMLTooltip.defaultOptions; - super(); - this._options = options; - this._htmlTooltipOptions = Object.assign({}, _HTMLTooltip.defaultOptions, htmlTooltipOptions); - // Use pointerup to mimic native click behaviour when we're in the top-frame webview - if (options.device.globalConfig.isTopFrame) { - window.addEventListener('pointerup', this, true); - } else { - // Pointerdown is needed here to avoid self-closing modals disappearing because this even happens in the page - window.addEventListener('pointerdown', this, true); + /** + * Used by the email web app + * Provides functionality to log the user out + */ + removeUserData() { + return chrome.runtime.sendMessage({ removeUserData: true }); } - } - _activeInput; - _activeInputOriginalAutocomplete; - - /** - * Cleans up after this UI controller by removing the tooltip and all - * listeners. - */ - destroy() { - this.removeTooltip(); - window.removeEventListener('pointerdown', this, true); - window.removeEventListener('pointerup', this, true); - } - - /** - * @param {import('./UIController').AttachArgs} args - */ - attach(args) { - if (this.getActiveTooltip()) { - return; + addDeviceListeners() { + let activeEl = null; + document.addEventListener("contextmenu", (e) => { + activeEl = e.target; + }); + chrome.runtime.onMessage.addListener((message, sender) => { + if (sender.id !== chrome.runtime.id) + return; + switch (message.type) { + case "ddgUserReady": + this.resetAutofillUI(() => this.setupSettingsPage({ shouldLog: true })); + break; + case "contextualAutofill": + setValue(activeEl, formatDuckAddress(message.alias), this.globalConfig); + activeEl.classList.add("ddg-autofilled"); + this.refreshAlias(); + activeEl.addEventListener( + "input", + (e) => e.target.classList.remove("ddg-autofilled"), + { once: true } + ); + break; + default: + break; + } + }); } - const { - topContextData, - getPosition, - input, - form - } = args; - const tooltip = this.createTooltip(getPosition, topContextData); - this.setActiveTooltip(tooltip); - form.showingTooltip(input); - this._activeInput = input; - this._activeInputOriginalAutocomplete = input.getAttribute('autocomplete'); - input.setAttribute('autocomplete', 'off'); - } - - /** - * Actually create the HTML Tooltip - * @param {PosFn} getPosition - * @param {TopContextData} topContextData - * @return {import("../HTMLTooltip").HTMLTooltip} - */ - createTooltip(getPosition, topContextData) { - this._attachListeners(); - const config = (0, _inputTypeConfig.getInputConfigFromType)(topContextData.inputType); - this._activeInputType = topContextData.inputType; + addLogoutListener(handler) { + if (this._logoutListenerHandler) { + chrome.runtime.onMessage.removeListener(this._logoutListenerHandler); + } + this._logoutListenerHandler = (message, sender) => { + if (sender.id === chrome.runtime.id && message.type === "logout") { + handler(); + } + }; + chrome.runtime.onMessage.addListener(this._logoutListenerHandler); + } + }; + // src/UI/controllers/OverlayUIController.js + var _state; + var OverlayUIController = class extends UIController { + /** + * @param {OverlayControllerOptions} options + */ + constructor(options) { + super(); + /** @type {"idle" | "parentShown"} */ + __privateAdd(this, _state, "idle"); + /** @type {import('../HTMLTooltip.js').HTMLTooltip | null} */ + __publicField(this, "_activeTooltip", null); + /** + * @type {OverlayControllerOptions} + */ + __publicField(this, "_options"); + this._options = options; + window.addEventListener("pointerdown", this, true); + } /** - * @type {import('../HTMLTooltip').HTMLTooltipOptions} + * @param {import('./UIController').AttachArgs} args */ - const tooltipOptions = { - ...this._htmlTooltipOptions, - remove: () => this.removeTooltip(), - isIncontextSignupAvailable: () => { - const subtype = (0, _matching.getSubtypeFromType)(topContextData.inputType); - return !!this._options.device.inContextSignup?.isAvailable(subtype); + attach(args) { + const { getPosition, topContextData, click, input } = args; + if (!input.parentNode) + return; + this._mutObs = new MutationObserver((mutationList) => { + for (const mutationRecord of mutationList) { + mutationRecord.removedNodes.forEach((el) => { + if (el.contains(input)) { + this.removeTooltip("mutation observer"); + } + }); + } + }); + this._mutObs.observe(document.body, { childList: true, subtree: true }); + const position = getPosition(); + if (!click && !this.elementIsInViewport(position)) { + input.scrollIntoView(true); + this._mutObs?.disconnect(); + setTimeout(() => { + this.attach(args); + }, 50); + return; } - }; - if (this._options.tooltipKind === 'legacy') { - this._options.device.firePixel({ - pixelName: 'autofill_show' + __privateSet(this, _state, "parentShown"); + this.showTopTooltip(click, position, topContextData).catch((e) => { + console.error("error from showTopTooltip", e); + __privateSet(this, _state, "idle"); }); - return new _EmailHTMLTooltip.default(config, topContextData.inputType, getPosition, tooltipOptions).render(this._options.device); } - if (this._options.tooltipKind === 'emailsignup') { - this._options.device.firePixel({ - pixelName: 'incontext_show' - }); - return new _EmailSignupHTMLTooltip.default(config, topContextData.inputType, getPosition, tooltipOptions).render(this._options.device); + /** + * @param {{ x: number; y: number; height: number; width: number; }} inputDimensions + * @returns {boolean} + */ + elementIsInViewport(inputDimensions) { + if (inputDimensions.x < 0 || inputDimensions.y < 0 || inputDimensions.x + inputDimensions.width > document.documentElement.clientWidth || inputDimensions.y + inputDimensions.height > document.documentElement.clientHeight) { + return false; + } + const viewport = document.documentElement; + if (inputDimensions.x + inputDimensions.width > viewport.clientWidth || inputDimensions.y + inputDimensions.height > viewport.clientHeight) { + return false; + } + return true; } - - // collect the data for each item to display - const data = this._dataForAutofill(config, topContextData.inputType, topContextData); - - // convert the data into tool tip item renderers - const asRenderers = data.map(d => config.tooltipItem(d)); - - // construct the autofill - return new _DataHTMLTooltip.default(config, topContextData.inputType, getPosition, tooltipOptions).render(config, asRenderers, { - onSelect: id => { - this._onSelect(topContextData.inputType, data, id); - }, - onManage: type => { - this._onManage(type); - }, - onIncontextSignupDismissed: flags => { - this._onIncontextSignupDismissed(flags); - }, - onIncontextSignup: () => { - this._onIncontextSignup(); + /** + * @param {{ x: number; y: number; } | null} click + * @param {{ x: number; y: number; height: number; width: number; }} inputDimensions + * @param {TopContextData} data + */ + async showTopTooltip(click, inputDimensions, data) { + let diffX = inputDimensions.x; + let diffY = inputDimensions.y; + if (click) { + diffX -= click.x; + diffY -= click.y; + } else if (!this.elementIsInViewport(inputDimensions)) { + return; } - }); - } - updateItems(data) { - if (this._activeInputType === 'unknown') return; - const config = (0, _inputTypeConfig.getInputConfigFromType)(this._activeInputType); - - // convert the data into tool tip item renderers - const asRenderers = data.map(d => config.tooltipItem(d)); - const activeTooltip = this.getActiveTooltip(); - if (activeTooltip instanceof _DataHTMLTooltip.default) { - activeTooltip?.render(config, asRenderers, { - onSelect: id => { - this._onSelect(this._activeInputType, data, id); - }, - onManage: type => { - this._onManage(type); - }, - onIncontextSignupDismissed: flags => { - this._onIncontextSignupDismissed(flags); - }, - onIncontextSignup: () => { - this._onIncontextSignup(); + if (!data.inputType) { + throw new Error("No input type found"); + } + const mainType = getMainTypeFromType(data.inputType); + const subType = getSubtypeFromType(data.inputType); + if (mainType === "unknown") { + throw new Error('unreachable, should not be here if (mainType === "unknown")'); + } + const details = { + inputType: data.inputType, + mainType, + subType, + serializedInputContext: JSON.stringify(data), + triggerContext: { + wasFromClick: Boolean(click), + inputTop: Math.floor(diffY), + inputLeft: Math.floor(diffX), + inputHeight: Math.floor(inputDimensions.height), + inputWidth: Math.floor(inputDimensions.width) } - }); + }; + try { + __privateSet(this, _state, "parentShown"); + this._attachListeners(); + await this._options.show(details); + } catch (e) { + console.error("could not show parent", e); + __privateSet(this, _state, "idle"); + } } - // TODO: can we remove this timeout once implemented with real APIs? - // The timeout is needed because clientHeight and clientWidth were returning 0 - setTimeout(() => { - this.getActiveTooltip()?.setSize(); - }, 10); - } - _attachListeners() { - window.addEventListener('input', this); - window.addEventListener('keydown', this, true); - } - _removeListeners() { - window.removeEventListener('input', this); - window.removeEventListener('keydown', this, true); - } - handleEvent(event) { - switch (event.type) { - case 'keydown': - if (['Escape', 'Tab', 'Enter'].includes(event.code)) { - if (event.code === 'Escape') { - event.preventDefault(); - event.stopImmediatePropagation(); + _attachListeners() { + window.addEventListener("scroll", this); + window.addEventListener("keydown", this, true); + window.addEventListener("input", this); + } + _removeListeners() { + window.removeEventListener("scroll", this); + window.removeEventListener("keydown", this, true); + window.removeEventListener("input", this); + } + handleEvent(event) { + switch (event.type) { + case "scroll": { + this.removeTooltip(event.type); + break; + } + case "keydown": { + if (["Escape", "Tab", "Enter"].includes(event.code)) { + if (event.code === "Escape") { + event.preventDefault(); + event.stopImmediatePropagation(); + } + this.removeTooltip(event.type); } - this.removeTooltip(); + break; } - break; - case 'input': - this.removeTooltip(); - break; - case 'pointerdown': - { - this._pointerDownListener(event); + case "input": { + this.removeTooltip(event.type); break; } - case 'pointerup': - { - this._pointerUpListener(event); + case "pointerdown": { + this.removeTooltip(event.type); break; } + } } - } - - // Global listener for event delegation - _pointerDownListener(e) { - if (!e.isTrusted) return; - // Ignore events on the Dax icon, we handle those elsewhere - if ((0, _autofillUtils.isEventWithinDax)(e, e.target)) return; - - // @ts-ignore - if (e.target.nodeName === 'DDG-AUTOFILL') { - this._handleClickInTooltip(e); - } else { - this.removeTooltip().catch(e => { - console.error('error removing tooltip', e); - }); - } - } - - // Global listener for event delegation - _pointerUpListener(e) { - if (!e.isTrusted) return; - // Ignore events on the Dax icon, we handle those elsewhere - if ((0, _autofillUtils.isEventWithinDax)(e, e.target)) return; - - // @ts-ignore - if (e.target.nodeName === 'DDG-AUTOFILL') { - this._handleClickInTooltip(e); - } - } - _handleClickInTooltip(e) { - e.preventDefault(); - e.stopImmediatePropagation(); - const isMainMouseButton = e.button === 0; - if (!isMainMouseButton) return; - const activeTooltip = this.getActiveTooltip(); - activeTooltip?.dispatchClick(); - } - async removeTooltip(_via) { - this._htmlTooltipOptions.remove(); - if (this._activeTooltip) { + /** + * @param {string} trigger + * @returns {Promise} + */ + async removeTooltip(trigger) { + if (trigger !== "pointerdown") { + if (__privateGet(this, _state) !== "parentShown") { + return; + } + } + try { + await this._options.remove(); + } catch (e) { + console.error("Could not close parent", e); + } + __privateSet(this, _state, "idle"); this._removeListeners(); - this._activeTooltip.remove(); - this._activeTooltip = null; + this._mutObs?.disconnect(); } - if (this._activeInput) { - if (this._activeInputOriginalAutocomplete) { - this._activeInput.setAttribute('autocomplete', this._activeInputOriginalAutocomplete); - } else { - this._activeInput.removeAttribute('autocomplete'); - } - this._activeInput = null; - this._activeInputOriginalAutocomplete = null; + isActive() { + return __privateGet(this, _state) === "parentShown"; } - } - - /** - * @returns {import("../HTMLTooltip.js").HTMLTooltip|null} - */ - getActiveTooltip() { - return this._activeTooltip; - } - - /** - * @param {import("../HTMLTooltip.js").HTMLTooltip} value - */ - setActiveTooltip(value) { - this._activeTooltip = value; - } - - /** - * Collect the data that's needed to populate the Autofill UI. - * - * Note: ideally we'd pass this data instead, so that we didn't have a circular dependency - * - * @param {InputTypeConfigs} config - This is the selected `InputTypeConfig` based on the type of field - * @param {import('../../Form/matching').SupportedTypes} inputType - The input type for the current field - * @param {TopContextData} topContextData - */ - _dataForAutofill(config, inputType, topContextData) { - return this._options.device.dataForAutofill(config, inputType, topContextData); - } - - /** - * When a field is selected, call the `onSelect` method from the device. - * - * Note: ideally we'd pass this data instead, so that we didn't have a circular dependency - * - * @param {import('../../Form/matching').SupportedTypes} inputType - * @param {(CreditCardObject | IdentityObject | CredentialsObject)[]} data - * @param {CreditCardObject['id']|IdentityObject['id']|CredentialsObject['id']} id - */ - _onSelect(inputType, data, id) { - return this._options.device.onSelect(inputType, data, id); - } + }; + _state = new WeakMap(); - /** - * Called when clicking on the Manage… button in the html tooltip - * - * @param {SupportedMainTypes} type - * @returns {*} - * @private - */ - _onManage(type) { - this.removeTooltip(); - switch (type) { - case 'credentials': - return this._options.device.openManagePasswords(); - case 'creditCards': - return this._options.device.openManageCreditCards(); - case 'identities': - return this._options.device.openManageIdentities(); - default: - // noop + // src/deviceApiCalls/additionalDeviceApiCalls.js + var GetAlias = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "emailHandlerGetAlias"); + __publicField(this, "id", "n/a"); + __publicField(this, "paramsValidator", getAliasParamsSchema); + __publicField(this, "resultValidator", getAliasResultSchema); } - } - - _onIncontextSignupDismissed(_ref) { - let { - hasOtherOptions - } = _ref; - this._options.device.inContextSignup?.onIncontextSignupDismissed({ - shouldHideTooltip: !hasOtherOptions - }); - - // If there are other options available, just force a re-render - if (hasOtherOptions) { - const topContextData = this._options.device.getTopContextData(); - if (!topContextData) return; - const config = (0, _inputTypeConfig.getInputConfigFromType)(topContextData.inputType); - const data = this._dataForAutofill(config, topContextData.inputType, topContextData); - this.updateItems(data); + preResultValidation(response) { + return { success: response }; } - } - _onIncontextSignup() { - this._options.device.inContextSignup?.onIncontextSignup(); - } - isActive() { - return Boolean(this.getActiveTooltip()); - } -} -exports.HTMLTooltipUIController = HTMLTooltipUIController; - -},{"../../Form/inputTypeConfig.js":38,"../../Form/matching.js":43,"../../autofill-utils.js":62,"../DataHTMLTooltip.js":52,"../EmailHTMLTooltip.js":53,"../EmailSignupHTMLTooltip.js":54,"../HTMLTooltip.js":55,"./UIController.js":59}],57:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.NativeUIController = void 0; -var _UIController = require("./UIController.js"); -var _matching = require("../../Form/matching.js"); -var _deviceApiCalls = require("../../deviceApiCalls/__generated__/deviceApiCalls.js"); -var _Credentials = require("../../InputTypes/Credentials.js"); -/** - * `NativeController` should be used in situations where you DO NOT - * want any Autofill-controlled user interface. - * - * Examples are with iOS/Android, where 'attaching' only means - * messaging a native layer to show a native tooltip. - * - * @example - * - * ```javascript - * const controller = new NativeController(); - * controller.attach(...); - * ``` - */ -class NativeUIController extends _UIController.UIController { - /** - * Keep track of when passwords were suggested/rejected/accepted etc - * State is kept here because it's specific to the interactions on mobile (eg: NativeUIController) - * - * @type {"default" | "rejected"} - */ - #passwordStatus = 'default'; + }; - /** - * @param {import('./UIController').AttachArgs} args - */ - attach(args) { - const { - form, - input, - device, - trigger, - triggerMetaData, - topContextData - } = args; - const inputType = (0, _matching.getInputType)(input); - const mainType = (0, _matching.getMainTypeFromType)(inputType); - const subType = (0, _matching.getSubtypeFromType)(inputType); - if (mainType === 'unknown') { - throw new Error('unreachable, should not be here if (mainType === "unknown")'); - } - if (trigger === 'autoprompt') { - window.scrollTo({ - behavior: 'smooth', - top: form.form.getBoundingClientRect().top - document.body.getBoundingClientRect().top - 50 - }); + // src/ThirdPartyProvider.js + var ThirdPartyProvider = class { + /** + * @param {import("./DeviceInterface/InterfacePrototype").default} device + */ + constructor(device) { + this.device = device; + } + init() { + if (this.device.settings.featureToggles.third_party_credentials_provider) { + if (this.device.globalConfig.hasModernWebkitAPI) { + Object.defineProperty(window, "providerStatusUpdated", { + enumerable: false, + configurable: false, + writable: false, + value: (data) => { + this.providerStatusUpdated(data); + } + }); + } else { + setTimeout(() => this._pollForUpdatesToCredentialsProvider(), 2e3); + } + } } - - /** @type {import('../../deviceApiCalls/__generated__/validators-ts').GetAutofillDataRequest} */ - let payload = { - inputType, - mainType, - subType, - trigger - }; - - // append generated password if enabled - if (device.settings.featureToggles.password_generation) { - payload = this.appendGeneratedPassword(topContextData, payload, triggerMetaData); + async askToUnlockProvider() { + const response = await this.device.deviceApi.request(new AskToUnlockProviderCall(null)); + this.providerStatusUpdated(response); } - device.deviceApi.request(new _deviceApiCalls.GetAutofillDataCall(payload)).then(resp => { - switch (resp.action) { - case 'fill': - { - if (mainType in resp) { - form.autofillData(resp[mainType], mainType); - } else { - throw new Error(`action: "fill" cannot occur because "${mainType}" was missing`); - } - break; - } - case 'focus': - { - form.activeInput?.focus(); - break; - } - case 'acceptGeneratedPassword': - { - form.autofillData({ - password: topContextData.credentials?.[0].password, - [_Credentials.AUTOGENERATED_KEY]: true - }, mainType); - break; - } - case 'rejectGeneratedPassword': - { - this.#passwordStatus = 'rejected'; - form.touchAllInputs('credentials'); - form.activeInput?.focus(); - break; - } - default: - { - if (args.device.isTestMode()) { - console.warn('response not handled', resp); - } + /** + * Called by the native layer on all tabs when the provider status is updated + * @param {import("./deviceApiCalls/__generated__/validators-ts").ProviderStatusUpdated} data + */ + providerStatusUpdated(data) { + try { + const { credentials, availableInputTypes } = validate(data, providerStatusUpdatedSchema); + this.device.settings.setAvailableInputTypes(availableInputTypes); + this.device.storeLocalCredentials(credentials); + this.device.uiController?.updateItems(credentials); + if (!this.device.globalConfig.isTopFrame) { + const currentInputSubtype = getSubtypeFromType(this.device.getCurrentInputType()); + if (!availableInputTypes.credentials?.[currentInputSubtype]) { + this.device.removeTooltip(); } + this.device.scanner.forms.forEach((form) => form.recategorizeAllInputs()); + } + } catch (e) { + if (this.device.globalConfig.isDDGTestMode) { + console.log("isDDGTestMode: providerStatusUpdated error: \u274C", e); + } } - }).catch(e => { - console.error('NativeTooltip::device.getAutofillData(payload)'); - console.error(e); - }); - } - - /** - * If a password exists in `topContextData`, we can append it to the outgoing data - * in a way that native platforms can easily understand. - * - * @param {TopContextData} topContextData - * @param {import('../../deviceApiCalls/__generated__/validators-ts.js').GetAutofillDataRequest} outgoingData - * @param {import('../../UI/controllers/UIController.js').AttachArgs['triggerMetaData']} triggerMetaData - * @return {import('../../deviceApiCalls/__generated__/validators-ts.js').GetAutofillDataRequest} - */ - appendGeneratedPassword(topContextData, outgoingData, triggerMetaData) { - const autoGeneratedCredential = topContextData.credentials?.find(credential => credential.autogenerated); - - // if there's no generated password, we don't need to do anything - if (!autoGeneratedCredential?.password) { - return outgoingData; } - function suggestPassword() { - if (!autoGeneratedCredential?.password) throw new Error('unreachable'); - return { - ...outgoingData, - generatedPassword: { - value: autoGeneratedCredential.password, - username: autoGeneratedCredential.username + // Only used on Catalina + async _pollForUpdatesToCredentialsProvider() { + try { + const response = await this.device.deviceApi.request(new CheckCredentialsProviderStatusCall(null)); + if (response.availableInputTypes.credentialsProviderStatus !== this.device.settings.availableInputTypes.credentialsProviderStatus) { + this.providerStatusUpdated(response); + } + setTimeout(() => this._pollForUpdatesToCredentialsProvider(), 2e3); + } catch (e) { + if (this.device.globalConfig.isDDGTestMode) { + console.log("isDDGTestMode: _pollForUpdatesToCredentialsProvider: \u274C", e); } - }; - } - - // for explicit opt-in, we should *always* append the password - // this can occur when the user clicks icon directly - in that instance we ignore - // any internal state and just append the password to the outgoing data - if (triggerMetaData.type === 'explicit-opt-in') { - return suggestPassword(); - } - - // When the opt-in is 'implicit' though we only append the password if the user has not previously rejected it. - // This helps the situation where the user has rejected a password for the username field, but then - // taps into the confirm password field - if (triggerMetaData.type === 'implicit-opt-in' && this.#passwordStatus !== 'rejected') { - return suggestPassword(); - } - - // if we get here there's nothing to do - return outgoingData; - } -} -exports.NativeUIController = NativeUIController; - -},{"../../Form/matching.js":43,"../../InputTypes/Credentials.js":45,"../../deviceApiCalls/__generated__/deviceApiCalls.js":66,"./UIController.js":59}],58:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.OverlayUIController = void 0; -var _UIController = require("./UIController.js"); -var _matching = require("../../Form/matching.js"); -/** - * @typedef {import('../../deviceApiCalls/__generated__/validators-ts').GetAutofillDataRequest} GetAutofillDataRequest - * @typedef {import('../../deviceApiCalls/__generated__/validators-ts').TriggerContext} TriggerContext - * - * @typedef OverlayControllerOptions - * @property {() => Promise} remove - A callback that will be fired when the tooltip should be removed - * @property {(details: GetAutofillDataRequest) => Promise} show - A callback that will be fired when the tooltip should be shown - */ - -/** - * Use this `OverlayController` when you want to control an overlay, but don't have - * your own UI to display. - * - * For example, on macOS this `OverlayController` would run in the main webpage - * and would then signal to its native side when the overlay should show/close - * - * @example `show` and `remove` can be implemented to match your native side's messaging needs - * - * ```javascript - * const controller = new OverlayController({ - * remove: async () => this.closeAutofillParent(), - * show: async (details) => this.show(details), - * onPointerDown: (e) => this.onPointerDown(e) - * }) - * - * controller.attach(...) - * ``` - */ -class OverlayUIController extends _UIController.UIController { - /** @type {"idle" | "parentShown"} */ - #state = 'idle'; - - /** @type {import('../HTMLTooltip.js').HTMLTooltip | null} */ - _activeTooltip = null; - - /** - * @type {OverlayControllerOptions} - */ - _options; - - /** - * @param {OverlayControllerOptions} options - */ - constructor(options) { - super(); - this._options = options; - - // We always register this 'pointerdown' event, regardless of - // whether we have a tooltip currently open or not. This is to ensure - // we can clear out any existing state before opening a new one. - window.addEventListener('pointerdown', this, true); - } - - /** - * @param {import('./UIController').AttachArgs} args - */ - attach(args) { - const { - getPosition, - topContextData, - click, - input - } = args; - - // Do not attach the tooltip if the input is not in the DOM - if (!input.parentNode) return; - - // If the input is removed from the DOM while the tooltip is attached, remove it - this._mutObs = new MutationObserver(mutationList => { - for (const mutationRecord of mutationList) { - mutationRecord.removedNodes.forEach(el => { - if (el.contains(input)) { - this.removeTooltip('mutation observer'); - } - }); } - }); - this._mutObs.observe(document.body, { - childList: true, - subtree: true - }); - const position = getPosition(); - - // If the element is not in viewport, scroll there and recurse. 50ms is arbitrary - if (!click && !this.elementIsInViewport(position)) { - input.scrollIntoView(true); - this._mutObs?.disconnect(); - setTimeout(() => { - this.attach(args); - }, 50); - return; } - this.#state = 'parentShown'; - this.showTopTooltip(click, position, topContextData).catch(e => { - console.error('error from showTopTooltip', e); - this.#state = 'idle'; - }); - } + }; - /** - * @param {{ x: number; y: number; height: number; width: number; }} inputDimensions - * @returns {boolean} - */ - elementIsInViewport(inputDimensions) { - if (inputDimensions.x < 0 || inputDimensions.y < 0 || inputDimensions.x + inputDimensions.width > document.documentElement.clientWidth || inputDimensions.y + inputDimensions.height > document.documentElement.clientHeight) { - return false; + // src/DeviceInterface/AppleDeviceInterface.js + var AppleDeviceInterface = class extends InterfacePrototype_default { + constructor() { + super(...arguments); + __publicField(this, "inContextSignup", new InContextSignup(this)); + /** @override */ + __publicField(this, "initialSetupDelayMs", 300); + __publicField(this, "thirdPartyProvider", new ThirdPartyProvider(this)); + /** @type {any} */ + __publicField(this, "pollingTimeout", null); } - const viewport = document.documentElement; - if (inputDimensions.x + inputDimensions.width > viewport.clientWidth || inputDimensions.y + inputDimensions.height > viewport.clientHeight) { - return false; + /** + * The default functionality of this class is to operate as an 'overlay controller' - + * which means it's purpose is to message the native layer about when to open/close the overlay. + * + * There is an additional use-case though, when running on older macOS versions, we just display the + * HTMLTooltip in-page (like the extension does). This is why the `!this.globalConfig.supportsTopFrame` + * check exists below - if we know we don't support the overlay, we fall back to in-page. + * + * @override + * @returns {import("../UI/controllers/UIController.js").UIController} + */ + createUIController() { + if (this.globalConfig.userPreferences?.platform?.name === "ios") { + return new NativeUIController(); + } + if (!this.globalConfig.supportsTopFrame) { + const options = { + ...defaultOptions, + testMode: this.isTestMode() + }; + return new HTMLTooltipUIController({ + device: this, + tooltipKind: "modern" + }, options); + } + return new OverlayUIController({ + remove: async () => this._closeAutofillParent(), + show: async (details) => this._show(details) + }); } - return true; - } - - /** - * @param {{ x: number; y: number; } | null} click - * @param {{ x: number; y: number; height: number; width: number; }} inputDimensions - * @param {TopContextData} data - */ - async showTopTooltip(click, inputDimensions, data) { - let diffX = inputDimensions.x; - let diffY = inputDimensions.y; - if (click) { - diffX -= click.x; - diffY -= click.y; - } else if (!this.elementIsInViewport(inputDimensions)) { - // If the focus event is outside the viewport ignore, we've already tried to scroll to it - return; + /** + * For now, this could be running + * 1) on iOS + * 2) on macOS + Overlay + * 3) on macOS + in-page HTMLTooltip + * + * @override + * @returns {Promise} + */ + async setupAutofill() { + if (!this.globalConfig.supportsTopFrame) { + await this._getAutofillInitData(); + } + await this.inContextSignup.init(); + const signedIn = await this._checkDeviceSignedIn(); + if (signedIn) { + if (this.globalConfig.isApp) { + await this.getAddresses(); + } + } } - if (!data.inputType) { - throw new Error('No input type found'); + /** + * Used by the email web app + * Settings page displays data of the logged in user data + */ + getUserData() { + return this.deviceApi.request(createRequest("emailHandlerGetUserData")); } - const mainType = (0, _matching.getMainTypeFromType)(data.inputType); - const subType = (0, _matching.getSubtypeFromType)(data.inputType); - if (mainType === 'unknown') { - throw new Error('unreachable, should not be here if (mainType === "unknown")'); + /** + * Used by the email web app + * Device capabilities determine which functionality is available to the user + */ + getEmailProtectionCapabilities() { + return this.deviceApi.request(createRequest("emailHandlerGetCapabilities")); } - - /** @type {GetAutofillDataRequest} */ - const details = { - inputType: data.inputType, - mainType, - subType, - serializedInputContext: JSON.stringify(data), - triggerContext: { - wasFromClick: Boolean(click), - inputTop: Math.floor(diffY), - inputLeft: Math.floor(diffX), - inputHeight: Math.floor(inputDimensions.height), - inputWidth: Math.floor(inputDimensions.width) - } - }; - try { - this.#state = 'parentShown'; - this._attachListeners(); - await this._options.show(details); - } catch (e) { - console.error('could not show parent', e); - this.#state = 'idle'; + /** + */ + async getSelectedCredentials() { + return this.deviceApi.request(createRequest("getSelectedCredentials")); } - } - _attachListeners() { - window.addEventListener('scroll', this); - window.addEventListener('keydown', this, true); - window.addEventListener('input', this); - } - _removeListeners() { - window.removeEventListener('scroll', this); - window.removeEventListener('keydown', this, true); - window.removeEventListener('input', this); - } - handleEvent(event) { - switch (event.type) { - case 'scroll': - { - this.removeTooltip(event.type); - break; - } - case 'keydown': - { - if (['Escape', 'Tab', 'Enter'].includes(event.code)) { - if (event.code === 'Escape') { - event.preventDefault(); - event.stopImmediatePropagation(); - } - this.removeTooltip(event.type); - } - break; - } - case 'input': - { - this.removeTooltip(event.type); - break; - } - case 'pointerdown': - { - this.removeTooltip(event.type); - break; + /** + * The data format provided here for `parentArgs` matches Window now. + * @param {GetAutofillDataRequest} parentArgs + */ + async _showAutofillParent(parentArgs) { + const applePayload = { + ...parentArgs.triggerContext, + serializedInputContext: parentArgs.serializedInputContext + }; + return this.deviceApi.notify(createNotification("showAutofillParent", applePayload)); + } + /** + * @returns {Promise} + */ + async _closeAutofillParent() { + return this.deviceApi.notify(createNotification("closeAutofillParent", {})); + } + /** + * @param {GetAutofillDataRequest} details + */ + async _show(details) { + await this._showAutofillParent(details); + this._listenForSelectedCredential(async (response) => { + if (!response) + return; + if ("configType" in response) { + this.selectedDetail(response.data, response.configType); + } else if ("stop" in response) { + await this.onFinishedAutofill(); + } else if ("stateChange" in response) { + await this.updateForStateChange(); } + }); } - } - - /** - * @param {string} trigger - * @returns {Promise} - */ - async removeTooltip(trigger) { - // for none pointer events, check to see if the tooltip is open before trying to close it - if (trigger !== 'pointerdown') { - if (this.#state !== 'parentShown') { - return; - } + async refreshData() { + await super.refreshData(); + await this._checkDeviceSignedIn(); } - try { - await this._options.remove(); - } catch (e) { - console.error('Could not close parent', e); + async getAddresses() { + if (!this.globalConfig.isApp) + return this.getAlias(); + const { addresses } = await this.deviceApi.request(createRequest("emailHandlerGetAddresses")); + this.storeLocalAddresses(addresses); + return addresses; } - this.#state = 'idle'; - this._removeListeners(); - this._mutObs?.disconnect(); - } - isActive() { - return this.#state === 'parentShown'; - } -} -exports.OverlayUIController = OverlayUIController; - -},{"../../Form/matching.js":43,"./UIController.js":59}],59:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.UIController = void 0; -/** - * @typedef AttachArgs The argument required to 'attach' a tooltip - * @property {import("../../Form/Form").Form} form the Form that triggered this 'attach' call - * @property {HTMLInputElement} input the input field that triggered this 'attach' call - * @property {() => { x: number; y: number; height: number; width: number; }} getPosition A function that provides positioning information - * @property {{x: number, y: number}|null} click The click positioning - * @property {TopContextData} topContextData - * @property {import("../../DeviceInterface/InterfacePrototype").default} device - * @property {import('../../deviceApiCalls/__generated__/validators-ts').GetAutofillDataRequest['trigger']} trigger - * @property {{type: 'explicit-opt-in' | 'implicit-opt-in' | 'transactional'}} triggerMetaData - metadata about the trigger, used to make client-side decisions - */ - -/** - * This is the base interface that `UIControllers` should extend/implement - */ -class UIController { - /** - * Implement this method to control what happen when Autofill - * has enough information to 'attach' a tooltip. - * - * @param {AttachArgs} _args - * @returns {void} - */ - attach(_args) { - throw new Error('must implement attach'); - } - /** - * Implement this if your tooltip can be created from positioning - * + topContextData. - * - * For example, in an 'overlay' on macOS/Windows this is needed since - * there's no page information to call 'attach' above. - * - * @param {PosFn} _pos - * @param {TopContextData} _topContextData - * @returns {any | null} - */ - createTooltip(_pos, _topContextData) {} - /** - * @param {string} _via - */ - removeTooltip(_via) {} - - /** - * Set the currently open HTMLTooltip instance - * - * @param {import("../HTMLTooltip.js").HTMLTooltip} _tooltip - */ - setActiveTooltip(_tooltip) {} - - /** - * Get the currently open HTMLTooltip instance, if one exists - * - * @returns {import("../HTMLTooltip.js").HTMLTooltip | null} - */ - getActiveTooltip() { - return null; - } - - /** - * Indicate whether the controller deems itself 'active' - * - * @returns {boolean} - */ - isActive() { - return false; - } - - /** - * Updates the items in the tooltip based on new data. Currently only supporting credentials. - * @param {CredentialsObject[]} _data - */ - updateItems(_data) {} - destroy() {} -} -exports.UIController = UIController; - -},{}],60:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.ddgPasswordIconFocused = exports.ddgPasswordIconFilled = exports.ddgPasswordIconBaseWhite = exports.ddgPasswordIconBase = exports.ddgPasswordGenIconFilled = exports.ddgPasswordGenIconBase = exports.ddgIdentityIconBase = exports.ddgCcIconFilled = exports.ddgCcIconBase = void 0; -const ddgPasswordIconBase = exports.ddgPasswordIconBase = 'data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNS4zMzQgNi42NjdhMiAyIDAgMSAwIDAgNCAyIDIgMCAwIDAgMC00Wm0tLjY2NyAyYS42NjcuNjY3IDAgMSAxIDEuMzMzIDAgLjY2Ny42NjcgMCAwIDEtMS4zMzMgMFoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTQuNjY3IDRhNS4zMzMgNS4zMzMgMCAwIDAtNS4xODggNi41NzhsLTUuMjg0IDUuMjg0YS42NjcuNjY3IDAgMCAwLS4xOTUuNDcxdjNjMCAuMzY5LjI5OC42NjcuNjY3LjY2N2gyLjY2NmMuNzM3IDAgMS4zMzQtLjU5NyAxLjMzNC0xLjMzM1YxOGguNjY2Yy43MzcgMCAxLjMzNC0uNTk3IDEuMzM0LTEuMzMzdi0xLjMzNEgxMmMuMTc3IDAgLjM0Ni0uMDcuNDcxLS4xOTVsLjY4OC0uNjg4QTUuMzMzIDUuMzMzIDAgMSAwIDE0LjY2NyA0Wm0tNCA1LjMzM2E0IDQgMCAxIDEgMi41NTUgMy43MzIuNjY3LjY2NyAwIDAgMC0uNzEzLjE1bC0uNzg1Ljc4NUgxMGEuNjY3LjY2NyAwIDAgMC0uNjY3LjY2N3YySDhhLjY2Ny42NjcgMCAwIDAtLjY2Ny42NjZ2MS4zMzRoLTJ2LTIuMDU4bDUuMzY1LTUuMzY0YS42NjcuNjY3IDAgMCAwIC4xNjMtLjY3NyAzLjk5NiAzLjk5NiAwIDAgMS0uMTk0LTEuMjM1WiIgY2xpcC1ydWxlPSJldmVub2RkIi8+Cjwvc3ZnPgo='; -const ddgPasswordIconBaseWhite = exports.ddgPasswordIconBaseWhite = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMjRweCIgaGVpZ2h0PSIyNHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8dGl0bGU+ZGRnLXBhc3N3b3JkLWljb24tYmFzZS13aGl0ZTwvdGl0bGU+CiAgICA8ZyBpZD0iZGRnLXBhc3N3b3JkLWljb24tYmFzZS13aGl0ZSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgaWQ9IlVuaW9uIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0LjAwMDAwMCwgNC4wMDAwMDApIiBmaWxsPSIjRkZGRkZGIj4KICAgICAgICAgICAgPHBhdGggZD0iTTExLjMzMzMsMi42NjY2NyBDMTAuMjI4OCwyLjY2NjY3IDkuMzMzMzMsMy41NjIxIDkuMzMzMzMsNC42NjY2NyBDOS4zMzMzMyw1Ljc3MTI0IDEwLjIyODgsNi42NjY2NyAxMS4zMzMzLDYuNjY2NjcgQzEyLjQzNzksNi42NjY2NyAxMy4zMzMzLDUuNzcxMjQgMTMuMzMzMyw0LjY2NjY3IEMxMy4zMzMzLDMuNTYyMSAxMi40Mzc5LDIuNjY2NjcgMTEuMzMzMywyLjY2NjY3IFogTTEwLjY2NjcsNC42NjY2NyBDMTAuNjY2Nyw0LjI5ODQ4IDEwLjk2NTEsNCAxMS4zMzMzLDQgQzExLjcwMTUsNCAxMiw0LjI5ODQ4IDEyLDQuNjY2NjcgQzEyLDUuMDM0ODYgMTEuNzAxNSw1LjMzMzMzIDExLjMzMzMsNS4zMzMzMyBDMTAuOTY1MSw1LjMzMzMzIDEwLjY2NjcsNS4wMzQ4NiAxMC42NjY3LDQuNjY2NjcgWiIgaWQ9IlNoYXBlIj48L3BhdGg+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0xMC42NjY3LDAgQzcuNzIxMTUsMCA1LjMzMzMzLDIuMzg3ODEgNS4zMzMzMyw1LjMzMzMzIEM1LjMzMzMzLDUuNzYxMTkgNS4zODM4NSw2LjE3Nzk4IDUuNDc5NDUsNi41Nzc3NSBMMC4xOTUyNjIsMTEuODYxOSBDMC4wNzAyMzc5LDExLjk4NyAwLDEyLjE1NjUgMCwxMi4zMzMzIEwwLDE1LjMzMzMgQzAsMTUuNzAxNSAwLjI5ODQ3NywxNiAwLjY2NjY2NywxNiBMMy4zMzMzMywxNiBDNC4wNjk3MSwxNiA0LjY2NjY3LDE1LjQwMyA0LjY2NjY3LDE0LjY2NjcgTDQuNjY2NjcsMTQgTDUuMzMzMzMsMTQgQzYuMDY5NzEsMTQgNi42NjY2NywxMy40MDMgNi42NjY2NywxMi42NjY3IEw2LjY2NjY3LDExLjMzMzMgTDgsMTEuMzMzMyBDOC4xNzY4MSwxMS4zMzMzIDguMzQ2MzgsMTEuMjYzMSA4LjQ3MTQxLDExLjEzODEgTDkuMTU5MDYsMTAuNDUwNCBDOS42Mzc3MiwxMC41OTEyIDEwLjE0MzksMTAuNjY2NyAxMC42NjY3LDEwLjY2NjcgQzEzLjYxMjIsMTAuNjY2NyAxNiw4LjI3ODg1IDE2LDUuMzMzMzMgQzE2LDIuMzg3ODEgMTMuNjEyMiwwIDEwLjY2NjcsMCBaIE02LjY2NjY3LDUuMzMzMzMgQzYuNjY2NjcsMy4xMjQxOSA4LjQ1NzUzLDEuMzMzMzMgMTAuNjY2NywxLjMzMzMzIEMxMi44NzU4LDEuMzMzMzMgMTQuNjY2NywzLjEyNDE5IDE0LjY2NjcsNS4zMzMzMyBDMTQuNjY2Nyw3LjU0MjQ3IDEyLjg3NTgsOS4zMzMzMyAxMC42NjY3LDkuMzMzMzMgQzEwLjE1NTgsOS4zMzMzMyA5LjY2ODg2LDkuMjM3OSA5LjIyMTUyLDkuMDY0NSBDOC45NzUyOCw4Ljk2OTA1IDguNjk1OTEsOS4wMjc5NSA4LjUwOTE2LDkuMjE0NjkgTDcuNzIzODYsMTAgTDYsMTAgQzUuNjMxODEsMTAgNS4zMzMzMywxMC4yOTg1IDUuMzMzMzMsMTAuNjY2NyBMNS4zMzMzMywxMi42NjY3IEw0LDEyLjY2NjcgQzMuNjMxODEsMTIuNjY2NyAzLjMzMzMzLDEyLjk2NTEgMy4zMzMzMywxMy4zMzMzIEwzLjMzMzMzLDE0LjY2NjcgTDEuMzMzMzMsMTQuNjY2NyBMMS4zMzMzMywxMi42MDk1IEw2LjY5Nzg3LDcuMjQ0OTQgQzYuODc1MDIsNy4wNjc3OSA2LjkzNzksNi44MDYyOSA2Ljg2MDY1LDYuNTY3OTggQzYuNzM0ODksNi4xNzk5NyA2LjY2NjY3LDUuNzY1MjcgNi42NjY2Nyw1LjMzMzMzIFoiIGlkPSJTaGFwZSI+PC9wYXRoPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+'; -const ddgPasswordIconFilled = exports.ddgPasswordIconFilled = 'data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjNzY0MzEwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNS4yNSA2Ljc1YTIgMiAwIDEgMCAwIDQgMiAyIDAgMCAwIDAtNFptLS41IDJhLjUuNSAwIDEgMSAxIDAgLjUuNSAwIDAgMS0xIDBaIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz4KICAgIDxwYXRoIGZpbGw9IiM3NjQzMTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTE0LjYyNSA0YTUuMzc1IDUuMzc1IDAgMCAwLTUuMjQ0IDYuNTU5bC01LjE2MSA1LjE2YS43NS43NSAwIDAgMC0uMjIuNTMxdjNjMCAuNDE0LjMzNi43NS43NS43NWgyLjk5N2MuNTU0IDAgMS4wMDMtLjQ1IDEuMDAzLTEuMDAzVjE4aC45OTdjLjU1NCAwIDEuMDAzLS40NSAxLjAwMy0xLjAwM1YxNS41SDEyYS43NS43NSAwIDAgMCAuNTMtLjIybC43MS0uNzFBNS4zOCA1LjM4IDAgMCAwIDIwIDkuMzc1IDUuMzc1IDUuMzc1IDAgMCAwIDE0LjYyNSA0Wk0xMC43NSA5LjM3NWEzLjg3NSAzLjg3NSAwIDEgMSAyLjU0IDMuNjQuNzUuNzUgMCAwIDAtLjc4OS4xNzNMMTEuNjkgMTRIMTBhLjc1Ljc1IDAgMCAwLS43NS43NXYxLjc1SDhhLjc1Ljc1IDAgMCAwLS43NS43NXYxLjI1SDUuNXYtMS45NGw1LjI0OC01LjI0OGEuNzUuNzUgMCAwIDAgLjE4NC0uNzU4IDMuODcyIDMuODcyIDAgMCAxLS4xODItMS4xNzlaIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz4KPC9zdmc+Cg=='; -const ddgPasswordIconFocused = exports.ddgPasswordIconFocused = 'data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8ZyBmaWxsPSIjMDAwIiBjbGlwLXBhdGg9InVybCgjZGRnLXBhc3N3b3JkLWljb24tZm9jdXNlZF9zdmdfX2EpIj4KICAgICAgICA8cGF0aCBmaWxsLW9wYWNpdHk9Ii4xIiBkPSJNMjQgMTJjMC02LjYyNy01LjM3My0xMi0xMi0xMlMwIDUuMzczIDAgMTJzNS4zNzMgMTIgMTIgMTIgMTItNS4zNzMgMTItMTJaIi8+CiAgICAgICAgPHBhdGggZmlsbC1vcGFjaXR5PSIuOSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTUuMjUgNi43NWEyIDIgMCAxIDAgMCA0IDIgMiAwIDAgMCAwLTRabS0uNSAyYS41LjUgMCAxIDEgMSAwIC41LjUgMCAwIDEtMSAwWiIgY2xpcC1ydWxlPSJldmVub2RkIi8+CiAgICAgICAgPHBhdGggZmlsbC1vcGFjaXR5PSIuOSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTQuNjI1IDRhNS4zNzUgNS4zNzUgMCAwIDAtNS4yNDQgNi41NTlsLTUuMTYxIDUuMTZhLjc1Ljc1IDAgMCAwLS4yMi41MzF2M2MwIC40MTQuMzM2Ljc1Ljc1Ljc1aDIuOTk3Yy41NTQgMCAxLjAwMy0uNDUgMS4wMDMtMS4wMDNWMThoLjk5N2MuNTU0IDAgMS4wMDMtLjQ1IDEuMDAzLTEuMDAzVjE1LjVIMTJhLjc1Ljc1IDAgMCAwIC41My0uMjJsLjcxLS43MUE1LjM4IDUuMzggMCAwIDAgMjAgOS4zNzUgNS4zNzUgNS4zNzUgMCAwIDAgMTQuNjI1IDRaTTEwLjc1IDkuMzc1YTMuODc1IDMuODc1IDAgMSAxIDIuNTQgMy42NC43NS43NSAwIDAgMC0uNzg5LjE3M0wxMS42OSAxNEgxMGEuNzUuNzUgMCAwIDAtLjc1Ljc1djEuNzVIOGEuNzUuNzUgMCAwIDAtLjc1Ljc1djEuMjVINS41di0xLjk0bDUuMjQ4LTUuMjQ4YS43NS43NSAwIDAgMCAuMTg0LS43NTggMy44NjkgMy44NjkgMCAwIDEtLjE4Mi0xLjE3OVoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPC9nPgo8L3N2Zz4K'; -const ddgPasswordGenIconBase = exports.ddgPasswordGenIconBase = 'data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjMDAwIiBkPSJNOC4wNDcgNC42MjVDNy45MzcgNC4xMjUgNy44NjIgNCA3LjUgNGMtLjM2MiAwLS40MzguMTI1LS41NDcuNjI1LS4wNjguMzEtLjE3NyAxLjMzOC0uMjUxIDIuMDc3LS43MzguMDc0LTEuNzY3LjE4My0yLjA3Ny4yNTEtLjUuMTEtLjYyNS4xODQtLjYyNS41NDcgMCAuMzYyLjEyNS40MzcuNjI1LjU0Ny4zMS4wNjcgMS4zMzYuMTc3IDIuMDc0LjI1LjA3My43NjcuMTg1IDEuODQyLjI1NCAyLjA3OC4xMS4zNzUuMTg1LjYyNS41NDcuNjI1LjM2MiAwIC40MzgtLjEyNS41NDctLjYyNS4wNjgtLjMxLjE3Ny0xLjMzNi4yNS0yLjA3NC43NjctLjA3MyAxLjg0Mi0uMTg1IDIuMDc4LS4yNTQuMzc1LS4xMS42MjUtLjE4NS42MjUtLjU0NyAwLS4zNjMtLjEyNS0uNDM4LS42MjUtLjU0Ny0uMzEtLjA2OC0xLjMzOS0uMTc3LTIuMDc3LS4yNTEtLjA3NC0uNzM5LS4xODMtMS43NjctLjI1MS0yLjA3N1oiLz4KICAgIDxwYXRoIGZpbGw9IiMwMDAiIGQ9Ik0xNC42ODEgNS4xOTljLS43NjYgMC0xLjQ4Mi4yMS0yLjA5My41NzhhLjYzNi42MzYgMCAwIDEtLjY1NS0xLjA5IDUuMzQgNS4zNCAwIDEgMSAxLjMwMiA5LjcyMmwtLjc3NS43NzZhLjYzNi42MzYgMCAwIDEtLjQ1LjE4NmgtMS4zOTh2MS42NWMwIC40OTMtLjQuODkzLS44OTMuODkzSDguNTc4djEuMTQxYzAgLjQ5NC0uNC44OTMtLjg5NC44OTNINC42MzZBLjYzNi42MzYgMCAwIDEgNCAxOS4zMTNWMTYuMjZjMC0uMTY5LjA2Ny0uMzMuMTg2LS40NWw1LjU2Mi01LjU2MmEuNjM2LjYzNiAwIDEgMSAuOS45bC01LjM3NiA1LjM3NXYyLjE1M2gyLjAzNHYtMS4zOTljMC0uMzUuMjg1LS42MzYuNjM2LS42MzZIOS4zNHYtMS45MDdjMC0uMzUxLjI4NC0uNjM2LjYzNS0uNjM2aDEuNzcxbC44NjQtLjg2M2EuNjM2LjYzNiAwIDAgMSAuNjY4LS4xNDcgNC4wNjkgNC4wNjkgMCAxIDAgMS40MDItNy44OVoiLz4KICAgIDxwYXRoIGZpbGw9IiMwMDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTEzLjYyNSA4LjQ5OWExLjg3NSAxLjg3NSAwIDEgMSAzLjc1IDAgMS44NzUgMS44NzUgMCAwIDEtMy43NSAwWm0xLjg3NS0uNjI1YS42MjUuNjI1IDAgMSAwIDAgMS4yNS42MjUuNjI1IDAgMCAwIDAtMS4yNVoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZmlsbD0iIzAwMCIgZD0iTTQuNjI1IDEyLjEyNWEuNjI1LjYyNSAwIDEgMCAwLTEuMjUuNjI1LjYyNSAwIDAgMCAwIDEuMjVaIi8+Cjwvc3ZnPgo='; -const ddgPasswordGenIconFilled = exports.ddgPasswordGenIconFilled = 'data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjNzY0MzEwIiBkPSJNOC4wNDcgNC42MjVDNy45MzcgNC4xMjUgNy44NjIgNCA3LjUgNGMtLjM2MiAwLS40MzguMTI1LS41NDcuNjI1LS4wNjguMzEtLjE3NyAxLjMzOS0uMjUxIDIuMDc3LS43MzguMDc0LTEuNzY3LjE4My0yLjA3Ny4yNTEtLjUuMTEtLjYyNS4xODUtLjYyNS41NDcgMCAuMzYyLjEyNS40MzcuNjI1LjU0Ny4zMS4wNjcgMS4zMzYuMTc3IDIuMDc0LjI1LjA3My43NjcuMTg1IDEuODQyLjI1NCAyLjA3OC4xMS4zNzUuMTg1LjYyNS41NDcuNjI1LjM2MiAwIC40MzgtLjEyNS41NDctLjYyNS4wNjgtLjMxLjE3Ny0xLjMzNi4yNS0yLjA3NC43NjctLjA3MyAxLjg0Mi0uMTg1IDIuMDc4LS4yNTQuMzc1LS4xMS42MjUtLjE4NS42MjUtLjU0NyAwLS4zNjItLjEyNS0uNDM4LS42MjUtLjU0Ny0uMzEtLjA2OC0xLjMzOS0uMTc3LTIuMDc3LS4yNTEtLjA3NC0uNzM4LS4xODMtMS43NjctLjI1MS0yLjA3N1oiLz4KICAgIDxwYXRoIGZpbGw9IiM3NjQzMTAiIGQ9Ik0xNC42ODEgNS4xOTljLS43NjYgMC0xLjQ4Mi4yMTEtMi4wOTMuNTc4YS42MzYuNjM2IDAgMCAxLS42NTUtMS4wOSA1LjM0IDUuMzQgMCAxIDEgMS4zMDIgOS43MjNsLS43NzUuNzc1YS42MzYuNjM2IDAgMCAxLS40NS4xODZoLTEuMzk4djEuNjVjMCAuNDkzLS40Ljg5My0uODkzLjg5M0g4LjU3OHYxLjE0MWMwIC40OTQtLjQuODk0LS44OTQuODk0SDQuNjM2QS42MzYuNjM2IDAgMCAxIDQgMTkuMzEzVjE2LjI2YS42NC42NCAwIDAgMSAuMTg2LS40NWw1LjU2Mi01LjU2MmEuNjM2LjYzNiAwIDEgMSAuOS45bC01LjM3NiA1LjM3NXYyLjE1M2gyLjAzNHYtMS4zOTljMC0uMzUuMjg1LS42MzYuNjM2LS42MzZIOS4zNHYtMS45MDdjMC0uMzUuMjg0LS42MzYuNjM1LS42MzZoMS43NzFsLjg2NC0uODYzYS42MzYuNjM2IDAgMCAxIC42NjgtLjE0NyA0LjA2OSA0LjA2OSAwIDEgMCAxLjQwMi03Ljg5WiIvPgogICAgPHBhdGggZmlsbD0iIzc2NDMxMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTMuNjI1IDguNWExLjg3NSAxLjg3NSAwIDEgMSAzLjc1IDAgMS44NzUgMS44NzUgMCAwIDEtMy43NSAwWm0xLjg3NS0uNjI2YS42MjUuNjI1IDAgMSAwIDAgMS4yNS42MjUuNjI1IDAgMCAwIDAtMS4yNVoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZmlsbD0iIzc2NDMxMCIgZD0iTTQuNjI1IDEyLjEyNWEuNjI1LjYyNSAwIDEgMCAwLTEuMjUuNjI1LjYyNSAwIDAgMCAwIDEuMjVaIi8+Cjwvc3ZnPgo='; -const ddgCcIconBase = exports.ddgCcIconBase = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBkPSJNNSA5Yy0uNTUyIDAtMSAuNDQ4LTEgMXYyYzAgLjU1Mi40NDggMSAxIDFoM2MuNTUyIDAgMS0uNDQ4IDEtMXYtMmMwLS41NTItLjQ0OC0xLTEtMUg1eiIgZmlsbD0iIzAwMCIvPgogICAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xIDZjMC0yLjIxIDEuNzktNCA0LTRoMTRjMi4yMSAwIDQgMS43OSA0IDR2MTJjMCAyLjIxLTEuNzkgNC00IDRINWMtMi4yMSAwLTQtMS43OS00LTRWNnptNC0yYy0xLjEwNSAwLTIgLjg5NS0yIDJ2OWgxOFY2YzAtMS4xMDUtLjg5NS0yLTItMkg1em0wIDE2Yy0xLjEwNSAwLTItLjg5NS0yLTJoMThjMCAxLjEwNS0uODk1IDItMiAySDV6IiBmaWxsPSIjMDAwIi8+Cjwvc3ZnPgo='; -const ddgCcIconFilled = exports.ddgCcIconFilled = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBkPSJNNSA5Yy0uNTUyIDAtMSAuNDQ4LTEgMXYyYzAgLjU1Mi40NDggMSAxIDFoM2MuNTUyIDAgMS0uNDQ4IDEtMXYtMmMwLS41NTItLjQ0OC0xLTEtMUg1eiIgZmlsbD0iIzc2NDMxMCIvPgogICAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xIDZjMC0yLjIxIDEuNzktNCA0LTRoMTRjMi4yMSAwIDQgMS43OSA0IDR2MTJjMCAyLjIxLTEuNzkgNC00IDRINWMtMi4yMSAwLTQtMS43OS00LTRWNnptNC0yYy0xLjEwNSAwLTIgLjg5NS0yIDJ2OWgxOFY2YzAtMS4xMDUtLjg5NS0yLTItMkg1em0wIDE2Yy0xLjEwNSAwLTItLjg5NS0yLTJoMThjMCAxLjEwNS0uODk1IDItMiAySDV6IiBmaWxsPSIjNzY0MzEwIi8+Cjwvc3ZnPgo='; -const ddgIdentityIconBase = exports.ddgIdentityIconBase = `data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTEyIDIxYzIuMTQzIDAgNC4xMTEtLjc1IDUuNjU3LTItLjYyNi0uNTA2LTEuMzE4LS45MjctMi4wNi0xLjI1LTEuMS0uNDgtMi4yODUtLjczNS0zLjQ4Ni0uNzUtMS4yLS4wMTQtMi4zOTIuMjExLTMuNTA0LjY2NC0uODE3LjMzMy0xLjU4Ljc4My0yLjI2NCAxLjMzNiAxLjU0NiAxLjI1IDMuNTE0IDIgNS42NTcgMnptNC4zOTctNS4wODNjLjk2Ny40MjIgMS44NjYuOTggMi42NzIgMS42NTVDMjAuMjc5IDE2LjAzOSAyMSAxNC4xMDQgMjEgMTJjMC00Ljk3LTQuMDMtOS05LTlzLTkgNC4wMy05IDljMCAyLjEwNC43MjIgNC4wNCAxLjkzMiA1LjU3Mi44NzQtLjczNCAxLjg2LTEuMzI4IDIuOTIxLTEuNzYgMS4zNi0uNTU0IDIuODE2LS44MyA0LjI4My0uODExIDEuNDY3LjAxOCAyLjkxNi4zMyA0LjI2LjkxNnpNMTIgMjNjNi4wNzUgMCAxMS00LjkyNSAxMS0xMVMxOC4wNzUgMSAxMiAxIDEgNS45MjUgMSAxMnM0LjkyNSAxMSAxMSAxMXptMy0xM2MwIDEuNjU3LTEuMzQzIDMtMyAzcy0zLTEuMzQzLTMtMyAxLjM0My0zIDMtMyAzIDEuMzQzIDMgM3ptMiAwYzAgMi43NjEtMi4yMzkgNS01IDVzLTUtMi4yMzktNS01IDIuMjM5LTUgNS01IDUgMi4yMzkgNSA1eiIgZmlsbD0iIzAwMCIvPgo8L3N2Zz4KPHBhdGggeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTEyIDIxYzIuMTQzIDAgNC4xMTEtLjc1IDUuNjU3LTItLjYyNi0uNTA2LTEuMzE4LS45MjctMi4wNi0xLjI1LTEuMS0uNDgtMi4yODUtLjczNS0zLjQ4Ni0uNzUtMS4yLS4wMTQtMi4zOTIuMjExLTMuNTA0LjY2NC0uODE3LjMzMy0xLjU4Ljc4My0yLjI2NCAxLjMzNiAxLjU0NiAxLjI1IDMuNTE0IDIgNS42NTcgMnptNC4zOTctNS4wODNjLjk2Ny40MjIgMS44NjYuOTggMi42NzIgMS42NTVDMjAuMjc5IDE2LjAzOSAyMSAxNC4xMDQgMjEgMTJjMC00Ljk3LTQuMDMtOS05LTlzLTkgNC4wMy05IDljMCAyLjEwNC43MjIgNC4wNCAxLjkzMiA1LjU3Mi44NzQtLjczNCAxLjg2LTEuMzI4IDIuOTIxLTEuNzYgMS4zNi0uNTU0IDIuODE2LS44MyA0LjI4My0uODExIDEuNDY3LjAxOCAyLjkxNi4zMyA0LjI2LjkxNnpNMTIgMjNjNi4wNzUgMCAxMS00LjkyNSAxMS0xMVMxOC4wNzUgMSAxMiAxIDEgNS45MjUgMSAxMnM0LjkyNSAxMSAxMSAxMXptMy0xM2MwIDEuNjU3LTEuMzQzIDMtMyAzcy0zLTEuMzQzLTMtMyAxLjM0My0zIDMtMyAzIDEuMzQzIDMgM3ptMiAwYzAgMi43NjEtMi4yMzkgNS01IDVzLTUtMi4yMzktNS01IDIuMjM5LTUgNS01IDUgMi4yMzkgNSA1eiIgZmlsbD0iIzAwMCIvPgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiBmaWxsPSJub25lIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xMiAyMWMyLjE0MyAwIDQuMTExLS43NSA1LjY1Ny0yLS42MjYtLjUwNi0xLjMxOC0uOTI3LTIuMDYtMS4yNS0xLjEtLjQ4LTIuMjg1LS43MzUtMy40ODYtLjc1LTEuMi0uMDE0LTIuMzkyLjIxMS0zLjUwNC42NjQtLjgxNy4zMzMtMS41OC43ODMtMi4yNjQgMS4zMzYgMS41NDYgMS4yNSAzLjUxNCAyIDUuNjU3IDJ6bTQuMzk3LTUuMDgzYy45NjcuNDIyIDEuODY2Ljk4IDIuNjcyIDEuNjU1QzIwLjI3OSAxNi4wMzkgMjEgMTQuMTA0IDIxIDEyYzAtNC45Ny00LjAzLTktOS05cy05IDQuMDMtOSA5YzAgMi4xMDQuNzIyIDQuMDQgMS45MzIgNS41NzIuODc0LS43MzQgMS44Ni0xLjMyOCAyLjkyMS0xLjc2IDEuMzYtLjU1NCAyLjgxNi0uODMgNC4yODMtLjgxMSAxLjQ2Ny4wMTggMi45MTYuMzMgNC4yNi45MTZ6TTEyIDIzYzYuMDc1IDAgMTEtNC45MjUgMTEtMTFTMTguMDc1IDEgMTIgMSAxIDUuOTI1IDEgMTJzNC45MjUgMTEgMTEgMTF6bTMtMTNjMCAxLjY1Ny0xLjM0MyAzLTMgM3MtMy0xLjM0My0zLTMgMS4zNDMtMyAzLTMgMyAxLjM0MyAzIDN6bTIgMGMwIDIuNzYxLTIuMjM5IDUtNSA1cy01LTIuMjM5LTUtNSAyLjIzOS01IDUtNSA1IDIuMjM5IDUgNXoiIGZpbGw9IiMwMDAiLz4KPC9zdmc+Cg==`; - -},{}],61:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.CSS_STYLES = void 0; -const CSS_STYLES = exports.CSS_STYLES = ":root {\n color-scheme: light dark;\n}\n\n.wrapper *, .wrapper *::before, .wrapper *::after {\n box-sizing: border-box;\n}\n.wrapper {\n position: fixed;\n top: 0;\n left: 0;\n padding: 0;\n font-family: 'DDG_ProximaNova', 'Proxima Nova', system-ui, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',\n 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;\n -webkit-font-smoothing: antialiased;\n z-index: 2147483647;\n}\n.wrapper--data {\n font-family: 'SF Pro Text', system-ui, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',\n 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;\n}\n:not(.top-autofill) .tooltip {\n position: absolute;\n width: 300px;\n max-width: calc(100vw - 25px);\n transform: translate(-1000px, -1000px);\n z-index: 2147483647;\n}\n.tooltip--data, #topAutofill {\n background-color: rgba(242, 240, 240, 1);\n -webkit-backdrop-filter: blur(40px);\n backdrop-filter: blur(40px);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip--data, #topAutofill {\n background: rgb(100, 98, 102, .9);\n }\n}\n.tooltip--data {\n padding: 6px;\n font-size: 13px;\n line-height: 14px;\n width: 315px;\n max-height: 290px;\n overflow-y: auto;\n}\n.top-autofill .tooltip--data {\n min-height: 100vh;\n}\n.tooltip--data.tooltip--incontext-signup {\n width: 360px;\n}\n:not(.top-autofill) .tooltip--data {\n top: 100%;\n left: 100%;\n border: 0.5px solid rgba(255, 255, 255, 0.2);\n border-radius: 6px;\n box-shadow: 0 10px 20px rgba(0, 0, 0, 0.32);\n}\n@media (prefers-color-scheme: dark) {\n :not(.top-autofill) .tooltip--data {\n border: 1px solid rgba(255, 255, 255, 0.2);\n }\n}\n:not(.top-autofill) .tooltip--email {\n top: calc(100% + 6px);\n right: calc(100% - 48px);\n padding: 8px;\n border: 1px solid #D0D0D0;\n border-radius: 10px;\n background-color: #FFFFFF;\n font-size: 14px;\n line-height: 1.3;\n color: #333333;\n box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);\n}\n.tooltip--email__caret {\n position: absolute;\n transform: translate(-1000px, -1000px);\n z-index: 2147483647;\n}\n.tooltip--email__caret::before,\n.tooltip--email__caret::after {\n content: \"\";\n width: 0;\n height: 0;\n border-left: 10px solid transparent;\n border-right: 10px solid transparent;\n display: block;\n border-bottom: 8px solid #D0D0D0;\n position: absolute;\n right: -28px;\n}\n.tooltip--email__caret::before {\n border-bottom-color: #D0D0D0;\n top: -1px;\n}\n.tooltip--email__caret::after {\n border-bottom-color: #FFFFFF;\n top: 0px;\n}\n\n/* Buttons */\n.tooltip__button {\n display: flex;\n width: 100%;\n padding: 8px 8px 8px 0px;\n font-family: inherit;\n color: inherit;\n background: transparent;\n border: none;\n border-radius: 6px;\n}\n.tooltip__button.currentFocus,\n.wrapper:not(.top-autofill) .tooltip__button:hover {\n background-color: #3969EF;\n color: #FFFFFF;\n}\n\n/* Data autofill tooltip specific */\n.tooltip__button--data {\n position: relative;\n min-height: 48px;\n flex-direction: row;\n justify-content: flex-start;\n font-size: inherit;\n font-weight: 500;\n line-height: 16px;\n text-align: left;\n border-radius: 3px;\n}\n.tooltip--data__item-container {\n max-height: 220px;\n overflow: auto;\n}\n.tooltip__button--data:first-child {\n margin-top: 0;\n}\n.tooltip__button--data:last-child {\n margin-bottom: 0;\n}\n.tooltip__button--data::before {\n content: '';\n flex-shrink: 0;\n display: block;\n width: 32px;\n height: 32px;\n margin: 0 8px;\n background-size: 20px 20px;\n background-repeat: no-repeat;\n background-position: center center;\n}\n#provider_locked::after {\n position: absolute;\n content: '';\n flex-shrink: 0;\n display: block;\n width: 32px;\n height: 32px;\n margin: 0 8px;\n background-size: 11px 13px;\n background-repeat: no-repeat;\n background-position: right bottom;\n}\n.tooltip__button--data.currentFocus:not(.tooltip__button--data--bitwarden)::before,\n.wrapper:not(.top-autofill) .tooltip__button--data:not(.tooltip__button--data--bitwarden):hover::before {\n filter: invert(100%);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip__button--data:not(.tooltip__button--data--bitwarden)::before,\n .tooltip__button--data:not(.tooltip__button--data--bitwarden)::before {\n filter: invert(100%);\n opacity: .9;\n }\n}\n.tooltip__button__text-container {\n margin: auto 0;\n}\n.label {\n display: block;\n font-weight: 400;\n letter-spacing: -0.25px;\n color: rgba(0,0,0,.8);\n font-size: 13px;\n line-height: 1;\n}\n.label + .label {\n margin-top: 2px;\n}\n.label.label--medium {\n font-weight: 500;\n letter-spacing: -0.25px;\n color: rgba(0,0,0,.9);\n}\n.label.label--small {\n font-size: 11px;\n font-weight: 400;\n letter-spacing: 0.06px;\n color: rgba(0,0,0,0.6);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip--data .label {\n color: #ffffff;\n }\n .tooltip--data .label--medium {\n color: #ffffff;\n }\n .tooltip--data .label--small {\n color: #cdcdcd;\n }\n}\n.tooltip__button.currentFocus .label,\n.wrapper:not(.top-autofill) .tooltip__button:hover .label {\n color: #FFFFFF;\n}\n\n.tooltip__button--manage {\n font-size: 13px;\n padding: 5px 9px;\n border-radius: 3px;\n margin: 0;\n}\n\n/* Icons */\n.tooltip__button--data--credentials::before,\n.tooltip__button--data--credentials__current::before {\n background-size: 28px 28px;\n background-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNS4zMzQgNi42NjdhMiAyIDAgMSAwIDAgNCAyIDIgMCAwIDAgMC00Wm0tLjY2NyAyYS42NjcuNjY3IDAgMSAxIDEuMzMzIDAgLjY2Ny42NjcgMCAwIDEtMS4zMzMgMFoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTQuNjY3IDRhNS4zMzMgNS4zMzMgMCAwIDAtNS4xODggNi41NzhsLTUuMjg0IDUuMjg0YS42NjcuNjY3IDAgMCAwLS4xOTUuNDcxdjNjMCAuMzY5LjI5OC42NjcuNjY3LjY2N2gyLjY2NmMuNzM3IDAgMS4zMzQtLjU5NyAxLjMzNC0xLjMzM1YxOGguNjY2Yy43MzcgMCAxLjMzNC0uNTk3IDEuMzM0LTEuMzMzdi0xLjMzNEgxMmMuMTc3IDAgLjM0Ni0uMDcuNDcxLS4xOTVsLjY4OC0uNjg4QTUuMzMzIDUuMzMzIDAgMSAwIDE0LjY2NyA0Wm0tNCA1LjMzM2E0IDQgMCAxIDEgMi41NTUgMy43MzIuNjY3LjY2NyAwIDAgMC0uNzEzLjE1bC0uNzg1Ljc4NUgxMGEuNjY3LjY2NyAwIDAgMC0uNjY3LjY2N3YySDhhLjY2Ny42NjcgMCAwIDAtLjY2Ny42NjZ2MS4zMzRoLTJ2LTIuMDU4bDUuMzY1LTUuMzY0YS42NjcuNjY3IDAgMCAwIC4xNjMtLjY3NyAzLjk5NiAzLjk5NiAwIDAgMS0uMTk0LTEuMjM1WiIgY2xpcC1ydWxlPSJldmVub2RkIi8+Cjwvc3ZnPgo=');\n}\n.tooltip__button--data--credentials__new::before {\n background-size: 28px 28px;\n background-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjMDAwIiBkPSJNOC4wNDcgNC42MjVDNy45MzcgNC4xMjUgNy44NjIgNCA3LjUgNGMtLjM2MiAwLS40MzguMTI1LS41NDcuNjI1LS4wNjguMzEtLjE3NyAxLjMzOC0uMjUxIDIuMDc3LS43MzguMDc0LTEuNzY3LjE4My0yLjA3Ny4yNTEtLjUuMTEtLjYyNS4xODQtLjYyNS41NDcgMCAuMzYyLjEyNS40MzcuNjI1LjU0Ny4zMS4wNjcgMS4zMzYuMTc3IDIuMDc0LjI1LjA3My43NjcuMTg1IDEuODQyLjI1NCAyLjA3OC4xMS4zNzUuMTg1LjYyNS41NDcuNjI1LjM2MiAwIC40MzgtLjEyNS41NDctLjYyNS4wNjgtLjMxLjE3Ny0xLjMzNi4yNS0yLjA3NC43NjctLjA3MyAxLjg0Mi0uMTg1IDIuMDc4LS4yNTQuMzc1LS4xMS42MjUtLjE4NS42MjUtLjU0NyAwLS4zNjMtLjEyNS0uNDM4LS42MjUtLjU0Ny0uMzEtLjA2OC0xLjMzOS0uMTc3LTIuMDc3LS4yNTEtLjA3NC0uNzM5LS4xODMtMS43NjctLjI1MS0yLjA3N1oiLz4KICAgIDxwYXRoIGZpbGw9IiMwMDAiIGQ9Ik0xNC42ODEgNS4xOTljLS43NjYgMC0xLjQ4Mi4yMS0yLjA5My41NzhhLjYzNi42MzYgMCAwIDEtLjY1NS0xLjA5IDUuMzQgNS4zNCAwIDEgMSAxLjMwMiA5LjcyMmwtLjc3NS43NzZhLjYzNi42MzYgMCAwIDEtLjQ1LjE4NmgtMS4zOTh2MS42NWMwIC40OTMtLjQuODkzLS44OTMuODkzSDguNTc4djEuMTQxYzAgLjQ5NC0uNC44OTMtLjg5NC44OTNINC42MzZBLjYzNi42MzYgMCAwIDEgNCAxOS4zMTNWMTYuMjZjMC0uMTY5LjA2Ny0uMzMuMTg2LS40NWw1LjU2Mi01LjU2MmEuNjM2LjYzNiAwIDEgMSAuOS45bC01LjM3NiA1LjM3NXYyLjE1M2gyLjAzNHYtMS4zOTljMC0uMzUuMjg1LS42MzYuNjM2LS42MzZIOS4zNHYtMS45MDdjMC0uMzUxLjI4NC0uNjM2LjYzNS0uNjM2aDEuNzcxbC44NjQtLjg2M2EuNjM2LjYzNiAwIDAgMSAuNjY4LS4xNDcgNC4wNjkgNC4wNjkgMCAxIDAgMS40MDItNy44OVoiLz4KICAgIDxwYXRoIGZpbGw9IiMwMDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTEzLjYyNSA4LjQ5OWExLjg3NSAxLjg3NSAwIDEgMSAzLjc1IDAgMS44NzUgMS44NzUgMCAwIDEtMy43NSAwWm0xLjg3NS0uNjI1YS42MjUuNjI1IDAgMSAwIDAgMS4yNS42MjUuNjI1IDAgMCAwIDAtMS4yNVoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZmlsbD0iIzAwMCIgZD0iTTQuNjI1IDEyLjEyNWEuNjI1LjYyNSAwIDEgMCAwLTEuMjUuNjI1LjYyNSAwIDAgMCAwIDEuMjVaIi8+Cjwvc3ZnPgo=');\n}\n.tooltip__button--data--creditCards::before {\n background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBkPSJNNSA5Yy0uNTUyIDAtMSAuNDQ4LTEgMXYyYzAgLjU1Mi40NDggMSAxIDFoM2MuNTUyIDAgMS0uNDQ4IDEtMXYtMmMwLS41NTItLjQ0OC0xLTEtMUg1eiIgZmlsbD0iIzAwMCIvPgogICAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xIDZjMC0yLjIxIDEuNzktNCA0LTRoMTRjMi4yMSAwIDQgMS43OSA0IDR2MTJjMCAyLjIxLTEuNzkgNC00IDRINWMtMi4yMSAwLTQtMS43OS00LTRWNnptNC0yYy0xLjEwNSAwLTIgLjg5NS0yIDJ2OWgxOFY2YzAtMS4xMDUtLjg5NS0yLTItMkg1em0wIDE2Yy0xLjEwNSAwLTItLjg5NS0yLTJoMThjMCAxLjEwNS0uODk1IDItMiAySDV6IiBmaWxsPSIjMDAwIi8+Cjwvc3ZnPgo=');\n}\n.tooltip__button--data--identities::before {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTEyIDIxYzIuMTQzIDAgNC4xMTEtLjc1IDUuNjU3LTItLjYyNi0uNTA2LTEuMzE4LS45MjctMi4wNi0xLjI1LTEuMS0uNDgtMi4yODUtLjczNS0zLjQ4Ni0uNzUtMS4yLS4wMTQtMi4zOTIuMjExLTMuNTA0LjY2NC0uODE3LjMzMy0xLjU4Ljc4My0yLjI2NCAxLjMzNiAxLjU0NiAxLjI1IDMuNTE0IDIgNS42NTcgMnptNC4zOTctNS4wODNjLjk2Ny40MjIgMS44NjYuOTggMi42NzIgMS42NTVDMjAuMjc5IDE2LjAzOSAyMSAxNC4xMDQgMjEgMTJjMC00Ljk3LTQuMDMtOS05LTlzLTkgNC4wMy05IDljMCAyLjEwNC43MjIgNC4wNCAxLjkzMiA1LjU3Mi44NzQtLjczNCAxLjg2LTEuMzI4IDIuOTIxLTEuNzYgMS4zNi0uNTU0IDIuODE2LS44MyA0LjI4My0uODExIDEuNDY3LjAxOCAyLjkxNi4zMyA0LjI2LjkxNnpNMTIgMjNjNi4wNzUgMCAxMS00LjkyNSAxMS0xMVMxOC4wNzUgMSAxMiAxIDEgNS45MjUgMSAxMnM0LjkyNSAxMSAxMSAxMXptMy0xM2MwIDEuNjU3LTEuMzQzIDMtMyAzcy0zLTEuMzQzLTMtMyAxLjM0My0zIDMtMyAzIDEuMzQzIDMgM3ptMiAwYzAgMi43NjEtMi4yMzkgNS01IDVzLTUtMi4yMzktNS01IDIuMjM5LTUgNS01IDUgMi4yMzkgNSA1eiIgZmlsbD0iIzAwMCIvPgo8L3N2Zz4=');\n}\n.tooltip__button--data--credentials.tooltip__button--data--bitwarden::before,\n.tooltip__button--data--credentials__current.tooltip__button--data--bitwarden::before {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiByeD0iOCIgZmlsbD0iIzE3NUREQyIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTE4LjU2OTYgNS40MzM1NUMxOC41MDg0IDUuMzc0NDIgMTguNDM0NyA1LjMyNzYzIDE4LjM1MzEgNS4yOTYxMUMxOC4yNzE1IDUuMjY0NiAxOC4xODM3IDUuMjQ5MDQgMTguMDk1MyA1LjI1MDQxSDUuOTIxOTFDNS44MzMyNiA1LjI0NzI5IDUuNzQ0OTMgNS4yNjIwNSA1LjY2MzA0IDUuMjkzNjdDNS41ODExNSA1LjMyNTI5IDUuNTA3NjUgNS4zNzMwMiA1LjQ0NzYyIDUuNDMzNTVDNS4zMjE3IDUuNTUwMTMgNS4yNTA2NSA1LjcwODE1IDUuMjUgNS44NzMxVjEzLjM4MjFDNS4yNTMzNiAxMy45NTM1IDUuMzc0MDggMTQuNTE5MSA1LjYwNTcyIDE1LjA0ODdDNS44MTkzMSAxNS41NzI4IDYuMTEyMDcgMTYuMDY2MSA2LjQ3NTI0IDE2LjUxMzlDNi44NDIgMTYuOTY4MyA3LjI1OTI5IDE3LjM4NTcgNy43MjAyNSAxNy43NTkzQzguMTQwNTMgMTguMTI1NiA4LjU4OTcxIDE4LjQ2MjMgOS4wNjQwNyAxOC43NjY2QzkuNDU5MzEgMTkuMDIzIDkuOTEzODMgMTkuMjc5NCAxMC4zNDg2IDE5LjUxNzVDMTAuNzgzNCAxOS43NTU2IDExLjA5OTYgMTkuOTIwNCAxMS4yNzc0IDE5Ljk5MzdDMTEuNDU1MyAyMC4wNjY5IDExLjYxMzQgMjAuMTQwMiAxMS43MTIyIDIwLjE5NTFDMTEuNzk5MiAyMC4yMzEzIDExLjg5MzUgMjAuMjUgMTEuOTg4OCAyMC4yNUMxMi4wODQyIDIwLjI1IDEyLjE3ODUgMjAuMjMxMyAxMi4yNjU1IDIwLjE5NTFDMTIuNDIxMiAyMC4xMzYzIDEyLjU3MjkgMjAuMDY5IDEyLjcyIDE5Ljk5MzdDMTIuNzcxMSAxOS45Njc0IDEyLjgzMzUgMTkuOTM2NiAxMi45MDY5IDE5LjkwMDRDMTMuMDg5MSAxOS44MTA1IDEzLjMzODggMTkuNjg3MiAxMy42NDg5IDE5LjUxNzVDMTQuMDgzNiAxOS4yNzk0IDE0LjUxODQgMTkuMDIzIDE0LjkzMzQgMTguNzY2NkMxNS40MDQgMTguNDU3NyAxNS44NTI4IDE4LjEyMTIgMTYuMjc3MiAxNy43NTkzQzE2LjczMzEgMTcuMzgwOSAxNy4xNDk5IDE2Ljk2NCAxNy41MjIyIDE2LjUxMzlDMTcuODc4IDE2LjA2MTcgMTguMTcwMiAxNS41NjkzIDE4LjM5MTcgMTUuMDQ4N0MxOC42MjM0IDE0LjUxOTEgMTguNzQ0MSAxMy45NTM1IDE4Ljc0NzQgMTMuMzgyMVY1Ljg3MzFDMTguNzU1NyA1Ljc5MjE0IDE4Ljc0MzkgNS43MTA1IDE4LjcxMzEgNS42MzQzNUMxOC42ODIzIDUuNTU4MiAxOC42MzMyIDUuNDg5NTQgMTguNTY5NiA1LjQzMzU1Wk0xNy4wMDg0IDEzLjQ1NTNDMTcuMDA4NCAxNi4xODQyIDEyLjAwODYgMTguNTI4NSAxMi4wMDg2IDE4LjUyODVWNi44NjIwOUgxNy4wMDg0VjEzLjQ1NTNaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K');\n}\n#provider_locked:after {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTEiIGhlaWdodD0iMTMiIHZpZXdCb3g9IjAgMCAxMSAxMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEgNy42MDA1N1Y3LjYwMjVWOS41MjI1QzEgMTAuMDgwMSAxLjIyMTUxIDEwLjYxNDkgMS42MTU4MSAxMS4wMDkyQzIuMDEwMSAxMS40MDM1IDIuNTQ0ODggMTEuNjI1IDMuMTAyNSAxMS42MjVINy4yNzI1QzcuNTQ4NjEgMTEuNjI1IDcuODIyMDEgMTEuNTcwNiA4LjA3NzA5IDExLjQ2NUM4LjMzMjE4IDExLjM1OTMgOC41NjM5NiAxMS4yMDQ0IDguNzU5MTkgMTEuMDA5MkM4Ljk1NDQzIDEwLjgxNCA5LjEwOTMgMTAuNTgyMiA5LjIxNDk2IDEwLjMyNzFDOS4zMjA2MiAxMC4wNzIgOS4zNzUgOS43OTg2MSA5LjM3NSA5LjUyMjVMOS4zNzUgNy42MDI1TDkuMzc1IDcuNjAwNTdDOS4zNzQxNSA3LjE2MTMxIDkuMjM1NzQgNi43MzMzNSA4Ljk3OTIyIDYuMzc2NzhDOC44NzY4MyA2LjIzNDQ2IDguNzU3NjggNi4xMDYzNyA4LjYyNSA1Ljk5NDg5VjUuMTg3NUM4LjYyNSA0LjI3NTgyIDguMjYyODQgMy40MDE0OCA3LjYxODE4IDIuNzU2ODJDNi45NzM1MiAyLjExMjE2IDYuMDk5MTggMS43NSA1LjE4NzUgMS43NUM0LjI3NTgyIDEuNzUgMy40MDE0OCAyLjExMjE2IDIuNzU2ODIgMi43NTY4MkMyLjExMjE2IDMuNDAxNDggMS43NSA0LjI3NTgyIDEuNzUgNS4xODc1VjUuOTk0ODlDMS42MTczMiA2LjEwNjM3IDEuNDk4MTcgNi4yMzQ0NiAxLjM5NTc4IDYuMzc2NzhDMS4xMzkyNiA2LjczMzM1IDEuMDAwODUgNy4xNjEzMSAxIDcuNjAwNTdaTTQuOTY4NyA0Ljk2ODdDNS4wMjY5NCA0LjkxMDQ3IDUuMTA1MzIgNC44NzY5OSA1LjE4NzUgNC44NzUwN0M1LjI2OTY4IDQuODc2OTkgNS4zNDgwNiA0LjkxMDQ3IDUuNDA2MyA0Ljk2ODdDNS40NjU0MiA1LjAyNzgzIDUuNDk5MDQgNS4xMDc3NCA1LjUgNS4xOTEzVjUuNUg0Ljg3NVY1LjE5MTNDNC44NzU5NiA1LjEwNzc0IDQuOTA5NTggNS4wMjc4MyA0Ljk2ODcgNC45Njg3WiIgZmlsbD0iIzIyMjIyMiIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIyIi8+Cjwvc3ZnPgo=');\n}\n\nhr {\n display: block;\n margin: 5px 9px;\n border: none; /* reset the border */\n border-top: 1px solid rgba(0,0,0,.1);\n}\n\nhr:first-child {\n display: none;\n}\n\n@media (prefers-color-scheme: dark) {\n hr {\n border-top: 1px solid rgba(255,255,255,.2);\n }\n}\n\n#privateAddress {\n align-items: flex-start;\n}\n#personalAddress::before,\n#privateAddress::before,\n#incontextSignup::before,\n#personalAddress.currentFocus::before,\n#personalAddress:hover::before,\n#privateAddress.currentFocus::before,\n#privateAddress:hover::before {\n filter: none;\n /* This is the same icon as `daxBase64` in `src/Form/logo-svg.js` */\n background-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMTI4IDEyOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0ibTY0IDEyOGMzNS4zNDYgMCA2NC0yOC42NTQgNjQtNjRzLTI4LjY1NC02NC02NC02NC02NCAyOC42NTQtNjQgNjQgMjguNjU0IDY0IDY0IDY0eiIgZmlsbD0iI2RlNTgzMyIgZmlsbC1ydWxlPSJldmVub2RkIi8+CiAgICA8cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Im03MyAxMTEuNzVjMC0uNS4xMjMtLjYxNC0xLjQ2Ni0zLjc4Mi00LjIyNC04LjQ1OS04LjQ3LTIwLjM4NC02LjU0LTI4LjA3NS4zNTMtMS4zOTctMy45NzgtNTEuNzQ0LTcuMDQtNTMuMzY1LTMuNDAyLTEuODEzLTcuNTg4LTQuNjktMTEuNDE4LTUuMzMtMS45NDMtLjMxLTQuNDktLjE2NC02LjQ4Mi4xMDUtLjM1My4wNDctLjM2OC42ODMtLjAzLjc5OCAxLjMwOC40NDMgMi44OTUgMS4yMTIgMy44MyAyLjM3NS4xNzguMjItLjA2LjU2Ni0uMzQyLjU3Ny0uODgyLjAzMi0yLjQ4Mi40MDItNC41OTMgMi4xOTUtLjI0NC4yMDctLjA0MS41OTIuMjczLjUzIDQuNTM2LS44OTcgOS4xNy0uNDU1IDExLjkgMi4wMjcuMTc3LjE2LjA4NC40NS0uMTQ3LjUxMi0yMy42OTQgNi40NC0xOS4wMDMgMjcuMDUtMTIuNjk2IDUyLjM0NCA1LjYxOSAyMi41MyA3LjczMyAyOS43OTIgOC40IDMyLjAwNGEuNzE4LjcxOCAwIDAgMCAuNDIzLjQ2N2M4LjE1NiAzLjI0OCAyNS45MjggMy4zOTIgMjUuOTI4LTIuMTMyeiIgZmlsbD0iI2RkZCIgZmlsbC1ydWxlPSJldmVub2RkIi8+CiAgICA8cGF0aCBkPSJtNzYuMjUgMTE2LjVjLTIuODc1IDEuMTI1LTguNSAxLjYyNS0xMS43NSAxLjYyNS00Ljc2NCAwLTExLjYyNS0uNzUtMTQuMTI1LTEuODc1LTEuNTQ0LTQuNzUxLTYuMTY0LTE5LjQ4LTEwLjcyNy0zOC4xODVsLS40NDctMS44MjctLjAwNC0uMDE1Yy01LjQyNC0yMi4xNTctOS44NTUtNDAuMjUzIDE0LjQyNy00NS45MzguMjIyLS4wNTIuMzMtLjMxNy4xODQtLjQ5Mi0yLjc4Ni0zLjMwNS04LjAwNS00LjM4OC0xNC42MDUtMi4xMTEtLjI3LjA5My0uNTA2LS4xOC0uMzM3LS40MTIgMS4yOTQtMS43ODMgMy44MjMtMy4xNTUgNS4wNzEtMy43NTYuMjU4LS4xMjQuMjQyLS41MDItLjAzLS41ODhhMjcuODc3IDI3Ljg3NyAwIDAgMCAtMy43NzItLjljLS4zNy0uMDU5LS40MDMtLjY5My0uMDMyLS43NDMgOS4zNTYtMS4yNTkgMTkuMTI1IDEuNTUgMjQuMDI4IDcuNzI2YS4zMjYuMzI2IDAgMCAwIC4xODYuMTE0YzE3Ljk1MiAzLjg1NiAxOS4yMzggMzIuMjM1IDE3LjE3IDMzLjUyOC0uNDA4LjI1NS0xLjcxNS4xMDgtMy40MzgtLjA4NS02Ljk4Ni0uNzgxLTIwLjgxOC0yLjMyOS05LjQwMiAxOC45NDguMTEzLjIxLS4wMzYuNDg4LS4yNzIuNTI1LTYuNDM4IDEgMS44MTIgMjEuMTczIDcuODc1IDM0LjQ2MXoiIGZpbGw9IiNmZmYiLz4KICAgIDxwYXRoIGQ9Im04NC4yOCA5MC42OThjLTEuMzY3LS42MzMtNi42MjEgMy4xMzUtMTAuMTEgNi4wMjgtLjcyOC0xLjAzMS0yLjEwMy0xLjc4LTUuMjAzLTEuMjQyLTIuNzEzLjQ3Mi00LjIxMSAxLjEyNi00Ljg4IDIuMjU0LTQuMjgzLTEuNjIzLTExLjQ4OC00LjEzLTEzLjIyOS0xLjcxLTEuOTAyIDIuNjQ2LjQ3NiAxNS4xNjEgMy4wMDMgMTYuNzg2IDEuMzIuODQ5IDcuNjMtMy4yMDggMTAuOTI2LTYuMDA1LjUzMi43NDkgMS4zODggMS4xNzggMy4xNDggMS4xMzcgMi42NjItLjA2MiA2Ljk3OS0uNjgxIDcuNjQ5LTEuOTIxLjA0LS4wNzUuMDc1LS4xNjQuMTA1LS4yNjYgMy4zODggMS4yNjYgOS4zNSAyLjYwNiAxMC42ODIgMi40MDYgMy40Ny0uNTIxLS40ODQtMTYuNzIzLTIuMDktMTcuNDY3eiIgZmlsbD0iIzNjYTgyYiIvPgogICAgPHBhdGggZD0ibTc0LjQ5IDk3LjA5N2MuMTQ0LjI1Ni4yNi41MjYuMzU4LjguNDgzIDEuMzUyIDEuMjcgNS42NDguNjc0IDYuNzA5LS41OTUgMS4wNjItNC40NTkgMS41NzQtNi44NDMgMS42MTVzLTIuOTItLjgzMS0zLjQwMy0yLjE4MWMtLjM4Ny0xLjA4MS0uNTc3LTMuNjIxLS41NzItNS4wNzUtLjA5OC0yLjE1OC42OS0yLjkxNiA0LjMzNC0zLjUwNiAyLjY5Ni0uNDM2IDQuMTIxLjA3MSA0Ljk0NC45NCAzLjgyOC0yLjg1NyAxMC4yMTUtNi44ODkgMTAuODM4LTYuMTUyIDMuMTA2IDMuNjc0IDMuNDk5IDEyLjQyIDIuODI2IDE1LjkzOS0uMjIgMS4xNTEtMTAuNTA1LTEuMTM5LTEwLjUwNS0yLjM4IDAtNS4xNTItMS4zMzctNi41NjUtMi42NS02Ljcxem0tMjIuNTMtMS42MDljLjg0My0xLjMzMyA3LjY3NC4zMjUgMTEuNDI0IDEuOTkzIDAgMC0uNzcgMy40OTEuNDU2IDcuNjA0LjM1OSAxLjIwMy04LjYyNyA2LjU1OC05LjggNS42MzctMS4zNTUtMS4wNjUtMy44NS0xMi40MzItMi4wOC0xNS4yMzR6IiBmaWxsPSIjNGNiYTNjIi8+CiAgICA8cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Im01NS4yNjkgNjguNDA2Yy41NTMtMi40MDMgMy4xMjctNi45MzIgMTIuMzIxLTYuODIyIDQuNjQ4LS4wMTkgMTAuNDIyLS4wMDIgMTQuMjUtLjQzNmE1MS4zMTIgNTEuMzEyIDAgMCAwIDEyLjcyNi0zLjA5NWMzLjk4LTEuNTE5IDUuMzkyLTEuMTggNS44ODctLjI3Mi41NDQuOTk5LS4wOTcgMi43MjItMS40ODggNC4zMDktMi42NTYgMy4wMy03LjQzMSA1LjM4LTE1Ljg2NSA2LjA3Ni04LjQzMy42OTgtMTQuMDItMS41NjUtMTYuNDI1IDIuMTE4LTEuMDM4IDEuNTg5LS4yMzYgNS4zMzMgNy45MiA2LjUxMiAxMS4wMiAxLjU5IDIwLjA3Mi0xLjkxNyAyMS4xOS4yMDEgMS4xMTkgMi4xMTgtNS4zMjMgNi40MjgtMTYuMzYyIDYuNTE4cy0xNy45MzQtMy44NjUtMjAuMzc5LTUuODNjLTMuMTAyLTIuNDk1LTQuNDktNi4xMzMtMy43NzUtOS4yNzl6IiBmaWxsPSIjZmMzIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz4KICAgIDxnIGZpbGw9IiMxNDMwN2UiIG9wYWNpdHk9Ii44Ij4KICAgICAgPHBhdGggZD0ibTY5LjMyNyA0Mi4xMjdjLjYxNi0xLjAwOCAxLjk4MS0xLjc4NiA0LjIxNi0xLjc4NiAyLjIzNCAwIDMuMjg1Ljg4OSA0LjAxMyAxLjg4LjE0OC4yMDItLjA3Ni40NC0uMzA2LjM0YTU5Ljg2OSA1OS44NjkgMCAwIDEgLS4xNjgtLjA3M2MtLjgxNy0uMzU3LTEuODItLjc5NS0zLjU0LS44Mi0xLjgzOC0uMDI2LTIuOTk3LjQzNS0zLjcyNy44MzEtLjI0Ni4xMzQtLjYzNC0uMTMzLS40ODgtLjM3MnptLTI1LjE1NyAxLjI5YzIuMTctLjkwNyAzLjg3Ni0uNzkgNS4wODEtLjUwNC4yNTQuMDYuNDMtLjIxMy4yMjctLjM3Ny0uOTM1LS43NTUtMy4wMy0xLjY5Mi01Ljc2LS42NzQtMi40MzcuOTA5LTMuNTg1IDIuNzk2LTMuNTkyIDQuMDM4LS4wMDIuMjkyLjYuMzE3Ljc1Ni4wNy40Mi0uNjcgMS4xMi0xLjY0NiAzLjI4OS0yLjU1M3oiLz4KICAgICAgPHBhdGggY2xpcC1ydWxlPSJldmVub2RkIiBkPSJtNzUuNDQgNTUuOTJhMy40NyAzLjQ3IDAgMCAxIC0zLjQ3NC0zLjQ2MiAzLjQ3IDMuNDcgMCAwIDEgMy40NzUtMy40NiAzLjQ3IDMuNDcgMCAwIDEgMy40NzQgMy40NiAzLjQ3IDMuNDcgMCAwIDEgLTMuNDc1IDMuNDYyem0yLjQ0Ny00LjYwOGEuODk5Ljg5OSAwIDAgMCAtMS43OTkgMGMwIC40OTQuNDA1Ljg5NS45Ljg5NS40OTkgMCAuOS0uNC45LS44OTV6bS0yNS40NjQgMy41NDJhNC4wNDIgNC4wNDIgMCAwIDEgLTQuMDQ5IDQuMDM3IDQuMDQ1IDQuMDQ1IDAgMCAxIC00LjA1LTQuMDM3IDQuMDQ1IDQuMDQ1IDAgMCAxIDQuMDUtNC4wMzcgNC4wNDUgNC4wNDUgMCAwIDEgNC4wNSA0LjAzN3ptLTEuMTkzLTEuMzM4YTEuMDUgMS4wNSAwIDAgMCAtMi4wOTcgMCAxLjA0OCAxLjA0OCAwIDAgMCAyLjA5NyAweiIgZmlsbC1ydWxlPSJldmVub2RkIi8+CiAgICA8L2c+CiAgICA8cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Im02NCAxMTcuNzVjMjkuNjg1IDAgNTMuNzUtMjQuMDY1IDUzLjc1LTUzLjc1cy0yNC4wNjUtNTMuNzUtNTMuNzUtNTMuNzUtNTMuNzUgMjQuMDY1LTUzLjc1IDUzLjc1IDI0LjA2NSA1My43NSA1My43NSA1My43NXptMCA1YzMyLjQ0NyAwIDU4Ljc1LTI2LjMwMyA1OC43NS01OC43NXMtMjYuMzAzLTU4Ljc1LTU4Ljc1LTU4Ljc1LTU4Ljc1IDI2LjMwMy01OC43NSA1OC43NSAyNi4zMDMgNTguNzUgNTguNzUgNTguNzV6IiBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz4KPC9zdmc+');\n}\n\n/* Email tooltip specific */\n.tooltip__button--email {\n flex-direction: column;\n justify-content: center;\n align-items: flex-start;\n font-size: 14px;\n padding: 4px 8px;\n}\n.tooltip__button--email__primary-text {\n font-weight: bold;\n}\n.tooltip__button--email__secondary-text {\n font-size: 12px;\n}\n\n/* Email Protection signup notice */\n:not(.top-autofill) .tooltip--email-signup {\n text-align: left;\n color: #222222;\n padding: 16px 20px;\n width: 380px;\n}\n\n.tooltip--email-signup h1 {\n font-weight: 700;\n font-size: 16px;\n line-height: 1.5;\n margin: 0;\n}\n\n.tooltip--email-signup p {\n font-weight: 400;\n font-size: 14px;\n line-height: 1.4;\n}\n\n.notice-controls {\n display: flex;\n}\n\n.tooltip--email-signup .notice-controls > * {\n border-radius: 8px;\n border: 0;\n cursor: pointer;\n display: inline-block;\n font-family: inherit;\n font-style: normal;\n font-weight: bold;\n padding: 8px 12px;\n text-decoration: none;\n}\n\n.notice-controls .ghost {\n margin-left: 1rem;\n}\n\n.tooltip--email-signup a.primary {\n background: #3969EF;\n color: #fff;\n}\n\n.tooltip--email-signup a.primary:hover,\n.tooltip--email-signup a.primary:focus {\n background: #2b55ca;\n}\n\n.tooltip--email-signup a.primary:active {\n background: #1e42a4;\n}\n\n.tooltip--email-signup button.ghost {\n background: transparent;\n color: #3969EF;\n}\n\n.tooltip--email-signup button.ghost:hover,\n.tooltip--email-signup button.ghost:focus {\n background-color: rgba(0, 0, 0, 0.06);\n color: #2b55ca;\n}\n\n.tooltip--email-signup button.ghost:active {\n background-color: rgba(0, 0, 0, 0.12);\n color: #1e42a4;\n}\n\n.tooltip--email-signup button.close-tooltip {\n background-color: transparent;\n background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iMTMiIHZpZXdCb3g9IjAgMCAxMiAxMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0wLjI5Mjg5NCAwLjY1NjkwN0MwLjY4MzQxOCAwLjI2NjM4MyAxLjMxNjU4IDAuMjY2MzgzIDEuNzA3MTEgMC42NTY5MDdMNiA0Ljk0OThMMTAuMjkyOSAwLjY1NjkwN0MxMC42ODM0IDAuMjY2MzgzIDExLjMxNjYgMC4yNjYzODMgMTEuNzA3MSAwLjY1NjkwN0MxMi4wOTc2IDEuMDQ3NDMgMTIuMDk3NiAxLjY4MDYgMTEuNzA3MSAyLjA3MTEyTDcuNDE0MjEgNi4zNjQwMUwxMS43MDcxIDEwLjY1NjlDMTIuMDk3NiAxMS4wNDc0IDEyLjA5NzYgMTEuNjgwNiAxMS43MDcxIDEyLjA3MTFDMTEuMzE2NiAxMi40NjE2IDEwLjY4MzQgMTIuNDYxNiAxMC4yOTI5IDEyLjA3MTFMNiA3Ljc3ODIzTDEuNzA3MTEgMTIuMDcxMUMxLjMxNjU4IDEyLjQ2MTYgMC42ODM0MTcgMTIuNDYxNiAwLjI5Mjg5MyAxMi4wNzExQy0wLjA5NzYzMTEgMTEuNjgwNiAtMC4wOTc2MzExIDExLjA0NzQgMC4yOTI4OTMgMTAuNjU2OUw0LjU4NTc5IDYuMzY0MDFMMC4yOTI4OTQgMi4wNzExMkMtMC4wOTc2MzA2IDEuNjgwNiAtMC4wOTc2MzA2IDEuMDQ3NDMgMC4yOTI4OTQgMC42NTY5MDdaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjg0Ii8+Cjwvc3ZnPgo=);\n background-position: center center;\n background-repeat: no-repeat;\n border: 0;\n cursor: pointer;\n padding: 16px;\n position: absolute;\n right: 12px;\n top: 12px;\n}\n"; - -},{}],62:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.buttonMatchesFormType = exports.autofillEnabled = exports.addInlineStyles = exports.SIGN_IN_MSG = exports.ADDRESS_DOMAIN = void 0; -exports.escapeXML = escapeXML; -exports.formatDuckAddress = void 0; -exports.getActiveElement = getActiveElement; -exports.isEventWithinDax = exports.isAutofillEnabledFromProcessedConfig = exports.getTextShallow = exports.getDaxBoundingBox = void 0; -exports.isFormLikelyToBeUsedAsPageWrapper = isFormLikelyToBeUsedAsPageWrapper; -exports.isLikelyASubmitButton = exports.isIncontextSignupEnabledFromProcessedConfig = void 0; -exports.isLocalNetwork = isLocalNetwork; -exports.isPotentiallyViewable = void 0; -exports.isValidTLD = isValidTLD; -exports.logPerformance = logPerformance; -exports.notifyWebApp = void 0; -exports.pierceShadowTree = pierceShadowTree; -exports.safeExecute = exports.removeInlineStyles = void 0; -exports.safeRegexTest = safeRegexTest; -exports.setValue = exports.sendAndWaitForAnswer = void 0; -exports.shouldLog = shouldLog; -exports.shouldLogPerformance = shouldLogPerformance; -exports.truncateFromMiddle = truncateFromMiddle; -exports.wasAutofilledByChrome = void 0; -exports.whenIdle = whenIdle; -var _matching = require("./Form/matching.js"); -var _constants = require("./constants.js"); -var _appleUtils = require("@duckduckgo/content-scope-scripts/src/apple-utils"); -const SIGN_IN_MSG = exports.SIGN_IN_MSG = { - signMeIn: true -}; - -// Send a message to the web app (only on DDG domains) -const notifyWebApp = message => { - window.postMessage(message, window.origin); -}; -/** - * Sends a message and returns a Promise that resolves with the response - * @param {{} | Function} msgOrFn - a fn to call or an object to send via postMessage - * @param {String} expectedResponse - the name of the response - * @returns {Promise<*>} - */ -exports.notifyWebApp = notifyWebApp; -const sendAndWaitForAnswer = (msgOrFn, expectedResponse) => { - if (typeof msgOrFn === 'function') { - msgOrFn(); - } else { - window.postMessage(msgOrFn, window.origin); - } - return new Promise(resolve => { - const handler = e => { - if (e.origin !== window.origin) return; - if (!e.data || e.data && !(e.data[expectedResponse] || e.data.type === expectedResponse)) return; - resolve(e.data); - window.removeEventListener('message', handler); - }; - window.addEventListener('message', handler); - }); -}; - -/** - * @param {Pick} globalConfig - * @return {boolean} - */ -exports.sendAndWaitForAnswer = sendAndWaitForAnswer; -const autofillEnabled = globalConfig => { - if (!globalConfig.contentScope) { - // Return enabled for platforms that haven't implemented the config yet - return true; - } - // already processed? this handles an edgecase in the extension where the config is already processed - if ('site' in globalConfig.contentScope) { - const enabled = isAutofillEnabledFromProcessedConfig(globalConfig.contentScope); - return enabled; - } - const { - contentScope, - userUnprotectedDomains, - userPreferences - } = globalConfig; - - // Check config on Apple platforms - const processedConfig = (0, _appleUtils.processConfig)(contentScope, userUnprotectedDomains, userPreferences); - return isAutofillEnabledFromProcessedConfig(processedConfig); -}; -exports.autofillEnabled = autofillEnabled; -const isAutofillEnabledFromProcessedConfig = processedConfig => { - const site = processedConfig.site; - if (site.isBroken || !site.enabledFeatures.includes('autofill')) { - if (shouldLog()) { - console.log('⚠️ Autofill disabled by remote config'); + async refreshAlias() { + await this.deviceApi.notify(createNotification("emailHandlerRefreshAlias")); + if (this.globalConfig.isApp) + this.getAddresses(); } - return false; - } - return true; -}; -exports.isAutofillEnabledFromProcessedConfig = isAutofillEnabledFromProcessedConfig; -const isIncontextSignupEnabledFromProcessedConfig = processedConfig => { - const site = processedConfig.site; - if (site.isBroken || !site.enabledFeatures.includes('incontextSignup')) { - if (shouldLog()) { - console.log('⚠️ In-context signup disabled by remote config'); + async _checkDeviceSignedIn() { + const { isAppSignedIn } = await this.deviceApi.request(createRequest("emailHandlerCheckAppSignedInStatus")); + this.isDeviceSignedIn = () => !!isAppSignedIn; + return !!isAppSignedIn; } - return false; - } - return true; -}; - -// Access the original setter (needed to bypass React's implementation on mobile) -// @ts-ignore -exports.isIncontextSignupEnabledFromProcessedConfig = isIncontextSignupEnabledFromProcessedConfig; -const originalSet = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set; - -/** - * Ensures the value is set properly and dispatches events to simulate real user action - * @param {HTMLInputElement} el - * @param {string} val - * @param {GlobalConfig} [config] - * @return {boolean} - */ -const setValueForInput = (el, val, config) => { - // Avoid keyboard flashing on Android - if (!config?.isAndroid) { - el.focus(); - } - - // todo(Shane): Not sending a 'key' property on these events can cause exceptions on 3rd party listeners that expect it - el.dispatchEvent(new Event('keydown', { - bubbles: true - })); - originalSet?.call(el, val); - const events = [new Event('input', { - bubbles: true - }), - // todo(Shane): Not sending a 'key' property on these events can cause exceptions on 3rd party listeners that expect it - new Event('keyup', { - bubbles: true - }), new Event('change', { - bubbles: true - })]; - events.forEach(ev => el.dispatchEvent(ev)); - // We call this again to make sure all forms are happy - originalSet?.call(el, val); - events.forEach(ev => el.dispatchEvent(ev)); - el.blur(); - return true; -}; - -/** - * Fires events on a select element to simulate user interaction - * @param {HTMLSelectElement} el - */ -const fireEventsOnSelect = el => { - /** @type {Event[]} */ - const events = [new Event('mousedown', { - bubbles: true - }), new Event('mouseup', { - bubbles: true - }), new Event('click', { - bubbles: true - }), new Event('change', { - bubbles: true - })]; - - // Events fire on the select el, not option - events.forEach(ev => el.dispatchEvent(ev)); - events.forEach(ev => el.dispatchEvent(ev)); - el.blur(); -}; - -/** - * Selects an option of a select element - * We assume Select is only used for dates, i.e. in the credit card - * @param {HTMLSelectElement} el - * @param {string} val - * @return {boolean} - */ -const setValueForSelect = (el, val) => { - const subtype = (0, _matching.getInputSubtype)(el); - const isMonth = subtype.includes('Month'); - const isZeroBasedNumber = isMonth && el.options[0].value === '0' && el.options.length === 12; - const stringVal = String(val); - const numberVal = Number(val); - - // Loop first through all values because they tend to be more precise - for (const option of el.options) { - // If values for months are zero-based (Jan === 0), add one to match our data type - let value = option.value; - if (isZeroBasedNumber) { - value = `${Number(value) + 1}`; - } - // TODO: try to match localised month names - // TODO: implement alternative versions of values (abbreviations for States/Provinces or variations like USA, US, United States, etc.) - if (value === stringVal || Number(value) === numberVal) { - if (option.selected) return false; - option.selected = true; - fireEventsOnSelect(el); - return true; + storeUserData({ addUserData: { token, userName, cohort } }) { + return this.deviceApi.notify(createNotification("emailHandlerStoreToken", { token, username: userName, cohort })); } - } - for (const option of el.options) { - if (option.innerText === stringVal || Number(option.innerText) === numberVal) { - if (option.selected) return false; - option.selected = true; - fireEventsOnSelect(el); - return true; + /** + * Used by the email web app + * Provides functionality to log the user out + */ + removeUserData() { + this.deviceApi.notify(createNotification("emailHandlerRemoveToken")); } - } - // If we didn't find a matching option return false - return false; -}; - -/** - * Sets or selects a value to a form element - * @param {HTMLInputElement | HTMLSelectElement} el - * @param {string} val - * @param {GlobalConfig} [config] - * @return {boolean} - */ -const setValue = (el, val, config) => { - if (el instanceof HTMLInputElement) return setValueForInput(el, val, config); - if (el instanceof HTMLSelectElement) return setValueForSelect(el, val); - return false; -}; - -/** - * Use IntersectionObserver v2 to make sure the element is visible when clicked - * https://developers.google.com/web/updates/2019/02/intersectionobserver-v2 - */ -exports.setValue = setValue; -const safeExecute = function (el, fn) { - let _opts = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - // TODO: temporary fix to misterious bug in Chrome - // const {checkVisibility = true} = opts - const intObs = new IntersectionObserver(changes => { - for (const change of changes) { - // Feature detection - if (typeof change.isVisible === 'undefined') { - // The browser doesn't support Intersection Observer v2, falling back to v1 behavior. - change.isVisible = true; - } - if (change.isIntersecting) { - /** - * If 'checkVisibility' is 'false' (like on Windows), then we always execute the function - * During testing it was found that windows does not `change.isVisible` properly. - */ - // TODO: temporary fix to misterious bug in Chrome - // if (!checkVisibility || change.isVisible) { - // fn() - // } - fn(); - } + /** + * Used by the email web app + * Provides functionality to close the window after in-context sign-up or sign-in + */ + closeEmailProtection() { + this.deviceApi.request(new CloseEmailProtectionTabCall(null)); } - intObs.disconnect(); - }, { - trackVisibility: true, - delay: 100 - }); - intObs.observe(el); -}; - -/** - * Checks that an element is potentially viewable (even if off-screen) - * @param {HTMLElement} el - * @return {boolean} - */ -exports.safeExecute = safeExecute; -const isPotentiallyViewable = el => { - const computedStyle = window.getComputedStyle(el); - const opacity = parseFloat(computedStyle.getPropertyValue('opacity') || '1'); - const visibility = computedStyle.getPropertyValue('visibility'); - const opacityThreshold = 0.6; - return el.clientWidth !== 0 && el.clientHeight !== 0 && opacity > opacityThreshold && visibility !== 'hidden'; -}; - -/** - * Gets the bounding box of the icon - * @param {HTMLInputElement} input - * @returns {{top: number, left: number, bottom: number, width: number, x: number, y: number, right: number, height: number}} - */ -exports.isPotentiallyViewable = isPotentiallyViewable; -const getDaxBoundingBox = input => { - const { - right: inputRight, - top: inputTop, - height: inputHeight - } = input.getBoundingClientRect(); - const inputRightPadding = parseInt(getComputedStyle(input).paddingRight); - const width = 30; - const height = 30; - const top = inputTop + (inputHeight - height) / 2; - const right = inputRight - inputRightPadding; - const left = right - width; - const bottom = top + height; - return { - bottom, - height, - left, - right, - top, - width, - x: left, - y: top - }; -}; - -/** - * Check if a mouse event is within the icon - * @param {MouseEvent} e - * @param {HTMLInputElement} input - * @returns {boolean} - */ -exports.getDaxBoundingBox = getDaxBoundingBox; -const isEventWithinDax = (e, input) => { - const { - left, - right, - top, - bottom - } = getDaxBoundingBox(input); - const withinX = e.clientX >= left && e.clientX <= right; - const withinY = e.clientY >= top && e.clientY <= bottom; - return withinX && withinY; -}; - -/** - * Adds inline styles from a prop:value object - * @param {HTMLElement} el - * @param {Object} styles - */ -exports.isEventWithinDax = isEventWithinDax; -const addInlineStyles = (el, styles) => Object.entries(styles).forEach(_ref => { - let [property, val] = _ref; - return el.style.setProperty(property, val, 'important'); -}); - -/** - * Removes inline styles from a prop:value object - * @param {HTMLElement} el - * @param {Object} styles - */ -exports.addInlineStyles = addInlineStyles; -const removeInlineStyles = (el, styles) => Object.keys(styles).forEach(property => el.style.removeProperty(property)); -exports.removeInlineStyles = removeInlineStyles; -const ADDRESS_DOMAIN = exports.ADDRESS_DOMAIN = '@duck.com'; -/** - * Given a username, returns the full email address - * @param {string} address - * @returns {string} - */ -const formatDuckAddress = address => address + ADDRESS_DOMAIN; - -/** - * Escapes any occurrences of &, ", <, > or / with XML entities. - * @param {string} str The string to escape. - * @return {string} The escaped string. - */ -exports.formatDuckAddress = formatDuckAddress; -function escapeXML(str) { - const replacements = { - '&': '&', - '"': '"', - "'": ''', - '<': '<', - '>': '>', - '/': '/' - }; - return String(str).replace(/[&"'<>/]/g, m => replacements[m]); -} - -/** - * Determines if an element is likely to be a submit button - * @param {HTMLElement} el A button, input, anchor or other element with role=button - * @param {import("./Form/matching").Matching} matching - * @return {boolean} - */ -const isLikelyASubmitButton = (el, matching) => { - const text = getTextShallow(el); - const ariaLabel = el.getAttribute('aria-label') || ''; - const dataTestId = el.getAttribute('data-test-id') || ''; - if ((el.getAttribute('type') === 'submit' || - // is explicitly set as "submit" - el.getAttribute('name') === 'submit') && - // is called "submit" - !safeRegexTest(matching.getDDGMatcherRegex('submitButtonUnlikelyRegex'), text + ' ' + ariaLabel)) return true; - return (safeRegexTest(/primary|submit/i, el.className) || - // has high-signal submit classes - safeRegexTest(/submit/i, dataTestId) || safeRegexTest(matching.getDDGMatcherRegex('submitButtonRegex'), text) || - // has high-signal text - el.offsetHeight * el.offsetWidth >= 10000 && !safeRegexTest(/secondary/i, el.className) // it's a large element 250x40px - ) && el.offsetHeight * el.offsetWidth >= 2000 && - // it's not a very small button like inline links and such - !safeRegexTest(matching.getDDGMatcherRegex('submitButtonUnlikelyRegex'), text + ' ' + ariaLabel); -}; - -/** - * Check that a button matches the form type - login buttons on a login form, signup buttons on a signup form - * @param {HTMLElement} el - * @param {import('./Form/Form').Form} formObj - */ -exports.isLikelyASubmitButton = isLikelyASubmitButton; -const buttonMatchesFormType = (el, formObj) => { - if (formObj.isLogin) { - return !safeRegexTest(/sign.?up|register|join/i, el.textContent || ''); - } else if (formObj.isSignup) { - return !safeRegexTest(/(log|sign).?([io])n/i, el.textContent || ''); - } else { - return true; - } -}; -exports.buttonMatchesFormType = buttonMatchesFormType; -const buttonInputTypes = ['submit', 'button']; -/** - * Get the text of an element, one level deep max - * @param {Node} el - * @returns {string} - */ -const getTextShallow = el => { - // for buttons, we don't care about descendants, just get the whole text as is - // this is important in order to give proper attribution of the text to the button - if (el instanceof HTMLButtonElement) return (0, _matching.removeExcessWhitespace)(el.textContent); - if (el instanceof HTMLInputElement) { - if (buttonInputTypes.includes(el.type)) { - return el.value; + /** + * PM endpoints + */ + /** + * Gets the init data from the device + * @returns {APIResponse} + */ + async _getAutofillInitData() { + const response = await this.deviceApi.request(createRequest("pmHandlerGetAutofillInitData")); + this.storeLocalData(response.success); + return response; } - if (el.type === 'image') { - return (0, _matching.removeExcessWhitespace)(el.alt || el.value || el.title || el.name); + /** + * Gets credentials ready for autofill + * @param {CredentialsObject['id']} id - the credential id + * @returns {APIResponseSingle} + */ + getAutofillCredentials(id) { + return this.deviceApi.request(createRequest("pmHandlerGetAutofillCredentials", { id })); } - } - let text = ''; - for (const childNode of el.childNodes) { - if (childNode instanceof Text) { - text += ' ' + childNode.textContent; + /** + * Opens the native UI for managing passwords + */ + openManagePasswords() { + return this.deviceApi.notify(createNotification("pmHandlerOpenManagePasswords")); + } + /** + * Gets a single identity obj once the user requests it + * @param {IdentityObject['id']} id + * @returns {Promise<{success: IdentityObject|undefined}>} + */ + getAutofillIdentity(id) { + const identity = this.getLocalIdentities().find(({ id: identityId }) => `${identityId}` === `${id}`); + return Promise.resolve({ success: identity }); + } + /** + * Gets a single complete credit card obj once the user requests it + * @param {CreditCardObject['id']} id + * @returns {APIResponse} + */ + getAutofillCreditCard(id) { + return this.deviceApi.request(createRequest("pmHandlerGetCreditCard", { id })); + } + getCurrentInputType() { + const topContextData = this.getTopContextData(); + return topContextData?.inputType ? topContextData.inputType : getInputType(this.activeForm?.activeInput); + } + /** + * @returns {Promise} + */ + async getAlias() { + const { alias } = await this.deviceApi.request(new GetAlias({ + requiresUserPermission: !this.globalConfig.isApp, + shouldConsumeAliasIfProvided: !this.globalConfig.isApp, + isIncontextSignupAvailable: this.inContextSignup.isAvailable() + })); + return alias ? formatDuckAddress(alias) : alias; + } + addLogoutListener(handler) { + if (!this.globalConfig.isDDGDomain) + return; + window.addEventListener("message", (e) => { + if (this.globalConfig.isDDGDomain && e.data.emailProtectionSignedOut) { + handler(); + } + }); + } + async addDeviceListeners() { + this.thirdPartyProvider.init(); + } + /** + * Poll the native listener until the user has selected a credential. + * Message return types are: + * - 'stop' is returned whenever the message sent doesn't match the native last opened tooltip. + * - This also is triggered when the close event is called and prevents any edge case continued polling. + * - 'ok' is when the user has selected a credential and the value can be injected into the page. + * - 'none' is when the tooltip is open in the native window however hasn't been entered. + * @param {(response: {data:IdentityObject|CreditCardObject|CredentialsObject, configType: string} | {stateChange: boolean} | {stop: boolean} | null) => void} callback + */ + async _listenForSelectedCredential(callback) { + const poll = async () => { + clearTimeout(this.pollingTimeout); + const response = await this.getSelectedCredentials(); + switch (response.type) { + case "none": + this.pollingTimeout = setTimeout(() => poll(), 100); + return; + case "ok": { + await callback({ data: response.data, configType: response.configType }); + return; + } + case "state": { + await callback({ stateChange: true }); + this.pollingTimeout = setTimeout(() => poll(), 100); + return; + } + case "stop": + await callback({ stop: true }); + } + }; + poll(); } - } - return (0, _matching.removeExcessWhitespace)(text); -}; - -/** - * Check if hostname is a local address - * @param {string} [hostname] - * @returns {boolean} - */ -exports.getTextShallow = getTextShallow; -function isLocalNetwork() { - let hostname = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : window.location.hostname; - return ['localhost', '', '::1'].includes(hostname) || hostname.includes('127.0.0.1') || hostname.includes('192.168.') || hostname.startsWith('10.0.') || hostname.endsWith('.local') || hostname.endsWith('.internal'); -} - -// Extracted from lib/DDG/Util/Constants.pm -const tldrs = /\.(?:c(?:o(?:m|op)?|at?|[iykgdmnxruhcfzvl])|o(?:rg|m)|n(?:et?|a(?:me)?|[ucgozrfpil])|e(?:d?u|[gechstr])|i(?:n(?:t|fo)?|[stqldroem])|m(?:o(?:bi)?|u(?:seum)?|i?l|[mcyvtsqhaerngxzfpwkd])|g(?:ov|[glqeriabtshdfmuywnp])|b(?:iz?|[drovfhtaywmzjsgbenl])|t(?:r(?:avel)?|[ncmfzdvkopthjwg]|e?l)|k[iemygznhwrp]|s[jtvberindlucygkhaozm]|u[gymszka]|h[nmutkr]|r[owesu]|d[kmzoej]|a(?:e(?:ro)?|r(?:pa)?|[qofiumsgzlwcnxdt])|p(?:ro?|[sgnthfymakwle])|v[aegiucn]|l[sayuvikcbrt]|j(?:o(?:bs)?|[mep])|w[fs]|z[amw]|f[rijkom]|y[eut]|qa)$/i; -/** - * Check if hostname is a valid top-level domain - * @param {string} [hostname] - * @returns {boolean} - */ -function isValidTLD() { - let hostname = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : window.location.hostname; - return tldrs.test(hostname) || hostname === 'fill.dev'; -} - -/** - * Chrome's UA adds styles using this selector when using the built-in autofill - * @param {HTMLInputElement} input - * @returns {boolean} - */ -const wasAutofilledByChrome = input => { - try { - // Other browsers throw because the selector is invalid - return input.matches('input:-internal-autofill-selected'); - } catch (e) { - return false; - } -}; - -/** - * Checks if we should log form analysis debug info to the console - * @returns {boolean} - */ -exports.wasAutofilledByChrome = wasAutofilledByChrome; -function shouldLog() { - return readDebugSetting('ddg-autofill-debug'); -} - -/** - * Checks if we should log performance info to the console - * @returns {boolean} - */ -function shouldLogPerformance() { - return readDebugSetting('ddg-autofill-perf'); -} - -/** - * Check if a sessionStorage item is set to 'true' - * @param setting - * @returns {boolean} - */ -function readDebugSetting(setting) { - // sessionStorage throws in invalid schemes like data: and file: - try { - return window.sessionStorage?.getItem(setting) === 'true'; - } catch (e) { - return false; - } -} -function logPerformance(markName) { - if (shouldLogPerformance()) { - const measurement = window.performance?.measure(`${markName}:init`, `${markName}:init:start`, `${markName}:init:end`); - console.log(`${markName} took ${Math.round(measurement?.duration)}ms`); - window.performance?.clearMarks(); - } -} - -/** - * - * @param {Function} callback - * @returns {Function} - */ -function whenIdle(callback) { - var _this = this; - let timer; - return function () { - for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { - args[_key] = arguments[_key]; - } - cancelIdleCallback(timer); - timer = requestIdleCallback(() => callback.apply(_this, args)); - }; -} - -/** - * Truncate string from the middle if exceeds the totalLength (default: 30) - * @param {string} string - * @param {number} totalLength - * @returns {string} - */ -function truncateFromMiddle(string) { - let totalLength = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 30; - if (totalLength < 4) { - throw new Error('Do not use with strings shorter than 4'); - } - if (string.length <= totalLength) return string; - const truncated = string.slice(0, totalLength / 2).concat('…', string.slice(totalLength / -2)); - return truncated; -} - -/** - * Determines if the form is likely to be enclosing most of the DOM - * @param {HTMLFormElement} form - * @returns {boolean} - */ -function isFormLikelyToBeUsedAsPageWrapper(form) { - if (form.parentElement !== document.body) return false; - const formChildren = form.querySelectorAll('*').length; - // If the form has few content elements, it's unlikely to cause issues anyway - if (formChildren < 100) return false; - const bodyChildren = document.body.querySelectorAll('*').length; - - /** - * Percentage of the formChildren on the total body elements - * form * 100 / body = x - */ - const formChildrenPercentage = formChildren * 100 / bodyChildren; - return formChildrenPercentage > 50; -} - -/** - * Wrapper around RegExp.test that safeguard against checking huge strings - * @param {RegExp | undefined} regex - * @param {String} string - * @returns {boolean} - */ -function safeRegexTest(regex, string) { - if (!string || !regex || string.length > _constants.constants.TEXT_LENGTH_CUTOFF) return false; - return regex.test(string); -} - -/** - * Returns the event target, or an element that matches wantedTargetType, piercing the shadow tree - * @param {PointerEvent | FocusEvent} event - * @param {typeof Element} [wantedTargetType] - * @returns {EventTarget | null} - */ -function pierceShadowTree(event, wantedTargetType) { - const { - target - } = event; - - // Sanity checks - if (!(target instanceof Element) || !target?.shadowRoot || !event.composedPath) return target; - const clickStack = event.composedPath(); - - // If we're not looking for a specific element, get the top of the stack - if (!wantedTargetType) { - return clickStack[0]; - } - - // Otherwise, search the wanted target, or return the original target - return clickStack.find(el => el instanceof wantedTargetType) || target; -} - -/** - * Return the active element, piercing through shadow DOMs, or null - * @param {Document | DocumentOrShadowRoot} root - * @returns {Element | null} - */ -function getActiveElement() { - let root = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document; - const activeElement = root.activeElement; - if (!(activeElement instanceof Element) || !activeElement.shadowRoot) return activeElement; - const innerActiveElement = activeElement.shadowRoot.activeElement; - if (innerActiveElement?.shadowRoot) { - return getActiveElement(innerActiveElement.shadowRoot); - } - return innerActiveElement; -} - -},{"./Form/matching.js":43,"./constants.js":65,"@duckduckgo/content-scope-scripts/src/apple-utils":1}],63:[function(require,module,exports){ -"use strict"; - -require("./requestIdleCallback.js"); -var _DeviceInterface = require("./DeviceInterface.js"); -var _autofillUtils = require("./autofill-utils.js"); -// Polyfills/shims - -(() => { - if ((0, _autofillUtils.shouldLog)()) { - console.log('DuckDuckGo Autofill Active'); - } - if (!window.isSecureContext) return false; - try { - const startupAutofill = () => { - if (document.visibilityState === 'visible') { - const deviceInterface = (0, _DeviceInterface.createDevice)(); - deviceInterface.init(); - } else { - document.addEventListener('visibilitychange', startupAutofill, { - once: true - }); - } - }; - startupAutofill(); - } catch (e) { - console.error(e); - // Noop, we errored - } -})(); - -},{"./DeviceInterface.js":22,"./autofill-utils.js":62,"./requestIdleCallback.js":74}],64:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.DDG_DOMAIN_REGEX = void 0; -exports.createGlobalConfig = createGlobalConfig; -const DDG_DOMAIN_REGEX = exports.DDG_DOMAIN_REGEX = new RegExp(/^https:\/\/(([a-z0-9-_]+?)\.)?duckduckgo\.com\/email/); - -/** - * This is a centralised place to contain all string/variable replacements - * - * @param {Partial} [overrides] - * @returns {GlobalConfig} - */ -function createGlobalConfig(overrides) { - let isApp = false; - let isTopFrame = false; - let supportsTopFrame = false; - // Do not remove -- Apple devices change this when they support modern webkit messaging - let hasModernWebkitAPI = false; - // INJECT isApp HERE - // INJECT isTopFrame HERE - // INJECT supportsTopFrame HERE - // INJECT hasModernWebkitAPI HERE - - let isWindows = false; - // INJECT isWindows HERE - - // This will be used when 'hasModernWebkitAPI' is false - /** @type {string[]} */ - let webkitMessageHandlerNames = []; - // INJECT webkitMessageHandlerNames HERE - - let isDDGTestMode = false; - isDDGTestMode = true; - - let contentScope = null; - let userUnprotectedDomains = null; - /** @type {Record | null} */ - let userPreferences = null; - // INJECT contentScope HERE - // INJECT userUnprotectedDomains HERE - // INJECT userPreferences HERE - - /** @type {Record | null} */ - let availableInputTypes = null; - // INJECT availableInputTypes HERE - - // The native layer will inject a randomised secret here and use it to verify the origin - let secret = 'PLACEHOLDER_SECRET'; - - // @ts-ignore - const isAndroid = userPreferences?.platform.name === 'android'; - // @ts-ignore - const isDDGApp = ['ios', 'android', 'macos', 'windows'].includes(userPreferences?.platform.name) || isWindows; - // @ts-ignore - const isMobileApp = ['ios', 'android'].includes(userPreferences?.platform.name); - const isFirefox = navigator.userAgent.includes('Firefox'); - const isDDGDomain = Boolean(window.location.href.match(DDG_DOMAIN_REGEX)); - const isExtension = false; - const config = { - isApp, - isDDGApp, - isAndroid, - isFirefox, - isMobileApp, - isExtension, - isTopFrame, - isWindows, - secret, - supportsTopFrame, - hasModernWebkitAPI, - contentScope, - userUnprotectedDomains, - userPreferences, - isDDGTestMode, - isDDGDomain, - availableInputTypes, - webkitMessageHandlerNames, - ...overrides }; - return config; -} - -},{}],65:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.constants = void 0; -const constants = exports.constants = { - ATTR_INPUT_TYPE: 'data-ddg-inputType', - ATTR_AUTOFILL: 'data-ddg-autofill', - TEXT_LENGTH_CUTOFF: 100, - MAX_INPUTS_PER_PAGE: 100, - MAX_FORMS_PER_PAGE: 30, - MAX_INPUTS_PER_FORM: 80, - MAX_FORM_RESCANS: 50 -}; - -},{}],66:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.StoreFormDataCall = exports.StartEmailProtectionSignupCall = exports.ShowInContextEmailProtectionSignupPromptCall = exports.SetSizeCall = exports.SetIncontextSignupPermanentlyDismissedAtCall = exports.SendJSPixelCall = exports.SelectedDetailCall = exports.OpenManagePasswordsCall = exports.OpenManageIdentitiesCall = exports.OpenManageCreditCardsCall = exports.GetRuntimeConfigurationCall = exports.GetIncontextSignupDismissedAtCall = exports.GetAvailableInputTypesCall = exports.GetAutofillInitDataCall = exports.GetAutofillDataCall = exports.GetAutofillCredentialsCall = exports.EmailProtectionStoreUserDataCall = exports.EmailProtectionRemoveUserDataCall = exports.EmailProtectionRefreshPrivateAddressCall = exports.EmailProtectionGetUserDataCall = exports.EmailProtectionGetIsLoggedInCall = exports.EmailProtectionGetCapabilitiesCall = exports.EmailProtectionGetAddressesCall = exports.CloseEmailProtectionTabCall = exports.CloseAutofillParentCall = exports.CheckCredentialsProviderStatusCall = exports.AskToUnlockProviderCall = exports.AddDebugFlagCall = void 0; -var _validatorsZod = require("./validators.zod.js"); -var _deviceApi = require("../../../packages/device-api"); -/* DO NOT EDIT, this file was generated by scripts/api-call-generator.js */ -/** - * @extends {DeviceApiCall} - */ -class AddDebugFlagCall extends _deviceApi.DeviceApiCall { - method = "addDebugFlag"; - paramsValidator = _validatorsZod.addDebugFlagParamsSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.AddDebugFlagCall = AddDebugFlagCall; -class GetAutofillDataCall extends _deviceApi.DeviceApiCall { - method = "getAutofillData"; - id = "getAutofillDataResponse"; - paramsValidator = _validatorsZod.getAutofillDataRequestSchema; - resultValidator = _validatorsZod.getAutofillDataResponseSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.GetAutofillDataCall = GetAutofillDataCall; -class GetRuntimeConfigurationCall extends _deviceApi.DeviceApiCall { - method = "getRuntimeConfiguration"; - id = "getRuntimeConfigurationResponse"; - resultValidator = _validatorsZod.getRuntimeConfigurationResponseSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.GetRuntimeConfigurationCall = GetRuntimeConfigurationCall; -class StoreFormDataCall extends _deviceApi.DeviceApiCall { - method = "storeFormData"; - paramsValidator = _validatorsZod.storeFormDataSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.StoreFormDataCall = StoreFormDataCall; -class GetAvailableInputTypesCall extends _deviceApi.DeviceApiCall { - method = "getAvailableInputTypes"; - id = "getAvailableInputTypesResponse"; - resultValidator = _validatorsZod.getAvailableInputTypesResultSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.GetAvailableInputTypesCall = GetAvailableInputTypesCall; -class GetAutofillInitDataCall extends _deviceApi.DeviceApiCall { - method = "getAutofillInitData"; - id = "getAutofillInitDataResponse"; - resultValidator = _validatorsZod.getAutofillInitDataResponseSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.GetAutofillInitDataCall = GetAutofillInitDataCall; -class GetAutofillCredentialsCall extends _deviceApi.DeviceApiCall { - method = "getAutofillCredentials"; - id = "getAutofillCredentialsResponse"; - paramsValidator = _validatorsZod.getAutofillCredentialsParamsSchema; - resultValidator = _validatorsZod.getAutofillCredentialsResultSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.GetAutofillCredentialsCall = GetAutofillCredentialsCall; -class SetSizeCall extends _deviceApi.DeviceApiCall { - method = "setSize"; - paramsValidator = _validatorsZod.setSizeParamsSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.SetSizeCall = SetSizeCall; -class SelectedDetailCall extends _deviceApi.DeviceApiCall { - method = "selectedDetail"; - paramsValidator = _validatorsZod.selectedDetailParamsSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.SelectedDetailCall = SelectedDetailCall; -class CloseAutofillParentCall extends _deviceApi.DeviceApiCall { - method = "closeAutofillParent"; -} -/** - * @extends {DeviceApiCall} - */ -exports.CloseAutofillParentCall = CloseAutofillParentCall; -class AskToUnlockProviderCall extends _deviceApi.DeviceApiCall { - method = "askToUnlockProvider"; - id = "askToUnlockProviderResponse"; - resultValidator = _validatorsZod.askToUnlockProviderResultSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.AskToUnlockProviderCall = AskToUnlockProviderCall; -class CheckCredentialsProviderStatusCall extends _deviceApi.DeviceApiCall { - method = "checkCredentialsProviderStatus"; - id = "checkCredentialsProviderStatusResponse"; - resultValidator = _validatorsZod.checkCredentialsProviderStatusResultSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.CheckCredentialsProviderStatusCall = CheckCredentialsProviderStatusCall; -class SendJSPixelCall extends _deviceApi.DeviceApiCall { - method = "sendJSPixel"; - paramsValidator = _validatorsZod.sendJSPixelParamsSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.SendJSPixelCall = SendJSPixelCall; -class SetIncontextSignupPermanentlyDismissedAtCall extends _deviceApi.DeviceApiCall { - method = "setIncontextSignupPermanentlyDismissedAt"; - paramsValidator = _validatorsZod.setIncontextSignupPermanentlyDismissedAtSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.SetIncontextSignupPermanentlyDismissedAtCall = SetIncontextSignupPermanentlyDismissedAtCall; -class GetIncontextSignupDismissedAtCall extends _deviceApi.DeviceApiCall { - method = "getIncontextSignupDismissedAt"; - id = "getIncontextSignupDismissedAt"; - resultValidator = _validatorsZod.getIncontextSignupDismissedAtSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.GetIncontextSignupDismissedAtCall = GetIncontextSignupDismissedAtCall; -class OpenManagePasswordsCall extends _deviceApi.DeviceApiCall { - method = "openManagePasswords"; -} -/** - * @extends {DeviceApiCall} - */ -exports.OpenManagePasswordsCall = OpenManagePasswordsCall; -class OpenManageCreditCardsCall extends _deviceApi.DeviceApiCall { - method = "openManageCreditCards"; -} -/** - * @extends {DeviceApiCall} - */ -exports.OpenManageCreditCardsCall = OpenManageCreditCardsCall; -class OpenManageIdentitiesCall extends _deviceApi.DeviceApiCall { - method = "openManageIdentities"; -} -/** - * @extends {DeviceApiCall} - */ -exports.OpenManageIdentitiesCall = OpenManageIdentitiesCall; -class EmailProtectionStoreUserDataCall extends _deviceApi.DeviceApiCall { - method = "emailProtectionStoreUserData"; - id = "emailProtectionStoreUserDataResponse"; - paramsValidator = _validatorsZod.emailProtectionStoreUserDataParamsSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.EmailProtectionStoreUserDataCall = EmailProtectionStoreUserDataCall; -class EmailProtectionRemoveUserDataCall extends _deviceApi.DeviceApiCall { - method = "emailProtectionRemoveUserData"; -} -/** - * @extends {DeviceApiCall} - */ -exports.EmailProtectionRemoveUserDataCall = EmailProtectionRemoveUserDataCall; -class EmailProtectionGetIsLoggedInCall extends _deviceApi.DeviceApiCall { - method = "emailProtectionGetIsLoggedIn"; - id = "emailProtectionGetIsLoggedInResponse"; - resultValidator = _validatorsZod.emailProtectionGetIsLoggedInResultSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.EmailProtectionGetIsLoggedInCall = EmailProtectionGetIsLoggedInCall; -class EmailProtectionGetUserDataCall extends _deviceApi.DeviceApiCall { - method = "emailProtectionGetUserData"; - id = "emailProtectionGetUserDataResponse"; - resultValidator = _validatorsZod.emailProtectionGetUserDataResultSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.EmailProtectionGetUserDataCall = EmailProtectionGetUserDataCall; -class EmailProtectionGetCapabilitiesCall extends _deviceApi.DeviceApiCall { - method = "emailProtectionGetCapabilities"; - id = "emailProtectionGetCapabilitiesResponse"; - resultValidator = _validatorsZod.emailProtectionGetCapabilitiesResultSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.EmailProtectionGetCapabilitiesCall = EmailProtectionGetCapabilitiesCall; -class EmailProtectionGetAddressesCall extends _deviceApi.DeviceApiCall { - method = "emailProtectionGetAddresses"; - id = "emailProtectionGetAddressesResponse"; - resultValidator = _validatorsZod.emailProtectionGetAddressesResultSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.EmailProtectionGetAddressesCall = EmailProtectionGetAddressesCall; -class EmailProtectionRefreshPrivateAddressCall extends _deviceApi.DeviceApiCall { - method = "emailProtectionRefreshPrivateAddress"; - id = "emailProtectionRefreshPrivateAddressResponse"; - resultValidator = _validatorsZod.emailProtectionRefreshPrivateAddressResultSchema; -} -/** - * @extends {DeviceApiCall} - */ -exports.EmailProtectionRefreshPrivateAddressCall = EmailProtectionRefreshPrivateAddressCall; -class StartEmailProtectionSignupCall extends _deviceApi.DeviceApiCall { - method = "startEmailProtectionSignup"; -} -/** - * @extends {DeviceApiCall} - */ -exports.StartEmailProtectionSignupCall = StartEmailProtectionSignupCall; -class CloseEmailProtectionTabCall extends _deviceApi.DeviceApiCall { - method = "closeEmailProtectionTab"; -} -/** - * @extends {DeviceApiCall} - */ -exports.CloseEmailProtectionTabCall = CloseEmailProtectionTabCall; -class ShowInContextEmailProtectionSignupPromptCall extends _deviceApi.DeviceApiCall { - method = "ShowInContextEmailProtectionSignupPrompt"; - id = "ShowInContextEmailProtectionSignupPromptResponse"; - resultValidator = _validatorsZod.showInContextEmailProtectionSignupPromptSchema; -} -exports.ShowInContextEmailProtectionSignupPromptCall = ShowInContextEmailProtectionSignupPromptCall; - -},{"../../../packages/device-api":12,"./validators.zod.js":67}],67:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.userPreferencesSchema = exports.triggerContextSchema = exports.storeFormDataSchema = exports.showInContextEmailProtectionSignupPromptSchema = exports.setSizeParamsSchema = exports.setIncontextSignupPermanentlyDismissedAtSchema = exports.sendJSPixelParamsSchema = exports.selectedDetailParamsSchema = exports.runtimeConfigurationSchema = exports.providerStatusUpdatedSchema = exports.outgoingCredentialsSchema = exports.getRuntimeConfigurationResponseSchema = exports.getIncontextSignupDismissedAtSchema = exports.getAvailableInputTypesResultSchema = exports.getAutofillInitDataResponseSchema = exports.getAutofillDataResponseSchema = exports.getAutofillDataRequestSchema = exports.getAutofillCredentialsResultSchema = exports.getAutofillCredentialsParamsSchema = exports.getAliasResultSchema = exports.getAliasParamsSchema = exports.genericErrorSchema = exports.generatedPasswordSchema = exports.emailProtectionStoreUserDataParamsSchema = exports.emailProtectionRefreshPrivateAddressResultSchema = exports.emailProtectionGetUserDataResultSchema = exports.emailProtectionGetIsLoggedInResultSchema = exports.emailProtectionGetCapabilitiesResultSchema = exports.emailProtectionGetAddressesResultSchema = exports.credentialsSchema = exports.contentScopeSchema = exports.checkCredentialsProviderStatusResultSchema = exports.availableInputTypesSchema = exports.availableInputTypes1Schema = exports.autofillSettingsSchema = exports.autofillFeatureTogglesSchema = exports.askToUnlockProviderResultSchema = exports.apiSchema = exports.addDebugFlagParamsSchema = void 0; -var _zod = require("zod"); -/* DO NOT EDIT, this file was generated by scripts/api-call-generator.js */ -// Generated by ts-to-zod - -const sendJSPixelParamsSchema = exports.sendJSPixelParamsSchema = _zod.z.union([_zod.z.object({ - pixelName: _zod.z.literal("autofill_identity"), - params: _zod.z.object({ - fieldType: _zod.z.string().optional() - }).optional() -}), _zod.z.object({ - pixelName: _zod.z.literal("autofill_show") -}), _zod.z.object({ - pixelName: _zod.z.literal("autofill_personal_address") -}), _zod.z.object({ - pixelName: _zod.z.literal("autofill_private_address") -}), _zod.z.object({ - pixelName: _zod.z.literal("incontext_show") -}), _zod.z.object({ - pixelName: _zod.z.literal("incontext_primary_cta") -}), _zod.z.object({ - pixelName: _zod.z.literal("incontext_dismiss_persisted") -}), _zod.z.object({ - pixelName: _zod.z.literal("incontext_close_x") -})]); -const addDebugFlagParamsSchema = exports.addDebugFlagParamsSchema = _zod.z.object({ - flag: _zod.z.string() -}); -const getAutofillCredentialsParamsSchema = exports.getAutofillCredentialsParamsSchema = _zod.z.object({ - id: _zod.z.string() -}); -const setSizeParamsSchema = exports.setSizeParamsSchema = _zod.z.object({ - height: _zod.z.number(), - width: _zod.z.number() -}); -const selectedDetailParamsSchema = exports.selectedDetailParamsSchema = _zod.z.object({ - data: _zod.z.record(_zod.z.unknown()), - configType: _zod.z.string() -}); -const setIncontextSignupPermanentlyDismissedAtSchema = exports.setIncontextSignupPermanentlyDismissedAtSchema = _zod.z.object({ - value: _zod.z.number().optional() -}); -const getIncontextSignupDismissedAtSchema = exports.getIncontextSignupDismissedAtSchema = _zod.z.object({ - success: _zod.z.object({ - permanentlyDismissedAt: _zod.z.number().optional(), - isInstalledRecently: _zod.z.boolean().optional() - }) -}); -const getAliasParamsSchema = exports.getAliasParamsSchema = _zod.z.object({ - requiresUserPermission: _zod.z.boolean(), - shouldConsumeAliasIfProvided: _zod.z.boolean(), - isIncontextSignupAvailable: _zod.z.boolean().optional() -}); -const getAliasResultSchema = exports.getAliasResultSchema = _zod.z.object({ - success: _zod.z.object({ - alias: _zod.z.string().optional() - }) -}); -const emailProtectionStoreUserDataParamsSchema = exports.emailProtectionStoreUserDataParamsSchema = _zod.z.object({ - token: _zod.z.string(), - userName: _zod.z.string(), - cohort: _zod.z.string() -}); -const showInContextEmailProtectionSignupPromptSchema = exports.showInContextEmailProtectionSignupPromptSchema = _zod.z.object({ - success: _zod.z.object({ - isSignedIn: _zod.z.boolean() - }) -}); -const generatedPasswordSchema = exports.generatedPasswordSchema = _zod.z.object({ - value: _zod.z.string(), - username: _zod.z.string() -}); -const triggerContextSchema = exports.triggerContextSchema = _zod.z.object({ - inputTop: _zod.z.number(), - inputLeft: _zod.z.number(), - inputHeight: _zod.z.number(), - inputWidth: _zod.z.number(), - wasFromClick: _zod.z.boolean() -}); -const credentialsSchema = exports.credentialsSchema = _zod.z.object({ - id: _zod.z.string().optional(), - username: _zod.z.string(), - password: _zod.z.string(), - origin: _zod.z.object({ - url: _zod.z.string() - }).optional(), - credentialsProvider: _zod.z.union([_zod.z.literal("duckduckgo"), _zod.z.literal("bitwarden")]).optional(), - providerStatus: _zod.z.union([_zod.z.literal("locked"), _zod.z.literal("unlocked")]).optional() -}); -const genericErrorSchema = exports.genericErrorSchema = _zod.z.object({ - message: _zod.z.string() -}); -const contentScopeSchema = exports.contentScopeSchema = _zod.z.object({ - features: _zod.z.record(_zod.z.object({ - exceptions: _zod.z.array(_zod.z.unknown()), - state: _zod.z.union([_zod.z.literal("enabled"), _zod.z.literal("disabled")]), - settings: _zod.z.record(_zod.z.unknown()).optional() - })), - unprotectedTemporary: _zod.z.array(_zod.z.unknown()) -}); -const userPreferencesSchema = exports.userPreferencesSchema = _zod.z.object({ - globalPrivacyControlValue: _zod.z.boolean().optional(), - sessionKey: _zod.z.string().optional(), - debug: _zod.z.boolean(), - platform: _zod.z.object({ - name: _zod.z.union([_zod.z.literal("ios"), _zod.z.literal("macos"), _zod.z.literal("windows"), _zod.z.literal("extension"), _zod.z.literal("android"), _zod.z.literal("unknown")]) - }), - features: _zod.z.record(_zod.z.object({ - settings: _zod.z.record(_zod.z.unknown()) - })) -}); -const outgoingCredentialsSchema = exports.outgoingCredentialsSchema = _zod.z.object({ - username: _zod.z.string().optional(), - password: _zod.z.string().optional() -}); -const availableInputTypesSchema = exports.availableInputTypesSchema = _zod.z.object({ - credentials: _zod.z.object({ - username: _zod.z.boolean().optional(), - password: _zod.z.boolean().optional() - }).optional(), - identities: _zod.z.object({ - firstName: _zod.z.boolean().optional(), - middleName: _zod.z.boolean().optional(), - lastName: _zod.z.boolean().optional(), - birthdayDay: _zod.z.boolean().optional(), - birthdayMonth: _zod.z.boolean().optional(), - birthdayYear: _zod.z.boolean().optional(), - addressStreet: _zod.z.boolean().optional(), - addressStreet2: _zod.z.boolean().optional(), - addressCity: _zod.z.boolean().optional(), - addressProvince: _zod.z.boolean().optional(), - addressPostalCode: _zod.z.boolean().optional(), - addressCountryCode: _zod.z.boolean().optional(), - phone: _zod.z.boolean().optional(), - emailAddress: _zod.z.boolean().optional() - }).optional(), - creditCards: _zod.z.object({ - cardName: _zod.z.boolean().optional(), - cardSecurityCode: _zod.z.boolean().optional(), - expirationMonth: _zod.z.boolean().optional(), - expirationYear: _zod.z.boolean().optional(), - cardNumber: _zod.z.boolean().optional() - }).optional(), - email: _zod.z.boolean().optional(), - credentialsProviderStatus: _zod.z.union([_zod.z.literal("locked"), _zod.z.literal("unlocked")]).optional() -}); -const availableInputTypes1Schema = exports.availableInputTypes1Schema = _zod.z.object({ - credentials: _zod.z.object({ - username: _zod.z.boolean().optional(), - password: _zod.z.boolean().optional() - }).optional(), - identities: _zod.z.object({ - firstName: _zod.z.boolean().optional(), - middleName: _zod.z.boolean().optional(), - lastName: _zod.z.boolean().optional(), - birthdayDay: _zod.z.boolean().optional(), - birthdayMonth: _zod.z.boolean().optional(), - birthdayYear: _zod.z.boolean().optional(), - addressStreet: _zod.z.boolean().optional(), - addressStreet2: _zod.z.boolean().optional(), - addressCity: _zod.z.boolean().optional(), - addressProvince: _zod.z.boolean().optional(), - addressPostalCode: _zod.z.boolean().optional(), - addressCountryCode: _zod.z.boolean().optional(), - phone: _zod.z.boolean().optional(), - emailAddress: _zod.z.boolean().optional() - }).optional(), - creditCards: _zod.z.object({ - cardName: _zod.z.boolean().optional(), - cardSecurityCode: _zod.z.boolean().optional(), - expirationMonth: _zod.z.boolean().optional(), - expirationYear: _zod.z.boolean().optional(), - cardNumber: _zod.z.boolean().optional() - }).optional(), - email: _zod.z.boolean().optional(), - credentialsProviderStatus: _zod.z.union([_zod.z.literal("locked"), _zod.z.literal("unlocked")]).optional() -}); -const autofillFeatureTogglesSchema = exports.autofillFeatureTogglesSchema = _zod.z.object({ - inputType_credentials: _zod.z.boolean().optional(), - inputType_identities: _zod.z.boolean().optional(), - inputType_creditCards: _zod.z.boolean().optional(), - emailProtection: _zod.z.boolean().optional(), - emailProtection_incontext_signup: _zod.z.boolean().optional(), - password_generation: _zod.z.boolean().optional(), - credentials_saving: _zod.z.boolean().optional(), - inlineIcon_credentials: _zod.z.boolean().optional(), - third_party_credentials_provider: _zod.z.boolean().optional() -}); -const getAutofillDataRequestSchema = exports.getAutofillDataRequestSchema = _zod.z.object({ - generatedPassword: generatedPasswordSchema.optional(), - inputType: _zod.z.string(), - mainType: _zod.z.union([_zod.z.literal("credentials"), _zod.z.literal("identities"), _zod.z.literal("creditCards")]), - subType: _zod.z.string(), - trigger: _zod.z.union([_zod.z.literal("userInitiated"), _zod.z.literal("autoprompt"), _zod.z.literal("postSignup")]).optional(), - serializedInputContext: _zod.z.string().optional(), - triggerContext: triggerContextSchema.optional() -}); -const getAutofillDataResponseSchema = exports.getAutofillDataResponseSchema = _zod.z.object({ - type: _zod.z.literal("getAutofillDataResponse").optional(), - success: _zod.z.object({ - credentials: credentialsSchema.optional(), - action: _zod.z.union([_zod.z.literal("fill"), _zod.z.literal("focus"), _zod.z.literal("none"), _zod.z.literal("acceptGeneratedPassword"), _zod.z.literal("rejectGeneratedPassword")]) - }).optional(), - error: genericErrorSchema.optional() -}); -const storeFormDataSchema = exports.storeFormDataSchema = _zod.z.object({ - credentials: outgoingCredentialsSchema.optional(), - trigger: _zod.z.union([_zod.z.literal("formSubmission"), _zod.z.literal("passwordGeneration"), _zod.z.literal("emailProtection")]).optional() -}); -const getAvailableInputTypesResultSchema = exports.getAvailableInputTypesResultSchema = _zod.z.object({ - type: _zod.z.literal("getAvailableInputTypesResponse").optional(), - success: availableInputTypesSchema, - error: genericErrorSchema.optional() -}); -const getAutofillInitDataResponseSchema = exports.getAutofillInitDataResponseSchema = _zod.z.object({ - type: _zod.z.literal("getAutofillInitDataResponse").optional(), - success: _zod.z.object({ - credentials: _zod.z.array(credentialsSchema), - identities: _zod.z.array(_zod.z.record(_zod.z.unknown())), - creditCards: _zod.z.array(_zod.z.record(_zod.z.unknown())), - serializedInputContext: _zod.z.string() - }).optional(), - error: genericErrorSchema.optional() -}); -const getAutofillCredentialsResultSchema = exports.getAutofillCredentialsResultSchema = _zod.z.object({ - type: _zod.z.literal("getAutofillCredentialsResponse").optional(), - success: _zod.z.object({ - id: _zod.z.string().optional(), - autogenerated: _zod.z.boolean().optional(), - username: _zod.z.string(), - password: _zod.z.string().optional() - }).optional(), - error: genericErrorSchema.optional() -}); -const autofillSettingsSchema = exports.autofillSettingsSchema = _zod.z.object({ - featureToggles: autofillFeatureTogglesSchema -}); -const emailProtectionGetIsLoggedInResultSchema = exports.emailProtectionGetIsLoggedInResultSchema = _zod.z.object({ - success: _zod.z.boolean().optional(), - error: genericErrorSchema.optional() -}); -const emailProtectionGetUserDataResultSchema = exports.emailProtectionGetUserDataResultSchema = _zod.z.object({ - success: _zod.z.object({ - userName: _zod.z.string(), - nextAlias: _zod.z.string(), - token: _zod.z.string() - }).optional(), - error: genericErrorSchema.optional() -}); -const emailProtectionGetCapabilitiesResultSchema = exports.emailProtectionGetCapabilitiesResultSchema = _zod.z.object({ - success: _zod.z.object({ - addUserData: _zod.z.boolean().optional(), - getUserData: _zod.z.boolean().optional(), - removeUserData: _zod.z.boolean().optional() - }).optional(), - error: genericErrorSchema.optional() -}); -const emailProtectionGetAddressesResultSchema = exports.emailProtectionGetAddressesResultSchema = _zod.z.object({ - success: _zod.z.object({ - personalAddress: _zod.z.string(), - privateAddress: _zod.z.string() - }).optional(), - error: genericErrorSchema.optional() -}); -const emailProtectionRefreshPrivateAddressResultSchema = exports.emailProtectionRefreshPrivateAddressResultSchema = _zod.z.object({ - success: _zod.z.object({ - personalAddress: _zod.z.string(), - privateAddress: _zod.z.string() - }).optional(), - error: genericErrorSchema.optional() -}); -const runtimeConfigurationSchema = exports.runtimeConfigurationSchema = _zod.z.object({ - contentScope: contentScopeSchema, - userUnprotectedDomains: _zod.z.array(_zod.z.string()), - userPreferences: userPreferencesSchema -}); -const providerStatusUpdatedSchema = exports.providerStatusUpdatedSchema = _zod.z.object({ - status: _zod.z.union([_zod.z.literal("locked"), _zod.z.literal("unlocked")]), - credentials: _zod.z.array(credentialsSchema), - availableInputTypes: availableInputTypes1Schema -}); -const getRuntimeConfigurationResponseSchema = exports.getRuntimeConfigurationResponseSchema = _zod.z.object({ - type: _zod.z.literal("getRuntimeConfigurationResponse").optional(), - success: runtimeConfigurationSchema.optional(), - error: genericErrorSchema.optional() -}); -const askToUnlockProviderResultSchema = exports.askToUnlockProviderResultSchema = _zod.z.object({ - type: _zod.z.literal("askToUnlockProviderResponse").optional(), - success: providerStatusUpdatedSchema, - error: genericErrorSchema.optional() -}); -const checkCredentialsProviderStatusResultSchema = exports.checkCredentialsProviderStatusResultSchema = _zod.z.object({ - type: _zod.z.literal("checkCredentialsProviderStatusResponse").optional(), - success: providerStatusUpdatedSchema, - error: genericErrorSchema.optional() -}); -const apiSchema = exports.apiSchema = _zod.z.object({ - addDebugFlag: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - paramsValidator: addDebugFlagParamsSchema.optional() - })).optional(), - getAutofillData: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("getAutofillDataResponse").optional(), - paramsValidator: getAutofillDataRequestSchema.optional(), - resultValidator: getAutofillDataResponseSchema.optional() - })).optional(), - getRuntimeConfiguration: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("getRuntimeConfigurationResponse").optional(), - resultValidator: getRuntimeConfigurationResponseSchema.optional() - })).optional(), - storeFormData: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - paramsValidator: storeFormDataSchema.optional() - })).optional(), - getAvailableInputTypes: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("getAvailableInputTypesResponse").optional(), - resultValidator: getAvailableInputTypesResultSchema.optional() - })).optional(), - getAutofillInitData: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("getAutofillInitDataResponse").optional(), - resultValidator: getAutofillInitDataResponseSchema.optional() - })).optional(), - getAutofillCredentials: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("getAutofillCredentialsResponse").optional(), - paramsValidator: getAutofillCredentialsParamsSchema.optional(), - resultValidator: getAutofillCredentialsResultSchema.optional() - })).optional(), - setSize: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - paramsValidator: setSizeParamsSchema.optional() - })).optional(), - selectedDetail: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - paramsValidator: selectedDetailParamsSchema.optional() - })).optional(), - closeAutofillParent: _zod.z.record(_zod.z.unknown()).optional(), - askToUnlockProvider: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("askToUnlockProviderResponse").optional(), - resultValidator: askToUnlockProviderResultSchema.optional() - })).optional(), - checkCredentialsProviderStatus: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("checkCredentialsProviderStatusResponse").optional(), - resultValidator: checkCredentialsProviderStatusResultSchema.optional() - })).optional(), - sendJSPixel: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - paramsValidator: sendJSPixelParamsSchema.optional() - })).optional(), - setIncontextSignupPermanentlyDismissedAt: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - paramsValidator: setIncontextSignupPermanentlyDismissedAtSchema.optional() - })).optional(), - getIncontextSignupDismissedAt: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("getIncontextSignupDismissedAt").optional(), - resultValidator: getIncontextSignupDismissedAtSchema.optional() - })).optional(), - autofillSettings: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - validatorsOnly: _zod.z.literal(true).optional(), - resultValidator: autofillSettingsSchema.optional() - })).optional(), - getAlias: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - validatorsOnly: _zod.z.literal(true).optional(), - paramValidator: getAliasParamsSchema.optional(), - resultValidator: getAliasResultSchema.optional() - })).optional(), - openManagePasswords: _zod.z.record(_zod.z.unknown()).optional(), - openManageCreditCards: _zod.z.record(_zod.z.unknown()).optional(), - openManageIdentities: _zod.z.record(_zod.z.unknown()).optional(), - emailProtectionStoreUserData: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("emailProtectionStoreUserDataResponse").optional(), - paramsValidator: emailProtectionStoreUserDataParamsSchema.optional() - })).optional(), - emailProtectionRemoveUserData: _zod.z.record(_zod.z.unknown()).optional(), - emailProtectionGetIsLoggedIn: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("emailProtectionGetIsLoggedInResponse").optional(), - resultValidator: emailProtectionGetIsLoggedInResultSchema.optional() - })).optional(), - emailProtectionGetUserData: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("emailProtectionGetUserDataResponse").optional(), - resultValidator: emailProtectionGetUserDataResultSchema.optional() - })).optional(), - emailProtectionGetCapabilities: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("emailProtectionGetCapabilitiesResponse").optional(), - resultValidator: emailProtectionGetCapabilitiesResultSchema.optional() - })).optional(), - emailProtectionGetAddresses: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("emailProtectionGetAddressesResponse").optional(), - resultValidator: emailProtectionGetAddressesResultSchema.optional() - })).optional(), - emailProtectionRefreshPrivateAddress: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("emailProtectionRefreshPrivateAddressResponse").optional(), - resultValidator: emailProtectionRefreshPrivateAddressResultSchema.optional() - })).optional(), - startEmailProtectionSignup: _zod.z.record(_zod.z.unknown()).optional(), - closeEmailProtectionTab: _zod.z.record(_zod.z.unknown()).optional(), - ShowInContextEmailProtectionSignupPrompt: _zod.z.record(_zod.z.unknown()).and(_zod.z.object({ - id: _zod.z.literal("ShowInContextEmailProtectionSignupPromptResponse").optional(), - resultValidator: showInContextEmailProtectionSignupPromptSchema.optional() - })).optional() -}); - -},{"zod":9}],68:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.GetAlias = void 0; -var _index = require("../../packages/device-api/index.js"); -var _validatorsZod = require("./__generated__/validators.zod.js"); -/** - * @extends {DeviceApiCall} - */ -class GetAlias extends _index.DeviceApiCall { - method = 'emailHandlerGetAlias'; - id = 'n/a'; - paramsValidator = _validatorsZod.getAliasParamsSchema; - resultValidator = _validatorsZod.getAliasResultSchema; - preResultValidation(response) { - // convert to the correct format, because this is a legacy API + // src/DeviceInterface/overlayApi.js + function overlayApi(device) { return { - success: response + /** + * When we are inside an 'overlay' - the HTML tooltip will be opened immediately + */ + showImmediately() { + const topContextData = device.getTopContextData(); + if (!topContextData) + throw new Error("unreachable, topContextData should be available"); + const getPosition = () => { + return { + x: 0, + y: 0, + height: 50, + width: 50 + }; + }; + const tooltip = device.uiController?.createTooltip?.(getPosition, topContextData); + if (tooltip) { + device.uiController?.setActiveTooltip?.(tooltip); + } + }, + /** + * @param {IdentityObject|CreditCardObject|CredentialsObject|{email:string, id: string}} data + * @param {string} type + * @returns {Promise} + */ + async selectedDetail(data, type) { + let detailsEntries = Object.entries(data).map(([key, value]) => { + return [key, String(value)]; + }); + const entries = Object.fromEntries(detailsEntries); + await device.deviceApi.notify(new SelectedDetailCall({ data: entries, configType: type })); + } }; } -} -exports.GetAlias = GetAlias; - -},{"../../packages/device-api/index.js":12,"./__generated__/validators.zod.js":67}],69:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.AndroidTransport = void 0; -var _index = require("../../../packages/device-api/index.js"); -var _deviceApiCalls = require("../__generated__/deviceApiCalls.js"); -class AndroidTransport extends _index.DeviceApiTransport { - /** @type {GlobalConfig} */ - config; - - /** @param {GlobalConfig} globalConfig */ - constructor(globalConfig) { - super(); - this.config = globalConfig; - if (this.config.isDDGTestMode) { - if (typeof window.BrowserAutofill?.getAutofillData !== 'function') { - console.warn('window.BrowserAutofill.getAutofillData missing'); - } - if (typeof window.BrowserAutofill?.storeFormData !== 'function') { - console.warn('window.BrowserAutofill.storeFormData missing'); - } - } - } - /** - * @param {import("../../../packages/device-api").DeviceApiCall} deviceApiCall - * @returns {Promise} - */ - async send(deviceApiCall) { - if (deviceApiCall instanceof _deviceApiCalls.GetRuntimeConfigurationCall) { - return androidSpecificRuntimeConfiguration(this.config); + // src/DeviceInterface/AppleOverlayDeviceInterface.js + var AppleOverlayDeviceInterface = class extends AppleDeviceInterface { + constructor() { + super(...arguments); + /** + * Mark top frame as not stripping credential data + * @type {boolean} + */ + __publicField(this, "stripCredentials", false); + /** + * overlay API helpers + */ + __publicField(this, "overlay", overlayApi(this)); + __publicField(this, "previousX", 0); + __publicField(this, "previousY", 0); } - if (deviceApiCall instanceof _deviceApiCalls.GetAvailableInputTypesCall) { - return androidSpecificAvailableInputTypes(this.config); + /** + * Because we're running inside the Overlay, we always create the HTML + * Tooltip controller. + * + * @override + * @returns {import("../UI/controllers/UIController.js").UIController} + */ + createUIController() { + return new HTMLTooltipUIController({ + tooltipKind: ( + /** @type {const} */ + "modern" + ), + device: this + }, { + wrapperClass: "top-autofill", + tooltipPositionClass: () => ".wrapper { transform: none; }", + setSize: (details) => this.deviceApi.notify(createNotification("setSize", details)), + remove: async () => this._closeAutofillParent(), + testMode: this.isTestMode() + }); } - if (deviceApiCall instanceof _deviceApiCalls.GetIncontextSignupDismissedAtCall) { - window.BrowserAutofill.getIncontextSignupDismissedAt(JSON.stringify(deviceApiCall.params)); - return waitForResponse(deviceApiCall.id, this.config); + addDeviceListeners() { + window.addEventListener("mouseMove", (event) => { + if (!this.previousX && !this.previousY || // if no previous coords + this.previousX === event.detail.x && this.previousY === event.detail.y) { + this.previousX = event.detail.x; + this.previousY = event.detail.y; + return; + } + const activeTooltip = this.uiController?.getActiveTooltip?.(); + activeTooltip?.focus(event.detail.x, event.detail.y); + this.previousX = event.detail.x; + this.previousY = event.detail.y; + }); + return super.addDeviceListeners(); } - if (deviceApiCall instanceof _deviceApiCalls.SetIncontextSignupPermanentlyDismissedAtCall) { - return window.BrowserAutofill.setIncontextSignupPermanentlyDismissedAt(JSON.stringify(deviceApiCall.params)); + /** + * Since we're running inside the Overlay we can limit what happens here to + * be only things that are needed to power the HTML Tooltip + * + * @override + * @returns {Promise} + */ + async setupAutofill() { + await this._getAutofillInitData(); + await this.inContextSignup.init(); + const signedIn = await this._checkDeviceSignedIn(); + if (signedIn) { + await this.getAddresses(); + } } - if (deviceApiCall instanceof _deviceApiCalls.StartEmailProtectionSignupCall) { - return window.BrowserAutofill.startEmailProtectionSignup(JSON.stringify(deviceApiCall.params)); + async postInit() { + this.overlay.showImmediately(); + super.postInit(); } - if (deviceApiCall instanceof _deviceApiCalls.CloseEmailProtectionTabCall) { - return window.BrowserAutofill.closeEmailProtectionTab(JSON.stringify(deviceApiCall.params)); + /** + * In the top-frame scenario we override the base 'selectedDetail'. + * + * This + * + * @override + * @param {IdentityObject|CreditCardObject|CredentialsObject|{email:string, id: string}} data + * @param {string} type + */ + async selectedDetail(data, type) { + return this.overlay.selectedDetail(data, type); } - if (deviceApiCall instanceof _deviceApiCalls.ShowInContextEmailProtectionSignupPromptCall) { - window.BrowserAutofill.showInContextEmailProtectionSignupPrompt(JSON.stringify(deviceApiCall.params)); - return waitForResponse(deviceApiCall.id, this.config); + }; + + // src/DeviceInterface/WindowsInterface.js + var EMAIL_PROTECTION_LOGOUT_MESSAGE = "EMAIL_PROTECTION_LOGOUT"; + var WindowsInterface = class extends InterfacePrototype_default { + constructor() { + super(...arguments); + __publicField(this, "ready", false); + /** @type {AbortController|null} */ + __publicField(this, "_abortController", null); + } + async setupAutofill() { + const loggedIn = await this._getIsLoggedIn(); + if (loggedIn) { + await this.getAddresses(); + } } - if (deviceApiCall instanceof _deviceApiCalls.GetAutofillDataCall) { - window.BrowserAutofill.getAutofillData(JSON.stringify(deviceApiCall.params)); - return waitForResponse(deviceApiCall.id, this.config); + postInit() { + super.postInit(); + this.ready = true; } - if (deviceApiCall instanceof _deviceApiCalls.StoreFormDataCall) { - return window.BrowserAutofill.storeFormData(JSON.stringify(deviceApiCall.params)); + createUIController() { + return new OverlayUIController({ + remove: async () => this._closeAutofillParent(), + show: async (details) => this._show(details) + }); } - throw new Error('android: not implemented: ' + deviceApiCall.method); - } -} - -/** - * @param {string} expectedResponse - the name/id of the response - * @param {GlobalConfig} config - * @returns {Promise<*>} - */ -exports.AndroidTransport = AndroidTransport; -function waitForResponse(expectedResponse, config) { - return new Promise(resolve => { - const handler = e => { - if (!config.isDDGTestMode) { - if (e.origin !== '') { - return; + /** + * @param {GetAutofillDataRequest} details + */ + async _show(details) { + const { mainType } = details; + if (this._abortController && !this._abortController.signal.aborted) { + this._abortController.abort(); + } + this._abortController = new AbortController(); + this.deviceApi.request(new GetAutofillDataCall(details), { signal: this._abortController.signal }).then((resp) => { + if (!this.activeForm) { + throw new Error("this.currentAttached was absent"); + } + switch (resp.action) { + case "fill": { + if (mainType in resp) { + this.activeForm?.autofillData(resp[mainType], mainType); + } else { + throw new Error(`action: "fill" cannot occur because "${mainType}" was missing`); + } + break; + } + case "focus": { + this.activeForm?.activeInput?.focus(); + break; + } + case "none": { + break; + } + default: { + if (this.globalConfig.isDDGTestMode) { + console.warn("unhandled response", resp); + } + } } - } - if (!e.data) { - return; - } - if (typeof e.data !== 'string') { - if (config.isDDGTestMode) { - console.log('❌ event.data was not a string. Expected a string so that it can be JSON parsed'); + return this._closeAutofillParent(); + }).catch((e) => { + if (this.globalConfig.isDDGTestMode) { + if (e.name === "AbortError") { + console.log("Promise Aborted"); + } else { + console.error("Promise Rejected", e); + } } + }); + } + /** + * @returns {Promise} + */ + async _closeAutofillParent() { + return this.deviceApi.notify(new CloseAutofillParentCall(null)); + } + /** + * Email Protection calls + */ + /** + * @returns {Promise} + */ + getEmailProtectionCapabilities() { + return this.deviceApi.request(new EmailProtectionGetCapabilitiesCall({})); + } + async _getIsLoggedIn() { + const isLoggedIn = await this.deviceApi.request(new EmailProtectionGetIsLoggedInCall({})); + this.isDeviceSignedIn = () => isLoggedIn; + return isLoggedIn; + } + addLogoutListener(handler) { + if (!this.globalConfig.isDDGDomain) return; - } - try { - let data = JSON.parse(e.data); - if (data.type === expectedResponse) { - window.removeEventListener('message', handler); - return resolve(data); + windowsInteropAddEventListener("message", (e) => { + if (this.globalConfig.isDDGDomain && e.data === EMAIL_PROTECTION_LOGOUT_MESSAGE) { + handler(); } - if (config.isDDGTestMode) { - console.log(`❌ event.data.type was '${data.type}', which didnt match '${expectedResponse}'`, JSON.stringify(data)); - } - } catch (e) { - window.removeEventListener('message', handler); - if (config.isDDGTestMode) { - console.log('❌ Could not JSON.parse the response'); - } - } - }; - window.addEventListener('message', handler); - }); -} - -/** - * @param {GlobalConfig} globalConfig - * @returns {{success: import('../__generated__/validators-ts').RuntimeConfiguration}} - */ -function androidSpecificRuntimeConfiguration(globalConfig) { - if (!globalConfig.userPreferences) { - throw new Error('globalConfig.userPreferences not supported yet on Android'); - } - return { - success: { - // @ts-ignore - contentScope: globalConfig.contentScope, - // @ts-ignore - userPreferences: globalConfig.userPreferences, - // @ts-ignore - userUnprotectedDomains: globalConfig.userUnprotectedDomains, - // @ts-ignore - availableInputTypes: globalConfig.availableInputTypes + }); } - }; -} - -/** - * @param {GlobalConfig} globalConfig - * @returns {{success: import('../__generated__/validators-ts').AvailableInputTypes}} - */ -function androidSpecificAvailableInputTypes(globalConfig) { - if (!globalConfig.availableInputTypes) { - throw new Error('globalConfig.availableInputTypes not supported yet on Android'); - } - return { - success: globalConfig.availableInputTypes - }; -} - -},{"../../../packages/device-api/index.js":12,"../__generated__/deviceApiCalls.js":66}],70:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.AppleTransport = void 0; -var _messaging = require("../../../packages/messaging/messaging.js"); -var _index = require("../../../packages/device-api/index.js"); -class AppleTransport extends _index.DeviceApiTransport { - /** @param {GlobalConfig} globalConfig */ - constructor(globalConfig) { - super(); - this.config = globalConfig; - const webkitConfig = new _messaging.WebkitMessagingConfig({ - hasModernWebkitAPI: this.config.hasModernWebkitAPI, - webkitMessageHandlerNames: this.config.webkitMessageHandlerNames, - secret: this.config.secret - }); - this.messaging = new _messaging.Messaging(webkitConfig); - } - async send(deviceApiCall) { - try { - // if the call has an `id`, it means that it expects a response - if (deviceApiCall.id) { - return await this.messaging.request(deviceApiCall.method, deviceApiCall.params || undefined); - } else { - return this.messaging.notify(deviceApiCall.method, deviceApiCall.params || undefined); - } - } catch (e) { - if (e instanceof _messaging.MissingHandler) { - if (this.config.isDDGTestMode) { - console.log('MissingWebkitHandler error for:', deviceApiCall.method); - } - throw new Error('unimplemented handler: ' + deviceApiCall.method); - } else { - throw e; - } + /** + * @returns {Promise} + */ + storeUserData({ addUserData }) { + return this.deviceApi.request(new EmailProtectionStoreUserDataCall(addUserData)); } - } -} -exports.AppleTransport = AppleTransport; - -},{"../../../packages/device-api/index.js":12,"../../../packages/messaging/messaging.js":15}],71:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.ExtensionTransport = void 0; -var _index = require("../../../packages/device-api/index.js"); -var _deviceApiCalls = require("../__generated__/deviceApiCalls.js"); -var _autofillUtils = require("../../autofill-utils.js"); -var _Settings = require("../../Settings.js"); -class ExtensionTransport extends _index.DeviceApiTransport { - /** @param {GlobalConfig} globalConfig */ - constructor(globalConfig) { - super(); - this.config = globalConfig; - } - async send(deviceApiCall) { - if (deviceApiCall instanceof _deviceApiCalls.GetRuntimeConfigurationCall) { - return deviceApiCall.result(await extensionSpecificRuntimeConfiguration(this)); + /** + * @returns {Promise} + */ + removeUserData() { + return this.deviceApi.request(new EmailProtectionRemoveUserDataCall({})); } - if (deviceApiCall instanceof _deviceApiCalls.GetAvailableInputTypesCall) { - return deviceApiCall.result(await extensionSpecificGetAvailableInputTypes()); + /** + * @returns {Promise} + */ + getUserData() { + return this.deviceApi.request(new EmailProtectionGetUserDataCall({})); } - if (deviceApiCall instanceof _deviceApiCalls.SetIncontextSignupPermanentlyDismissedAtCall) { - return deviceApiCall.result(await extensionSpecificSetIncontextSignupPermanentlyDismissedAtCall(deviceApiCall.params)); + async refreshAlias() { + const addresses = await this.deviceApi.request(new EmailProtectionRefreshPrivateAddressCall({})); + this.storeLocalAddresses(addresses); } - if (deviceApiCall instanceof _deviceApiCalls.GetIncontextSignupDismissedAtCall) { - return deviceApiCall.result(await extensionSpecificGetIncontextSignupDismissedAt()); + async getAddresses() { + const addresses = await this.deviceApi.request(new EmailProtectionGetAddressesCall({})); + this.storeLocalAddresses(addresses); + return addresses; } + }; - // TODO: unify all calls to use deviceApiCall.method instead of all these if blocks - if (deviceApiCall instanceof _deviceApiCalls.SendJSPixelCall) { - return deviceApiCall.result(await extensionSpecificSendPixel(deviceApiCall.params)); - } - if (deviceApiCall instanceof _deviceApiCalls.AddDebugFlagCall) { - return deviceApiCall.result(await extensionSpecificAddDebugFlag(deviceApiCall.params)); + // src/DeviceInterface/WindowsOverlayDeviceInterface.js + var WindowsOverlayDeviceInterface = class extends InterfacePrototype_default { + constructor() { + super(...arguments); + /** + * Mark top frame as not stripping credential data + * @type {boolean} + */ + __publicField(this, "stripCredentials", false); + /** + * overlay API helpers + */ + __publicField(this, "overlay", overlayApi(this)); + __publicField(this, "previousScreenX", 0); + __publicField(this, "previousScreenY", 0); } - if (deviceApiCall instanceof _deviceApiCalls.CloseAutofillParentCall || deviceApiCall instanceof _deviceApiCalls.StartEmailProtectionSignupCall) { - return; // noop + /** + * Because we're running inside the Overlay, we always create the HTML + * Tooltip controller. + * + * @override + * @returns {import("../UI/controllers/UIController.js").UIController} + */ + createUIController() { + return new HTMLTooltipUIController({ + tooltipKind: ( + /** @type {const} */ + "modern" + ), + device: this + }, { + wrapperClass: "top-autofill", + tooltipPositionClass: () => ".wrapper { transform: none; }", + setSize: (details) => this.deviceApi.notify(new SetSizeCall(details)), + remove: async () => this._closeAutofillParent(), + testMode: this.isTestMode(), + /** + * Note: This is needed because Mutation observer didn't support visibility checks on Windows + */ + checkVisibility: false + }); } - - console.error('Send not implemented for ' + deviceApiCall.method); - } -} - -/** - * @param {ExtensionTransport} deviceApi - * @returns {Promise>} - */ -exports.ExtensionTransport = ExtensionTransport; -async function extensionSpecificRuntimeConfiguration(deviceApi) { - const contentScope = await getContentScopeConfig(); - const emailProtectionEnabled = (0, _autofillUtils.isAutofillEnabledFromProcessedConfig)(contentScope); - const incontextSignupEnabled = (0, _autofillUtils.isIncontextSignupEnabledFromProcessedConfig)(contentScope); - return { - success: { - // @ts-ignore - contentScope: contentScope, - // @ts-ignore - userPreferences: { - features: { - autofill: { - settings: { - featureToggles: { - ..._Settings.Settings.defaults.featureToggles, - emailProtection: emailProtectionEnabled, - emailProtection_incontext_signup: incontextSignupEnabled - } - } - } + addDeviceListeners() { + window.addEventListener("mousemove", (event) => { + if (!this.previousScreenX && !this.previousScreenY || // if no previous coords + this.previousScreenX === event.screenX && this.previousScreenY === event.screenY) { + this.previousScreenX = event.screenX; + this.previousScreenY = event.screenY; + return; } - }, - // @ts-ignore - userUnprotectedDomains: deviceApi.config?.userUnprotectedDomains || [] + const activeTooltip = this.uiController?.getActiveTooltip?.(); + activeTooltip?.focus(event.x, event.y); + this.previousScreenX = event.screenX; + this.previousScreenY = event.screenY; + }); + return super.addDeviceListeners(); } - }; -} -async function extensionSpecificGetAvailableInputTypes() { - const contentScope = await getContentScopeConfig(); - const emailProtectionEnabled = (0, _autofillUtils.isAutofillEnabledFromProcessedConfig)(contentScope); - return { - success: { - ..._Settings.Settings.defaults.availableInputTypes, - email: emailProtectionEnabled + /** + * @returns {Promise} + */ + async _closeAutofillParent() { + return this.deviceApi.notify(new CloseAutofillParentCall(null)); } - }; -} -async function getContentScopeConfig() { - return new Promise(resolve => { - chrome.runtime.sendMessage({ - registeredTempAutofillContentScript: true, - documentUrl: window.location.href - }, response => { - if (response && 'site' in response) { - resolve(response); + /** + * @returns {Promise} + */ + openManagePasswords() { + return this.deviceApi.notify(new OpenManagePasswordsCall({})); + } + /** + * @returns {Promise} + */ + openManageCreditCards() { + return this.deviceApi.notify(new OpenManageCreditCardsCall({})); + } + /** + * @returns {Promise} + */ + openManageIdentities() { + return this.deviceApi.notify(new OpenManageIdentitiesCall({})); + } + /** + * Since we're running inside the Overlay we can limit what happens here to + * be only things that are needed to power the HTML Tooltip + * + * @override + * @returns {Promise} + */ + async setupAutofill() { + const loggedIn = await this._getIsLoggedIn(); + if (loggedIn) { + await this.getAddresses(); } - }); - }); -} - -/** - * @param {import('../__generated__/validators-ts').SendJSPixelParams} params - */ -async function extensionSpecificSendPixel(params) { - return new Promise(resolve => { - chrome.runtime.sendMessage({ - messageType: 'sendJSPixel', - options: params - }, () => { - resolve(true); - }); - }); -} - -/** - * @param {import('../__generated__/validators-ts').AddDebugFlagParams} params - */ -async function extensionSpecificAddDebugFlag(params) { - return new Promise(resolve => { - chrome.runtime.sendMessage({ - messageType: 'addDebugFlag', - options: params - }, () => { - resolve(true); - }); - }); -} -async function extensionSpecificGetIncontextSignupDismissedAt() { - return new Promise(resolve => { - chrome.runtime.sendMessage({ - messageType: 'getIncontextSignupDismissedAt' - }, response => { - resolve(response); - }); - }); -} - -/** - * @param {import('../__generated__/validators-ts').SetIncontextSignupPermanentlyDismissedAt} params - */ -async function extensionSpecificSetIncontextSignupPermanentlyDismissedAtCall(params) { - return new Promise(resolve => { - chrome.runtime.sendMessage({ - messageType: 'setIncontextSignupPermanentlyDismissedAt', - options: params - }, () => { - resolve(true); - }); - }); -} - -},{"../../../packages/device-api/index.js":12,"../../Settings.js":50,"../../autofill-utils.js":62,"../__generated__/deviceApiCalls.js":66}],72:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.createTransport = createTransport; -var _appleTransport = require("./apple.transport.js"); -var _androidTransport = require("./android.transport.js"); -var _extensionTransport = require("./extension.transport.js"); -var _windowsTransport = require("./windows.transport.js"); -/** - * @param {GlobalConfig} globalConfig - * @returns {import("../../../packages/device-api").DeviceApiTransport} - */ - -function createTransport(globalConfig) { - if (typeof globalConfig.userPreferences?.platform?.name === 'string') { - switch (globalConfig.userPreferences?.platform?.name) { - case 'ios': - case 'macos': - return new _appleTransport.AppleTransport(globalConfig); - case 'android': - return new _androidTransport.AndroidTransport(globalConfig); - default: - throw new Error('selectSender unimplemented!'); + const response = await this.deviceApi.request(new GetAutofillInitDataCall(null)); + this.storeLocalData(response); } - } - if (globalConfig.isWindows) { - return new _windowsTransport.WindowsTransport(); - } - - // fallback for when `globalConfig.userPreferences.platform.name` is absent - if (globalConfig.isDDGApp) { - if (globalConfig.isAndroid) { - return new _androidTransport.AndroidTransport(globalConfig); + async postInit() { + this.overlay.showImmediately(); + super.postInit(); + } + /** + * In the top-frame scenario, we send a message to the native + * side to indicate a selection. Once received, the native side will store that selection so that a + * subsequence call from main webpage can retrieve it + * + * @override + * @param {IdentityObject|CreditCardObject|CredentialsObject|{email:string, id: string}} data + * @param {string} type + */ + async selectedDetail(data, type) { + return this.overlay.selectedDetail(data, type); + } + /** + * Email Protection calls + */ + async _getIsLoggedIn() { + const isLoggedIn = await this.deviceApi.request(new EmailProtectionGetIsLoggedInCall({})); + this.isDeviceSignedIn = () => isLoggedIn; + return isLoggedIn; + } + async getAddresses() { + const addresses = await this.deviceApi.request(new EmailProtectionGetAddressesCall({})); + this.storeLocalAddresses(addresses); + return addresses; } - throw new Error('unreachable, createTransport'); - } - - // falls back to extension... is this still the best way to determine this? - return new _extensionTransport.ExtensionTransport(globalConfig); -} - -},{"./android.transport.js":69,"./apple.transport.js":70,"./extension.transport.js":71,"./windows.transport.js":73}],73:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.WindowsTransport = void 0; -var _index = require("../../../packages/device-api/index.js"); -/** - * @typedef {import('../../../packages/device-api/lib/device-api').CallOptions} CallOptions - * @typedef {import("../../../packages/device-api").DeviceApiCall} DeviceApiCall - */ -class WindowsTransport extends _index.DeviceApiTransport { - async send(deviceApiCall, options) { - if (deviceApiCall.id) { - return windowsTransport(deviceApiCall, options).withResponse(deviceApiCall.id); - } - return windowsTransport(deviceApiCall, options); - } -} - -/** - * @param {DeviceApiCall} deviceApiCall - * @param {CallOptions} [options] - */ -exports.WindowsTransport = WindowsTransport; -function windowsTransport(deviceApiCall, options) { - windowsInteropPostMessage({ - Feature: 'Autofill', - Name: deviceApiCall.method, - Data: deviceApiCall.params - }); - return { /** - * Sends a message and returns a Promise that resolves with the response - * @param responseId - * @returns {Promise<*>} + * Gets a single identity obj once the user requests it + * @param {Number} id + * @returns {Promise<{success: IdentityObject|undefined}>} */ - withResponse(responseId) { - return waitForWindowsResponse(responseId, options); + getAutofillIdentity(id) { + const identity = this.getLocalIdentities().find(({ id: identityId }) => `${identityId}` === `${id}`); + return Promise.resolve({ success: identity }); } }; -} -/** - * @param {string} responseId - * @param {CallOptions} [options] - * @returns {Promise} - */ -function waitForWindowsResponse(responseId, options) { - return new Promise((resolve, reject) => { - // if already aborted, reject immediately - if (options?.signal?.aborted) { - return reject(new DOMException('Aborted', 'AbortError')); - } - let teardown; - // The event handler - const handler = event => { - // console.log(`📩 windows, ${window.location.href}`, [event.origin, JSON.stringify(event.data)]) - if (!event.data) { - console.warn('data absent from message'); - return; + // src/DeviceInterface.js + function createDevice() { + const globalConfig = createGlobalConfig(); + const transport = createTransport(globalConfig); + const loggingTransport = { + async send(deviceApiCall) { + console.log("[->outgoing]", "id:", deviceApiCall.method, deviceApiCall.params || null); + const result = await transport.send(deviceApiCall); + console.log("[<-incoming]", "id:", deviceApiCall.method, result || null); + return result; } - if (event.data.type === responseId) { - teardown(); - resolve(event.data); - } - }; - - // what to do if this promise is aborted - const abortHandler = () => { - teardown(); - reject(new DOMException('Aborted', 'AbortError')); }; - - // setup - windowsInteropAddEventListener('message', handler); - options?.signal?.addEventListener('abort', abortHandler); - teardown = () => { - windowsInteropRemoveEventListener('message', handler); - options?.signal?.removeEventListener('abort', abortHandler); - }; - }); -} - -},{"../../../packages/device-api/index.js":12}],74:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -/*! - * Copyright 2015 Google Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -/* - * @see https://developers.google.com/web/updates/2015/08/using-requestidlecallback - */ -// @ts-ignore -window.requestIdleCallback = window.requestIdleCallback || function (cb) { - return setTimeout(function () { - const start = Date.now(); - // eslint-disable-next-line standard/no-callback-literal - cb({ - didTimeout: false, - timeRemaining: function () { - return Math.max(0, 50 - (Date.now() - start)); + let deviceApi = new DeviceApi(globalConfig.isDDGTestMode ? loggingTransport : transport); + const settings = new Settings(globalConfig, deviceApi); + if (globalConfig.isWindows) { + if (globalConfig.isTopFrame) { + return new WindowsOverlayDeviceInterface(globalConfig, deviceApi, settings); } - }); - }, 1); -}; -window.cancelIdleCallback = window.cancelIdleCallback || function (id) { - clearTimeout(id); -}; -var _default = exports.default = {}; + return new WindowsInterface(globalConfig, deviceApi, settings); + } + if (globalConfig.isDDGApp) { + if (globalConfig.isAndroid) { + return new AndroidInterface(globalConfig, deviceApi, settings); + } + if (globalConfig.isTopFrame) { + return new AppleOverlayDeviceInterface(globalConfig, deviceApi, settings); + } + return new AppleDeviceInterface(globalConfig, deviceApi, settings); + } + globalConfig.isExtension = true; + return new ExtensionInterface(globalConfig, deviceApi, settings); + } -},{}]},{},[63]); + // src/autofill.js + (() => { + if (shouldLog()) { + console.log("DuckDuckGo Autofill Active"); + } + if (!window.isSecureContext) + return false; + try { + const startupAutofill = () => { + if (document.visibilityState === "visible") { + const deviceInterface = createDevice(); + deviceInterface.init(); + } else { + document.addEventListener("visibilitychange", startupAutofill, { once: true }); + } + }; + startupAutofill(); + } catch (e) { + console.error(e); + } + })(); +})(); diff --git a/swift-package/Resources/assets/autofill.js b/swift-package/Resources/assets/autofill.js index aad112632..6418c8663 100644 --- a/swift-package/Resources/assets/autofill.js +++ b/swift-package/Resources/assets/autofill.js @@ -1,10768 +1,8893 @@ -(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i 1 && !unprotectedDomain) { - const partialDomain = domainParts.join('.'); - unprotectedDomain = featureList.filter(domain => domain.domain === partialDomain).length > 0; - domainParts.shift(); - } - return unprotectedDomain; -} -function processConfig(data, userList, preferences) { - const topLevelUrl = getTopLevelURL(); - const allowlisted = userList.filter(domain => domain === topLevelUrl.host).length > 0; - const enabledFeatures = Object.keys(data.features).filter(featureName => { - const feature = data.features[featureName]; - return feature.state === 'enabled' && !isUnprotectedDomain(topLevelUrl, feature.exceptions); - }); - const isBroken = isUnprotectedDomain(topLevelUrl, data.unprotectedTemporary); - preferences.site = { - domain: topLevelUrl.hostname, - isBroken, - allowlisted, - enabledFeatures - }; - // TODO - preferences.cookie = {}; - return preferences; -} - -},{}],2:[function(require,module,exports){ "use strict"; +(() => { + var __defProp = Object.defineProperty; + var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __publicField = (obj, key, value) => { + __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); + return value; + }; + var __accessCheck = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); + }; + var __privateGet = (obj, member, getter) => { + __accessCheck(obj, member, "read from private field"); + return getter ? getter.call(obj) : member.get(obj); + }; + var __privateAdd = (obj, member, value) => { + if (member.has(obj)) + throw TypeError("Cannot add the same private member more than once"); + member instanceof WeakSet ? member.add(obj) : member.set(obj, value); + }; + var __privateSet = (obj, member, value, setter) => { + __accessCheck(obj, member, "write to private field"); + setter ? setter.call(obj, value) : member.set(obj, value); + return value; + }; -Object.defineProperty(exports, "__esModule", { - value: true -}); -Object.defineProperty(exports, "DeviceApi", { - enumerable: true, - get: function () { - return _deviceApi.DeviceApi; - } -}); -Object.defineProperty(exports, "DeviceApiCall", { - enumerable: true, - get: function () { - return _deviceApiCall.DeviceApiCall; - } -}); -Object.defineProperty(exports, "DeviceApiTransport", { - enumerable: true, - get: function () { - return _deviceApi.DeviceApiTransport; - } -}); -Object.defineProperty(exports, "createNotification", { - enumerable: true, - get: function () { - return _deviceApiCall.createNotification; - } -}); -Object.defineProperty(exports, "createRequest", { - enumerable: true, - get: function () { - return _deviceApiCall.createRequest; - } -}); -Object.defineProperty(exports, "validate", { - enumerable: true, - get: function () { - return _deviceApiCall.validate; - } -}); -var _deviceApiCall = require("./lib/device-api-call.js"); -var _deviceApi = require("./lib/device-api.js"); + // src/requestIdleCallback.js + /*! + * Copyright 2015 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + window.requestIdleCallback = window.requestIdleCallback || function(cb) { + return setTimeout(function() { + const start = Date.now(); + cb({ + didTimeout: false, + timeRemaining: function() { + return Math.max(0, 50 - (Date.now() - start)); + } + }); + }, 1); + }; + window.cancelIdleCallback = window.cancelIdleCallback || function(id) { + clearTimeout(id); + }; -},{"./lib/device-api-call.js":3,"./lib/device-api.js":4}],3:[function(require,module,exports){ -"use strict"; + // src/config.js + var DDG_DOMAIN_REGEX = new RegExp(/^https:\/\/(([a-z0-9-_]+?)\.)?duckduckgo\.com\/email/); + function createGlobalConfig(overrides) { + let isApp = false; + let isTopFrame = false; + let supportsTopFrame = false; + let hasModernWebkitAPI = false; + // INJECT isApp HERE + // INJECT isTopFrame HERE + // INJECT supportsTopFrame HERE + // INJECT hasModernWebkitAPI HERE + let isWindows = false; + // INJECT isWindows HERE + let webkitMessageHandlerNames = []; + // INJECT webkitMessageHandlerNames HERE + let isDDGTestMode = false; + // INJECT isDDGTestMode HERE + let contentScope = null; + let userUnprotectedDomains = null; + let userPreferences = null; + // INJECT contentScope HERE + // INJECT userUnprotectedDomains HERE + // INJECT userPreferences HERE + let availableInputTypes = null; + // INJECT availableInputTypes HERE + let secret = "PLACEHOLDER_SECRET"; + const isAndroid = userPreferences?.platform.name === "android"; + const isDDGApp = ["ios", "android", "macos", "windows"].includes(userPreferences?.platform.name) || isWindows; + const isMobileApp = ["ios", "android"].includes(userPreferences?.platform.name); + const isFirefox = navigator.userAgent.includes("Firefox"); + const isDDGDomain = Boolean(window.location.href.match(DDG_DOMAIN_REGEX)); + const isExtension = false; + const config = { + isApp, + isDDGApp, + isAndroid, + isFirefox, + isMobileApp, + isExtension, + isTopFrame, + isWindows, + secret, + supportsTopFrame, + hasModernWebkitAPI, + contentScope, + userUnprotectedDomains, + userPreferences, + isDDGTestMode, + isDDGDomain, + availableInputTypes, + webkitMessageHandlerNames, + ...overrides + }; + return config; + } + + // src/constants.js + var constants = { + ATTR_INPUT_TYPE: "data-ddg-inputType", + ATTR_AUTOFILL: "data-ddg-autofill", + TEXT_LENGTH_CUTOFF: 100, + MAX_INPUTS_PER_PAGE: 100, + MAX_FORMS_PER_PAGE: 30, + MAX_INPUTS_PER_FORM: 80, + MAX_FORM_RESCANS: 50 + }; -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.SchemaValidationError = exports.DeviceApiCallError = exports.DeviceApiCall = void 0; -exports.createDeviceApiCall = createDeviceApiCall; -exports.createNotification = void 0; -exports.createRequest = createRequest; -exports.validate = validate; -/** - * This roughly follows https://www.jsonrpc.org/specification - * @template {import("zod").ZodType} Params=import("zod").ZodType - * @template {import("zod").ZodType} Result=import("zod").ZodType - */ -class DeviceApiCall { - /** @type {string} */ - method = 'unknown'; - /** - * An optional 'id' - used to indicate if a request requires a response. - * @type {string|null} - */ - id = null; - /** @type {Params | null | undefined} */ - paramsValidator = null; - /** @type {Result | null | undefined} */ - resultValidator = null; - /** @type {import("zod").infer} */ - params; - /** - * This is a carve-out for legacy messages that are not typed yet. - * If you set this to 'true', then the response will not be checked to conform - * to any shape - * @deprecated this is here to aid migration, should be removed ASAP - * @type {boolean} - */ - throwOnResultKeysMissing = true; - /** - * New messages should be in a particular format, eg: { success: T }, - * but you can set this to false if you want to access the result as-is, - * without any unwrapping logic - * @deprecated this is here to aid migration, should be removed ASAP - * @type {boolean} - */ - unwrapResult = true; - /** - * @param {import("zod").infer} data - */ - constructor(data) { - this.params = data; - } + // src/Form/label-util.js + var EXCLUDED_TAGS = ["BR", "SCRIPT", "NOSCRIPT", "OPTION", "STYLE"]; + var extractElementStrings = (element) => { + const strings = /* @__PURE__ */ new Set(); + const _extractElementStrings = (el) => { + if (EXCLUDED_TAGS.includes(el.tagName)) { + return; + } + if (el.nodeType === el.TEXT_NODE || !el.childNodes.length) { + let trimmedText = removeExcessWhitespace(el.textContent); + if (trimmedText) { + strings.add(trimmedText); + } + return; + } + for (let node of el.childNodes) { + let nodeType = node.nodeType; + if (nodeType !== node.ELEMENT_NODE && nodeType !== node.TEXT_NODE) { + continue; + } + _extractElementStrings(node); + } + }; + _extractElementStrings(element); + return [...strings]; + }; - /** - * @returns {import("zod").infer|undefined} - */ - validateParams() { - if (this.params === undefined) { - return undefined; + // src/Form/matching-config/__generated__/compiled-matching-config.js + var matchingConfiguration = { + matchers: { + fields: { + unknown: { type: "unknown", strategies: { ddgMatcher: "unknown" } }, + emailAddress: { + type: "emailAddress", + strategies: { + cssSelector: "emailAddress", + ddgMatcher: "emailAddress", + vendorRegex: "email" + } + }, + password: { + type: "password", + strategies: { cssSelector: "password", ddgMatcher: "password" } + }, + username: { + type: "username", + strategies: { cssSelector: "username", ddgMatcher: "username" } + }, + firstName: { + type: "firstName", + strategies: { + cssSelector: "firstName", + ddgMatcher: "firstName", + vendorRegex: "given-name" + } + }, + middleName: { + type: "middleName", + strategies: { + cssSelector: "middleName", + ddgMatcher: "middleName", + vendorRegex: "additional-name" + } + }, + lastName: { + type: "lastName", + strategies: { + cssSelector: "lastName", + ddgMatcher: "lastName", + vendorRegex: "family-name" + } + }, + fullName: { + type: "fullName", + strategies: { + cssSelector: "fullName", + ddgMatcher: "fullName", + vendorRegex: "name" + } + }, + phone: { + type: "phone", + strategies: { + cssSelector: "phone", + ddgMatcher: "phone", + vendorRegex: "tel" + } + }, + addressStreet: { + type: "addressStreet", + strategies: { + cssSelector: "addressStreet", + ddgMatcher: "addressStreet", + vendorRegex: "address-line1" + } + }, + addressStreet2: { + type: "addressStreet2", + strategies: { + cssSelector: "addressStreet2", + ddgMatcher: "addressStreet2", + vendorRegex: "address-line2" + } + }, + addressCity: { + type: "addressCity", + strategies: { + cssSelector: "addressCity", + ddgMatcher: "addressCity", + vendorRegex: "address-level2" + } + }, + addressProvince: { + type: "addressProvince", + strategies: { + cssSelector: "addressProvince", + ddgMatcher: "addressProvince", + vendorRegex: "address-level1" + } + }, + addressPostalCode: { + type: "addressPostalCode", + strategies: { + cssSelector: "addressPostalCode", + ddgMatcher: "addressPostalCode", + vendorRegex: "postal-code" + } + }, + addressCountryCode: { + type: "addressCountryCode", + strategies: { + cssSelector: "addressCountryCode", + ddgMatcher: "addressCountryCode", + vendorRegex: "country" + } + }, + birthdayDay: { + type: "birthdayDay", + strategies: { cssSelector: "birthdayDay", ddgMatcher: "birthdayDay" } + }, + birthdayMonth: { + type: "birthdayMonth", + strategies: { cssSelector: "birthdayMonth", ddgMatcher: "birthdayMonth" } + }, + birthdayYear: { + type: "birthdayYear", + strategies: { cssSelector: "birthdayYear", ddgMatcher: "birthdayYear" } + }, + cardName: { + type: "cardName", + strategies: { + cssSelector: "cardName", + ddgMatcher: "cardName", + vendorRegex: "cc-name" + } + }, + cardNumber: { + type: "cardNumber", + strategies: { + cssSelector: "cardNumber", + ddgMatcher: "cardNumber", + vendorRegex: "cc-number" + } + }, + cardSecurityCode: { + type: "cardSecurityCode", + strategies: { + cssSelector: "cardSecurityCode", + ddgMatcher: "cardSecurityCode" + } + }, + expirationMonth: { + type: "expirationMonth", + strategies: { + cssSelector: "expirationMonth", + ddgMatcher: "expirationMonth", + vendorRegex: "cc-exp-month" + } + }, + expirationYear: { + type: "expirationYear", + strategies: { + cssSelector: "expirationYear", + ddgMatcher: "expirationYear", + vendorRegex: "cc-exp-year" + } + }, + expiration: { + type: "expiration", + strategies: { + cssSelector: "expiration", + ddgMatcher: "expiration", + vendorRegex: "cc-exp" + } + } + }, + lists: { + unknown: ["unknown"], + emailAddress: ["emailAddress"], + password: ["password"], + username: ["username"], + cc: [ + "cardName", + "cardNumber", + "cardSecurityCode", + "expirationMonth", + "expirationYear", + "expiration" + ], + id: [ + "firstName", + "middleName", + "lastName", + "fullName", + "phone", + "addressStreet", + "addressStreet2", + "addressCity", + "addressProvince", + "addressPostalCode", + "addressCountryCode", + "birthdayDay", + "birthdayMonth", + "birthdayYear" + ] + } + }, + strategies: { + cssSelector: { + selectors: { + genericTextField: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month])', + submitButtonSelector: 'input[type=submit], input[type=button], input[type=image], button:not([role=switch]):not([role=link]), [role=button], a[href="#"][id*=button i], a[href="#"][id*=btn i]', + formInputsSelector: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month]),[autocomplete=username],select', + safeUniversalSelector: "*:not(select):not(option):not(script):not(noscript):not(style):not(br)", + emailAddress: 'input:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]), input[type=""][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([type=tel]), input[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]):not([name*=code i]), input:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]), input[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=""][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=email], input[type=text][aria-label*=email i]:not([aria-label*=search i]), input:not([type])[aria-label*=email i]:not([aria-label*=search i]), input[name=username][type=email], input[autocomplete=username][type=email], input[autocomplete=username][placeholder*=email i], input[autocomplete=email],input[name="mail_tel" i],input[value=email i]', + username: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month])[autocomplete^=user i],input[name=username i],input[name="loginId" i],input[name="userid" i],input[id="userid" i],input[name="user_id" i],input[name="user-id" i],input[id="login-id" i],input[id="login_id" i],input[id="loginid" i],input[name="login" i],input[name=accountname i],input[autocomplete=username i],input[name*=accountid i],input[name="j_username" i],input[id="j_username" i],input[name="uwinid" i],input[name="livedoor_id" i],input[name="ssousername" i],input[name="j_userlogin_pwd" i],input[name="user[login]" i],input[name="user" i],input[name$="_username" i],input[id="lmSsoinput" i],input[name="account_subdomain" i],input[name="masterid" i],input[name="tridField" i],input[id="signInName" i],input[id="w3c_accountsbundle_accountrequeststep1_login" i],input[id="username" i],input[name="_user" i],input[name="login_username" i],input[name^="login-user-account" i],input[id="loginusuario" i],input[name="usuario" i],input[id="UserLoginFormUsername" i],input[id="nw_username" i],input[can-field="accountName"],input[placeholder^="username" i]', + password: "input[type=password]:not([autocomplete*=cc]):not([autocomplete=one-time-code]):not([name*=answer i]):not([name*=mfa i]):not([name*=tin i]):not([name*=card i]):not([name*=cvv i]),input.js-cloudsave-phrase", + cardName: 'input[autocomplete="cc-name" i], input[autocomplete="ccname" i], input[name="ccname" i], input[name="cc-name" i], input[name="ppw-accountHolderName" i], input[id*=cardname i], input[id*=card-name i], input[id*=card_name i]', + cardNumber: 'input[autocomplete="cc-number" i], input[autocomplete="ccnumber" i], input[autocomplete="cardnumber" i], input[autocomplete="card-number" i], input[name="ccnumber" i], input[name="cc-number" i], input[name*=card i][name*=number i], input[name*=cardnumber i], input[id*=cardnumber i], input[id*=card-number i], input[id*=card_number i]', + cardSecurityCode: 'input[autocomplete="cc-csc" i], input[autocomplete="csc" i], input[autocomplete="cc-cvc" i], input[autocomplete="cvc" i], input[name="cvc" i], input[name="cc-cvc" i], input[name="cc-csc" i], input[name="csc" i], input[name*=security i][name*=code i]', + expirationMonth: '[autocomplete="cc-exp-month" i], [autocomplete="cc_exp_month" i], [name="ccmonth" i], [name="ppw-expirationDate_month" i], [name=cardExpiryMonth i], [name*=ExpDate_Month i], [name*=expiration i][name*=month i], [id*=expiration i][id*=month i], [name*=cc-exp-month i], [name*="card_exp-month" i], [name*=cc_exp_month i], [id*=cc-month i]', + expirationYear: '[autocomplete="cc-exp-year" i], [autocomplete="cc_exp_year" i], [name="ccyear" i], [name="ppw-expirationDate_year" i], [name=cardExpiryYear i], [name*=ExpDate_Year i], [name*=expiration i][name*=year i], [id*=expiration i][id*=year i], [name*="cc-exp-year" i], [name*="card_exp-year" i], [name*=cc_exp_year i], [id*=cc-year i]', + expiration: '[autocomplete="cc-exp" i], [name="cc-exp" i], [name="exp-date" i], [name="expirationDate" i], input[id*=expiration i]', + firstName: "[name*=fname i], [autocomplete*=given-name i], [name*=firstname i], [autocomplete*=firstname i], [name*=first-name i], [autocomplete*=first-name i], [name*=first_name i], [autocomplete*=first_name i], [name*=givenname i], [autocomplete*=givenname i], [name*=given-name i], [name*=given_name i], [autocomplete*=given_name i], [name*=forename i], [autocomplete*=forename i]", + middleName: "[name*=mname i], [autocomplete*=additional-name i], [name*=middlename i], [autocomplete*=middlename i], [name*=middle-name i], [autocomplete*=middle-name i], [name*=middle_name i], [autocomplete*=middle_name i], [name*=additionalname i], [autocomplete*=additionalname i], [name*=additional-name i], [name*=additional_name i], [autocomplete*=additional_name i]", + lastName: "[name=lname], [autocomplete*=family-name i], [name*=lastname i], [autocomplete*=lastname i], [name*=last-name i], [autocomplete*=last-name i], [name*=last_name i], [autocomplete*=last_name i], [name*=familyname i], [autocomplete*=familyname i], [name*=family-name i], [name*=family_name i], [autocomplete*=family_name i], [name*=surname i], [autocomplete*=surname i]", + fullName: "[autocomplete=name], [name*=fullname i], [autocomplete*=fullname i], [name*=full-name i], [autocomplete*=full-name i], [name*=full_name i], [autocomplete*=full_name i], [name*=your-name i], [autocomplete*=your-name i]", + phone: '[name*=phone i]:not([name*=extension i]):not([name*=type i]):not([name*=country i]), [name*=mobile i]:not([name*=type i]), [autocomplete=tel], [autocomplete="tel-national"], [placeholder*="phone number" i]', + addressStreet: "[name=address i], [autocomplete=street-address i], [autocomplete=address-line1 i], [name=street i], [name=ppw-line1 i], [name*=addressLine1 i]", + addressStreet2: "[name=address2 i], [autocomplete=address-line2 i], [name=ppw-line2 i], [name*=addressLine2 i]", + addressCity: "[name=city i], [autocomplete=address-level2 i], [name=ppw-city i], [name*=addressCity i]", + addressProvince: "[name=province i], [name=state i], [autocomplete=address-level1 i]", + addressPostalCode: "[name=zip i], [name=zip2 i], [name=postal i], [autocomplete=postal-code i], [autocomplete=zip-code i], [name*=postalCode i], [name*=zipcode i]", + addressCountryCode: "[name=country i], [autocomplete=country i], [name*=countryCode i], [name*=country-code i], [name*=countryName i], [name*=country-name i],select.idms-address-country", + birthdayDay: '[name=bday-day i], [name*=birthday_day i], [name*=birthday-day i], [name=date_of_birth_day i], [name=date-of-birth-day i], [name^=birthdate_d i], [name^=birthdate-d i], [aria-label="birthday" i][placeholder="day" i]', + birthdayMonth: '[name=bday-month i], [name*=birthday_month i], [name*=birthday-month i], [name=date_of_birth_month i], [name=date-of-birth-month i], [name^=birthdate_m i], [name^=birthdate-m i], select[name="mm" i]', + birthdayYear: '[name=bday-year i], [name*=birthday_year i], [name*=birthday-year i], [name=date_of_birth_year i], [name=date-of-birth-year i], [name^=birthdate_y i], [name^=birthdate-y i], [aria-label="birthday" i][placeholder="year" i]' + } + }, + ddgMatcher: { + matchers: { + unknown: { + match: /search|filter|subject|title|captcha|mfa|2fa|(two|2).?factor|one-time|otp|cerca|filtr|oggetto|titolo|(due|2|più).?fattori|suche|filtern|betreff|zoeken|filter|onderwerp|titel|chercher|filtrer|objet|titre|authentification multifacteur|double authentification|à usage unique|busca|busqueda|filtra|dos pasos|un solo uso|sök|filter|ämne|multifaktorsautentisering|tvåfaktorsautentisering|två.?faktor|engångs/iu, + skip: /phone|mobile|email|password/iu + }, + emailAddress: { + match: /.mail\b|apple.?id|posta elettronica|e.?mailadres|correo electr|correo-e|^correo$|\be.?post|e.?postadress/iu, + skip: /phone|(first.?|last.?)name|number|code/iu, + forceUnknown: /search|filter|subject|title|\btab\b|otp/iu + }, + password: { + match: /password|passwort|kennwort|wachtwoord|mot de passe|clave|contraseña|lösenord/iu, + skip: /email|one-time|error|hint/iu, + forceUnknown: /captcha|mfa|2fa|two factor|otp|pin/iu + }, + newPassword: { match: /new|re.?(enter|type)|repeat|update|reset\b/iu }, + currentPassword: { match: /current|old|previous|expired|existing/iu }, + username: { + match: /(user|account|online.?id|log(i|o)n|net)((.)?(name|i.?d.?|log(i|o)n).?)?(.?((or|\/).+|\*|:)( required)?)?$|(nome|id|login).?utente|(nome|id) (dell.)?account|codice cliente|nutzername|anmeldename|gebruikersnaam|nom d.utilisateur|identifiant|pseudo|usuari|cuenta|identificador|apodo|\bdni\b|\bnie\b| del? documento|documento de identidad|användarnamn|kontonamn|användar-id/iu, + skip: /phone/iu, + forceUnknown: /search|policy|choose a user\b/iu + }, + cardName: { + match: /(card.*name|name.*card)|(card.*holder|holder.*card)|(card.*owner|owner.*card)/iu + }, + cardNumber: { + match: /card.*number|number.*card/iu, + skip: /phone/iu, + forceUnknown: /plus/iu + }, + cardSecurityCode: { + match: /security.?code|card.?verif|cvv|csc|cvc|cv2|card id/iu + }, + expirationMonth: { + match: /(card|\bcc\b)?.?(exp(iry|iration)?)?.?(month|\bmm\b(?![.\s/-]yy))/iu, + skip: /mm[/\s.\-_—–]|check/iu + }, + expirationYear: { + match: /(card|\bcc\b)?.?(exp(iry|iration)?)?.?(year|yy)/iu, + skip: /mm[/\s.\-_—–]|check/iu + }, + expiration: { + match: /(\bmm\b|\b\d\d\b)[/\s.\-_—–](\byy|\bjj|\baa|\b\d\d)|\bexp|\bvalid(idity| through| until)/iu, + skip: /invalid|^dd\/|check/iu + }, + firstName: { + match: /(first|given|fore).?name|\bnome/iu, + skip: /last|cognome|completo/iu + }, + middleName: { match: /(middle|additional).?name/iu }, + lastName: { + match: /(last|family|sur)[^i]?name|cognome/iu, + skip: /first|\bnome/iu + }, + fullName: { + match: /^(full.?|whole\s|first.*last\s|real\s|contact.?)?name\b|\bnome/iu, + forceUnknown: /company|org|item/iu + }, + phone: { + match: /phone|mobile|telefono|cellulare/iu, + skip: /code|pass|country/iu, + forceUnknown: /ext|type|otp/iu + }, + addressStreet: { + match: /address/iu, + forceUnknown: /\bip\b|duck|web|url/iu, + skip: /address.*(2|two|3|three)|email|log.?in|sign.?in|civico/iu + }, + addressStreet2: { + match: /address.*(2|two)|apartment|\bapt\b|\bflat\b|\bline.*(2|two)/iu, + forceUnknown: /\bip\b|duck/iu, + skip: /email|log.?in|sign.?in/iu + }, + addressCity: { + match: /city|town|città|comune/iu, + skip: /\bzip\b|\bcap\b/iu, + forceUnknown: /vatican/iu + }, + addressProvince: { + match: /state|province|region|county|provincia|regione/iu, + forceUnknown: /united/iu, + skip: /country/iu + }, + addressPostalCode: { + match: /\bzip\b|postal\b|post.?code|\bcap\b|codice postale/iu + }, + addressCountryCode: { match: /country|\bnation\b|nazione|paese/iu }, + birthdayDay: { match: /(birth.*day|day.*birth)/iu, skip: /month|year/iu }, + birthdayMonth: { match: /(birth.*month|month.*birth)/iu, skip: /year/iu }, + birthdayYear: { match: /(birth.*year|year.*birth)/iu }, + loginRegex: { + match: /sign(ing)?.?[io]n(?!g)|log.?[io]n|log.?out|unsubscri|(forgot(ten)?|reset) (your )?password|password (forgotten|lost)|mfa-submit-form|unlock|logged in as|entra|accedi|accesso|resetta password|password dimenticata|dimenticato la password|recuper[ao] password|(ein|aus)loggen|anmeld(eformular|ung|efeld)|abmelden|passwort (vergessen|verloren)|zugang| zugangsformular|einwahl|inloggen|se (dé)?connecter|(dé)?connexion|récupérer ((mon|ton|votre|le) )?mot de passe|mot de passe (oublié|perdu)|clave(?! su)|olvidó su (clave|contraseña)|.*sesión|conect(arse|ado)|conéctate|acce(de|so)|entrar|logga (in|ut)|avprenumerera|avregistrera|glömt lösenord|återställ lösenord/iu + }, + signupRegex: { + match: /sign(ing)?.?up|join|\bregist(er|ration)|newsletter|\bsubscri(be|ption)|contact|create|start|enroll|settings|preferences|profile|update|checkout|purchase|buy|order|schedule|estimate|request|new.?customer|(confirm|re.?(type|enter)|repeat) password|password confirm|iscri(viti|zione)|registra(ti|zione)|(?:nuovo|crea(?:zione)?) account|contatt(?:ac)i|sottoscriv|sottoscrizione|compra|acquist(a|o)|ordin[aeio]|richie(?:di|sta)|(?:conferma|ripeti) password|inizia|nuovo cliente|impostazioni|preferenze|profilo|aggiorna|paga|registrier(ung|en)|profil (anlegen|erstellen)| nachrichten|verteiler|neukunde|neuer (kunde|benutzer|nutzer)|passwort wiederholen|anmeldeseite|nieuwsbrief|aanmaken|profiel|s.inscrire|inscription|s.abonner|créer|préférences|profil|mise à jour|payer|ach(eter|at)| nouvel utilisateur|(confirmer|réessayer) ((mon|ton|votre|le) )?mot de passe|regis(trarse|tro)|regístrate|inscr(ibirse|ipción|íbete)|solicitar|crea(r cuenta)?|nueva cuenta|nuevo (cliente|usuario)|preferencias|perfil|lista de correo|registrer(a|ing)|(nytt|öppna) konto|nyhetsbrev|prenumer(era|ation)|kontakt|skapa|starta|inställningar|min (sida|kundvagn)|uppdatera|till kassan|gäst|köp|beställ|schemalägg|ny kund|(repetera|bekräfta) lösenord/iu + }, + conservativeSignupRegex: { + match: /sign.?up|join|register|enroll|(create|new).+account|newsletter|subscri(be|ption)|settings|preferences|profile|update|iscri(viti|zione)|registra(ti|zione)|(?:nuovo|crea(?:zione)?) account|contatt(?:ac)?i|sottoscriv|sottoscrizione|impostazioni|preferenze|aggiorna|anmeld(en|ung)|registrier(en|ung)|neukunde|neuer (kunde|benutzer|nutzer)|registreren|eigenschappen|profiel|bijwerken|s.inscrire|inscription|s.abonner|abonnement|préférences|profil|créer un compte|regis(trarse|tro)|regístrate|inscr(ibirse|ipción|íbete)|crea(r cuenta)?|nueva cuenta|nuevo (cliente|usuario)|preferencias|perfil|lista de correo|registrer(a|ing)|(nytt|öppna) konto|nyhetsbrev|prenumer(era|ation)|kontakt|skapa|starta|inställningar|min (sida|kundvagn)|uppdatera/iu + }, + resetPasswordLink: { + match: /(forgot(ten)?|reset|don't remember) (your )?password|password forgotten|password dimenticata|reset(?:ta) password|recuper[ao] password|(vergessen|verloren|verlegt|wiederherstellen) passwort|wachtwoord (vergeten|reset)|(oublié|récupérer) ((mon|ton|votre|le) )?mot de passe|mot de passe (oublié|perdu)|re(iniciar|cuperar) (contraseña|clave)|olvid(ó su|aste tu|é mi) (contraseña|clave)|recordar( su)? (contraseña|clave)|glömt lösenord|återställ lösenord/iu + }, + loginProvidersRegex: { match: / with | con | mit | met | avec /iu }, + submitButtonRegex: { + match: /submit|send|confirm|save|continue|next|sign|log.?([io])n|buy|purchase|check.?out|subscribe|donate|update|\bset\b|invia|conferma|salva|continua|entra|acced|accesso|compra|paga|sottoscriv|registra|dona|senden|\bja\b|bestätigen|weiter|nächste|kaufen|bezahlen|spenden|versturen|verzenden|opslaan|volgende|koop|kopen|voeg toe|aanmelden|envoyer|confirmer|sauvegarder|continuer|suivant|signer|connexion|acheter|payer|s.abonner|donner|enviar|confirmar|registrarse|continuar|siguiente|comprar|donar|skicka|bekräfta|spara|fortsätt|nästa|logga in|köp|handla|till kassan|registrera|donera/iu + }, + submitButtonUnlikelyRegex: { + match: /facebook|twitter|google|apple|cancel|show|toggle|reveal|hide|print|back|already|annulla|mostra|nascondi|stampa|indietro|già|abbrechen|passwort|zeigen|verbergen|drucken|zurück|annuleer|wachtwoord|toon|vorige|annuler|mot de passe|montrer|cacher|imprimer|retour|déjà|anular|cancelar|imprimir|cerrar|avbryt|lösenord|visa|dölj|skirv ut|tillbaka|redan/iu + } + } + }, + vendorRegex: { + rules: { + email: /((^e-?mail$)|(^email-?address$))|(e.?mail|courriel|correo.*electr(o|ó)nico|メールアドレス|электронной.?почты|邮件|邮箱|電郵地址|ഇ-മെയില്‍|ഇലക്ട്രോണിക്.?മെയിൽ|ایمیل|پست.*الکترونیک|ईमेल|इलॅक्ट्रॉनिक.?मेल|(\b|_)eposta(\b|_)|(?:이메일|전자.?우편|[ee]-?mail)(.?주소)?)/iu, + tel: /((^phone$)|(^mobile$)|(^mobile-?phone$)|(^tel$)|(^telephone$)|(^phone-?number$))|(phone|mobile|contact.?number|telefonnummer|telefono|teléfono|telfixe|電話|telefone|telemovel|телефон|मोबाइल|(\b|_|\*)telefon(\b|_|\*)|电话|മൊബൈല്‍|(?:전화|핸드폰|휴대폰|휴대전화)(?:.?번호)?)/iu, + organization: /((^company$)|(^company-?name$)|(^organization$)|(^organization-?name$))|(company|business|organization|organisation|empresa|societe|société|ragione.?sociale|会社|название.?компании|单位|公司|شرکت|회사|직장)/iu, + "street-address": /((^address$)|(^street-?address$)|(^addr$)|(^street$)|(^mailing-?addr(ess)?$)|(^billing-?addr(ess)?$)|(^mail-?addr(ess)?$)|(^bill-?addr(ess)?$))|(streetaddress|street-address)/iu, + "address-line1": /(addrline1|address_1)|((^address-?1$)|(^address-?line-?1$)|(^addr-?1$)|(^street-?1$))|(^address$|address[_-]?line[_-]?(1|one)|address1|addr1|street|(?:shipping|billing)address$|strasse|straße|hausnummer|housenumber|house.?name|direccion|dirección|adresse|indirizzo|^住所$|住所1|адрес|地址|(\b|_)adres(?! (başlığı(nız)?|tarifi))(\b|_)|^주소.?$|주소.?1)/iu, + "address-line2": /(addrline2|address_2)|((^address-?2$)|(^address-?line-?2$)|(^addr-?2$)|(^street-?2$))|(address[_-]?line(2|two)|address2|addr2|street|suite|unit(?!e)|adresszusatz|ergänzende.?angaben|direccion2|colonia|adicional|addresssuppl|complementnom|appartement|indirizzo2|住所2|complemento|addrcomplement|улица|地址2|주소.?2)/iu, + "address-line3": /(addrline3|address_3)|((^address-?3$)|(^address-?line-?3$)|(^addr-?3$)|(^street-?3$))|(address[_-]?line(3|three)|address3|addr3|street|suite|unit(?!e)|adresszusatz|ergänzende.?angaben|direccion3|colonia|adicional|addresssuppl|complementnom|appartement|indirizzo3|住所3|complemento|addrcomplement|улица|地址3|주소.?3)/iu, + "address-level2": /((^city$)|(^town$)|(^address-?level-?2$)|(^address-?city$)|(^address-?town$))|(city|town|\bort\b|stadt|suburb|ciudad|provincia|localidad|poblacion|ville|commune|localit(a|à)|citt(a|à)|市区町村|cidade|город|市|分區|شهر|शहर|ग्राम|गाँव|നഗരം|ഗ്രാമം|((\b|_|\*)([i̇ii̇]l[cç]e(miz|niz)?)(\b|_|\*))|^시[^도·・]|시[·・]?군[·・]?구)/iu, + "address-level1": /(land)|((^state$)|(^province$)|(^provence$)|(^address-?level-?1$)|(^address-?state$)|(^address-?province$))|(county|region|province|county|principality|都道府県|estado|provincia|область|省|地區|സംസ്ഥാനം|استان|राज्य|((\b|_|\*)(eyalet|[şs]ehir|[i̇ii̇]limiz|kent)(\b|_|\*))|^시[·・]?도)/iu, + "postal-code": /((^postal$)|(^zip$)|(^zip2$)|(^zip-?code$)|(^postal-?code$)|(^post-?code$)|(^address-?zip$)|(^address-?postal$)|(^address-?code$)|(^address-?postal-?code$)|(^address-?zip-?code$))|(zip|postal|post.*code|pcode|pin.?code|postleitzahl|\bcp\b|\bcdp\b|\bcap\b|郵便番号|codigo|codpos|\bcep\b|почтовый.?индекс|पिन.?कोड|പിന്‍കോഡ്|邮政编码|邮编|郵遞區號|(\b|_)posta kodu(\b|_)|우편.?번호)/iu, + country: /((^country$)|(^country-?code$)|(^country-?name$)|(^address-?country$)|(^address-?country-?name$)|(^address-?country-?code$))|(country|countries|país|pais|(\b|_)land(\b|_)(?!.*(mark.*))|国家|국가|나라|(\b|_)(ülke|ulce|ulke)(\b|_)|کشور)/iu, + "cc-name": /(accountholdername|titulaire)|(cc-?name|card-?name|cardholder-?name|cardholder|(^nom$))|(card.?(?:holder|owner)|name.*(\b)?on(\b)?.*card|(?:card|cc).?name|cc.?full.?name|karteninhaber|nombre.*tarjeta|nom.*carte|nome.*cart|名前|имя.*карты|信用卡开户名|开户名|持卡人姓名|持卡人姓名)/iu, + name: /((^name$)|full-?name|your-?name)|(^name|full.?name|your.?name|customer.?name|bill.?name|ship.?name|name.*first.*last|firstandlastname|nombre.*y.*apellidos|^nom(?!bre)\b|お名前|氏名|^nome|نام.*نام.*خانوادگی|姓名|(\b|_|\*)ad[ı]? soyad[ı]?(\b|_|\*)|성명)/iu, + "given-name": /((^f-?name$)|(^first-?name$)|(^given-?name$)|(^first-?n$))|(first.*name|initials|fname|first$|given.*name|vorname|nombre|forename|prénom|prenom|名|\bnome|имя|نام|이름|പേര്|(\b|_|\*)(isim|ad|ad(i|ı|iniz|ınız)?)(\b|_|\*)|नाम)/iu, + "additional-name": /(apellido.?materno|lastlastname)|((^m-?name$)|(^middle-?name$)|(^additional-?name$)|(^middle-?initial$)|(^middle-?n$)|(^middle-?i$))|(middle.*name|mname|middle$|middle.*initial|m\.i\.|mi$|\bmi\b)/iu, + "family-name": /((^l-?name$)|(^last-?name$)|(^s-?name$)|(^surname$)|(^family-?name$)|(^family-?n$)|(^last-?n$))|(last.*name|lname|surname|last$|secondname|family.*name|nachname|apellidos?|famille|^nom(?!bre)|cognome|姓|apelidos|surename|sobrenome|фамилия|نام.*خانوادگی|उपनाम|മറുപേര്|(\b|_|\*)(soyisim|soyad(i|ı|iniz|ınız)?)(\b|_|\*)|\b성(?:[^명]|\b))/iu, + "cc-number": /((cc|kk)nr)|(cc-?number|cc-?num|card-?number|card-?num|(^number$)|(^cc$)|cc-?no|card-?no|(^credit-?card$)|numero-?carte|(^carte$)|(^carte-?credit$)|num-?carte|cb-?num)|((add)?(?:card|cc|acct).?(?:number|#|no|num|field)|カード番号|номер.*карты|信用卡号|信用卡号码|信用卡卡號|카드|(numero|número|numéro)(?!.*(document|fono|phone|réservation)))/iu, + "cc-exp-month": /((cc|kk)month)|((^exp-?month$)|(^cc-?exp-?month$)|(^cc-?month$)|(^card-?month$)|(^cc-?mo$)|(^card-?mo$)|(^exp-?mo$)|(^card-?exp-?mo$)|(^cc-?exp-?mo$)|(^card-?expiration-?month$)|(^expiration-?month$)|(^cc-?mm$)|(^cc-?m$)|(^card-?mm$)|(^card-?m$)|(^card-?exp-?mm$)|(^cc-?exp-?mm$)|(^exp-?mm$)|(^exp-?m$)|(^expire-?month$)|(^expire-?mo$)|(^expiry-?month$)|(^expiry-?mo$)|(^card-?expire-?month$)|(^card-?expire-?mo$)|(^card-?expiry-?month$)|(^card-?expiry-?mo$)|(^mois-?validite$)|(^mois-?expiration$)|(^m-?validite$)|(^m-?expiration$)|(^expiry-?date-?field-?month$)|(^expiration-?date-?month$)|(^expiration-?date-?mm$)|(^exp-?mon$)|(^validity-?mo$)|(^exp-?date-?mo$)|(^cb-?date-?mois$)|(^date-?m$))|(gueltig|gültig|monat|fecha|date.*exp|scadenza|有効期限|validade|срок действия карты|月)/iu, + "cc-exp-year": /((cc|kk)year)|((^exp-?year$)|(^cc-?exp-?year$)|(^cc-?year$)|(^card-?year$)|(^cc-?yr$)|(^card-?yr$)|(^exp-?yr$)|(^card-?exp-?yr$)|(^cc-?exp-?yr$)|(^card-?expiration-?year$)|(^expiration-?year$)|(^cc-?yy$)|(^cc-?y$)|(^card-?yy$)|(^card-?y$)|(^card-?exp-?yy$)|(^cc-?exp-?yy$)|(^exp-?yy$)|(^exp-?y$)|(^cc-?yyyy$)|(^card-?yyyy$)|(^card-?exp-?yyyy$)|(^cc-?exp-?yyyy$)|(^expire-?year$)|(^expire-?yr$)|(^expiry-?year$)|(^expiry-?yr$)|(^card-?expire-?year$)|(^card-?expire-?yr$)|(^card-?expiry-?year$)|(^card-?expiry-?yr$)|(^an-?validite$)|(^an-?expiration$)|(^annee-?validite$)|(^annee-?expiration$)|(^expiry-?date-?field-?year$)|(^expiration-?date-?year$)|(^cb-?date-?ann$)|(^expiration-?date-?yy$)|(^expiration-?date-?yyyy$)|(^validity-?year$)|(^exp-?date-?year$)|(^date-?y$))|(ablaufdatum|gueltig|gültig|jahr|fecha|scadenza|有効期限|validade|срок действия карты|年|有效期)/iu, + "cc-exp": /((^cc-?exp$)|(^card-?exp$)|(^cc-?expiration$)|(^card-?expiration$)|(^cc-?ex$)|(^card-?ex$)|(^card-?expire$)|(^card-?expiry$)|(^validite$)|(^expiration$)|(^expiry$)|mm-?yy|mm-?yyyy|yy-?mm|yyyy-?mm|expiration-?date|payment-?card-?expiration|(^payment-?cc-?date$))|(expir|exp.*date|^expfield$|gueltig|gültig|fecha|date.*exp|scadenza|有効期限|validade|срок действия карты)/iu, + "cc-type": /(type|kartenmarke)|((^cc-?type$)|(^card-?type$)|(^card-?brand$)|(^cc-?brand$)|(^cb-?type$))/iu + }, + ruleSets: [ + { + "address-line1": "addrline1|address_1", + "address-line2": "addrline2|address_2", + "address-line3": "addrline3|address_3", + "address-level1": "land", + "additional-name": "apellido.?materno|lastlastname", + "cc-name": "accountholdername|titulaire", + "cc-number": "(cc|kk)nr", + "cc-exp-month": "(cc|kk)month", + "cc-exp-year": "(cc|kk)year", + "cc-type": "type|kartenmarke" + }, + { + email: "(^e-?mail$)|(^email-?address$)", + tel: "(^phone$)|(^mobile$)|(^mobile-?phone$)|(^tel$)|(^telephone$)|(^phone-?number$)", + organization: "(^company$)|(^company-?name$)|(^organization$)|(^organization-?name$)", + "street-address": "(^address$)|(^street-?address$)|(^addr$)|(^street$)|(^mailing-?addr(ess)?$)|(^billing-?addr(ess)?$)|(^mail-?addr(ess)?$)|(^bill-?addr(ess)?$)", + "address-line1": "(^address-?1$)|(^address-?line-?1$)|(^addr-?1$)|(^street-?1$)", + "address-line2": "(^address-?2$)|(^address-?line-?2$)|(^addr-?2$)|(^street-?2$)", + "address-line3": "(^address-?3$)|(^address-?line-?3$)|(^addr-?3$)|(^street-?3$)", + "address-level2": "(^city$)|(^town$)|(^address-?level-?2$)|(^address-?city$)|(^address-?town$)", + "address-level1": "(^state$)|(^province$)|(^provence$)|(^address-?level-?1$)|(^address-?state$)|(^address-?province$)", + "postal-code": "(^postal$)|(^zip$)|(^zip2$)|(^zip-?code$)|(^postal-?code$)|(^post-?code$)|(^address-?zip$)|(^address-?postal$)|(^address-?code$)|(^address-?postal-?code$)|(^address-?zip-?code$)", + country: "(^country$)|(^country-?code$)|(^country-?name$)|(^address-?country$)|(^address-?country-?name$)|(^address-?country-?code$)", + name: "(^name$)|full-?name|your-?name", + "given-name": "(^f-?name$)|(^first-?name$)|(^given-?name$)|(^first-?n$)", + "additional-name": "(^m-?name$)|(^middle-?name$)|(^additional-?name$)|(^middle-?initial$)|(^middle-?n$)|(^middle-?i$)", + "family-name": "(^l-?name$)|(^last-?name$)|(^s-?name$)|(^surname$)|(^family-?name$)|(^family-?n$)|(^last-?n$)", + "cc-name": "cc-?name|card-?name|cardholder-?name|cardholder|(^nom$)", + "cc-number": "cc-?number|cc-?num|card-?number|card-?num|(^number$)|(^cc$)|cc-?no|card-?no|(^credit-?card$)|numero-?carte|(^carte$)|(^carte-?credit$)|num-?carte|cb-?num", + "cc-exp": "(^cc-?exp$)|(^card-?exp$)|(^cc-?expiration$)|(^card-?expiration$)|(^cc-?ex$)|(^card-?ex$)|(^card-?expire$)|(^card-?expiry$)|(^validite$)|(^expiration$)|(^expiry$)|mm-?yy|mm-?yyyy|yy-?mm|yyyy-?mm|expiration-?date|payment-?card-?expiration|(^payment-?cc-?date$)", + "cc-exp-month": "(^exp-?month$)|(^cc-?exp-?month$)|(^cc-?month$)|(^card-?month$)|(^cc-?mo$)|(^card-?mo$)|(^exp-?mo$)|(^card-?exp-?mo$)|(^cc-?exp-?mo$)|(^card-?expiration-?month$)|(^expiration-?month$)|(^cc-?mm$)|(^cc-?m$)|(^card-?mm$)|(^card-?m$)|(^card-?exp-?mm$)|(^cc-?exp-?mm$)|(^exp-?mm$)|(^exp-?m$)|(^expire-?month$)|(^expire-?mo$)|(^expiry-?month$)|(^expiry-?mo$)|(^card-?expire-?month$)|(^card-?expire-?mo$)|(^card-?expiry-?month$)|(^card-?expiry-?mo$)|(^mois-?validite$)|(^mois-?expiration$)|(^m-?validite$)|(^m-?expiration$)|(^expiry-?date-?field-?month$)|(^expiration-?date-?month$)|(^expiration-?date-?mm$)|(^exp-?mon$)|(^validity-?mo$)|(^exp-?date-?mo$)|(^cb-?date-?mois$)|(^date-?m$)", + "cc-exp-year": "(^exp-?year$)|(^cc-?exp-?year$)|(^cc-?year$)|(^card-?year$)|(^cc-?yr$)|(^card-?yr$)|(^exp-?yr$)|(^card-?exp-?yr$)|(^cc-?exp-?yr$)|(^card-?expiration-?year$)|(^expiration-?year$)|(^cc-?yy$)|(^cc-?y$)|(^card-?yy$)|(^card-?y$)|(^card-?exp-?yy$)|(^cc-?exp-?yy$)|(^exp-?yy$)|(^exp-?y$)|(^cc-?yyyy$)|(^card-?yyyy$)|(^card-?exp-?yyyy$)|(^cc-?exp-?yyyy$)|(^expire-?year$)|(^expire-?yr$)|(^expiry-?year$)|(^expiry-?yr$)|(^card-?expire-?year$)|(^card-?expire-?yr$)|(^card-?expiry-?year$)|(^card-?expiry-?yr$)|(^an-?validite$)|(^an-?expiration$)|(^annee-?validite$)|(^annee-?expiration$)|(^expiry-?date-?field-?year$)|(^expiration-?date-?year$)|(^cb-?date-?ann$)|(^expiration-?date-?yy$)|(^expiration-?date-?yyyy$)|(^validity-?year$)|(^exp-?date-?year$)|(^date-?y$)", + "cc-type": "(^cc-?type$)|(^card-?type$)|(^card-?brand$)|(^cc-?brand$)|(^cb-?type$)" + }, + { + email: "e.?mail|courriel|correo.*electr(o|\xF3)nico|\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9|\u042D\u043B\u0435\u043A\u0442\u0440\u043E\u043D\u043D\u043E\u0439.?\u041F\u043E\u0447\u0442\u044B|\u90AE\u4EF6|\u90AE\u7BB1|\u96FB\u90F5\u5730\u5740|\u0D07-\u0D2E\u0D46\u0D2F\u0D3F\u0D32\u0D4D\u200D|\u0D07\u0D32\u0D15\u0D4D\u0D1F\u0D4D\u0D30\u0D4B\u0D23\u0D3F\u0D15\u0D4D.?\u0D2E\u0D46\u0D2F\u0D3F\u0D7D|\u0627\u06CC\u0645\u06CC\u0644|\u067E\u0633\u062A.*\u0627\u0644\u06A9\u062A\u0631\u0648\u0646\u06CC\u06A9|\u0908\u092E\u0947\u0932|\u0907\u0932\u0945\u0915\u094D\u091F\u094D\u0930\u0949\u0928\u093F\u0915.?\u092E\u0947\u0932|(\\b|_)eposta(\\b|_)|(?:\uC774\uBA54\uC77C|\uC804\uC790.?\uC6B0\uD3B8|[Ee]-?mail)(.?\uC8FC\uC18C)?", + tel: "phone|mobile|contact.?number|telefonnummer|telefono|tel\xE9fono|telfixe|\u96FB\u8A71|telefone|telemovel|\u0442\u0435\u043B\u0435\u0444\u043E\u043D|\u092E\u094B\u092C\u093E\u0907\u0932|(\\b|_|\\*)telefon(\\b|_|\\*)|\u7535\u8BDD|\u0D2E\u0D4A\u0D2C\u0D48\u0D32\u0D4D\u200D|(?:\uC804\uD654|\uD578\uB4DC\uD3F0|\uD734\uB300\uD3F0|\uD734\uB300\uC804\uD654)(?:.?\uBC88\uD638)?", + organization: "company|business|organization|organisation|empresa|societe|soci\xE9t\xE9|ragione.?sociale|\u4F1A\u793E|\u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435.?\u043A\u043E\u043C\u043F\u0430\u043D\u0438\u0438|\u5355\u4F4D|\u516C\u53F8|\u0634\u0631\u06A9\u062A|\uD68C\uC0AC|\uC9C1\uC7A5", + "street-address": "streetaddress|street-address", + "address-line1": "^address$|address[_-]?line[_-]?(1|one)|address1|addr1|street|(?:shipping|billing)address$|strasse|stra\xDFe|hausnummer|housenumber|house.?name|direccion|direcci\xF3n|adresse|indirizzo|^\u4F4F\u6240$|\u4F4F\u62401|\u0410\u0434\u0440\u0435\u0441|\u5730\u5740|(\\b|_)adres(?! (ba\u015Fl\u0131\u011F\u0131(n\u0131z)?|tarifi))(\\b|_)|^\uC8FC\uC18C.?$|\uC8FC\uC18C.?1", + "address-line2": "address[_-]?line(2|two)|address2|addr2|street|suite|unit(?!e)|adresszusatz|erg\xE4nzende.?angaben|direccion2|colonia|adicional|addresssuppl|complementnom|appartement|indirizzo2|\u4F4F\u62402|complemento|addrcomplement|\u0423\u043B\u0438\u0446\u0430|\u5730\u57402|\uC8FC\uC18C.?2", + "address-line3": "address[_-]?line(3|three)|address3|addr3|street|suite|unit(?!e)|adresszusatz|erg\xE4nzende.?angaben|direccion3|colonia|adicional|addresssuppl|complementnom|appartement|indirizzo3|\u4F4F\u62403|complemento|addrcomplement|\u0423\u043B\u0438\u0446\u0430|\u5730\u57403|\uC8FC\uC18C.?3", + "address-level2": "city|town|\\bort\\b|stadt|suburb|ciudad|provincia|localidad|poblacion|ville|commune|localit(a|\xE0)|citt(a|\xE0)|\u5E02\u533A\u753A\u6751|cidade|\u0413\u043E\u0440\u043E\u0434|\u5E02|\u5206\u5340|\u0634\u0647\u0631|\u0936\u0939\u0930|\u0917\u094D\u0930\u093E\u092E|\u0917\u093E\u0901\u0935|\u0D28\u0D17\u0D30\u0D02|\u0D17\u0D4D\u0D30\u0D3E\u0D2E\u0D02|((\\b|_|\\*)([\u0130ii\u0307]l[c\xE7]e(miz|niz)?)(\\b|_|\\*))|^\uC2DC[^\uB3C4\xB7\u30FB]|\uC2DC[\xB7\u30FB]?\uAD70[\xB7\u30FB]?\uAD6C", + "address-level1": "county|region|province|county|principality|\u90FD\u9053\u5E9C\u770C|estado|provincia|\u043E\u0431\u043B\u0430\u0441\u0442\u044C|\u7701|\u5730\u5340|\u0D38\u0D02\u0D38\u0D4D\u0D25\u0D3E\u0D28\u0D02|\u0627\u0633\u062A\u0627\u0646|\u0930\u093E\u091C\u094D\u092F|((\\b|_|\\*)(eyalet|[\u015Fs]ehir|[\u0130ii\u0307]limiz|kent)(\\b|_|\\*))|^\uC2DC[\xB7\u30FB]?\uB3C4", + "postal-code": "zip|postal|post.*code|pcode|pin.?code|postleitzahl|\\bcp\\b|\\bcdp\\b|\\bcap\\b|\u90F5\u4FBF\u756A\u53F7|codigo|codpos|\\bcep\\b|\u041F\u043E\u0447\u0442\u043E\u0432\u044B\u0439.?\u0418\u043D\u0434\u0435\u043A\u0441|\u092A\u093F\u0928.?\u0915\u094B\u0921|\u0D2A\u0D3F\u0D28\u0D4D\u200D\u0D15\u0D4B\u0D21\u0D4D|\u90AE\u653F\u7F16\u7801|\u90AE\u7F16|\u90F5\u905E\u5340\u865F|(\\b|_)posta kodu(\\b|_)|\uC6B0\uD3B8.?\uBC88\uD638", + country: "country|countries|pa\xEDs|pais|(\\b|_)land(\\b|_)(?!.*(mark.*))|\u56FD\u5BB6|\uAD6D\uAC00|\uB098\uB77C|(\\b|_)(\xFClke|ulce|ulke)(\\b|_)|\u06A9\u0634\u0648\u0631", + "cc-name": "card.?(?:holder|owner)|name.*(\\b)?on(\\b)?.*card|(?:card|cc).?name|cc.?full.?name|karteninhaber|nombre.*tarjeta|nom.*carte|nome.*cart|\u540D\u524D|\u0418\u043C\u044F.*\u043A\u0430\u0440\u0442\u044B|\u4FE1\u7528\u5361\u5F00\u6237\u540D|\u5F00\u6237\u540D|\u6301\u5361\u4EBA\u59D3\u540D|\u6301\u5361\u4EBA\u59D3\u540D", + name: "^name|full.?name|your.?name|customer.?name|bill.?name|ship.?name|name.*first.*last|firstandlastname|nombre.*y.*apellidos|^nom(?!bre)\\b|\u304A\u540D\u524D|\u6C0F\u540D|^nome|\u0646\u0627\u0645.*\u0646\u0627\u0645.*\u062E\u0627\u0646\u0648\u0627\u062F\u06AF\u06CC|\u59D3\u540D|(\\b|_|\\*)ad[\u0131]? soyad[\u0131]?(\\b|_|\\*)|\uC131\uBA85", + "given-name": "first.*name|initials|fname|first$|given.*name|vorname|nombre|forename|pr\xE9nom|prenom|\u540D|\\bnome|\u0418\u043C\u044F|\u0646\u0627\u0645|\uC774\uB984|\u0D2A\u0D47\u0D30\u0D4D|(\\b|_|\\*)(isim|ad|ad(i|\u0131|iniz|\u0131n\u0131z)?)(\\b|_|\\*)|\u0928\u093E\u092E", + "additional-name": "middle.*name|mname|middle$|middle.*initial|m\\.i\\.|mi$|\\bmi\\b", + "family-name": "last.*name|lname|surname|last$|secondname|family.*name|nachname|apellidos?|famille|^nom(?!bre)|cognome|\u59D3|apelidos|surename|sobrenome|\u0424\u0430\u043C\u0438\u043B\u0438\u044F|\u0646\u0627\u0645.*\u062E\u0627\u0646\u0648\u0627\u062F\u06AF\u06CC|\u0909\u092A\u0928\u093E\u092E|\u0D2E\u0D31\u0D41\u0D2A\u0D47\u0D30\u0D4D|(\\b|_|\\*)(soyisim|soyad(i|\u0131|iniz|\u0131n\u0131z)?)(\\b|_|\\*)|\\b\uC131(?:[^\uBA85]|\\b)", + "cc-number": "(add)?(?:card|cc|acct).?(?:number|#|no|num|field)|\u30AB\u30FC\u30C9\u756A\u53F7|\u041D\u043E\u043C\u0435\u0440.*\u043A\u0430\u0440\u0442\u044B|\u4FE1\u7528\u5361\u53F7|\u4FE1\u7528\u5361\u53F7\u7801|\u4FE1\u7528\u5361\u5361\u865F|\uCE74\uB4DC|(numero|n\xFAmero|num\xE9ro)(?!.*(document|fono|phone|r\xE9servation))", + "cc-exp-month": "gueltig|g\xFCltig|monat|fecha|date.*exp|scadenza|\u6709\u52B9\u671F\u9650|validade|\u0421\u0440\u043E\u043A \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044F \u043A\u0430\u0440\u0442\u044B|\u6708", + "cc-exp-year": "ablaufdatum|gueltig|g\xFCltig|jahr|fecha|scadenza|\u6709\u52B9\u671F\u9650|validade|\u0421\u0440\u043E\u043A \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044F \u043A\u0430\u0440\u0442\u044B|\u5E74|\u6709\u6548\u671F", + "cc-exp": "expir|exp.*date|^expfield$|gueltig|g\xFCltig|fecha|date.*exp|scadenza|\u6709\u52B9\u671F\u9650|validade|\u0421\u0440\u043E\u043A \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044F \u043A\u0430\u0440\u0442\u044B" + } + ] + } } - this._validate(this.params, this.paramsValidator); - return this.params; - } + }; - /** - * @param {any|null} incoming - * @returns {import("zod").infer} - */ - validateResult(incoming) { - this._validate(incoming, this.resultValidator); - if (!incoming) { - return incoming; + // src/Form/matching-utils.js + function logMatching(el, matchingResult) { + if (!shouldLog()) + return; + const fieldIdentifier = getInputIdentifier(el); + console.group(fieldIdentifier); + console.log(el); + const { strategyName, matchedString, matchedFrom, matcherType } = matchingResult; + const verb = getVerb(matchingResult); + let stringToLog = `${verb} for "${matcherType}" with "${strategyName}"`; + if (matchedString && matchedFrom) { + stringToLog += ` +String: "${matchedString}" +Source: "${matchedFrom}"`; + } + console.log(stringToLog); + console.groupEnd(); + } + function getVerb(matchingResult) { + if (matchingResult.matched) + return "Matched"; + if (matchingResult.proceed === false) + return "Matched forceUnknown"; + if (matchingResult.skip) + return "Skipped"; + return ""; + } + function getInputIdentifier(el) { + const label = getExplicitLabelsText(el); + const placeholder = el instanceof HTMLInputElement && el.placeholder ? `${el.placeholder}` : ""; + const name = el.name ? `${el.name}` : ""; + const id = el.id ? `#${el.id}` : ""; + return "Field: " + (label || placeholder || name || id); + } + function logUnmatched(el, allStrings) { + if (!shouldLog()) + return; + const fieldIdentifier = getInputIdentifier(el); + console.group(fieldIdentifier); + console.log(el); + const stringToLog = "Field not matched."; + console.log(stringToLog, allStrings); + console.groupEnd(); + } + + // src/Form/matching.js + var { TEXT_LENGTH_CUTOFF, ATTR_INPUT_TYPE } = constants; + var dimensionBounds = { + emailAddress: { minWidth: 35 } + }; + var _config, _cssSelectors, _ddgMatchers, _vendorRegexRules, _matcherLists, _defaultStrategyOrder; + var Matching = class { + /** + * @param {MatchingConfiguration} config + */ + constructor(config) { + /** @type {MatchingConfiguration} */ + __privateAdd(this, _config, void 0); + /** @type {CssSelectorConfiguration['selectors']} */ + __privateAdd(this, _cssSelectors, void 0); + /** @type {Record} */ + __privateAdd(this, _ddgMatchers, void 0); + /** + * This acts as an internal cache for the larger vendorRegexes + * @type {VendorRegexConfiguration['rules']} + */ + __privateAdd(this, _vendorRegexRules, void 0); + /** @type {MatcherLists} */ + __privateAdd(this, _matcherLists, void 0); + /** @type {Array} */ + __privateAdd(this, _defaultStrategyOrder, ["cssSelector", "ddgMatcher", "vendorRegex"]); + /** @type {Record} */ + __publicField(this, "activeElementStrings", { + nameAttr: "", + labelText: "", + placeholderAttr: "", + relatedText: "", + id: "" + }); + /** + * Yield strings in the order in which they should be checked against. + * + * Note: some strategies may not want to accept all strings, which is + * where `matchableStrings` helps. It defaults to when you see below but can + * be overridden. + * + * For example, `nameAttr` is first, since this has the highest chance of matching + * and then the rest are in decreasing order of value vs cost + * + * A generator function is used here to prevent any potentially expensive + * lookups occurring if they are rare. For example if 90% of all matching never needs + * to look at the output from `relatedText`, then the cost of computing it will be avoided. + * + * @param {HTMLInputElement|HTMLSelectElement} el + * @param {HTMLElement} form + * @returns {Record} + */ + __publicField(this, "_elementStringCache", /* @__PURE__ */ new WeakMap()); + __privateSet(this, _config, config); + __privateSet(this, _vendorRegexRules, __privateGet(this, _config).strategies.vendorRegex.rules); + __privateSet(this, _cssSelectors, __privateGet(this, _config).strategies.cssSelector.selectors); + __privateSet(this, _ddgMatchers, __privateGet(this, _config).strategies.ddgMatcher.matchers); + __privateSet(this, _matcherLists, { + unknown: [], + cc: [], + id: [], + password: [], + username: [], + emailAddress: [] + }); + for (let [listName, matcherNames] of Object.entries(__privateGet(this, _config).matchers.lists)) { + for (let fieldName of matcherNames) { + if (!__privateGet(this, _matcherLists)[listName]) { + __privateGet(this, _matcherLists)[listName] = []; + } + __privateGet(this, _matcherLists)[listName].push(__privateGet(this, _config).matchers.fields[fieldName]); + } + } } - if (!this.unwrapResult) { - return incoming; + /** + * @param {HTMLInputElement|HTMLSelectElement} input + * @param {HTMLElement} formEl + */ + setActiveElementStrings(input, formEl) { + this.activeElementStrings = this.getElementStrings(input, formEl); + } + /** + * Try to access a 'vendor regex' by name + * @param {string} regexName + * @returns {RegExp | undefined} + */ + vendorRegex(regexName) { + const match = __privateGet(this, _vendorRegexRules)[regexName]; + if (!match) { + console.warn("Vendor Regex not found for", regexName); + return void 0; + } + return match; } - if ('data' in incoming) { - console.warn('response had `data` property. Please migrate to `success`'); - return incoming.data; + /** + * Strategies can have different lookup names. This returns the correct one + * @param {MatcherTypeNames} matcherName + * @param {StrategyNames} vendorRegex + * @returns {MatcherTypeNames} + */ + getStrategyLookupByType(matcherName, vendorRegex) { + return __privateGet(this, _config).matchers.fields[matcherName]?.strategies[vendorRegex]; } - if ('success' in incoming) { - return incoming.success; + /** + * Try to access a 'css selector' by name from configuration + * @param {RequiredCssSelectors | string} selectorName + * @returns {string}; + */ + cssSelector(selectorName) { + const match = __privateGet(this, _cssSelectors)[selectorName]; + if (!match) { + console.warn("CSS selector not found for %s, using a default value", selectorName); + return ""; + } + return match; } - if ('error' in incoming) { - if (typeof incoming.error.message === 'string') { - throw new DeviceApiCallError(`${this.method}: ${incoming.error.message}`); + /** + * Try to access a 'ddg matcher' by name from configuration + * @param {MatcherTypeNames | string} matcherName + * @returns {DDGMatcher | undefined} + */ + ddgMatcher(matcherName) { + const match = __privateGet(this, _ddgMatchers)[matcherName]; + if (!match) { + console.warn("DDG matcher not found for", matcherName); + return void 0; } + return match; } - if (this.throwOnResultKeysMissing) { - throw new Error('unreachable. Response did not contain `success` or `data`'); + /** + * Returns the RegExp for the given matcherName, with proper flags + * @param {AllDDGMatcherNames} matcherName + * @returns {RegExp|undefined} + */ + getDDGMatcherRegex(matcherName) { + const matcher = this.ddgMatcher(matcherName); + if (!matcher || !matcher.match) { + console.warn("DDG matcher has unexpected format"); + return void 0; + } + return matcher?.match; } - return incoming; - } - - /** - * @param {any} data - * @param {import("zod").ZodType|undefined|null} [validator] - * @private - */ - _validate(data, validator) { - if (!validator) return data; - if (validator) { - const result = validator?.safeParse(data); - if (!result) { - throw new Error('unreachable, data failure', data); + /** + * Try to access a list of matchers by name - these are the ones collected in the constructor + * @param {keyof MatcherLists} listName + * @return {Matcher[]} + */ + matcherList(listName) { + const matcherList = __privateGet(this, _matcherLists)[listName]; + if (!matcherList) { + console.warn("MatcherList not found for ", listName); + return []; } - if (!result.success) { - if ('error' in result) { - this.throwError(result.error.issues); - } else { - console.error('unknown error from validate'); + return matcherList; + } + /** + * Convert a list of matchers into a single CSS selector. + * + * This will consider all matchers in the list and if it + * contains a CSS Selector it will be added to the final output + * + * @param {keyof MatcherLists} listName + * @returns {string | undefined} + */ + joinCssSelectors(listName) { + const matcherList = this.matcherList(listName); + if (!matcherList) { + console.warn("Matcher list not found for", listName); + return void 0; + } + const selectors = []; + for (let matcher of matcherList) { + if (matcher.strategies.cssSelector) { + const css = this.cssSelector(matcher.strategies.cssSelector); + if (css) { + selectors.push(css); + } } } + return selectors.join(", "); } - } - - /** - * @param {import('zod').ZodIssue[]} errors - */ - throwError(errors) { - const error = SchemaValidationError.fromZodErrors(errors, this.constructor.name); - throw error; - } - - /** - * Use this helper for creating stand-in response messages that are typed correctly. - * - * @examples - * - * ```js - * const msg = new Message(); - * const response = msg.response({}) // <-- This argument will be typed correctly - * ``` - * - * @param {import("zod").infer} response - * @returns {import("zod").infer} - */ - result(response) { - return response; - } - /** - * @returns {import("zod").infer} - */ - preResultValidation(response) { - return response; - } -} -exports.DeviceApiCall = DeviceApiCall; -class DeviceApiCallError extends Error {} - -/** - * Check for this error if you'd like to - */ -exports.DeviceApiCallError = DeviceApiCallError; -class SchemaValidationError extends Error { - /** @type {import("zod").ZodIssue[]} */ - validationErrors = []; - - /** - * @param {import("zod").ZodIssue[]} errors - * @param {string} name - * @returns {SchemaValidationError} - */ - static fromZodErrors(errors, name) { - const heading = `${errors.length} SchemaValidationError(s) errors for ` + name; - function log(issue) { - switch (issue.code) { - case 'invalid_literal': - case 'invalid_type': - { - console.log(`${name}. Path: '${issue.path.join('.')}', Error: '${issue.message}'`); - break; + /** + * Returns true if the field is visible and large enough + * @param {keyof MatcherLists} matchedType + * @param {HTMLInputElement} input + * @returns {boolean} + */ + isInputLargeEnough(matchedType, input) { + const expectedDimensionBounds = dimensionBounds[matchedType]; + if (!expectedDimensionBounds) + return true; + const width = input.offsetWidth; + const height = input.offsetHeight; + const isHidden = height === 0 && width === 0; + if (isHidden) + return true; + return width >= expectedDimensionBounds.minWidth; + } + /** + * Tries to infer the input type for an input + * + * @param {HTMLInputElement|HTMLSelectElement} input + * @param {HTMLElement} formEl + * @param {SetInputTypeOpts} [opts] + * @returns {SupportedTypes} + */ + inferInputType(input, formEl, opts = {}) { + const presetType = getInputType(input); + if (presetType !== "unknown") { + return presetType; + } + this.setActiveElementStrings(input, formEl); + if (this.subtypeFromMatchers("unknown", input)) + return "unknown"; + if (opts.isCCForm) { + const subtype = this.subtypeFromMatchers("cc", input); + if (subtype && isValidCreditCardSubtype(subtype)) { + return `creditCards.${subtype}`; + } + } + if (input instanceof HTMLInputElement) { + if (this.subtypeFromMatchers("password", input)) { + if (["password", "text"].includes(input.type) && input.name !== "email" && // pcsretirement.com, improper use of the for attribute + input.name !== "Username") { + return this.inferPasswordVariant(input, opts); } - case 'invalid_union': - { - for (let unionError of issue.unionErrors) { - for (let issue1 of unionError.issues) { - log(issue1); - } + } + if (this.subtypeFromMatchers("emailAddress", input)) { + if (!this.isInputLargeEnough("emailAddress", input)) { + if (shouldLog()) { + console.log("Field matched for Email Address, but discarded because too small when scanned"); } - break; + return "unknown"; } - default: - { - console.log(name, 'other issue:', issue); + if (opts.isLogin || opts.isHybrid) { + return "credentials.username"; + } + if (window.location.href.includes("https://accounts.google.com/v3/signin/identifier") && input.matches("[type=email][autocomplete=username]")) { + return "credentials.username"; } + return "identities.emailAddress"; + } + if (this.subtypeFromMatchers("username", input)) { + return "credentials.username"; + } } + const idSubtype = this.subtypeFromMatchers("id", input); + if (idSubtype && isValidIdentitiesSubtype(idSubtype)) { + return `identities.${idSubtype}`; + } + logUnmatched(input, this.activeElementStrings); + return "unknown"; } - for (let error of errors) { - log(error); + /** + * @typedef {{ + * isLogin?: boolean, + * isHybrid?: boolean, + * isCCForm?: boolean, + * hasCredentials?: boolean, + * supportsIdentitiesAutofill?: boolean + * }} SetInputTypeOpts + */ + /** + * Sets the input type as a data attribute to the element and returns it + * @param {HTMLInputElement} input + * @param {HTMLElement} formEl + * @param {SetInputTypeOpts} [opts] + * @returns {SupportedSubTypes | string} + */ + setInputType(input, formEl, opts = {}) { + const type = this.inferInputType(input, formEl, opts); + input.setAttribute(ATTR_INPUT_TYPE, type); + return type; } - const message = [heading, 'please see the details above'].join('\n '); - const error = new SchemaValidationError(message); - error.validationErrors = errors; - return error; - } -} - -/** - * Creates an instance of `DeviceApiCall` from only a name and 'params' - * and optional validators. Use this to help migrate existing messages. - * - * @template {import("zod").ZodType} Params - * @template {import("zod").ZodType} Result - * @param {string} method - * @param {import("zod").infer} [params] - * @param {Params|null} [paramsValidator] - * @param {Result|null} [resultValidator] - * @returns {DeviceApiCall} - */ -exports.SchemaValidationError = SchemaValidationError; -function createDeviceApiCall(method, params) { - let paramsValidator = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; - let resultValidator = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; - /** @type {DeviceApiCall} */ - const deviceApiCall = new DeviceApiCall(params); - deviceApiCall.paramsValidator = paramsValidator; - deviceApiCall.resultValidator = resultValidator; - deviceApiCall.method = method; - deviceApiCall.throwOnResultKeysMissing = false; - deviceApiCall.unwrapResult = false; - return deviceApiCall; -} - -/** - * Creates an instance of `DeviceApiCall` from only a name and 'params' - * and optional validators. Use this to help migrate existing messages. - * - * Note: This creates a regular DeviceApiCall, but adds the 'id' as a string - * so that transports know that it expects a response. - * - * @template {import("zod").ZodType} Params - * @template {import("zod").ZodType} Result - * @param {string} method - * @param {import("zod").infer} [params] - * @param {string} [id] - * @param {Params|null} [paramsValidator] - * @param {Result|null} [resultValidator] - * @returns {DeviceApiCall} - */ -function createRequest(method, params) { - let id = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'n/a'; - let paramsValidator = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; - let resultValidator = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null; - const call = createDeviceApiCall(method, params, paramsValidator, resultValidator); - call.id = id; - return call; -} -const createNotification = exports.createNotification = createDeviceApiCall; - -/** - * Validate any arbitrary data with any Zod validator - * - * @template {import("zod").ZodType} Validator - * @param {any} data - * @param {Validator | null} [validator] - * @returns {import("zod").infer} - */ -function validate(data) { - let validator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; - if (validator) { - return validator.parse(data); - } - return data; -} - -},{}],4:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.DeviceApiTransport = exports.DeviceApi = void 0; -/** - * Platforms should only need to implement this `send` method - */ -class DeviceApiTransport { - /** - * @param {import("./device-api-call.js").DeviceApiCall} _deviceApiCall - * @param {CallOptions} [_options] - * @returns {Promise} - */ - async send(_deviceApiCall, _options) { - return undefined; - } -} - -/** - * This is the base Sender class that platforms can will implement. - * - * Note: The 'handle' method must be implemented, unless you also implement 'send' - * - * @typedef CallOptions - * @property {AbortSignal} [signal] - */ -exports.DeviceApiTransport = DeviceApiTransport; -class DeviceApi { - /** @type {DeviceApiTransport} */ - transport; - /** @param {DeviceApiTransport} transport */ - constructor(transport) { - this.transport = transport; - } - /** - * @template {import("./device-api-call").DeviceApiCall} D - * @param {D} deviceApiCall - * @param {CallOptions} [options] - * @returns {Promise['success']>>} - */ - async request(deviceApiCall, options) { - deviceApiCall.validateParams(); - let result = await this.transport.send(deviceApiCall, options); - let processed = deviceApiCall.preResultValidation(result); - return deviceApiCall.validateResult(processed); - } - /** - * @template {import("./device-api-call").DeviceApiCall} P - * @param {P} deviceApiCall - * @param {CallOptions} [options] - * @returns {Promise} - */ - async notify(deviceApiCall, options) { - deviceApiCall.validateParams(); - return this.transport.send(deviceApiCall, options); - } -} -exports.DeviceApi = DeviceApi; - -},{}],5:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.MissingHandler = exports.MessagingTransport = exports.Messaging = void 0; -Object.defineProperty(exports, "WebkitMessagingConfig", { - enumerable: true, - get: function () { - return _webkit.WebkitMessagingConfig; - } -}); -var _webkit = require("./webkit.js"); -/** - * @module Messaging - * - * @description - * - * An abstraction for communications between JavaScript and host platforms. - * - * 1) First you construct your platform-specific configuration (eg: {@link WebkitMessagingConfig}) - * 2) Then use that to get an instance of the Messaging utility which allows - * you to send and receive data in a unified way - * 3) Each platform implements {@link MessagingTransport} along with its own Configuration - * - For example, to learn what configuration is required for Webkit, see: {@link "Webkit Messaging".WebkitMessagingConfig} - * - Or, to learn about how messages are sent and received in Webkit, see {@link "Webkit Messaging".WebkitMessagingTransport} - * - * @example Webkit Messaging - * - * ```js - * import { Messaging, WebkitMessagingConfig } from "@duckduckgo/content-scope-scripts/lib/messaging.js" - * - * // This config would be injected into the UserScript - * const injectedConfig = { - * hasModernWebkitAPI: true, - * webkitMessageHandlerNames: ["foo", "bar", "baz"], - * secret: "dax", - * }; - * - * // Then use that config to construct platform-specific configuration - * const config = new WebkitMessagingConfig(injectedConfig); - * - * // finally, get an instance of Messaging and start sending messages in a unified way 🚀 - * const messaging = new Messaging(config); - * messaging.notify("hello world!", {foo: "bar"}) - * - * ``` - * - * @example Windows Messaging - * - * ```js - * import { Messaging, WindowsMessagingConfig } from "@duckduckgo/content-scope-scripts/lib/messaging.js" - * - * // Messaging on Windows is namespaced, so you can create multiple messaging instances - * const autofillConfig = new WindowsMessagingConfig({ featureName: "Autofill" }); - * const debugConfig = new WindowsMessagingConfig({ featureName: "Debugging" }); - * - * const autofillMessaging = new Messaging(autofillConfig); - * const debugMessaging = new Messaging(debugConfig); - * - * // Now send messages to both features as needed 🚀 - * autofillMessaging.notify("storeFormData", { "username": "dax" }) - * debugMessaging.notify("pageLoad", { time: window.performance.now() }) - * ``` - */ - -/** - * @implements {MessagingTransport} - */ -class Messaging { - /** - * @param {WebkitMessagingConfig} config - */ - constructor(config) { - this.transport = getTransport(config); - } - /** - * Send a 'fire-and-forget' message. - * @throws {Error} - * {@link MissingHandler} - * - * @example - * - * ``` - * const messaging = new Messaging(config) - * messaging.notify("foo", {bar: "baz"}) - * ``` - * @param {string} name - * @param {Record} [data] - */ - notify(name) { - let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - this.transport.notify(name, data); - } - /** - * Send a request, and wait for a response - * @throws {Error} - * {@link MissingHandler} - * - * @example - * ``` - * const messaging = new Messaging(config) - * const response = await messaging.request("foo", {bar: "baz"}) - * ``` - * - * @param {string} name - * @param {Record} [data] - * @return {Promise} - */ - request(name) { - let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - return this.transport.request(name, data); - } -} - -/** - * @interface - */ -exports.Messaging = Messaging; -class MessagingTransport { - /** - * @param {string} name - * @param {Record} [data] - * @returns {void} - */ - // @ts-ignore - ignoring a no-unused ts error, this is only an interface. - notify(name) { - let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - throw new Error("must implement 'notify'"); - } - /** - * @param {string} name - * @param {Record} [data] - * @return {Promise} - */ - // @ts-ignore - ignoring a no-unused ts error, this is only an interface. - request(name) { - let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - throw new Error('must implement'); - } -} - -/** - * @param {WebkitMessagingConfig} config - * @returns {MessagingTransport} - */ -exports.MessagingTransport = MessagingTransport; -function getTransport(config) { - if (config instanceof _webkit.WebkitMessagingConfig) { - return new _webkit.WebkitMessagingTransport(config); - } - throw new Error('unreachable'); -} - -/** - * Thrown when a handler cannot be found - */ -class MissingHandler extends Error { - /** - * @param {string} message - * @param {string} handlerName - */ - constructor(message, handlerName) { - super(message); - this.handlerName = handlerName; - } -} - -/** - * Some re-exports for convenience - */ -exports.MissingHandler = MissingHandler; - -},{"./webkit.js":6}],6:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.WebkitMessagingTransport = exports.WebkitMessagingConfig = exports.SecureMessagingParams = void 0; -var _messaging = require("./messaging.js"); -/** - * @module Webkit Messaging - * - * @description - * - * A wrapper for messaging on WebKit platforms. It supports modern WebKit messageHandlers - * along with encryption for older versions (like macOS Catalina) - * - * Note: If you wish to support Catalina then you'll need to implement the native - * part of the message handling, see {@link WebkitMessagingTransport} for details. - * - * ```js - * import { Messaging, WebkitMessagingConfig } from "@duckduckgo/content-scope-scripts/lib/messaging.js" - * - * // This config would be injected into the UserScript - * const injectedConfig = { - * hasModernWebkitAPI: true, - * webkitMessageHandlerNames: ["foo", "bar", "baz"], - * secret: "dax", - * }; - * - * // Then use that config to construct platform-specific configuration - * const config = new WebkitMessagingConfig(injectedConfig); - * - * // finally, get an instance of Messaging and start sending messages in a unified way 🚀 - * const messaging = new Messaging(config); - * messaging.notify("hello world!", {foo: "bar"}) - * - * ``` - */ - -/** - * @typedef {import("./messaging").MessagingTransport} MessagingTransport - */ - -/** - * @example - * On macOS 11+, this will just call through to `window.webkit.messageHandlers.x.postMessage` - * - * Eg: for a `foo` message defined in Swift that accepted the payload `{"bar": "baz"}`, the following - * would occur: - * - * ```js - * const json = await window.webkit.messageHandlers.foo.postMessage({ bar: "baz" }); - * const response = JSON.parse(json) - * ``` - * - * @example - * On macOS 10 however, the process is a little more involved. A method will be appended to `window` - * that allows the response to be delivered there instead. It's not exactly this, but you can visualize the flow - * as being something along the lines of: - * - * ```js - * // add the window method - * window["_0123456"] = (response) => { - * // decrypt `response` and deliver the result to the caller here - * // then remove the temporary method - * delete window["_0123456"] - * }; - * - * // send the data + `messageHanding` values - * window.webkit.messageHandlers.foo.postMessage({ - * bar: "baz", - * messagingHandling: { - * methodName: "_0123456", - * secret: "super-secret", - * key: [1, 2, 45, 2], - * iv: [34, 4, 43], - * } - * }); - * - * // later in swift, the following JavaScript snippet will be executed - * (() => { - * window["_0123456"]({ - * ciphertext: [12, 13, 4], - * tag: [3, 5, 67, 56] - * }) - * })() - * ``` - * @implements {MessagingTransport} - */ -class WebkitMessagingTransport { - /** @type {WebkitMessagingConfig} */ - config; - globals; - /** - * @param {WebkitMessagingConfig} config - */ - constructor(config) { - this.config = config; - this.globals = captureGlobals(); - if (!this.config.hasModernWebkitAPI) { - this.captureWebkitHandlers(this.config.webkitMessageHandlerNames); + /** + * Tries to infer input subtype, with checks in decreasing order of reliability + * @param {keyof MatcherLists} listName + * @param {HTMLInputElement|HTMLSelectElement} el + * @return {MatcherTypeNames|undefined} + */ + subtypeFromMatchers(listName, el) { + const matchers = this.matcherList(listName); + for (let strategyName of __privateGet(this, _defaultStrategyOrder)) { + let result; + for (let matcher of matchers) { + const lookup = matcher.strategies[strategyName]; + if (!lookup) + continue; + if (strategyName === "cssSelector") { + result = this.execCssSelector(lookup, el); + } + if (strategyName === "ddgMatcher") { + result = this.execDDGMatcher(lookup); + } + if (strategyName === "vendorRegex") { + result = this.execVendorRegex(lookup); + } + if (result?.matched) { + logMatching(el, result); + return matcher.type; + } + if (!result?.matched && result?.proceed === false) { + logMatching(el, result); + return void 0; + } + } + if (result?.skip) { + logMatching(el, result); + break; + } + } + return void 0; } - } - /** - * Sends message to the webkit layer (fire and forget) - * @param {String} handler - * @param {*} data - * @internal - */ - wkSend(handler) { - let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - if (!(handler in this.globals.window.webkit.messageHandlers)) { - throw new _messaging.MissingHandler(`Missing webkit handler: '${handler}'`, handler); - } - const outgoing = { - ...data, - messageHandling: { - ...data.messageHandling, - secret: this.config.secret + /** + * Returns the password type string including the variant + * @param {HTMLInputElement} input + * @param opts + * @returns {'credentials.password.new'|'credentials.password.current'} + */ + inferPasswordVariant(input, opts) { + const attrsToCheck = [input.autocomplete, input.name, input.id]; + if (opts.isSignup && attrsToCheck.some((str) => safeRegexTest(/new.?password|password.?new/i, str))) { + return "credentials.password.new"; } - }; - if (!this.config.hasModernWebkitAPI) { - if (!(handler in this.globals.capturedWebkitHandlers)) { - throw new _messaging.MissingHandler(`cannot continue, method ${handler} not captured on macos < 11`, handler); - } else { - return this.globals.capturedWebkitHandlers[handler](outgoing); + if ((opts.isLogin || opts.isHybrid) && attrsToCheck.some((str) => safeRegexTest(/(current|old|previous).?password|password.?(current|old|previous)/i, str))) { + return "credentials.password.current"; } - } - return this.globals.window.webkit.messageHandlers[handler].postMessage?.(outgoing); - } - - /** - * Sends message to the webkit layer and waits for the specified response - * @param {String} handler - * @param {*} data - * @returns {Promise<*>} - * @internal - */ - async wkSendAndWait(handler) { - let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - if (this.config.hasModernWebkitAPI) { - const response = await this.wkSend(handler, data); - return this.globals.JSONparse(response || '{}'); - } - try { - const randMethodName = this.createRandMethodName(); - const key = await this.createRandKey(); - const iv = this.createRandIv(); - const { - ciphertext, - tag - } = await new this.globals.Promise(( /** @type {any} */resolve) => { - this.generateRandomMethod(randMethodName, resolve); - data.messageHandling = new SecureMessagingParams({ - methodName: randMethodName, - secret: this.config.secret, - key: this.globals.Arrayfrom(key), - iv: this.globals.Arrayfrom(iv) - }); - this.wkSend(handler, data); - }); - const cipher = new this.globals.Uint8Array([...ciphertext, ...tag]); - const decrypted = await this.decrypt(cipher, key, iv); - return this.globals.JSONparse(decrypted || '{}'); - } catch (e) { - // re-throw when the error is just a 'MissingHandler' - if (e instanceof _messaging.MissingHandler) { - throw e; - } else { - console.error('decryption failed', e); - console.error(e); - return { - error: e - }; + const newPasswordMatch = this.execDDGMatcher("newPassword"); + if (newPasswordMatch.matched) { + return "credentials.password.new"; } - } - } - /** - * @param {string} name - * @param {Record} [data] - */ - notify(name) { - let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - this.wkSend(name, data); - } - /** - * @param {string} name - * @param {Record} [data] - */ - request(name) { - let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - return this.wkSendAndWait(name, data); - } - /** - * Generate a random method name and adds it to the global scope - * The native layer will use this method to send the response - * @param {string | number} randomMethodName - * @param {Function} callback - */ - generateRandomMethod(randomMethodName, callback) { - var _this = this; - this.globals.ObjectDefineProperty(this.globals.window, randomMethodName, { - enumerable: false, - // configurable, To allow for deletion later - configurable: true, - writable: false, - /** - * @param {any[]} args - */ - value: function () { - callback(...arguments); - // @ts-ignore - we want this to throw if it fails as it would indicate a fatal error. - delete _this.globals.window[randomMethodName]; + const currentPasswordMatch = this.execDDGMatcher("currentPassword"); + if (currentPasswordMatch.matched) { + return "credentials.password.current"; } - }); - } - randomString() { - return '' + this.globals.getRandomValues(new this.globals.Uint32Array(1))[0]; - } - createRandMethodName() { - return '_' + this.randomString(); - } - - /** - * @type {{name: string, length: number}} - */ - algoObj = { - name: 'AES-GCM', - length: 256 - }; - - /** - * @returns {Promise} - */ - async createRandKey() { - const key = await this.globals.generateKey(this.algoObj, true, ['encrypt', 'decrypt']); - const exportedKey = await this.globals.exportKey('raw', key); - return new this.globals.Uint8Array(exportedKey); - } - - /** - * @returns {Uint8Array} - */ - createRandIv() { - return this.globals.getRandomValues(new this.globals.Uint8Array(12)); - } - - /** - * @param {BufferSource} ciphertext - * @param {BufferSource} key - * @param {Uint8Array} iv - * @returns {Promise} - */ - async decrypt(ciphertext, key, iv) { - const cryptoKey = await this.globals.importKey('raw', key, 'AES-GCM', false, ['decrypt']); - const algo = { - name: 'AES-GCM', - iv - }; - let decrypted = await this.globals.decrypt(algo, cryptoKey, ciphertext); - let dec = new this.globals.TextDecoder(); - return dec.decode(decrypted); - } - - /** - * When required (such as on macos 10.x), capture the `postMessage` method on - * each webkit messageHandler - * - * @param {string[]} handlerNames - */ - captureWebkitHandlers(handlerNames) { - const handlers = window.webkit.messageHandlers; - if (!handlers) throw new _messaging.MissingHandler('window.webkit.messageHandlers was absent', 'all'); - for (let webkitMessageHandlerName of handlerNames) { - if (typeof handlers[webkitMessageHandlerName]?.postMessage === 'function') { - /** - * `bind` is used here to ensure future calls to the captured - * `postMessage` have the correct `this` context - */ - const original = handlers[webkitMessageHandlerName]; - const bound = handlers[webkitMessageHandlerName].postMessage?.bind(original); - this.globals.capturedWebkitHandlers[webkitMessageHandlerName] = bound; - delete handlers[webkitMessageHandlerName].postMessage; + if (opts.isLogin || opts.isHybrid) { + return "credentials.password.current"; } + return "credentials.password.new"; } - } -} - -/** - * Use this configuration to create an instance of {@link Messaging} for WebKit - * - * ```js - * import { fromConfig, WebkitMessagingConfig } from "@duckduckgo/content-scope-scripts/lib/messaging.js" - * - * const config = new WebkitMessagingConfig({ - * hasModernWebkitAPI: true, - * webkitMessageHandlerNames: ["foo", "bar", "baz"], - * secret: "dax", - * }); - * - * const messaging = new Messaging(config) - * const resp = await messaging.request("debugConfig") - * ``` - */ -exports.WebkitMessagingTransport = WebkitMessagingTransport; -class WebkitMessagingConfig { - /** - * @param {object} params - * @param {boolean} params.hasModernWebkitAPI - * @param {string[]} params.webkitMessageHandlerNames - * @param {string} params.secret - */ - constructor(params) { /** - * Whether or not the current WebKit Platform supports secure messaging - * by default (eg: macOS 11+) + * CSS selector matching just leverages the `.matches` method on elements + * + * @param {MatcherTypeNames} lookup + * @param {HTMLInputElement|HTMLSelectElement} el + * @returns {MatchingResult} */ - this.hasModernWebkitAPI = params.hasModernWebkitAPI; - /** - * A list of WebKit message handler names that a user script can send - */ - this.webkitMessageHandlerNames = params.webkitMessageHandlerNames; - /** - * A string provided by native platforms to be sent with future outgoing - * messages - */ - this.secret = params.secret; - } -} - -/** - * This is the additional payload that gets appended to outgoing messages. - * It's used in the Swift side to encrypt the response that comes back - */ -exports.WebkitMessagingConfig = WebkitMessagingConfig; -class SecureMessagingParams { - /** - * @param {object} params - * @param {string} params.methodName - * @param {string} params.secret - * @param {number[]} params.key - * @param {number[]} params.iv - */ - constructor(params) { + execCssSelector(lookup, el) { + const selector = this.cssSelector(lookup); + return { + matched: el.matches(selector), + strategyName: "cssSelector", + matcherType: lookup + }; + } /** - * The method that's been appended to `window` to be called later + * A DDG Matcher can have a `match` regex along with a `not` regex. This is done + * to allow it to be driven by configuration as it avoids needing to invoke custom functions. + * + * todo: maxDigits was added as an edge-case when converting this over to be declarative, but I'm + * unsure if it's actually needed. It's not urgent, but we should consider removing it if that's the case + * + * @param {MatcherTypeNames} lookup + * @returns {MatchingResult} */ - this.methodName = params.methodName; - /** - * The secret used to ensure message sender validity - */ - this.secret = params.secret; - /** - * The CipherKey as number[] - */ - this.key = params.key; + execDDGMatcher(lookup) { + const defaultResult = { matched: false, strategyName: "ddgMatcher", matcherType: lookup }; + const ddgMatcher = this.ddgMatcher(lookup); + if (!ddgMatcher || !ddgMatcher.match) { + return defaultResult; + } + let matchRexExp = this.getDDGMatcherRegex(lookup); + if (!matchRexExp) { + return defaultResult; + } + let requiredScore = ["match", "forceUnknown", "maxDigits"].filter((ddgMatcherProp) => ddgMatcherProp in ddgMatcher).length; + const matchableStrings = ddgMatcher.matchableStrings || ["labelText", "placeholderAttr", "relatedText"]; + for (let stringName of matchableStrings) { + let elementString = this.activeElementStrings[stringName]; + if (!elementString) + continue; + let score = 0; + const result = { + ...defaultResult, + matchedString: elementString, + matchedFrom: stringName + }; + if (ddgMatcher.forceUnknown) { + let notRegex = ddgMatcher.forceUnknown; + if (!notRegex) { + return { ...result, matched: false }; + } + if (safeRegexTest(notRegex, elementString)) { + return { ...result, matched: false, proceed: false }; + } else { + score++; + } + } + if (ddgMatcher.skip) { + let skipRegex = ddgMatcher.skip; + if (!skipRegex) { + return { ...result, matched: false }; + } + if (safeRegexTest(skipRegex, elementString)) { + return { ...result, matched: false, skip: true }; + } + } + if (!safeRegexTest(matchRexExp, elementString)) { + continue; + } + score++; + if (ddgMatcher.maxDigits) { + const digitLength = elementString.replace(/[^0-9]/g, "").length; + if (digitLength > ddgMatcher.maxDigits) { + return { ...result, matched: false }; + } else { + score++; + } + } + if (score === requiredScore) { + return { ...result, matched: true }; + } + } + return defaultResult; + } /** - * The Initial Vector as number[] - */ - this.iv = params.iv; - } -} - -/** - * Capture some globals used for messaging handling to prevent page - * scripts from tampering with this - */ -exports.SecureMessagingParams = SecureMessagingParams; -function captureGlobals() { - // Creat base with null prototype - return { - window, - // Methods must be bound to their interface, otherwise they throw Illegal invocation - encrypt: window.crypto.subtle.encrypt.bind(window.crypto.subtle), - decrypt: window.crypto.subtle.decrypt.bind(window.crypto.subtle), - generateKey: window.crypto.subtle.generateKey.bind(window.crypto.subtle), - exportKey: window.crypto.subtle.exportKey.bind(window.crypto.subtle), - importKey: window.crypto.subtle.importKey.bind(window.crypto.subtle), - getRandomValues: window.crypto.getRandomValues.bind(window.crypto), - TextEncoder, - TextDecoder, - Uint8Array, - Uint16Array, - Uint32Array, - JSONstringify: window.JSON.stringify, - JSONparse: window.JSON.parse, - Arrayfrom: window.Array.from, - Promise: window.Promise, - ObjectDefineProperty: window.Object.defineProperty, - addEventListener: window.addEventListener.bind(window), - /** @type {Record} */ - capturedWebkitHandlers: {} - }; -} - -},{"./messaging.js":5}],7:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.HostnameInputError = void 0; -Object.defineProperty(exports, "ParserError", { - enumerable: true, - get: function () { - return _rulesParser.ParserError; - } -}); -exports._selectPasswordRules = _selectPasswordRules; -Object.defineProperty(exports, "constants", { - enumerable: true, - get: function () { - return _constants.constants; - } -}); -exports.generate = generate; -var _applePassword = require("./lib/apple.password.js"); -var _rulesParser = require("./lib/rules-parser.js"); -var _constants = require("./lib/constants.js"); -/** - * @typedef {{ - * domain?: string | null | undefined; - * input?: string | null | undefined; - * rules?: RulesFormat | null | undefined; - * onError?: ((error: unknown) => void) | null | undefined; - * }} GenerateOptions - */ -/** - * Generate a random password based on the following attempts - * - * 1) using `options.input` if provided -> falling back to default ruleset - * 2) using `options.domain` if provided -> falling back to default ruleset - * 3) using default ruleset - * - * Note: This API is designed to never throw - if you want to observe errors - * during development, you can provide an `onError` callback - * - * @param {GenerateOptions} [options] - */ -function generate() { - let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - try { - if (typeof options?.input === 'string') { - return _applePassword.Password.generateOrThrow(options.input); - } - if (typeof options?.domain === 'string') { - if (options?.rules) { - const rules = _selectPasswordRules(options.domain, options.rules); - if (rules) { - return _applePassword.Password.generateOrThrow(rules); - } - } - } - } catch (e) { - // if an 'onError' callback was provided, forward all errors - if (options?.onError && typeof options?.onError === 'function') { - options.onError(e); - } else { - // otherwise, only console.error unknown errors (which could be implementation bugs) - const isKnownError = e instanceof _rulesParser.ParserError || e instanceof HostnameInputError; - if (!isKnownError) { - console.error(e); + * If we get here, a firefox/vendor regex was given and we can execute it on the element + * strings + * @param {MatcherTypeNames} lookup + * @return {MatchingResult} + */ + execVendorRegex(lookup) { + const defaultResult = { matched: false, strategyName: "vendorRegex", matcherType: lookup }; + const regex = this.vendorRegex(lookup); + if (!regex) { + return defaultResult; + } + const stringsToMatch = ["placeholderAttr", "nameAttr", "labelText", "id", "relatedText"]; + for (let stringName of stringsToMatch) { + let elementString = this.activeElementStrings[stringName]; + if (!elementString) + continue; + if (safeRegexTest(regex, elementString)) { + return { + ...defaultResult, + matched: true, + matchedString: elementString, + matchedFrom: stringName + }; + } } + return defaultResult; } - } - - // At this point, we have to trust the generation will not throw - // as it is NOT using any user/page-provided data - return _applePassword.Password.generateDefault(); -} - -// An extension type to differentiate between known errors -class HostnameInputError extends Error {} - -/** - * @typedef {Record} RulesFormat - */ - -/** - * @private - * @param {string} inputHostname - * @param {RulesFormat} rules - * @returns {string | undefined} - * @throws {HostnameInputError} - */ -exports.HostnameInputError = HostnameInputError; -function _selectPasswordRules(inputHostname, rules) { - const hostname = _safeHostname(inputHostname); - // direct match - if (rules[hostname]) { - return rules[hostname]['password-rules']; - } - - // otherwise, start chopping off subdomains and re-joining to compare - const pieces = hostname.split('.'); - while (pieces.length > 1) { - pieces.shift(); - const joined = pieces.join('.'); - if (rules[joined]) { - return rules[joined]['password-rules']; + getElementStrings(el, form) { + if (this._elementStringCache.has(el)) { + return this._elementStringCache.get(el); + } + const explicitLabelsText = getExplicitLabelsText(el); + const next = { + nameAttr: el.name, + labelText: explicitLabelsText, + placeholderAttr: el.placeholder || "", + id: el.id, + relatedText: explicitLabelsText ? "" : getRelatedText(el, form, this.cssSelector("formInputsSelector")) + }; + this._elementStringCache.set(el, next); + return next; + } + clear() { + this._elementStringCache = /* @__PURE__ */ new WeakMap(); } - } - return undefined; -} - -/** - * @private - * @param {string} inputHostname; - * @throws {HostnameInputError} - * @returns {string} - */ -function _safeHostname(inputHostname) { - if (inputHostname.startsWith('http:') || inputHostname.startsWith('https:')) { - throw new HostnameInputError('invalid input, you can only provide a hostname but you gave a scheme'); - } - if (inputHostname.includes(':')) { - throw new HostnameInputError('invalid input, you can only provide a hostname but you gave a :port'); - } - try { - const asUrl = new URL('https://' + inputHostname); - return asUrl.hostname; - } catch (e) { - throw new HostnameInputError(`could not instantiate a URL from that hostname ${inputHostname}`); - } -} - -},{"./lib/apple.password.js":8,"./lib/constants.js":9,"./lib/rules-parser.js":10}],8:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.Password = void 0; -var parser = _interopRequireWildcard(require("./rules-parser.js")); -var _constants = require("./constants.js"); -function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } -function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } -/* - * - * NOTE: - * - * This file was created with inspiration from https://developer.apple.com/password-rules - * - * * The changes made by DuckDuckGo employees are: - * - * 1) removed all logic relating to 'more typeable passwords' - * 2) reduced the number of password styles from 4 to only the 1 which suits our needs - * 2) added JSDoc comments (for Typescript checking) - * - */ - -/** - * @typedef {{ - * PasswordAllowedCharacters?: string, - * PasswordRequiredCharacters?: string[], - * PasswordRepeatedCharacterLimit?: number, - * PasswordConsecutiveCharacterLimit?: number, - * PasswordMinLength?: number, - * PasswordMaxLength?: number, - * }} Requirements - */ -/** - * @typedef {{ - * NumberOfRequiredRandomCharacters: number, - * PasswordAllowedCharacters: string, - * RequiredCharacterSets: string[] - * }} PasswordParameters - */ -const defaults = Object.freeze({ - SCAN_SET_ORDER: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-~!@#$%^&*_+=`|(){}[:;\\\"'<>,.?/ ]", - defaultUnambiguousCharacters: 'abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ0123456789', - defaultPasswordLength: _constants.constants.DEFAULT_MIN_LENGTH, - defaultPasswordRules: _constants.constants.DEFAULT_PASSWORD_RULES, - defaultRequiredCharacterSets: ['abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', '0123456789'], - /** - * @type {typeof window.crypto.getRandomValues | null} - */ - getRandomValues: null -}); - -/** - * This is added here to ensure: - * - * 1) `getRandomValues` is called with the correct prototype chain - * 2) `window` is not accessed when in a node environment - * 3) `bind` is not called in a hot code path - * - * @type {{ getRandomValues: typeof window.crypto.getRandomValues }} - */ -const safeGlobals = {}; -if (typeof window !== 'undefined') { - safeGlobals.getRandomValues = window.crypto.getRandomValues.bind(window.crypto); -} -class Password { - /** - * @param {Partial} [options] - */ - constructor() { - let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; /** - * @type {typeof defaults} + * Only used for testing + * @param {HTMLInputElement|HTMLSelectElement} input + * @param {HTMLElement} form + * @returns {Matching} */ - this.options = { - ...defaults, - ...options - }; - return this; - } - static get defaults() { - return defaults; - } - - /** - * Generates a password from the given input. - * - * Note: This method will throw an error if parsing fails - use with caution - * - * @example - * - * ```javascript - * const password = Password.generateOrThrow("minlength: 20") - * ``` - * @public - * @param {string} inputString - * @param {Partial} [options] - * @throws {ParserError|Error} - * @returns {string} - */ - static generateOrThrow(inputString) { - let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - return new Password(options).parse(inputString).generate(); - } - /** - * Generates a password using the default ruleset. - * - * @example - * - * ```javascript - * const password = Password.generateDefault() - * ``` - * - * @public - * @param {Partial} [options] - * @returns {string} - */ - static generateDefault() { - let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - return new Password(options).parse(Password.defaults.defaultPasswordRules).generate(); - } - + forInput(input, form) { + this.setActiveElementStrings(input, form); + return this; + } + }; + _config = new WeakMap(); + _cssSelectors = new WeakMap(); + _ddgMatchers = new WeakMap(); + _vendorRegexRules = new WeakMap(); + _matcherLists = new WeakMap(); + _defaultStrategyOrder = new WeakMap(); /** - * Convert a ruleset into it's internally-used component pieces. - * - * @param {string} inputString - * @throws {parser.ParserError|Error} - * @returns {{ - * requirements: Requirements; - * parameters: PasswordParameters; - * rules: parser.Rule[], - * get entropy(): number; - * generate: () => string; - * }} + * @type {MatchingConfiguration} */ - parse(inputString) { - const rules = parser.parsePasswordRules(inputString); - const requirements = this._requirementsFromRules(rules); - if (!requirements) throw new Error('could not generate requirements for ' + JSON.stringify(inputString)); - const parameters = this._passwordGenerationParametersDictionary(requirements); - return { - requirements, - parameters, - rules, - get entropy() { - return Math.log2(parameters.PasswordAllowedCharacters.length ** parameters.NumberOfRequiredRandomCharacters); + __publicField(Matching, "emptyConfig", { + matchers: { + lists: {}, + fields: {} + }, + strategies: { + "vendorRegex": { + rules: {}, + ruleSets: [] }, - generate: () => { - const password = this._generatedPasswordMatchingRequirements(requirements, parameters); - /** - * The following is unreachable because if user input was incorrect then - * the parsing phase would throw. The following lines is to satisfy Typescript - */ - if (password === '') throw new Error('unreachable'); - return password; + "ddgMatcher": { + matchers: {} + }, + "cssSelector": { + selectors: {} } - }; - } - - /** - * Given an array of `Rule's`, convert into `Requirements` - * - * @param {parser.Rule[]} passwordRules - * @returns {Requirements | null} - */ - _requirementsFromRules(passwordRules) { - /** @type {Requirements} */ - const requirements = {}; - for (let rule of passwordRules) { - if (rule.name === parser.RuleName.ALLOWED) { - console.assert(!('PasswordAllowedCharacters' in requirements)); - const chars = this._charactersFromCharactersClasses(rule.value); - const scanSet = this._canonicalizedScanSetFromCharacters(chars); - if (scanSet) { - requirements.PasswordAllowedCharacters = scanSet; - } - } else if (rule.name === parser.RuleName.MAX_CONSECUTIVE) { - console.assert(!('PasswordRepeatedCharacterLimit' in requirements)); - requirements.PasswordRepeatedCharacterLimit = rule.value; - } else if (rule.name === parser.RuleName.REQUIRED) { - let requiredCharacters = requirements.PasswordRequiredCharacters; - if (!requiredCharacters) { - requiredCharacters = requirements.PasswordRequiredCharacters = []; - } - requiredCharacters.push(this._canonicalizedScanSetFromCharacters(this._charactersFromCharactersClasses(rule.value))); - } else if (rule.name === parser.RuleName.MIN_LENGTH) { - requirements.PasswordMinLength = rule.value; - } else if (rule.name === parser.RuleName.MAX_LENGTH) { - requirements.PasswordMaxLength = rule.value; + } + }); + function getInputType(input) { + const attr = input?.getAttribute(ATTR_INPUT_TYPE); + if (isValidSupportedType(attr)) { + return attr; + } + return "unknown"; + } + function getMainTypeFromType(type) { + const mainType = type.split(".")[0]; + switch (mainType) { + case "credentials": + case "creditCards": + case "identities": + return mainType; + } + return "unknown"; + } + var getInputMainType = (input) => getMainTypeFromType(getInputType(input)); + var supportedIdentitiesSubtypes = ( + /** @type {const} */ + [ + "emailAddress", + "firstName", + "middleName", + "lastName", + "fullName", + "phone", + "addressStreet", + "addressStreet2", + "addressCity", + "addressProvince", + "addressPostalCode", + "addressCountryCode", + "birthdayDay", + "birthdayMonth", + "birthdayYear" + ] + ); + function isValidIdentitiesSubtype(supportedType) { + return supportedIdentitiesSubtypes.includes(supportedType); + } + var supportedCreditCardSubtypes = ( + /** @type {const} */ + [ + "cardName", + "cardNumber", + "cardSecurityCode", + "expirationMonth", + "expirationYear", + "expiration" + ] + ); + function isValidCreditCardSubtype(supportedType) { + return supportedCreditCardSubtypes.includes(supportedType); + } + var supportedCredentialsSubtypes = ( + /** @type {const} */ + [ + "password", + "password.new", + "password.current", + "username" + ] + ); + var supportedVariants = ( + /** @type {const} */ + [ + "new", + "current" + ] + ); + function isValidCredentialsSubtype(supportedType) { + return supportedCredentialsSubtypes.includes(supportedType); + } + var supportedTypes = [ + ...supportedIdentitiesSubtypes.map((type) => `identities.${type}`), + ...supportedCreditCardSubtypes.map((type) => `creditCards.${type}`), + ...supportedCredentialsSubtypes.map((type) => `credentials.${type}`) + ]; + function getSubtypeFromType(type) { + const subType = type?.split(".")[1]; + const validType = isValidSubtype(subType); + return validType ? subType : "unknown"; + } + function getVariantFromType(type) { + const variant = type?.split(".")[2]; + const validVariant = isValidVariant(variant); + return validVariant ? variant : ""; + } + function isValidSubtype(supportedSubType) { + return isValidIdentitiesSubtype(supportedSubType) || isValidCreditCardSubtype(supportedSubType) || isValidCredentialsSubtype(supportedSubType); + } + function isValidSupportedType(supportedType) { + return supportedTypes.includes(supportedType); + } + function isValidVariant(supportedVariant) { + return supportedVariants.includes(supportedVariant); + } + function getInputSubtype(input) { + const type = getInputType(input); + return getSubtypeFromType(type); + } + function getInputVariant(input) { + const type = getInputType(input); + return getVariantFromType(type); + } + var removeExcessWhitespace = (string = "") => { + string = string?.trim() || ""; + if (!string || string.length > TEXT_LENGTH_CUTOFF + 50) + return ""; + return string.replace(/\n/g, " ").replace(/\s{2,}/g, " "); + }; + var getExplicitLabelsText = (el) => { + const labelTextCandidates = []; + for (let label of el.labels || []) { + labelTextCandidates.push(...extractElementStrings(label)); + } + if (el.hasAttribute("aria-label")) { + labelTextCandidates.push(removeExcessWhitespace(el.getAttribute("aria-label"))); + } + const ariaLabelAttr = removeExcessWhitespace(el.getAttribute("aria-labelled") || el.getAttribute("aria-labelledby")); + if (ariaLabelAttr) { + const labelledByElement = document.getElementById(ariaLabelAttr); + if (labelledByElement) { + labelTextCandidates.push(...extractElementStrings(labelledByElement)); } } - - // Only include an allowed rule matching SCAN_SET_ORDER (all characters) when a required rule is also present. - if (requirements.PasswordAllowedCharacters === this.options.SCAN_SET_ORDER && !requirements.PasswordRequiredCharacters) { - delete requirements.PasswordAllowedCharacters; + const filteredLabels = labelTextCandidates.filter((string) => string.length < 65); + if (filteredLabels.length > 0) { + return filteredLabels.join(" "); } - - // Fix up PasswordRequiredCharacters, if needed. - if (requirements.PasswordRequiredCharacters && requirements.PasswordRequiredCharacters.length === 1 && requirements.PasswordRequiredCharacters[0] === this.options.SCAN_SET_ORDER) { - delete requirements.PasswordRequiredCharacters; + return ""; + }; + var recursiveGetPreviousElSibling = (el) => { + const previousEl = el.previousElementSibling; + if (!previousEl) + return null; + if (EXCLUDED_TAGS.includes(previousEl.tagName)) { + return recursiveGetPreviousElSibling(previousEl); } - return Object.keys(requirements).length ? requirements : null; - } - - /** - * @param {number} range - * @returns {number} - */ - _randomNumberWithUniformDistribution(range) { - const getRandomValues = this.options.getRandomValues || safeGlobals.getRandomValues; - // Based on the algorithm described in https://pthree.org/2018/06/13/why-the-multiply-and-floor-rng-method-is-biased/ - const max = Math.floor(2 ** 32 / range) * range; - let x; - do { - x = getRandomValues(new Uint32Array(1))[0]; - } while (x >= max); - return x % range; - } - - /** - * @param {number} numberOfRequiredRandomCharacters - * @param {string} allowedCharacters - */ - _classicPassword(numberOfRequiredRandomCharacters, allowedCharacters) { - const length = allowedCharacters.length; - const randomCharArray = Array(numberOfRequiredRandomCharacters); - for (let i = 0; i < numberOfRequiredRandomCharacters; i++) { - const index = this._randomNumberWithUniformDistribution(length); - randomCharArray[i] = allowedCharacters[index]; - } - return randomCharArray.join(''); - } - - /** - * @param {string} password - * @param {number} consecutiveCharLimit - * @returns {boolean} - */ - _passwordHasNotExceededConsecutiveCharLimit(password, consecutiveCharLimit) { - let longestConsecutiveCharLength = 1; - let firstConsecutiveCharIndex = 0; - // Both "123" or "abc" and "321" or "cba" are considered consecutive. - let isSequenceAscending; - for (let i = 1; i < password.length; i++) { - const currCharCode = password.charCodeAt(i); - const prevCharCode = password.charCodeAt(i - 1); - if (isSequenceAscending) { - // If `isSequenceAscending` is defined, then we know that we are in the middle of an existing - // pattern. Check if the pattern continues based on whether the previous pattern was - // ascending or descending. - if (isSequenceAscending.valueOf() && currCharCode === prevCharCode + 1 || !isSequenceAscending.valueOf() && currCharCode === prevCharCode - 1) { - continue; - } - - // Take into account the case when the sequence transitions from descending - // to ascending. - if (currCharCode === prevCharCode + 1) { - firstConsecutiveCharIndex = i - 1; - isSequenceAscending = Boolean(true); - continue; - } - - // Take into account the case when the sequence transitions from ascending - // to descending. - if (currCharCode === prevCharCode - 1) { - firstConsecutiveCharIndex = i - 1; - isSequenceAscending = Boolean(false); - continue; + return previousEl; + }; + var getRelatedText = (el, form, cssSelector) => { + let scope = getLargestMeaningfulContainer(el, form, cssSelector); + if (scope === el) { + let previousEl = recursiveGetPreviousElSibling(el); + if (previousEl instanceof HTMLElement) { + scope = previousEl; + } + if (scope === el || scope instanceof HTMLSelectElement) { + if (el.previousSibling instanceof Text) { + return removeExcessWhitespace(el.previousSibling.textContent); } - isSequenceAscending = null; - } else if (currCharCode === prevCharCode + 1) { - isSequenceAscending = Boolean(true); - continue; - } else if (currCharCode === prevCharCode - 1) { - isSequenceAscending = Boolean(false); - continue; + return ""; } - const currConsecutiveCharLength = i - firstConsecutiveCharIndex; - if (currConsecutiveCharLength > longestConsecutiveCharLength) { - longestConsecutiveCharLength = currConsecutiveCharLength; - } - firstConsecutiveCharIndex = i; } - if (isSequenceAscending) { - const currConsecutiveCharLength = password.length - firstConsecutiveCharIndex; - if (currConsecutiveCharLength > longestConsecutiveCharLength) { - longestConsecutiveCharLength = currConsecutiveCharLength; + if (scope === el || scope instanceof HTMLSelectElement) { + if (el.previousSibling instanceof Text) { + return removeExcessWhitespace(el.previousSibling.textContent); } + return ""; } - return longestConsecutiveCharLength <= consecutiveCharLimit; + let trimmedText = ""; + const label = scope.querySelector("label"); + if (label) { + trimmedText = getTextShallow(label); + } else { + trimmedText = extractElementStrings(scope).join(" "); + } + if (trimmedText.length < TEXT_LENGTH_CUTOFF) + return trimmedText; + return ""; + }; + var getLargestMeaningfulContainer = (el, form, cssSelector) => { + const parentElement = el.parentElement; + if (!parentElement || el === form || !cssSelector) + return el; + const inputsInParentsScope = parentElement.querySelectorAll(cssSelector); + if (inputsInParentsScope.length === 1) { + return getLargestMeaningfulContainer(parentElement, form, cssSelector); + } + return el; + }; + var matchInPlaceholderAndLabels = (input, regex, form, cssSelector) => { + return input.placeholder?.match(regex) || getExplicitLabelsText(input).match(regex) || getRelatedText(input, form, cssSelector).match(regex); + }; + var checkPlaceholderAndLabels = (input, regex, form, cssSelector) => { + return !!matchInPlaceholderAndLabels(input, regex, form, cssSelector); + }; + function createMatching() { + return new Matching(matchingConfiguration); } - /** - * @param {string} password - * @param {number} repeatedCharLimit - * @returns {boolean} - */ - _passwordHasNotExceededRepeatedCharLimit(password, repeatedCharLimit) { - let longestRepeatedCharLength = 1; - let lastRepeatedChar = password.charAt(0); - let lastRepeatedCharIndex = 0; - for (let i = 1; i < password.length; i++) { - const currChar = password.charAt(i); - if (currChar === lastRepeatedChar) { - continue; - } - const currRepeatedCharLength = i - lastRepeatedCharIndex; - if (currRepeatedCharLength > longestRepeatedCharLength) { - longestRepeatedCharLength = currRepeatedCharLength; + // node_modules/@duckduckgo/content-scope-scripts/src/apple-utils.js + function getTopLevelURL() { + try { + if (window.location !== window.parent.location) { + return new URL(window.location.href !== "about:blank" ? document.referrer : window.parent.location.href); + } else { + return new URL(window.location.href); } - lastRepeatedChar = currChar; - lastRepeatedCharIndex = i; + } catch (error) { + return new URL(location.href); } - return longestRepeatedCharLength <= repeatedCharLimit; } - - /** - * @param {string} password - * @param {string[]} requiredCharacterSets - * @returns {boolean} - */ - _passwordContainsRequiredCharacters(password, requiredCharacterSets) { - const requiredCharacterSetsLength = requiredCharacterSets.length; - const passwordLength = password.length; - for (let i = 0; i < requiredCharacterSetsLength; i++) { - const requiredCharacterSet = requiredCharacterSets[i]; - let hasRequiredChar = false; - for (let j = 0; j < passwordLength; j++) { - const char = password.charAt(j); - if (requiredCharacterSet.indexOf(char) !== -1) { - hasRequiredChar = true; - break; - } - } - if (!hasRequiredChar) { - return false; - } + function isUnprotectedDomain(topLevelUrl, featureList) { + let unprotectedDomain = false; + const domainParts = topLevelUrl && topLevelUrl.host ? topLevelUrl.host.split(".") : []; + while (domainParts.length > 1 && !unprotectedDomain) { + const partialDomain = domainParts.join("."); + unprotectedDomain = featureList.filter((domain) => domain.domain === partialDomain).length > 0; + domainParts.shift(); } - return true; + return unprotectedDomain; } - - /** - * @param {string} string1 - * @param {string} string2 - * @returns {boolean} - */ - _stringsHaveAtLeastOneCommonCharacter(string1, string2) { - const string2Length = string2.length; - for (let i = 0; i < string2Length; i++) { - const char = string2.charAt(i); - if (string1.indexOf(char) !== -1) { - return true; - } - } - return false; + function processConfig(data, userList, preferences) { + const topLevelUrl = getTopLevelURL(); + const allowlisted = userList.filter((domain) => domain === topLevelUrl.host).length > 0; + const enabledFeatures = Object.keys(data.features).filter((featureName) => { + const feature = data.features[featureName]; + return feature.state === "enabled" && !isUnprotectedDomain(topLevelUrl, feature.exceptions); + }); + const isBroken = isUnprotectedDomain(topLevelUrl, data.unprotectedTemporary); + preferences.site = { + domain: topLevelUrl.hostname, + isBroken, + allowlisted, + enabledFeatures + }; + preferences.cookie = {}; + return preferences; } - /** - * @param {Requirements} requirements - * @returns {PasswordParameters} - */ - _passwordGenerationParametersDictionary(requirements) { - let minPasswordLength = requirements.PasswordMinLength; - const maxPasswordLength = requirements.PasswordMaxLength; - - // @ts-ignore - if (minPasswordLength > maxPasswordLength) { - // Resetting invalid value of min length to zero means "ignore min length parameter in password generation". - minPasswordLength = 0; - } - const requiredCharacterArray = requirements.PasswordRequiredCharacters; - let allowedCharacters = requirements.PasswordAllowedCharacters; - let requiredCharacterSets = this.options.defaultRequiredCharacterSets; - if (requiredCharacterArray) { - const mutatedRequiredCharacterSets = []; - const requiredCharacterArrayLength = requiredCharacterArray.length; - for (let i = 0; i < requiredCharacterArrayLength; i++) { - const requiredCharacters = requiredCharacterArray[i]; - if (allowedCharacters && this._stringsHaveAtLeastOneCommonCharacter(requiredCharacters, allowedCharacters)) { - mutatedRequiredCharacterSets.push(requiredCharacters); - } - } - requiredCharacterSets = mutatedRequiredCharacterSets; - } - - // If requirements allow, we will generateOrThrow the password in default format: "xxx-xxx-xxx-xxx". - let numberOfRequiredRandomCharacters = this.options.defaultPasswordLength; - if (minPasswordLength && minPasswordLength > numberOfRequiredRandomCharacters) { - numberOfRequiredRandomCharacters = minPasswordLength; - } - if (maxPasswordLength && maxPasswordLength < numberOfRequiredRandomCharacters) { - numberOfRequiredRandomCharacters = maxPasswordLength; + // src/autofill-utils.js + var SIGN_IN_MSG = { signMeIn: true }; + var notifyWebApp = (message) => { + window.postMessage(message, window.origin); + }; + var sendAndWaitForAnswer = (msgOrFn, expectedResponse) => { + if (typeof msgOrFn === "function") { + msgOrFn(); + } else { + window.postMessage(msgOrFn, window.origin); } - if (!allowedCharacters) { - allowedCharacters = this.options.defaultUnambiguousCharacters; + return new Promise((resolve) => { + const handler = (e) => { + if (e.origin !== window.origin) + return; + if (!e.data || e.data && !(e.data[expectedResponse] || e.data.type === expectedResponse)) + return; + resolve(e.data); + window.removeEventListener("message", handler); + }; + window.addEventListener("message", handler); + }); + }; + var autofillEnabled = (globalConfig) => { + if (!globalConfig.contentScope) { + return true; } - - // In default password format, we use dashes only as separators, not as symbols you can encounter at a random position. - - if (!requiredCharacterSets) { - requiredCharacterSets = this.options.defaultRequiredCharacterSets; + if ("site" in globalConfig.contentScope) { + const enabled = isAutofillEnabledFromProcessedConfig(globalConfig.contentScope); + return enabled; } - - // If we have more requirements of the type "need a character from set" than the length of the password we want to generateOrThrow, then - // we will never be able to meet these requirements, and we'll end up in an infinite loop generating passwords. To avoid this, - // reset required character sets if the requirements are impossible to meet. - if (requiredCharacterSets.length > numberOfRequiredRandomCharacters) { - requiredCharacterSets = []; + const { contentScope, userUnprotectedDomains, userPreferences } = globalConfig; + const processedConfig = processConfig(contentScope, userUnprotectedDomains, userPreferences); + return isAutofillEnabledFromProcessedConfig(processedConfig); + }; + var isAutofillEnabledFromProcessedConfig = (processedConfig) => { + const site = processedConfig.site; + if (site.isBroken || !site.enabledFeatures.includes("autofill")) { + if (shouldLog()) { + console.log("\u26A0\uFE0F Autofill disabled by remote config"); + } + return false; } - - // Do not require any character sets that do not contain allowed characters. - const requiredCharacterSetsLength = requiredCharacterSets.length; - const mutatedRequiredCharacterSets = []; - const allowedCharactersLength = allowedCharacters.length; - for (let i = 0; i < requiredCharacterSetsLength; i++) { - const requiredCharacterSet = requiredCharacterSets[i]; - let requiredCharacterSetContainsAllowedCharacters = false; - for (let j = 0; j < allowedCharactersLength; j++) { - const character = allowedCharacters.charAt(j); - if (requiredCharacterSet.indexOf(character) !== -1) { - requiredCharacterSetContainsAllowedCharacters = true; - break; - } + return true; + }; + var isIncontextSignupEnabledFromProcessedConfig = (processedConfig) => { + const site = processedConfig.site; + if (site.isBroken || !site.enabledFeatures.includes("incontextSignup")) { + if (shouldLog()) { + console.log("\u26A0\uFE0F In-context signup disabled by remote config"); } - if (requiredCharacterSetContainsAllowedCharacters) { - mutatedRequiredCharacterSets.push(requiredCharacterSet); + return false; + } + return true; + }; + var originalSet = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set; + var setValueForInput = (el, val, config) => { + if (!config?.isAndroid) { + el.focus(); + } + el.dispatchEvent(new Event("keydown", { bubbles: true })); + originalSet?.call(el, val); + const events = [ + new Event("input", { bubbles: true }), + // todo(Shane): Not sending a 'key' property on these events can cause exceptions on 3rd party listeners that expect it + new Event("keyup", { bubbles: true }), + new Event("change", { bubbles: true }) + ]; + events.forEach((ev) => el.dispatchEvent(ev)); + originalSet?.call(el, val); + events.forEach((ev) => el.dispatchEvent(ev)); + el.blur(); + return true; + }; + var fireEventsOnSelect = (el) => { + const events = [ + new Event("mousedown", { bubbles: true }), + new Event("mouseup", { bubbles: true }), + new Event("click", { bubbles: true }), + new Event("change", { bubbles: true }) + ]; + events.forEach((ev) => el.dispatchEvent(ev)); + events.forEach((ev) => el.dispatchEvent(ev)); + el.blur(); + }; + var setValueForSelect = (el, val) => { + const subtype = getInputSubtype(el); + const isMonth = subtype.includes("Month"); + const isZeroBasedNumber = isMonth && el.options[0].value === "0" && el.options.length === 12; + const stringVal = String(val); + const numberVal = Number(val); + for (const option of el.options) { + let value = option.value; + if (isZeroBasedNumber) { + value = `${Number(value) + 1}`; + } + if (value === stringVal || Number(value) === numberVal) { + if (option.selected) + return false; + option.selected = true; + fireEventsOnSelect(el); + return true; } } - requiredCharacterSets = mutatedRequiredCharacterSets; - return { - NumberOfRequiredRandomCharacters: numberOfRequiredRandomCharacters, - PasswordAllowedCharacters: allowedCharacters, - RequiredCharacterSets: requiredCharacterSets - }; - } - - /** - * @param {Requirements | null} requirements - * @param {PasswordParameters} [parameters] - * @returns {string} - */ - _generatedPasswordMatchingRequirements(requirements, parameters) { - requirements = requirements || {}; - parameters = parameters || this._passwordGenerationParametersDictionary(requirements); - const numberOfRequiredRandomCharacters = parameters.NumberOfRequiredRandomCharacters; - const repeatedCharLimit = requirements.PasswordRepeatedCharacterLimit; - const allowedCharacters = parameters.PasswordAllowedCharacters; - const shouldCheckRepeatedCharRequirement = !!repeatedCharLimit; - while (true) { - const password = this._classicPassword(numberOfRequiredRandomCharacters, allowedCharacters); - if (!this._passwordContainsRequiredCharacters(password, parameters.RequiredCharacterSets)) { - continue; + for (const option of el.options) { + if (option.innerText === stringVal || Number(option.innerText) === numberVal) { + if (option.selected) + return false; + option.selected = true; + fireEventsOnSelect(el); + return true; } - if (shouldCheckRepeatedCharRequirement) { - if (repeatedCharLimit !== undefined && repeatedCharLimit >= 1 && !this._passwordHasNotExceededRepeatedCharLimit(password, repeatedCharLimit)) { - continue; + } + return false; + }; + var setValue = (el, val, config) => { + if (el instanceof HTMLInputElement) + return setValueForInput(el, val, config); + if (el instanceof HTMLSelectElement) + return setValueForSelect(el, val); + return false; + }; + var safeExecute = (el, fn, _opts = {}) => { + const intObs = new IntersectionObserver((changes) => { + for (const change of changes) { + if (typeof change.isVisible === "undefined") { + change.isVisible = true; } - } - const consecutiveCharLimit = requirements.PasswordConsecutiveCharacterLimit; - if (consecutiveCharLimit && consecutiveCharLimit >= 1) { - if (!this._passwordHasNotExceededConsecutiveCharLimit(password, consecutiveCharLimit)) { - continue; + if (change.isIntersecting) { + fn(); } } - return password || ''; + intObs.disconnect(); + }, { trackVisibility: true, delay: 100 }); + intObs.observe(el); + }; + var isPotentiallyViewable = (el) => { + const computedStyle = window.getComputedStyle(el); + const opacity = parseFloat(computedStyle.getPropertyValue("opacity") || "1"); + const visibility = computedStyle.getPropertyValue("visibility"); + const opacityThreshold = 0.6; + return el.clientWidth !== 0 && el.clientHeight !== 0 && opacity > opacityThreshold && visibility !== "hidden"; + }; + var getDaxBoundingBox = (input) => { + const { right: inputRight, top: inputTop, height: inputHeight } = input.getBoundingClientRect(); + const inputRightPadding = parseInt(getComputedStyle(input).paddingRight); + const width = 30; + const height = 30; + const top = inputTop + (inputHeight - height) / 2; + const right = inputRight - inputRightPadding; + const left = right - width; + const bottom = top + height; + return { bottom, height, left, right, top, width, x: left, y: top }; + }; + var isEventWithinDax = (e, input) => { + const { left, right, top, bottom } = getDaxBoundingBox(input); + const withinX = e.clientX >= left && e.clientX <= right; + const withinY = e.clientY >= top && e.clientY <= bottom; + return withinX && withinY; + }; + var addInlineStyles = (el, styles) => Object.entries(styles).forEach(([property, val]) => el.style.setProperty(property, val, "important")); + var removeInlineStyles = (el, styles) => Object.keys(styles).forEach((property) => el.style.removeProperty(property)); + var ADDRESS_DOMAIN = "@duck.com"; + var formatDuckAddress = (address) => address + ADDRESS_DOMAIN; + function escapeXML(str) { + const replacements = { "&": "&", '"': """, "'": "'", "<": "<", ">": ">", "/": "/" }; + return String(str).replace(/[&"'<>/]/g, (m) => replacements[m]); + } + var isLikelyASubmitButton = (el, matching) => { + const text = getTextShallow(el); + const ariaLabel = el.getAttribute("aria-label") || ""; + const dataTestId = el.getAttribute("data-test-id") || ""; + if ((el.getAttribute("type") === "submit" || // is explicitly set as "submit" + el.getAttribute("name") === "submit") && // is called "submit" + !safeRegexTest(matching.getDDGMatcherRegex("submitButtonUnlikelyRegex"), text + " " + ariaLabel)) + return true; + return (safeRegexTest(/primary|submit/i, el.className) || // has high-signal submit classes + safeRegexTest(/submit/i, dataTestId) || safeRegexTest(matching.getDDGMatcherRegex("submitButtonRegex"), text) || // has high-signal text + el.offsetHeight * el.offsetWidth >= 1e4 && !safeRegexTest(/secondary/i, el.className)) && el.offsetHeight * el.offsetWidth >= 2e3 && // it's not a very small button like inline links and such + !safeRegexTest(matching.getDDGMatcherRegex("submitButtonUnlikelyRegex"), text + " " + ariaLabel); + }; + var buttonMatchesFormType = (el, formObj) => { + if (formObj.isLogin) { + return !safeRegexTest(/sign.?up|register|join/i, el.textContent || ""); + } else if (formObj.isSignup) { + return !safeRegexTest(/(log|sign).?([io])n/i, el.textContent || ""); + } else { + return true; } - } - - /** - * @param {parser.CustomCharacterClass | parser.NamedCharacterClass} characterClass - * @returns {string[]} - */ - _scanSetFromCharacterClass(characterClass) { - if (characterClass instanceof parser.CustomCharacterClass) { - return characterClass.characters; - } - console.assert(characterClass instanceof parser.NamedCharacterClass); - switch (characterClass.name) { - case parser.Identifier.ASCII_PRINTABLE: - case parser.Identifier.UNICODE: - return this.options.SCAN_SET_ORDER.split(''); - case parser.Identifier.DIGIT: - return this.options.SCAN_SET_ORDER.substring(this.options.SCAN_SET_ORDER.indexOf('0'), this.options.SCAN_SET_ORDER.indexOf('9') + 1).split(''); - case parser.Identifier.LOWER: - return this.options.SCAN_SET_ORDER.substring(this.options.SCAN_SET_ORDER.indexOf('a'), this.options.SCAN_SET_ORDER.indexOf('z') + 1).split(''); - case parser.Identifier.SPECIAL: - return this.options.SCAN_SET_ORDER.substring(this.options.SCAN_SET_ORDER.indexOf('-'), this.options.SCAN_SET_ORDER.indexOf(']') + 1).split(''); - case parser.Identifier.UPPER: - return this.options.SCAN_SET_ORDER.substring(this.options.SCAN_SET_ORDER.indexOf('A'), this.options.SCAN_SET_ORDER.indexOf('Z') + 1).split(''); - } - console.assert(false, parser.SHOULD_NOT_BE_REACHED); - return []; - } - - /** - * @param {(parser.CustomCharacterClass | parser.NamedCharacterClass)[]} characterClasses - */ - _charactersFromCharactersClasses(characterClasses) { - const output = []; - for (let characterClass of characterClasses) { - output.push(...this._scanSetFromCharacterClass(characterClass)); + }; + var buttonInputTypes = ["submit", "button"]; + var getTextShallow = (el) => { + if (el instanceof HTMLButtonElement) + return removeExcessWhitespace(el.textContent); + if (el instanceof HTMLInputElement) { + if (buttonInputTypes.includes(el.type)) { + return el.value; + } + if (el.type === "image") { + return removeExcessWhitespace(el.alt || el.value || el.title || el.name); + } } - return output; - } - - /** - * @param {string[]} characters - * @returns {string} - */ - _canonicalizedScanSetFromCharacters(characters) { - if (!characters.length) { - return ''; - } - let shadowCharacters = Array.prototype.slice.call(characters); - shadowCharacters.sort((a, b) => this.options.SCAN_SET_ORDER.indexOf(a) - this.options.SCAN_SET_ORDER.indexOf(b)); - let uniqueCharacters = [shadowCharacters[0]]; - for (let i = 1, length = shadowCharacters.length; i < length; ++i) { - if (shadowCharacters[i] === shadowCharacters[i - 1]) { - continue; + let text = ""; + for (const childNode of el.childNodes) { + if (childNode instanceof Text) { + text += " " + childNode.textContent; } - uniqueCharacters.push(shadowCharacters[i]); } - return uniqueCharacters.join(''); - } -} -exports.Password = Password; - -},{"./constants.js":9,"./rules-parser.js":10}],9:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.constants = void 0; -const DEFAULT_MIN_LENGTH = 20; -const DEFAULT_MAX_LENGTH = 30; -const DEFAULT_REQUIRED_CHARS = '-!?$&#%'; -const DEFAULT_UNAMBIGUOUS_CHARS = 'abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ0123456789'; -const DEFAULT_PASSWORD_RULES = [`minlength: ${DEFAULT_MIN_LENGTH}`, `maxlength: ${DEFAULT_MAX_LENGTH}`, `required: [${DEFAULT_REQUIRED_CHARS}]`, `allowed: [${DEFAULT_UNAMBIGUOUS_CHARS}]`].join('; '); -const constants = exports.constants = { - DEFAULT_MIN_LENGTH, - DEFAULT_MAX_LENGTH, - DEFAULT_PASSWORD_RULES, - DEFAULT_REQUIRED_CHARS, - DEFAULT_UNAMBIGUOUS_CHARS -}; - -},{}],10:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.SHOULD_NOT_BE_REACHED = exports.RuleName = exports.Rule = exports.ParserError = exports.NamedCharacterClass = exports.Identifier = exports.CustomCharacterClass = void 0; -exports.parsePasswordRules = parsePasswordRules; -// Copyright (c) 2019 - 2020 Apple Inc. Licensed under MIT License. - -/* - * - * NOTE: - * - * This file was taken as intended from https://github.com/apple/password-manager-resources. - * - * The only additions from DuckDuckGo employees are - * - * 1) exporting some identifiers - * 2) adding some JSDoc comments - * 3) making this parser throw when it cannot produce any rules - * ^ the default implementation still returns a base-line ruleset, which we didn't want. - * - */ - -const Identifier = exports.Identifier = { - ASCII_PRINTABLE: 'ascii-printable', - DIGIT: 'digit', - LOWER: 'lower', - SPECIAL: 'special', - UNICODE: 'unicode', - UPPER: 'upper' -}; -const RuleName = exports.RuleName = { - ALLOWED: 'allowed', - MAX_CONSECUTIVE: 'max-consecutive', - REQUIRED: 'required', - MIN_LENGTH: 'minlength', - MAX_LENGTH: 'maxlength' -}; -const CHARACTER_CLASS_START_SENTINEL = '['; -const CHARACTER_CLASS_END_SENTINEL = ']'; -const PROPERTY_VALUE_SEPARATOR = ','; -const PROPERTY_SEPARATOR = ';'; -const PROPERTY_VALUE_START_SENTINEL = ':'; -const SPACE_CODE_POINT = ' '.codePointAt(0); -const SHOULD_NOT_BE_REACHED = exports.SHOULD_NOT_BE_REACHED = 'Should not be reached'; -class Rule { - constructor(name, value) { - this._name = name; - this.value = value; - } - get name() { - return this._name; - } - toString() { - return JSON.stringify(this); + return removeExcessWhitespace(text); + }; + function isLocalNetwork(hostname = window.location.hostname) { + return ["localhost", "", "::1"].includes(hostname) || hostname.includes("127.0.0.1") || hostname.includes("192.168.") || hostname.startsWith("10.0.") || hostname.endsWith(".local") || hostname.endsWith(".internal"); } -} -exports.Rule = Rule; -; -class NamedCharacterClass { - constructor(name) { - console.assert(_isValidRequiredOrAllowedPropertyValueIdentifier(name)); - this._name = name; + var tldrs = /\.(?:c(?:o(?:m|op)?|at?|[iykgdmnxruhcfzvl])|o(?:rg|m)|n(?:et?|a(?:me)?|[ucgozrfpil])|e(?:d?u|[gechstr])|i(?:n(?:t|fo)?|[stqldroem])|m(?:o(?:bi)?|u(?:seum)?|i?l|[mcyvtsqhaerngxzfpwkd])|g(?:ov|[glqeriabtshdfmuywnp])|b(?:iz?|[drovfhtaywmzjsgbenl])|t(?:r(?:avel)?|[ncmfzdvkopthjwg]|e?l)|k[iemygznhwrp]|s[jtvberindlucygkhaozm]|u[gymszka]|h[nmutkr]|r[owesu]|d[kmzoej]|a(?:e(?:ro)?|r(?:pa)?|[qofiumsgzlwcnxdt])|p(?:ro?|[sgnthfymakwle])|v[aegiucn]|l[sayuvikcbrt]|j(?:o(?:bs)?|[mep])|w[fs]|z[amw]|f[rijkom]|y[eut]|qa)$/i; + function isValidTLD(hostname = window.location.hostname) { + return tldrs.test(hostname) || hostname === "fill.dev"; } - get name() { - return this._name.toLowerCase(); + var wasAutofilledByChrome = (input) => { + try { + return input.matches("input:-internal-autofill-selected"); + } catch (e) { + return false; + } + }; + function shouldLog() { + return readDebugSetting("ddg-autofill-debug"); } - toString() { - return this._name; + function shouldLogPerformance() { + return readDebugSetting("ddg-autofill-perf"); } - toHTMLString() { - return this._name; + function readDebugSetting(setting) { + try { + return window.sessionStorage?.getItem(setting) === "true"; + } catch (e) { + return false; + } } -} -exports.NamedCharacterClass = NamedCharacterClass; -; -class ParserError extends Error {} -exports.ParserError = ParserError; -; -class CustomCharacterClass { - constructor(characters) { - console.assert(characters instanceof Array); - this._characters = characters; + function logPerformance(markName) { + if (shouldLogPerformance()) { + const measurement = window.performance?.measure(`${markName}:init`, `${markName}:init:start`, `${markName}:init:end`); + console.log(`${markName} took ${Math.round(measurement?.duration)}ms`); + window.performance?.clearMarks(); + } } - get characters() { - return this._characters; + function whenIdle(callback) { + let timer; + return (...args) => { + cancelIdleCallback(timer); + timer = requestIdleCallback(() => callback.apply(this, args)); + }; } - toString() { - return `[${this._characters.join('')}]`; + function truncateFromMiddle(string, totalLength = 30) { + if (totalLength < 4) { + throw new Error("Do not use with strings shorter than 4"); + } + if (string.length <= totalLength) + return string; + const truncated = string.slice(0, totalLength / 2).concat("\u2026", string.slice(totalLength / -2)); + return truncated; } - toHTMLString() { - return `[${this._characters.join('').replace('"', '"')}]`; + function isFormLikelyToBeUsedAsPageWrapper(form) { + if (form.parentElement !== document.body) + return false; + const formChildren = form.querySelectorAll("*").length; + if (formChildren < 100) + return false; + const bodyChildren = document.body.querySelectorAll("*").length; + const formChildrenPercentage = formChildren * 100 / bodyChildren; + return formChildrenPercentage > 50; } -} -exports.CustomCharacterClass = CustomCharacterClass; -; - -// MARK: Lexer functions - -function _isIdentifierCharacter(c) { - console.assert(c.length === 1); - // eslint-disable-next-line no-mixed-operators - return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c === '-'; -} -function _isASCIIDigit(c) { - console.assert(c.length === 1); - return c >= '0' && c <= '9'; -} -function _isASCIIPrintableCharacter(c) { - console.assert(c.length === 1); - return c >= ' ' && c <= '~'; -} -function _isASCIIWhitespace(c) { - console.assert(c.length === 1); - return c === ' ' || c === '\f' || c === '\n' || c === '\r' || c === '\t'; -} - -// MARK: ASCII printable character bit set and canonicalization functions + function safeRegexTest(regex, string) { + if (!string || !regex || string.length > constants.TEXT_LENGTH_CUTOFF) + return false; + return regex.test(string); + } + function pierceShadowTree(event, wantedTargetType) { + const { target } = event; + if (!(target instanceof Element) || !target?.shadowRoot || !event.composedPath) + return target; + const clickStack = event.composedPath(); + if (!wantedTargetType) { + return clickStack[0]; + } + return clickStack.find((el) => el instanceof wantedTargetType) || target; + } + function getActiveElement(root = document) { + const activeElement = root.activeElement; + if (!(activeElement instanceof Element) || !activeElement.shadowRoot) + return activeElement; + const innerActiveElement = activeElement.shadowRoot.activeElement; + if (innerActiveElement?.shadowRoot) { + return getActiveElement(innerActiveElement.shadowRoot); + } + return innerActiveElement; + } + + // src/Form/countryNames.js + var COUNTRY_CODES_TO_NAMES = { + AC: "Ascension Island", + AD: "Andorra", + AE: "United Arab Emirates", + AF: "Afghanistan", + AG: "Antigua & Barbuda", + AI: "Anguilla", + AL: "Albania", + AM: "Armenia", + AN: "Cura\xE7ao", + AO: "Angola", + AQ: "Antarctica", + AR: "Argentina", + AS: "American Samoa", + AT: "Austria", + AU: "Australia", + AW: "Aruba", + AX: "\xC5land Islands", + AZ: "Azerbaijan", + BA: "Bosnia & Herzegovina", + BB: "Barbados", + BD: "Bangladesh", + BE: "Belgium", + BF: "Burkina Faso", + BG: "Bulgaria", + BH: "Bahrain", + BI: "Burundi", + BJ: "Benin", + BL: "St. Barth\xE9lemy", + BM: "Bermuda", + BN: "Brunei", + BO: "Bolivia", + BQ: "Caribbean Netherlands", + BR: "Brazil", + BS: "Bahamas", + BT: "Bhutan", + BU: "Myanmar (Burma)", + BV: "Bouvet Island", + BW: "Botswana", + BY: "Belarus", + BZ: "Belize", + CA: "Canada", + CC: "Cocos (Keeling) Islands", + CD: "Congo - Kinshasa", + CF: "Central African Republic", + CG: "Congo - Brazzaville", + CH: "Switzerland", + CI: "C\xF4te d\u2019Ivoire", + CK: "Cook Islands", + CL: "Chile", + CM: "Cameroon", + CN: "China mainland", + CO: "Colombia", + CP: "Clipperton Island", + CR: "Costa Rica", + CS: "Serbia", + CU: "Cuba", + CV: "Cape Verde", + CW: "Cura\xE7ao", + CX: "Christmas Island", + CY: "Cyprus", + CZ: "Czechia", + DD: "Germany", + DE: "Germany", + DG: "Diego Garcia", + DJ: "Djibouti", + DK: "Denmark", + DM: "Dominica", + DO: "Dominican Republic", + DY: "Benin", + DZ: "Algeria", + EA: "Ceuta & Melilla", + EC: "Ecuador", + EE: "Estonia", + EG: "Egypt", + EH: "Western Sahara", + ER: "Eritrea", + ES: "Spain", + ET: "Ethiopia", + EU: "European Union", + EZ: "Eurozone", + FI: "Finland", + FJ: "Fiji", + FK: "Falkland Islands", + FM: "Micronesia", + FO: "Faroe Islands", + FR: "France", + FX: "France", + GA: "Gabon", + GB: "United Kingdom", + GD: "Grenada", + GE: "Georgia", + GF: "French Guiana", + GG: "Guernsey", + GH: "Ghana", + GI: "Gibraltar", + GL: "Greenland", + GM: "Gambia", + GN: "Guinea", + GP: "Guadeloupe", + GQ: "Equatorial Guinea", + GR: "Greece", + GS: "So. Georgia & So. Sandwich Isl.", + GT: "Guatemala", + GU: "Guam", + GW: "Guinea-Bissau", + GY: "Guyana", + HK: "Hong Kong", + HM: "Heard & McDonald Islands", + HN: "Honduras", + HR: "Croatia", + HT: "Haiti", + HU: "Hungary", + HV: "Burkina Faso", + IC: "Canary Islands", + ID: "Indonesia", + IE: "Ireland", + IL: "Israel", + IM: "Isle of Man", + IN: "India", + IO: "Chagos Archipelago", + IQ: "Iraq", + IR: "Iran", + IS: "Iceland", + IT: "Italy", + JE: "Jersey", + JM: "Jamaica", + JO: "Jordan", + JP: "Japan", + KE: "Kenya", + KG: "Kyrgyzstan", + KH: "Cambodia", + KI: "Kiribati", + KM: "Comoros", + KN: "St. Kitts & Nevis", + KP: "North Korea", + KR: "South Korea", + KW: "Kuwait", + KY: "Cayman Islands", + KZ: "Kazakhstan", + LA: "Laos", + LB: "Lebanon", + LC: "St. Lucia", + LI: "Liechtenstein", + LK: "Sri Lanka", + LR: "Liberia", + LS: "Lesotho", + LT: "Lithuania", + LU: "Luxembourg", + LV: "Latvia", + LY: "Libya", + MA: "Morocco", + MC: "Monaco", + MD: "Moldova", + ME: "Montenegro", + MF: "St. Martin", + MG: "Madagascar", + MH: "Marshall Islands", + MK: "North Macedonia", + ML: "Mali", + MM: "Myanmar (Burma)", + MN: "Mongolia", + MO: "Macao", + MP: "Northern Mariana Islands", + MQ: "Martinique", + MR: "Mauritania", + MS: "Montserrat", + MT: "Malta", + MU: "Mauritius", + MV: "Maldives", + MW: "Malawi", + MX: "Mexico", + MY: "Malaysia", + MZ: "Mozambique", + NA: "Namibia", + NC: "New Caledonia", + NE: "Niger", + NF: "Norfolk Island", + NG: "Nigeria", + NH: "Vanuatu", + NI: "Nicaragua", + NL: "Netherlands", + NO: "Norway", + NP: "Nepal", + NR: "Nauru", + NU: "Niue", + NZ: "New Zealand", + OM: "Oman", + PA: "Panama", + PE: "Peru", + PF: "French Polynesia", + PG: "Papua New Guinea", + PH: "Philippines", + PK: "Pakistan", + PL: "Poland", + PM: "St. Pierre & Miquelon", + PN: "Pitcairn Islands", + PR: "Puerto Rico", + PS: "Palestinian Territories", + PT: "Portugal", + PW: "Palau", + PY: "Paraguay", + QA: "Qatar", + QO: "Outlying Oceania", + RE: "R\xE9union", + RH: "Zimbabwe", + RO: "Romania", + RS: "Serbia", + RU: "Russia", + RW: "Rwanda", + SA: "Saudi Arabia", + SB: "Solomon Islands", + SC: "Seychelles", + SD: "Sudan", + SE: "Sweden", + SG: "Singapore", + SH: "St. Helena", + SI: "Slovenia", + SJ: "Svalbard & Jan Mayen", + SK: "Slovakia", + SL: "Sierra Leone", + SM: "San Marino", + SN: "Senegal", + SO: "Somalia", + SR: "Suriname", + SS: "South Sudan", + ST: "S\xE3o Tom\xE9 & Pr\xEDncipe", + SU: "Russia", + SV: "El Salvador", + SX: "Sint Maarten", + SY: "Syria", + SZ: "Eswatini", + TA: "Tristan da Cunha", + TC: "Turks & Caicos Islands", + TD: "Chad", + TF: "French Southern Territories", + TG: "Togo", + TH: "Thailand", + TJ: "Tajikistan", + TK: "Tokelau", + TL: "Timor-Leste", + TM: "Turkmenistan", + TN: "Tunisia", + TO: "Tonga", + TP: "Timor-Leste", + TR: "Turkey", + TT: "Trinidad & Tobago", + TV: "Tuvalu", + TW: "Taiwan", + TZ: "Tanzania", + UA: "Ukraine", + UG: "Uganda", + UK: "United Kingdom", + UM: "U.S. Outlying Islands", + UN: "United Nations", + US: "United States", + UY: "Uruguay", + UZ: "Uzbekistan", + VA: "Vatican City", + VC: "St. Vincent & Grenadines", + VD: "Vietnam", + VE: "Venezuela", + VG: "British Virgin Islands", + VI: "U.S. Virgin Islands", + VN: "Vietnam", + VU: "Vanuatu", + WF: "Wallis & Futuna", + WS: "Samoa", + XA: "Pseudo-Accents", + XB: "Pseudo-Bidi", + XK: "Kosovo", + YD: "Yemen", + YE: "Yemen", + YT: "Mayotte", + YU: "Serbia", + ZA: "South Africa", + ZM: "Zambia", + ZR: "Congo - Kinshasa", + ZW: "Zimbabwe", + ZZ: "Unknown Region" + }; + var COUNTRY_NAMES_TO_CODES = { + "Ascension Island": "AC", + Andorra: "AD", + "United Arab Emirates": "AE", + Afghanistan: "AF", + "Antigua & Barbuda": "AG", + Anguilla: "AI", + Albania: "AL", + Armenia: "AM", + "Cura\xE7ao": "CW", + Angola: "AO", + Antarctica: "AQ", + Argentina: "AR", + "American Samoa": "AS", + Austria: "AT", + Australia: "AU", + Aruba: "AW", + "\xC5land Islands": "AX", + Azerbaijan: "AZ", + "Bosnia & Herzegovina": "BA", + Barbados: "BB", + Bangladesh: "BD", + Belgium: "BE", + "Burkina Faso": "HV", + Bulgaria: "BG", + Bahrain: "BH", + Burundi: "BI", + Benin: "DY", + "St. Barth\xE9lemy": "BL", + Bermuda: "BM", + Brunei: "BN", + Bolivia: "BO", + "Caribbean Netherlands": "BQ", + Brazil: "BR", + Bahamas: "BS", + Bhutan: "BT", + "Myanmar (Burma)": "MM", + "Bouvet Island": "BV", + Botswana: "BW", + Belarus: "BY", + Belize: "BZ", + Canada: "CA", + "Cocos (Keeling) Islands": "CC", + "Congo - Kinshasa": "ZR", + "Central African Republic": "CF", + "Congo - Brazzaville": "CG", + Switzerland: "CH", + "C\xF4te d\u2019Ivoire": "CI", + "Cook Islands": "CK", + Chile: "CL", + Cameroon: "CM", + "China mainland": "CN", + Colombia: "CO", + "Clipperton Island": "CP", + "Costa Rica": "CR", + Serbia: "YU", + Cuba: "CU", + "Cape Verde": "CV", + "Christmas Island": "CX", + Cyprus: "CY", + Czechia: "CZ", + Germany: "DE", + "Diego Garcia": "DG", + Djibouti: "DJ", + Denmark: "DK", + Dominica: "DM", + "Dominican Republic": "DO", + Algeria: "DZ", + "Ceuta & Melilla": "EA", + Ecuador: "EC", + Estonia: "EE", + Egypt: "EG", + "Western Sahara": "EH", + Eritrea: "ER", + Spain: "ES", + Ethiopia: "ET", + "European Union": "EU", + Eurozone: "EZ", + Finland: "FI", + Fiji: "FJ", + "Falkland Islands": "FK", + Micronesia: "FM", + "Faroe Islands": "FO", + France: "FX", + Gabon: "GA", + "United Kingdom": "UK", + Grenada: "GD", + Georgia: "GE", + "French Guiana": "GF", + Guernsey: "GG", + Ghana: "GH", + Gibraltar: "GI", + Greenland: "GL", + Gambia: "GM", + Guinea: "GN", + Guadeloupe: "GP", + "Equatorial Guinea": "GQ", + Greece: "GR", + "So. Georgia & So. Sandwich Isl.": "GS", + Guatemala: "GT", + Guam: "GU", + "Guinea-Bissau": "GW", + Guyana: "GY", + "Hong Kong": "HK", + "Heard & McDonald Islands": "HM", + Honduras: "HN", + Croatia: "HR", + Haiti: "HT", + Hungary: "HU", + "Canary Islands": "IC", + Indonesia: "ID", + Ireland: "IE", + Israel: "IL", + "Isle of Man": "IM", + India: "IN", + "Chagos Archipelago": "IO", + Iraq: "IQ", + Iran: "IR", + Iceland: "IS", + Italy: "IT", + Jersey: "JE", + Jamaica: "JM", + Jordan: "JO", + Japan: "JP", + Kenya: "KE", + Kyrgyzstan: "KG", + Cambodia: "KH", + Kiribati: "KI", + Comoros: "KM", + "St. Kitts & Nevis": "KN", + "North Korea": "KP", + "South Korea": "KR", + Kuwait: "KW", + "Cayman Islands": "KY", + Kazakhstan: "KZ", + Laos: "LA", + Lebanon: "LB", + "St. Lucia": "LC", + Liechtenstein: "LI", + "Sri Lanka": "LK", + Liberia: "LR", + Lesotho: "LS", + Lithuania: "LT", + Luxembourg: "LU", + Latvia: "LV", + Libya: "LY", + Morocco: "MA", + Monaco: "MC", + Moldova: "MD", + Montenegro: "ME", + "St. Martin": "MF", + Madagascar: "MG", + "Marshall Islands": "MH", + "North Macedonia": "MK", + Mali: "ML", + Mongolia: "MN", + Macao: "MO", + "Northern Mariana Islands": "MP", + Martinique: "MQ", + Mauritania: "MR", + Montserrat: "MS", + Malta: "MT", + Mauritius: "MU", + Maldives: "MV", + Malawi: "MW", + Mexico: "MX", + Malaysia: "MY", + Mozambique: "MZ", + Namibia: "NA", + "New Caledonia": "NC", + Niger: "NE", + "Norfolk Island": "NF", + Nigeria: "NG", + Vanuatu: "VU", + Nicaragua: "NI", + Netherlands: "NL", + Norway: "NO", + Nepal: "NP", + Nauru: "NR", + Niue: "NU", + "New Zealand": "NZ", + Oman: "OM", + Panama: "PA", + Peru: "PE", + "French Polynesia": "PF", + "Papua New Guinea": "PG", + Philippines: "PH", + Pakistan: "PK", + Poland: "PL", + "St. Pierre & Miquelon": "PM", + "Pitcairn Islands": "PN", + "Puerto Rico": "PR", + "Palestinian Territories": "PS", + Portugal: "PT", + Palau: "PW", + Paraguay: "PY", + Qatar: "QA", + "Outlying Oceania": "QO", + "R\xE9union": "RE", + Zimbabwe: "ZW", + Romania: "RO", + Russia: "SU", + Rwanda: "RW", + "Saudi Arabia": "SA", + "Solomon Islands": "SB", + Seychelles: "SC", + Sudan: "SD", + Sweden: "SE", + Singapore: "SG", + "St. Helena": "SH", + Slovenia: "SI", + "Svalbard & Jan Mayen": "SJ", + Slovakia: "SK", + "Sierra Leone": "SL", + "San Marino": "SM", + Senegal: "SN", + Somalia: "SO", + Suriname: "SR", + "South Sudan": "SS", + "S\xE3o Tom\xE9 & Pr\xEDncipe": "ST", + "El Salvador": "SV", + "Sint Maarten": "SX", + Syria: "SY", + Eswatini: "SZ", + "Tristan da Cunha": "TA", + "Turks & Caicos Islands": "TC", + Chad: "TD", + "French Southern Territories": "TF", + Togo: "TG", + Thailand: "TH", + Tajikistan: "TJ", + Tokelau: "TK", + "Timor-Leste": "TP", + Turkmenistan: "TM", + Tunisia: "TN", + Tonga: "TO", + Turkey: "TR", + "Trinidad & Tobago": "TT", + Tuvalu: "TV", + Taiwan: "TW", + Tanzania: "TZ", + Ukraine: "UA", + Uganda: "UG", + "U.S. Outlying Islands": "UM", + "United Nations": "UN", + "United States": "US", + Uruguay: "UY", + Uzbekistan: "UZ", + "Vatican City": "VA", + "St. Vincent & Grenadines": "VC", + Vietnam: "VN", + Venezuela: "VE", + "British Virgin Islands": "VG", + "U.S. Virgin Islands": "VI", + "Wallis & Futuna": "WF", + Samoa: "WS", + "Pseudo-Accents": "XA", + "Pseudo-Bidi": "XB", + Kosovo: "XK", + Yemen: "YE", + Mayotte: "YT", + "South Africa": "ZA", + Zambia: "ZM", + "Unknown Region": "ZZ" + }; -function _bitSetIndexForCharacter(c) { - console.assert(c.length === 1); - // @ts-ignore - return c.codePointAt(0) - SPACE_CODE_POINT; -} -function _characterAtBitSetIndex(index) { - return String.fromCodePoint(index + SPACE_CODE_POINT); -} -function _markBitsForNamedCharacterClass(bitSet, namedCharacterClass) { - console.assert(bitSet instanceof Array); - console.assert(namedCharacterClass.name !== Identifier.UNICODE); - console.assert(namedCharacterClass.name !== Identifier.ASCII_PRINTABLE); - if (namedCharacterClass.name === Identifier.UPPER) { - bitSet.fill(true, _bitSetIndexForCharacter('A'), _bitSetIndexForCharacter('Z') + 1); - } else if (namedCharacterClass.name === Identifier.LOWER) { - bitSet.fill(true, _bitSetIndexForCharacter('a'), _bitSetIndexForCharacter('z') + 1); - } else if (namedCharacterClass.name === Identifier.DIGIT) { - bitSet.fill(true, _bitSetIndexForCharacter('0'), _bitSetIndexForCharacter('9') + 1); - } else if (namedCharacterClass.name === Identifier.SPECIAL) { - bitSet.fill(true, _bitSetIndexForCharacter(' '), _bitSetIndexForCharacter('/') + 1); - bitSet.fill(true, _bitSetIndexForCharacter(':'), _bitSetIndexForCharacter('@') + 1); - bitSet.fill(true, _bitSetIndexForCharacter('['), _bitSetIndexForCharacter('`') + 1); - bitSet.fill(true, _bitSetIndexForCharacter('{'), _bitSetIndexForCharacter('~') + 1); - } else { - console.assert(false, SHOULD_NOT_BE_REACHED, namedCharacterClass); - } -} -function _markBitsForCustomCharacterClass(bitSet, customCharacterClass) { - for (let character of customCharacterClass.characters) { - bitSet[_bitSetIndexForCharacter(character)] = true; - } -} -function _canonicalizedPropertyValues(propertyValues, keepCustomCharacterClassFormatCompliant) { - // @ts-ignore - let asciiPrintableBitSet = new Array('~'.codePointAt(0) - ' '.codePointAt(0) + 1); - for (let propertyValue of propertyValues) { - if (propertyValue instanceof NamedCharacterClass) { - if (propertyValue.name === Identifier.UNICODE) { - return [new NamedCharacterClass(Identifier.UNICODE)]; - } - if (propertyValue.name === Identifier.ASCII_PRINTABLE) { - return [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)]; - } - _markBitsForNamedCharacterClass(asciiPrintableBitSet, propertyValue); - } else if (propertyValue instanceof CustomCharacterClass) { - _markBitsForCustomCharacterClass(asciiPrintableBitSet, propertyValue); + // src/Form/formatters.js + var DATE_SEPARATOR_REGEX = /\b((.)\2{1,3}|\d+)(?\s?[/\s.\-_—–]\s?)((.)\5{1,3}|\d+)\b/i; + var FOUR_DIGIT_YEAR_REGEX = /(\D)\1{3}|\d{4}/i; + var formatCCYear = (input, year, form) => { + const selector = form.matching.cssSelector("formInputsSelector"); + if (input.maxLength === 4 || checkPlaceholderAndLabels(input, FOUR_DIGIT_YEAR_REGEX, form.form, selector)) + return year; + return `${Number(year) - 2e3}`; + }; + var getUnifiedExpiryDate = (input, month, year, form) => { + const formattedYear = formatCCYear(input, year, form); + const paddedMonth = `${month}`.padStart(2, "0"); + const cssSelector = form.matching.cssSelector("formInputsSelector"); + const separator = matchInPlaceholderAndLabels(input, DATE_SEPARATOR_REGEX, form.form, cssSelector)?.groups?.separator || "/"; + return `${paddedMonth}${separator}${formattedYear}`; + }; + var formatFullName = ({ firstName = "", middleName = "", lastName = "" }) => `${firstName} ${middleName ? middleName + " " : ""}${lastName}`.trim(); + var getCountryDisplayName = (locale, addressCountryCode) => { + try { + const regionNames = new Intl.DisplayNames([locale], { type: "region" }); + return regionNames.of(addressCountryCode); + } catch (e) { + return COUNTRY_CODES_TO_NAMES[addressCountryCode] || addressCountryCode; } - } - let charactersSeen = []; - function checkRange(start, end) { - let temp = []; - for (let i = _bitSetIndexForCharacter(start); i <= _bitSetIndexForCharacter(end); ++i) { - if (asciiPrintableBitSet[i]) { - temp.push(_characterAtBitSetIndex(i)); + }; + var inferElementLocale = (el) => el.lang || el.form?.lang || document.body.lang || document.documentElement.lang || "en"; + var getCountryName = (el, options = {}) => { + const { addressCountryCode } = options; + if (!addressCountryCode) + return ""; + const elLocale = inferElementLocale(el); + const localisedCountryName = getCountryDisplayName(elLocale, addressCountryCode); + if (el.nodeName === "SELECT") { + const englishCountryName = getCountryDisplayName("en", addressCountryCode); + const countryNameRegex = new RegExp(String.raw`${localisedCountryName.replace(/ /g, ".?")}|${englishCountryName.replace(/ /g, ".?")}`, "i"); + const countryCodeRegex = new RegExp(String.raw`\b${addressCountryCode}\b`, "i"); + if (el instanceof HTMLSelectElement) { + for (const option of el.options) { + if (countryCodeRegex.test(option.value)) { + return option.value; + } + } + for (const option of el.options) { + if (countryNameRegex.test(option.value) || countryNameRegex.test(option.innerText)) + return option.value; + } } } - let result = temp.length === _bitSetIndexForCharacter(end) - _bitSetIndexForCharacter(start) + 1; - if (!result) { - charactersSeen = charactersSeen.concat(temp); + return localisedCountryName; + }; + var getLocalisedCountryNamesToCodes = (el) => { + if (typeof Intl.DisplayNames !== "function") + return COUNTRY_NAMES_TO_CODES; + const elLocale = inferElementLocale(el); + return Object.fromEntries( + Object.entries(COUNTRY_CODES_TO_NAMES).map(([code]) => [getCountryDisplayName(elLocale, code), code]) + ); + }; + var inferCountryCodeFromElement = (el) => { + if (COUNTRY_CODES_TO_NAMES[el.value]) + return el.value; + if (COUNTRY_NAMES_TO_CODES[el.value]) + return COUNTRY_NAMES_TO_CODES[el.value]; + const localisedCountryNamesToCodes = getLocalisedCountryNamesToCodes(el); + if (localisedCountryNamesToCodes[el.value]) + return localisedCountryNamesToCodes[el.value]; + if (el instanceof HTMLSelectElement) { + const selectedText = el.selectedOptions[0]?.text; + if (COUNTRY_CODES_TO_NAMES[selectedText]) + return selectedText; + if (COUNTRY_NAMES_TO_CODES[selectedText]) + return localisedCountryNamesToCodes[selectedText]; + if (localisedCountryNamesToCodes[selectedText]) + return localisedCountryNamesToCodes[selectedText]; + } + return ""; + }; + var getMMAndYYYYFromString = (expiration) => { + const values = expiration.match(/(\d+)/g) || []; + return values?.reduce((output, current) => { + if (Number(current) > 12) { + output.expirationYear = current.padStart(4, "20"); + } else { + output.expirationMonth = current.padStart(2, "0"); + } + return output; + }, { expirationYear: "", expirationMonth: "" }); + }; + var shouldStoreCredentials = ({ credentials }) => Boolean(credentials.password); + var shouldStoreIdentities = ({ identities }) => Boolean( + (identities.firstName || identities.fullName) && identities.addressStreet && identities.addressCity + ); + var shouldStoreCreditCards = ({ creditCards }) => { + if (!creditCards.cardNumber) + return false; + if (creditCards.cardSecurityCode) + return true; + if (creditCards.expiration) + return true; + return Boolean(creditCards.expirationYear && creditCards.expirationMonth); + }; + var formatPhoneNumber = (phone) => phone.replaceAll(/[^0-9|+]/g, ""); + var prepareFormValuesForStorage = (formValues) => { + let { credentials, identities, creditCards } = formValues; + if (!creditCards.cardName && (identities?.fullName || identities?.firstName)) { + creditCards.cardName = identities?.fullName || formatFullName(identities); } - return result; - } - let hasAllUpper = checkRange('A', 'Z'); - let hasAllLower = checkRange('a', 'z'); - let hasAllDigits = checkRange('0', '9'); - - // Check for special characters, accounting for characters that are given special treatment (i.e. '-' and ']') - let hasAllSpecial = false; - let hasDash = false; - let hasRightSquareBracket = false; - let temp = []; - for (let i = _bitSetIndexForCharacter(' '); i <= _bitSetIndexForCharacter('/'); ++i) { - if (!asciiPrintableBitSet[i]) { - continue; - } - let character = _characterAtBitSetIndex(i); - if (keepCustomCharacterClassFormatCompliant && character === '-') { - hasDash = true; + if (shouldStoreCredentials(formValues)) { + if (credentials.password && !credentials.username && identities.emailAddress) { + credentials.username = identities.emailAddress; + } + } else { + credentials = void 0; + } + if (shouldStoreIdentities(formValues)) { + if (identities.fullName) { + if (!(identities.firstName && identities.lastName)) { + const nameParts = identities.fullName.trim().split(/\s+/); + if (nameParts.length === 2) { + identities.firstName = nameParts[0]; + identities.lastName = nameParts[1]; + } else { + identities.firstName = identities.fullName; + } + } + delete identities.fullName; + } + if (identities.phone) { + identities.phone = formatPhoneNumber(identities.phone); + } } else { - temp.push(character); + identities = void 0; } - } - for (let i = _bitSetIndexForCharacter(':'); i <= _bitSetIndexForCharacter('@'); ++i) { - if (asciiPrintableBitSet[i]) { - temp.push(_characterAtBitSetIndex(i)); + if (shouldStoreCreditCards(formValues)) { + if (creditCards.expiration) { + const { expirationMonth, expirationYear } = getMMAndYYYYFromString(creditCards.expiration); + creditCards.expirationMonth = expirationMonth; + creditCards.expirationYear = expirationYear; + delete creditCards.expiration; + } + creditCards.expirationYear = creditCards.expirationYear?.padStart(4, "20"); + if (creditCards.cardNumber) { + creditCards.cardNumber = creditCards.cardNumber.replace(/\D/g, ""); + } + } else { + creditCards = void 0; } - } - for (let i = _bitSetIndexForCharacter('['); i <= _bitSetIndexForCharacter('`'); ++i) { - if (!asciiPrintableBitSet[i]) { - continue; + return { credentials, identities, creditCards }; + }; + + // src/InputTypes/Credentials.js + var AUTOGENERATED_KEY = "autogenerated"; + var PROVIDER_LOCKED = "provider_locked"; + var _data; + var CredentialsTooltipItem = class { + /** @param {CredentialsObject} data */ + constructor(data) { + /** @type {CredentialsObject} */ + __privateAdd(this, _data, void 0); + __publicField(this, "id", () => String(__privateGet(this, _data).id)); + __publicField(this, "labelMedium", (_subtype) => { + if (__privateGet(this, _data).username) { + return __privateGet(this, _data).username; + } + if (__privateGet(this, _data).origin?.url) { + return `Password for ${truncateFromMiddle(__privateGet(this, _data).origin.url)}`; + } + return ""; + }); + __publicField(this, "labelSmall", (_subtype) => { + if (__privateGet(this, _data).origin?.url) { + return truncateFromMiddle(__privateGet(this, _data).origin.url); + } + return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"; + }); + __publicField(this, "credentialsProvider", () => __privateGet(this, _data).credentialsProvider); + __privateSet(this, _data, data); } - let character = _characterAtBitSetIndex(i); - if (keepCustomCharacterClassFormatCompliant && character === ']') { - hasRightSquareBracket = true; - } else { - temp.push(character); + }; + _data = new WeakMap(); + var _data2; + var AutoGeneratedCredential = class { + /** @param {CredentialsObject} data */ + constructor(data) { + /** @type {CredentialsObject} */ + __privateAdd(this, _data2, void 0); + __publicField(this, "id", () => String(__privateGet(this, _data2).id)); + __publicField(this, "label", (_subtype) => __privateGet(this, _data2).password); + __publicField(this, "labelMedium", (_subtype) => "Generated password"); + __publicField(this, "labelSmall", (_subtype) => "Password will be saved for this website"); + __privateSet(this, _data2, data); } + }; + _data2 = new WeakMap(); + function fromPassword(password, username) { + return { + [AUTOGENERATED_KEY]: true, + password, + username + }; } - for (let i = _bitSetIndexForCharacter('{'); i <= _bitSetIndexForCharacter('~'); ++i) { - if (asciiPrintableBitSet[i]) { - temp.push(_characterAtBitSetIndex(i)); + var _data3; + var ProviderLockedItem = class { + /** @param {CredentialsObject} data */ + constructor(data) { + /** @type {CredentialsObject} */ + __privateAdd(this, _data3, void 0); + __publicField(this, "id", () => String(__privateGet(this, _data3).id)); + __publicField(this, "labelMedium", (_subtype) => "Bitwarden is locked"); + __publicField(this, "labelSmall", (_subtype) => "Unlock your vault to access credentials or generate passwords"); + __publicField(this, "credentialsProvider", () => __privateGet(this, _data3).credentialsProvider); + __privateSet(this, _data3, data); } - } - if (hasDash) { - temp.unshift('-'); - } - if (hasRightSquareBracket) { - temp.push(']'); - } - let numberOfSpecialCharacters = _bitSetIndexForCharacter('/') - _bitSetIndexForCharacter(' ') + 1 + (_bitSetIndexForCharacter('@') - _bitSetIndexForCharacter(':') + 1) + (_bitSetIndexForCharacter('`') - _bitSetIndexForCharacter('[') + 1) + (_bitSetIndexForCharacter('~') - _bitSetIndexForCharacter('{') + 1); - hasAllSpecial = temp.length === numberOfSpecialCharacters; - if (!hasAllSpecial) { - charactersSeen = charactersSeen.concat(temp); - } - let result = []; - if (hasAllUpper && hasAllLower && hasAllDigits && hasAllSpecial) { - return [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)]; - } - if (hasAllUpper) { - result.push(new NamedCharacterClass(Identifier.UPPER)); - } - if (hasAllLower) { - result.push(new NamedCharacterClass(Identifier.LOWER)); - } - if (hasAllDigits) { - result.push(new NamedCharacterClass(Identifier.DIGIT)); - } - if (hasAllSpecial) { - result.push(new NamedCharacterClass(Identifier.SPECIAL)); - } - if (charactersSeen.length) { - result.push(new CustomCharacterClass(charactersSeen)); - } - return result; -} - -// MARK: Parser functions - -function _indexOfNonWhitespaceCharacter(input) { - let position = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; - console.assert(position >= 0); - console.assert(position <= input.length); - let length = input.length; - while (position < length && _isASCIIWhitespace(input[position])) { - ++position; - } - return position; -} -function _parseIdentifier(input, position) { - console.assert(position >= 0); - console.assert(position < input.length); - console.assert(_isIdentifierCharacter(input[position])); - let length = input.length; - let seenIdentifiers = []; - do { - let c = input[position]; - if (!_isIdentifierCharacter(c)) { - break; - } - seenIdentifiers.push(c); - ++position; - } while (position < length); - return [seenIdentifiers.join(''), position]; -} -function _isValidRequiredOrAllowedPropertyValueIdentifier(identifier) { - return identifier && Object.values(Identifier).includes(identifier.toLowerCase()); -} -function _parseCustomCharacterClass(input, position) { - console.assert(position >= 0); - console.assert(position < input.length); - console.assert(input[position] === CHARACTER_CLASS_START_SENTINEL); - let length = input.length; - ++position; - if (position >= length) { - // console.error('Found end-of-line instead of character class character') - return [null, position]; - } - let initialPosition = position; - let result = []; - do { - let c = input[position]; - if (!_isASCIIPrintableCharacter(c)) { - ++position; - continue; + }; + _data3 = new WeakMap(); + function appendGeneratedKey(data, autofilledFields = {}) { + let autogenerated = false; + if (autofilledFields.password && data.credentials?.password === autofilledFields.password) { + autogenerated = true; } - if (c === '-' && position - initialPosition > 0) { - // FIXME: Should this be an error? - console.warn("Ignoring '-'; a '-' may only appear as the first character in a character class"); - ++position; - continue; + if (autofilledFields.username && data.credentials?.username === autofilledFields.username) { + autogenerated = true; } - result.push(c); - ++position; - if (c === CHARACTER_CLASS_END_SENTINEL) { - break; - } - } while (position < length); - if (position < length && input[position] !== CHARACTER_CLASS_END_SENTINEL) { - // Fix up result; we over consumed. - result.pop(); - return [result, position]; - } else if (position === length && input[position - 1] === CHARACTER_CLASS_END_SENTINEL) { - // Fix up result; we over consumed. - result.pop(); - return [result, position]; + if (!autogenerated) + return data; + return { + ...data, + credentials: { + ...data.credentials, + [AUTOGENERATED_KEY]: true + } + }; } - if (position < length && input[position] === CHARACTER_CLASS_END_SENTINEL) { - return [result, position + 1]; + function createCredentialsTooltipItem(data) { + if (data.id === PROVIDER_LOCKED) { + return new ProviderLockedItem(data); + } + if (AUTOGENERATED_KEY in data && data.password) { + return new AutoGeneratedCredential(data); + } + return new CredentialsTooltipItem(data); } - // console.error('Found end-of-line instead of end of character class') - return [null, position]; -} -function _parsePasswordRequiredOrAllowedPropertyValue(input, position) { - console.assert(position >= 0); - console.assert(position < input.length); - let length = input.length; - let propertyValues = []; - while (true) { - if (_isIdentifierCharacter(input[position])) { - let identifierStartPosition = position; - // eslint-disable-next-line no-redeclare - var [propertyValue, position] = _parseIdentifier(input, position); - if (!_isValidRequiredOrAllowedPropertyValueIdentifier(propertyValue)) { - // console.error('Unrecognized property value identifier: ' + propertyValue) - return [null, identifierStartPosition]; - } - propertyValues.push(new NamedCharacterClass(propertyValue)); - } else if (input[position] === CHARACTER_CLASS_START_SENTINEL) { - // eslint-disable-next-line no-redeclare - var [propertyValue, position] = _parseCustomCharacterClass(input, position); - if (propertyValue && propertyValue.length) { - propertyValues.push(new CustomCharacterClass(propertyValue)); - } - } else { - // console.error('Failed to find start of property value: ' + input.substr(position)) - return [null, position]; + // packages/password/lib/rules-parser.js + var Identifier = { + ASCII_PRINTABLE: "ascii-printable", + DIGIT: "digit", + LOWER: "lower", + SPECIAL: "special", + UNICODE: "unicode", + UPPER: "upper" + }; + var RuleName = { + ALLOWED: "allowed", + MAX_CONSECUTIVE: "max-consecutive", + REQUIRED: "required", + MIN_LENGTH: "minlength", + MAX_LENGTH: "maxlength" + }; + var CHARACTER_CLASS_START_SENTINEL = "["; + var CHARACTER_CLASS_END_SENTINEL = "]"; + var PROPERTY_VALUE_SEPARATOR = ","; + var PROPERTY_SEPARATOR = ";"; + var PROPERTY_VALUE_START_SENTINEL = ":"; + var SPACE_CODE_POINT = " ".codePointAt(0); + var SHOULD_NOT_BE_REACHED = "Should not be reached"; + var Rule = class { + constructor(name, value) { + this._name = name; + this.value = value; + } + get name() { + return this._name; + } + toString() { + return JSON.stringify(this); } - position = _indexOfNonWhitespaceCharacter(input, position); - if (position >= length || input[position] === PROPERTY_SEPARATOR) { - break; + }; + var NamedCharacterClass = class { + constructor(name) { + console.assert(_isValidRequiredOrAllowedPropertyValueIdentifier(name)); + this._name = name; } - if (input[position] === PROPERTY_VALUE_SEPARATOR) { - position = _indexOfNonWhitespaceCharacter(input, position + 1); - if (position >= length) { - // console.error('Found end-of-line instead of start of next property value') - return [null, position]; - } - continue; + get name() { + return this._name.toLowerCase(); + } + toString() { + return this._name; + } + toHTMLString() { + return this._name; + } + }; + var ParserError = class extends Error { + }; + var CustomCharacterClass = class { + constructor(characters) { + console.assert(characters instanceof Array); + this._characters = characters; + } + get characters() { + return this._characters; + } + toString() { + return `[${this._characters.join("")}]`; + } + toHTMLString() { + return `[${this._characters.join("").replace('"', """)}]`; + } + }; + function _isIdentifierCharacter(c) { + console.assert(c.length === 1); + return c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c === "-"; + } + function _isASCIIDigit(c) { + console.assert(c.length === 1); + return c >= "0" && c <= "9"; + } + function _isASCIIPrintableCharacter(c) { + console.assert(c.length === 1); + return c >= " " && c <= "~"; + } + function _isASCIIWhitespace(c) { + console.assert(c.length === 1); + return c === " " || c === "\f" || c === "\n" || c === "\r" || c === " "; + } + function _bitSetIndexForCharacter(c) { + console.assert(c.length === 1); + return c.codePointAt(0) - SPACE_CODE_POINT; + } + function _characterAtBitSetIndex(index) { + return String.fromCodePoint(index + SPACE_CODE_POINT); + } + function _markBitsForNamedCharacterClass(bitSet, namedCharacterClass) { + console.assert(bitSet instanceof Array); + console.assert(namedCharacterClass.name !== Identifier.UNICODE); + console.assert(namedCharacterClass.name !== Identifier.ASCII_PRINTABLE); + if (namedCharacterClass.name === Identifier.UPPER) { + bitSet.fill(true, _bitSetIndexForCharacter("A"), _bitSetIndexForCharacter("Z") + 1); + } else if (namedCharacterClass.name === Identifier.LOWER) { + bitSet.fill(true, _bitSetIndexForCharacter("a"), _bitSetIndexForCharacter("z") + 1); + } else if (namedCharacterClass.name === Identifier.DIGIT) { + bitSet.fill(true, _bitSetIndexForCharacter("0"), _bitSetIndexForCharacter("9") + 1); + } else if (namedCharacterClass.name === Identifier.SPECIAL) { + bitSet.fill(true, _bitSetIndexForCharacter(" "), _bitSetIndexForCharacter("/") + 1); + bitSet.fill(true, _bitSetIndexForCharacter(":"), _bitSetIndexForCharacter("@") + 1); + bitSet.fill(true, _bitSetIndexForCharacter("["), _bitSetIndexForCharacter("`") + 1); + bitSet.fill(true, _bitSetIndexForCharacter("{"), _bitSetIndexForCharacter("~") + 1); + } else { + console.assert(false, SHOULD_NOT_BE_REACHED, namedCharacterClass); } - - // console.error('Failed to find start of next property or property value: ' + input.substr(position)) - return [null, position]; - } - return [propertyValues, position]; -} - -/** - * @param input - * @param position - * @returns {[Rule|null, number, string|undefined]} - * @private - */ -function _parsePasswordRule(input, position) { - console.assert(position >= 0); - console.assert(position < input.length); - console.assert(_isIdentifierCharacter(input[position])); - let length = input.length; - var mayBeIdentifierStartPosition = position; - // eslint-disable-next-line no-redeclare - var [identifier, position] = _parseIdentifier(input, position); - if (!Object.values(RuleName).includes(identifier)) { - // console.error('Unrecognized property name: ' + identifier) - return [null, mayBeIdentifierStartPosition, undefined]; - } - if (position >= length) { - // console.error('Found end-of-line instead of start of property value') - return [null, position, undefined]; - } - if (input[position] !== PROPERTY_VALUE_START_SENTINEL) { - // console.error('Failed to find start of property value: ' + input.substr(position)) - return [null, position, undefined]; } - let property = { - name: identifier, - value: null - }; - position = _indexOfNonWhitespaceCharacter(input, position + 1); - // Empty value - if (position >= length || input[position] === PROPERTY_SEPARATOR) { - return [new Rule(property.name, property.value), position, undefined]; + function _markBitsForCustomCharacterClass(bitSet, customCharacterClass) { + for (let character of customCharacterClass.characters) { + bitSet[_bitSetIndexForCharacter(character)] = true; + } } - switch (identifier) { - case RuleName.ALLOWED: - case RuleName.REQUIRED: - { - // eslint-disable-next-line no-redeclare - var [propertyValue, position] = _parsePasswordRequiredOrAllowedPropertyValue(input, position); - if (propertyValue) { - property.value = propertyValue; + function _canonicalizedPropertyValues(propertyValues, keepCustomCharacterClassFormatCompliant) { + let asciiPrintableBitSet = new Array("~".codePointAt(0) - " ".codePointAt(0) + 1); + for (let propertyValue of propertyValues) { + if (propertyValue instanceof NamedCharacterClass) { + if (propertyValue.name === Identifier.UNICODE) { + return [new NamedCharacterClass(Identifier.UNICODE)]; } - return [new Rule(property.name, property.value), position, undefined]; - } - case RuleName.MAX_CONSECUTIVE: - { - // eslint-disable-next-line no-redeclare - var [propertyValue, position] = _parseMaxConsecutivePropertyValue(input, position); - if (propertyValue) { - property.value = propertyValue; + if (propertyValue.name === Identifier.ASCII_PRINTABLE) { + return [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)]; } - return [new Rule(property.name, property.value), position, undefined]; + _markBitsForNamedCharacterClass(asciiPrintableBitSet, propertyValue); + } else if (propertyValue instanceof CustomCharacterClass) { + _markBitsForCustomCharacterClass(asciiPrintableBitSet, propertyValue); } - case RuleName.MIN_LENGTH: - case RuleName.MAX_LENGTH: - { - // eslint-disable-next-line no-redeclare - var [propertyValue, position] = _parseMinLengthMaxLengthPropertyValue(input, position); - if (propertyValue) { - property.value = propertyValue; + } + let charactersSeen = []; + function checkRange(start, end) { + let temp2 = []; + for (let i = _bitSetIndexForCharacter(start); i <= _bitSetIndexForCharacter(end); ++i) { + if (asciiPrintableBitSet[i]) { + temp2.push(_characterAtBitSetIndex(i)); } - return [new Rule(property.name, property.value), position, undefined]; } - } - console.assert(false, SHOULD_NOT_BE_REACHED); - return [null, -1, undefined]; -} -function _parseMinLengthMaxLengthPropertyValue(input, position) { - return _parseInteger(input, position); -} -function _parseMaxConsecutivePropertyValue(input, position) { - return _parseInteger(input, position); -} -function _parseInteger(input, position) { - console.assert(position >= 0); - console.assert(position < input.length); - if (!_isASCIIDigit(input[position])) { - // console.error('Failed to parse value of type integer; not a number: ' + input.substr(position)) - return [null, position]; - } - let length = input.length; - // let initialPosition = position - let result = 0; - do { - result = 10 * result + parseInt(input[position], 10); - ++position; - } while (position < length && input[position] !== PROPERTY_SEPARATOR && _isASCIIDigit(input[position])); - if (position >= length || input[position] === PROPERTY_SEPARATOR) { - return [result, position]; - } - - // console.error('Failed to parse value of type integer; not a number: ' + input.substr(initialPosition)) - return [null, position]; -} - -/** - * @param input - * @returns {[Rule[]|null, string|undefined]} - * @private - */ -function _parsePasswordRulesInternal(input) { - let parsedProperties = []; - let length = input.length; - var position = _indexOfNonWhitespaceCharacter(input); - while (position < length) { - if (!_isIdentifierCharacter(input[position])) { - // console.warn('Failed to find start of property: ' + input.substr(position)) - return [parsedProperties, undefined]; + let result2 = temp2.length === _bitSetIndexForCharacter(end) - _bitSetIndexForCharacter(start) + 1; + if (!result2) { + charactersSeen = charactersSeen.concat(temp2); + } + return result2; } - - // eslint-disable-next-line no-redeclare - var [parsedProperty, position, message] = _parsePasswordRule(input, position); - if (parsedProperty && parsedProperty.value) { - parsedProperties.push(parsedProperty); + let hasAllUpper = checkRange("A", "Z"); + let hasAllLower = checkRange("a", "z"); + let hasAllDigits = checkRange("0", "9"); + let hasAllSpecial = false; + let hasDash = false; + let hasRightSquareBracket = false; + let temp = []; + for (let i = _bitSetIndexForCharacter(" "); i <= _bitSetIndexForCharacter("/"); ++i) { + if (!asciiPrintableBitSet[i]) { + continue; + } + let character = _characterAtBitSetIndex(i); + if (keepCustomCharacterClassFormatCompliant && character === "-") { + hasDash = true; + } else { + temp.push(character); + } } - position = _indexOfNonWhitespaceCharacter(input, position); - if (position >= length) { - break; + for (let i = _bitSetIndexForCharacter(":"); i <= _bitSetIndexForCharacter("@"); ++i) { + if (asciiPrintableBitSet[i]) { + temp.push(_characterAtBitSetIndex(i)); + } } - if (input[position] === PROPERTY_SEPARATOR) { - position = _indexOfNonWhitespaceCharacter(input, position + 1); - if (position >= length) { - return [parsedProperties, undefined]; + for (let i = _bitSetIndexForCharacter("["); i <= _bitSetIndexForCharacter("`"); ++i) { + if (!asciiPrintableBitSet[i]) { + continue; + } + let character = _characterAtBitSetIndex(i); + if (keepCustomCharacterClassFormatCompliant && character === "]") { + hasRightSquareBracket = true; + } else { + temp.push(character); } - continue; } - - // console.error('Failed to find start of next property: ' + input.substr(position)) - return [null, message || 'Failed to find start of next property: ' + input.substr(position)]; - } - return [parsedProperties, undefined]; -} - -/** - * @param {string} input - * @param {boolean} [formatRulesForMinifiedVersion] - * @returns {Rule[]} - */ -function parsePasswordRules(input, formatRulesForMinifiedVersion) { - let [passwordRules, maybeMessage] = _parsePasswordRulesInternal(input); - if (!passwordRules) { - throw new ParserError(maybeMessage); + for (let i = _bitSetIndexForCharacter("{"); i <= _bitSetIndexForCharacter("~"); ++i) { + if (asciiPrintableBitSet[i]) { + temp.push(_characterAtBitSetIndex(i)); + } + } + if (hasDash) { + temp.unshift("-"); + } + if (hasRightSquareBracket) { + temp.push("]"); + } + let numberOfSpecialCharacters = _bitSetIndexForCharacter("/") - _bitSetIndexForCharacter(" ") + 1 + (_bitSetIndexForCharacter("@") - _bitSetIndexForCharacter(":") + 1) + (_bitSetIndexForCharacter("`") - _bitSetIndexForCharacter("[") + 1) + (_bitSetIndexForCharacter("~") - _bitSetIndexForCharacter("{") + 1); + hasAllSpecial = temp.length === numberOfSpecialCharacters; + if (!hasAllSpecial) { + charactersSeen = charactersSeen.concat(temp); + } + let result = []; + if (hasAllUpper && hasAllLower && hasAllDigits && hasAllSpecial) { + return [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)]; + } + if (hasAllUpper) { + result.push(new NamedCharacterClass(Identifier.UPPER)); + } + if (hasAllLower) { + result.push(new NamedCharacterClass(Identifier.LOWER)); + } + if (hasAllDigits) { + result.push(new NamedCharacterClass(Identifier.DIGIT)); + } + if (hasAllSpecial) { + result.push(new NamedCharacterClass(Identifier.SPECIAL)); + } + if (charactersSeen.length) { + result.push(new CustomCharacterClass(charactersSeen)); + } + return result; } - if (passwordRules.length === 0) { - throw new ParserError('No valid rules were provided'); + function _indexOfNonWhitespaceCharacter(input, position = 0) { + console.assert(position >= 0); + console.assert(position <= input.length); + let length = input.length; + while (position < length && _isASCIIWhitespace(input[position])) { + ++position; + } + return position; } - - // When formatting rules for minified version, we should keep the formatted rules - // as similar to the input as possible. Avoid copying required rules to allowed rules. - let suppressCopyingRequiredToAllowed = formatRulesForMinifiedVersion; - let requiredRules = []; - let newAllowedValues = []; - let minimumMaximumConsecutiveCharacters = null; - let maximumMinLength = 0; - let minimumMaxLength = null; - for (let rule of passwordRules) { - switch (rule.name) { - case RuleName.MAX_CONSECUTIVE: - minimumMaximumConsecutiveCharacters = minimumMaximumConsecutiveCharacters ? Math.min(rule.value, minimumMaximumConsecutiveCharacters) : rule.value; - break; - case RuleName.MIN_LENGTH: - maximumMinLength = Math.max(rule.value, maximumMinLength); + function _parseIdentifier(input, position) { + console.assert(position >= 0); + console.assert(position < input.length); + console.assert(_isIdentifierCharacter(input[position])); + let length = input.length; + let seenIdentifiers = []; + do { + let c = input[position]; + if (!_isIdentifierCharacter(c)) { break; - case RuleName.MAX_LENGTH: - minimumMaxLength = minimumMaxLength ? Math.min(rule.value, minimumMaxLength) : rule.value; + } + seenIdentifiers.push(c); + ++position; + } while (position < length); + return [seenIdentifiers.join(""), position]; + } + function _isValidRequiredOrAllowedPropertyValueIdentifier(identifier) { + return identifier && Object.values(Identifier).includes(identifier.toLowerCase()); + } + function _parseCustomCharacterClass(input, position) { + console.assert(position >= 0); + console.assert(position < input.length); + console.assert(input[position] === CHARACTER_CLASS_START_SENTINEL); + let length = input.length; + ++position; + if (position >= length) { + return [null, position]; + } + let initialPosition = position; + let result = []; + do { + let c = input[position]; + if (!_isASCIIPrintableCharacter(c)) { + ++position; + continue; + } + if (c === "-" && position - initialPosition > 0) { + console.warn("Ignoring '-'; a '-' may only appear as the first character in a character class"); + ++position; + continue; + } + result.push(c); + ++position; + if (c === CHARACTER_CLASS_END_SENTINEL) { break; - case RuleName.REQUIRED: - rule.value = _canonicalizedPropertyValues(rule.value, formatRulesForMinifiedVersion); - requiredRules.push(rule); - if (!suppressCopyingRequiredToAllowed) { - newAllowedValues = newAllowedValues.concat(rule.value); + } + } while (position < length); + if (position < length && input[position] !== CHARACTER_CLASS_END_SENTINEL) { + result.pop(); + return [result, position]; + } else if (position === length && input[position - 1] === CHARACTER_CLASS_END_SENTINEL) { + result.pop(); + return [result, position]; + } + if (position < length && input[position] === CHARACTER_CLASS_END_SENTINEL) { + return [result, position + 1]; + } + return [null, position]; + } + function _parsePasswordRequiredOrAllowedPropertyValue(input, position) { + console.assert(position >= 0); + console.assert(position < input.length); + let length = input.length; + let propertyValues = []; + while (true) { + if (_isIdentifierCharacter(input[position])) { + let identifierStartPosition = position; + var [propertyValue, position] = _parseIdentifier(input, position); + if (!_isValidRequiredOrAllowedPropertyValueIdentifier(propertyValue)) { + return [null, identifierStartPosition]; } + propertyValues.push(new NamedCharacterClass(propertyValue)); + } else if (input[position] === CHARACTER_CLASS_START_SENTINEL) { + var [propertyValue, position] = _parseCustomCharacterClass(input, position); + if (propertyValue && propertyValue.length) { + propertyValues.push(new CustomCharacterClass(propertyValue)); + } + } else { + return [null, position]; + } + position = _indexOfNonWhitespaceCharacter(input, position); + if (position >= length || input[position] === PROPERTY_SEPARATOR) { break; - case RuleName.ALLOWED: - newAllowedValues = newAllowedValues.concat(rule.value); - break; + } + if (input[position] === PROPERTY_VALUE_SEPARATOR) { + position = _indexOfNonWhitespaceCharacter(input, position + 1); + if (position >= length) { + return [null, position]; + } + continue; + } + return [null, position]; } + return [propertyValues, position]; } - let newPasswordRules = []; - if (maximumMinLength > 0) { - newPasswordRules.push(new Rule(RuleName.MIN_LENGTH, maximumMinLength)); - } - if (minimumMaxLength !== null) { - newPasswordRules.push(new Rule(RuleName.MAX_LENGTH, minimumMaxLength)); - } - if (minimumMaximumConsecutiveCharacters !== null) { - newPasswordRules.push(new Rule(RuleName.MAX_CONSECUTIVE, minimumMaximumConsecutiveCharacters)); - } - let sortedRequiredRules = requiredRules.sort(function (a, b) { - const namedCharacterClassOrder = [Identifier.LOWER, Identifier.UPPER, Identifier.DIGIT, Identifier.SPECIAL, Identifier.ASCII_PRINTABLE, Identifier.UNICODE]; - let aIsJustOneNamedCharacterClass = a.value.length === 1 && a.value[0] instanceof NamedCharacterClass; - let bIsJustOneNamedCharacterClass = b.value.length === 1 && b.value[0] instanceof NamedCharacterClass; - if (aIsJustOneNamedCharacterClass && !bIsJustOneNamedCharacterClass) { - return -1; - } - if (!aIsJustOneNamedCharacterClass && bIsJustOneNamedCharacterClass) { - return 1; - } - if (aIsJustOneNamedCharacterClass && bIsJustOneNamedCharacterClass) { - let aIndex = namedCharacterClassOrder.indexOf(a.value[0].name); - let bIndex = namedCharacterClassOrder.indexOf(b.value[0].name); - return aIndex - bIndex; - } - return 0; - }); - newPasswordRules = newPasswordRules.concat(sortedRequiredRules); - newAllowedValues = _canonicalizedPropertyValues(newAllowedValues, suppressCopyingRequiredToAllowed); - if (!suppressCopyingRequiredToAllowed && !newAllowedValues.length) { - newAllowedValues = [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)]; - } - if (newAllowedValues.length) { - newPasswordRules.push(new Rule(RuleName.ALLOWED, newAllowedValues)); - } - return newPasswordRules; -} - -},{}],11:[function(require,module,exports){ -module.exports={ - "163.com": { - "password-rules": "minlength: 6; maxlength: 16;" - }, - "1800flowers.com": { - "password-rules": "minlength: 6; required: lower, upper; required: digit;" - }, - "access.service.gov.uk": { - "password-rules": "minlength: 10; required: lower; required: upper; required: digit; required: special;" - }, - "admiral.com": { - "password-rules": "minlength: 8; required: digit; required: [- !\"#$&'()*+,.:;<=>?@[^_`{|}~]]; allowed: lower, upper;" - }, - "ae.com": { - "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit;" - }, - "aetna.com": { - "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 2; required: upper; required: digit; allowed: lower, [-_&#@];" - }, - "airasia.com": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;" - }, - "airfrance.com": { - "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit; allowed: [-!#$&+/?@_];" - }, - "airfrance.us": { - "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit; allowed: [-!#$&+/?@_];" - }, - "ajisushionline.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; allowed: [ !#$%&*?@];" - }, - "aliexpress.com": { - "password-rules": "minlength: 6; maxlength: 20; allowed: lower, upper, digit;" - }, - "alliantcreditunion.com": { - "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower, upper; required: digit; allowed: [!#$*];" - }, - "allianz.com.br": { - "password-rules": "minlength: 4; maxlength: 4;" - }, - "americanexpress.com": { - "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 4; required: lower, upper; required: digit; allowed: [%&_?#=];" - }, - "anatel.gov.br": { - "password-rules": "minlength: 6; maxlength: 15; allowed: lower, upper, digit;" - }, - "ancestry.com": { - "password-rules": "minlength: 8; required: lower, upper; required: digit;" - }, - "angieslist.com": { - "password-rules": "minlength: 6; maxlength: 15;" - }, - "anthem.com": { - "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower, upper; required: digit; allowed: [!$*?@|];" - }, - "app.digio.in": { - "password-rules": "minlength: 8; maxlength: 15;" - }, - "app.parkmobile.io": { - "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit; required: [!@#$%^&];" - }, - "apple.com": { - "password-rules": "minlength: 8; maxlength: 63; required: lower; required: upper; required: digit; allowed: ascii-printable;" - }, - "areariservata.bancaetica.it": { - "password-rules": "minlength: 8; maxlength: 10; required: lower; required: upper; required: digit; required: [!#&*+/=@_];" - }, - "artscyclery.com": { - "password-rules": "minlength: 6; maxlength: 19;" - }, - "astonmartinf1.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: special;" - }, - "auth.readymag.com": { - "password-rules": "minlength: 8; maxlength: 128; required: lower; required: upper; allowed: special;" - }, - "autify.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!\"#$%&'()*+,./:;<=>?@[^_`{|}~]];" - }, - "axa.de": { - "password-rules": "minlength: 8; maxlength: 65; required: lower; required: upper; required: digit; allowed: [-!\"§$%&/()=?;:_+*'#];" - }, - "baidu.com": { - "password-rules": "minlength: 6; maxlength: 14;" - }, - "bancochile.cl": { - "password-rules": "minlength: 8; maxlength: 8; required: lower; required: upper; required: digit;" - }, - "bankofamerica.com": { - "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower; required: upper; required: digit; allowed: [-@#*()+={}/?~;,._];" - }, - "battle.net": { - "password-rules": "minlength: 8; maxlength: 16; required: lower, upper; allowed: digit, special;" - }, - "bcassessment.ca": { - "password-rules": "minlength: 8; maxlength: 14;" - }, - "belkin.com": { - "password-rules": "minlength: 8; required: lower, upper; required: digit; required: [$!@~_,%&];" - }, - "benefitslogin.discoverybenefits.com": { - "password-rules": "minlength: 10; required: upper; required: digit; required: [!#$%&*?@]; allowed: lower;" - }, - "benjerry.com": { - "password-rules": "required: upper; required: upper; required: digit; required: digit; required: special; required: special; allowed: lower;" - }, - "bestbuy.com": { - "password-rules": "minlength: 20; required: lower; required: upper; required: digit; required: special;" - }, - "bhphotovideo.com": { - "password-rules": "maxlength: 15;" - }, - "bilibili.com": { - "password-rules": "maxlength: 16;" - }, - "billerweb.com": { - "password-rules": "minlength: 8; max-consecutive: 2; required: digit; required: upper,lower;" - }, - "biovea.com": { - "password-rules": "maxlength: 19;" - }, - "bitly.com": { - "password-rules": "minlength: 6; required: lower; required: upper; required: digit; required: [`!@#$%^&*()+~{}'\";:<>?]];" - }, - "bloomingdales.com": { - "password-rules": "minlength: 7; maxlength: 16; required: lower, upper; required: digit; required: [`!@#$%^&*()+~{}'\";:<>?]];" - }, - "bluesguitarunleashed.com": { - "password-rules": "allowed: lower, upper, digit, [!$#@];" - }, - "bochk.com": { - "password-rules": "minlength: 8; maxlength: 12; max-consecutive: 3; required: lower; required: upper; required: digit; allowed: [#$%&()*+,.:;<=>?@_];" - }, - "box.com": { - "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: digit;" - }, - "brighthorizons.com": { - "password-rules": "minlength: 8; maxlength: 16;" - }, - "callofduty.com": { - "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 2; required: lower, upper; required: digit;" - }, - "capitalone.com": { - "password-rules": "minlength: 8; maxlength: 32; required: lower, upper; required: digit; allowed: [-_./\\@$*&!#];" - }, - "cardbenefitservices.com": { - "password-rules": "minlength: 7; maxlength: 100; required: lower, upper; required: digit;" - }, - "carrefour.it": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&*?@_];" - }, - "cb2.com": { - "password-rules": "minlength: 7; maxlength: 18; required: lower, upper; required: digit;" - }, - "ccs-grp.com": { - "password-rules": "minlength: 8; maxlength: 16; required: digit; required: upper,lower; allowed: [-!#$%&'+./=?\\^_`{|}~];" - }, - "cecredentialtrust.com": { - "password-rules": "minlength: 12; required: lower; required: upper; required: digit; required: [!#$%&*@^];" - }, - "chase.com": { - "password-rules": "minlength: 8; maxlength: 32; max-consecutive: 2; required: lower, upper; required: digit; required: [!#$%+/=@~];" - }, - "cigna.co.uk": { - "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit;" - }, - "citi.com": { - "password-rules": "minlength: 8; maxlength: 64; max-consecutive: 2; required: digit; required: upper; required: lower; required: [-~`!@#$%^&*()_\\/|];" - }, - "claimlookup.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [@#$%^&+=!];" - }, - "claro.com.br": { - "password-rules": "minlength: 8; required: lower; allowed: upper, digit, [-!@#$%&*_+=<>];" - }, - "classmates.com": { - "password-rules": "minlength: 6; maxlength: 20; allowed: lower, upper, digit, [!@#$%^&*];" - }, - "clegc-gckey.gc.ca": { - "password-rules": "minlength: 8; maxlength: 16; required: lower, upper, digit;" - }, - "clien.net": { - "password-rules": "minlength: 5; required: lower, upper; required: digit;" - }, - "cogmembers.org": { - "password-rules": "minlength: 8; maxlength: 14; required: upper; required: digit, allowed: lower;" - }, - "collectivehealth.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" - }, - "comcastpaymentcenter.com": { - "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 2;required: lower, upper; required: digit;" - }, - "comed.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [-~!@#$%^&*_+=`|(){}[:;\"'<>,.?/\\]];" - }, - "commerzbank.de": { - "password-rules": "minlength: 5; maxlength: 8; required: lower, upper; required: digit;" - }, - "consorsbank.de": { - "password-rules": "minlength: 5; maxlength: 5; required: lower, upper, digit;" - }, - "consorsfinanz.de": { - "password-rules": "minlength: 6; maxlength: 15; allowed: lower, upper, digit, [-.];" - }, - "costco.com": { - "password-rules": "minlength: 8; maxlength: 20; required: lower, upper; allowed: digit, [-!#$%&'()*+/:;=?@[^_`{|}~]];" - }, - "coursera.com": { - "password-rules": "minlength: 8; maxlength: 72;" - }, - "cox.com": { - "password-rules": "minlength: 8; maxlength: 24; required: digit; required: upper,lower; allowed: [!#$%()*@^];" - }, - "crateandbarrel.com": { - "password-rules": "minlength: 9; maxlength: 64; required: lower; required: upper; required: digit; required: [!\"#$%&()*,.:<>?@^_{|}];" - }, - "cvs.com": { - "password-rules": "minlength: 8; maxlength: 25; required: lower, upper; required: digit; allowed: [!@#$%^&*()];" - }, - "dailymail.co.uk": { - "password-rules": "minlength: 5; maxlength: 15;" - }, - "dan.org": { - "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit; required: [!@$%^&*];" - }, - "danawa.com": { - "password-rules": "minlength: 8; maxlength: 21; required: lower, upper; required: digit; required: [!@$%^&*];" - }, - "darty.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" - }, - "dbs.com.hk": { - "password-rules": "minlength: 8; maxlength: 30; required: lower; required: upper; required: digit;" - }, - "decluttr.com": { - "password-rules": "minlength: 8; maxlength: 45; required: lower; required: upper; required: digit;" - }, - "delta.com": { - "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit;" - }, - "deutsche-bank.de": { - "password-rules": "minlength: 5; maxlength: 5; required: lower, upper, digit;" - }, - "devstore.cn": { - "password-rules": "minlength: 6; maxlength: 12;" - }, - "dickssportinggoods.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&*?@^];" - }, - "dkb.de": { - "password-rules": "minlength: 8; maxlength: 38; required: lower, upper; required: digit; allowed: [-äüöÄÜÖß!$%&/()=?+#,.:];" - }, - "dmm.com": { - "password-rules": "minlength: 4; maxlength: 16; required: lower; required: upper; required: digit;" - }, - "dowjones.com": { - "password-rules": "maxlength: 15;" - }, - "ea.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: special;" - }, - "easycoop.com": { - "password-rules": "minlength: 8; required: upper; required: special; allowed: lower, digit;" - }, - "easyjet.com": { - "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: [-];" - }, - "ebrap.org": { - "password-rules": "minlength: 15; required: lower; required: lower; required: upper; required: upper; required: digit; required: digit; required: [-!@#$%^&*()_+|~=`{}[:\";'?,./.]]; required: [-!@#$%^&*()_+|~=`{}[:\";'?,./.]];" - }, - "ecompanystore.com": { - "password-rules": "minlength: 8; maxlength: 16; max-consecutive: 2; required: lower; required: upper; required: digit; required: [#$%*+.=@^_];" - }, - "eddservices.edd.ca.gov": { - "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit; required: [!@#$%^&*()];" - }, - "edistrict.kerala.gov.in": { - "password-rules": "minlength: 5; maxlength: 15; required: lower; required: upper; required: digit; required: [!@#$];" - }, - "empower-retirement.com": { - "password-rules": "minlength: 8; maxlength: 16;" - }, - "epicgames.com": { - "password-rules": "minlength: 7; required: lower; required: upper; required: digit; required: [-!\"#$%&'()*+,./:;<=>?@[^_`{|}~]];" - }, - "epicmix.com": { - "password-rules": "minlength: 8; maxlength: 16;" - }, - "equifax.com": { - "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; required: [!$*+@];" - }, - "essportal.excelityglobal.com": { - "password-rules": "minlength: 6; maxlength: 8; allowed: lower, upper, digit;" - }, - "ettoday.net": { - "password-rules": "minlength: 6; maxlength: 12;" - }, - "examservice.com.tw": { - "password-rules": "minlength: 6; maxlength: 8;" - }, - "expertflyer.com": { - "password-rules": "minlength: 5; maxlength: 16; required: lower, upper; required: digit;" - }, - "extraspace.com": { - "password-rules": "minlength: 8; maxlength: 20; allowed: lower; required: upper, digit, [!#$%&*?@];" - }, - "ezpassva.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;" - }, - "fc2.com": { - "password-rules": "minlength: 8; maxlength: 16;" - }, - "fccaccessonline.com": { - "password-rules": "minlength: 8; maxlength: 14; max-consecutive: 3; required: lower; required: upper; required: digit; required: [!#$%*^_];" - }, - "fedex.com": { - "password-rules": "minlength: 8; max-consecutive: 3; required: lower; required: upper; required: digit; allowed: [-!@#$%^&*_+=`|(){}[:;,.?]];" - }, - "fidelity.com": { - "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: [-!$%+,./:;=?@^_|]; max-consecutive: 2;" - }, - "flysas.com": { - "password-rules": "minlength: 8; maxlength: 14; required: lower; required: upper; required: digit; required: [-~!@#$%^&_+=`|(){}[:\"'<>,.?]];" - }, - "fnac.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" - }, - "fuelrewards.com": { - "password-rules": "minlength: 8; maxlength: 16; allowed: upper,lower,digit,[!#$%@];" - }, - "gamestop.com": { - "password-rules": "minlength: 8; maxlength: 225; required: lower; required: upper; required: digit; required: [!@#$%];" - }, - "garmin.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" - }, - "getflywheel.com": { - "password-rules": "minlength: 7; maxlength: 72;" - }, - "girlscouts.org": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [$#!];" - }, - "gmx.net": { - "password-rules": "minlength: 8; maxlength: 40; allowed: lower, upper, digit, [-<=>~!|()@#{}$%,.?^'&*_+`:;\"[]];" - }, - "gocurb.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [$%&#*?!@^];" - }, - "google.com": { - "password-rules": "minlength: 8; allowed: lower, upper, digit, [-!\"#$%&'()*+,./:;<=>?@[^_{|}~]];" - }, - "guardiananytime.com": { - "password-rules": "minlength: 8; maxlength: 50; max-consecutive: 2; required: lower; required: upper; required: digit, [-~!@#$%^&*_+=`|(){}[:;,.?]];" - }, - "gwl.greatwestlife.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [-!#$%_=+<>];" - }, - "hangseng.com": { - "password-rules": "minlength: 8; maxlength: 30; required: lower; required: upper; required: digit;" - }, - "hawaiianairlines.com": { - "password-rules": "maxlength: 16;" - }, - "hertz-japan.com": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz-kuwait.com": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz-saudi.com": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.at": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.be": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.bh": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.ca": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.ch": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.cn": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.co.ao": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.co.id": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.co.kr": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.co.nz": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.co.th": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.co.uk": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.au": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.bh": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.hk": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.kw": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.mt": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.pl": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.pt": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.sg": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.com.tw": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.cv": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.cz": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.de": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.ee": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.es": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.fi": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.fr": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.hu": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.ie": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.it": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.jo": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.lt": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.nl": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.no": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.nu": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.pl": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.pt": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.qa": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.ru": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.se": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertz.si": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hertzcaribbean.com": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" - }, - "hetzner.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit, special;" - }, - "hilton.com": { - "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit;" - }, - "hkbea.com": { - "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit;" - }, - "hkexpress.com": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: special;" - }, - "hotels.com": { - "password-rules": "minlength: 6; maxlength: 20; required: digit; required: [-~#@$%&!*_?^]; allowed: lower, upper;" - }, - "hotwire.com": { - "password-rules": "minlength: 6; maxlength: 30; allowed: lower, upper, digit, [-~!@#$%^&*_+=`|(){}[:;\"'<>,.?]];" - }, - "hrblock.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [$#%!];" - }, - "hsbc.com.hk": { - "password-rules": "minlength: 6; maxlength: 30; required: lower; required: upper; required: digit; allowed: ['.@_];" - }, - "hsbc.com.my": { - "password-rules": "minlength: 8; maxlength: 30; required: lower, upper; required: digit; allowed: [-!$*.=?@_'];" - }, - "hypovereinsbank.de": { - "password-rules": "minlength: 6; maxlength: 10; required: lower, upper, digit; allowed: [!\"#$%&()*+:;<=>?@[{}~]];" - }, - "hyresbostader.se": { - "password-rules": "minlength: 6; maxlength: 20; required: lower, upper; required: digit;" - }, - "ichunqiu.com": { - "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit;" - }, - "id.sonyentertainmentnetwork.com": { - "password-rules": "minlength: 8; maxlength: 30; required: lower, upper; required: digit; allowed: [-!@#^&*=+;:];" - }, - "identitytheft.gov": { - "password-rules": "allowed: lower, upper, digit, [!#%&*@^];" - }, - "idestination.info": { - "password-rules": "maxlength: 15;" - }, - "impots.gouv.fr": { - "password-rules": "minlength: 12; maxlength: 128; required: lower; required: digit; allowed: [-!#$%&*+/=?^_'.{|}];" - }, - "indochino.com": { - "password-rules": "minlength: 6; maxlength: 15; required: upper; required: digit; allowed: lower, special;" - }, - "inntopia.travel": { - "password-rules": "minlength: 7; maxlength: 19; required: digit; allowed: upper,lower,[-];" - }, - "internationalsos.com": { - "password-rules": "required: lower; required: upper; required: digit; required: [@#$%^&+=_];" - }, - "irctc.co.in": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [!@#$%^&*()+];" - }, - "irs.gov": { - "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit; required: [!#$%&*@];" - }, - "jal.co.jp": { - "password-rules": "minlength: 8; maxlength: 16;" - }, - "japanpost.jp": { - "password-rules": "minlength: 8; maxlength: 16; required: digit; required: upper,lower;" - }, - "jordancu-onlinebanking.org": { - "password-rules": "minlength: 6; maxlength: 32; allowed: upper, lower, digit,[-!\"#$%&'()*+,.:;<=>?@[^_`{|}~]];" - }, - "keldoc.com": { - "password-rules": "minlength: 12; required: lower; required: upper; required: digit; required: [!@#$%^&*];" - }, - "key.harvard.edu": { - "password-rules": "minlength: 10; maxlength: 100; required: lower; required: upper; required: digit; allowed: [-@_#!&$`%*+()./,;~:{}|?>=<^[']];" - }, - "kfc.ca": { - "password-rules": "minlength: 6; maxlength: 15; required: lower; required: upper; required: digit; required: [!@#$%&?*];" - }, - "klm.com": { - "password-rules": "minlength: 8; maxlength: 12;" - }, - "kundenportal.edeka-smart.de": { - "password-rules": "minlength: 8; maxlength: 16; required: digit; required: upper, lower; required: [!\"§$%&#];" - }, - "la-z-boy.com": { - "password-rules": "minlength: 6; maxlength: 15; required: lower, upper; required: digit;" - }, - "labcorp.com": { - "password-rules": "minlength: 8; maxlength: 20; required: upper; required: lower; required: digit; required: [!@#$%^&*];" - }, - "ladwp.com": { - "password-rules": "minlength: 8; maxlength: 20; required: digit; allowed: lower, upper;" - }, - "launtel.net.au": { - "password-rules": "minlength: 8; required: digit; required: digit; allowed: lower, upper;" - }, - "leetchi.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&()*+,./:;<>?@\"_];" - }, - "lepida.it": { - "password-rules": "minlength: 8; maxlength: 16; max-consecutive: 2; required: lower; required: upper; required: digit; required: [-!\"#$%&'()*+,.:;<=>?@[^_`{|}~]];" - }, - "lg.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [-!#$%&'()*+,.:;=?@[^_{|}~]];" - }, - "linearity.io": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: special;" - }, - "live.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; allowed: [-@_#!&$`%*+()./,;~:{}|?>=<^'[]];" - }, - "lloydsbank.co.uk": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: digit; allowed: upper;" - }, - "lowes.com": { - "password-rules": "minlength: 8; maxlength: 128; max-consecutive: 3; required: lower, upper; required: digit;" - }, - "loyalty.accor.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&=@];" - }, - "lsacsso.b2clogin.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit, [-!#$%&*?@^_];" - }, - "lufthansa.com": { - "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit; required: [!#$%&()*+,./:;<>?@\"_];" - }, - "macys.com": { - "password-rules": "minlength: 7; maxlength: 16; allowed: lower, upper, digit, [~!@#$%^&*+`(){}[:;\"'<>?]];" - }, - "mailbox.org": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; allowed: [-!$\"%&/()=*+#.,;:@?{}[]];" - }, - "makemytrip.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [@$!%*#?&];" - }, - "marriott.com": { - "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; allowed: [$!#&@?%=];" - }, - "maybank2u.com.my": { - "password-rules": "minlength: 8; maxlength: 12; max-consecutive: 2; required: lower; required: upper; required: digit; required: [-~!@#$%^&*_+=`|(){}[:;\"'<>,.?];" - }, - "medicare.gov": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [@!$%^*()];" - }, - "member.everbridge.net": { - "password-rules": "minlength: 8; required: lower, upper; required: digit; allowed: [!@#$%^&*()];" - }, - "metlife.com": { - "password-rules": "minlength: 6; maxlength: 20;" - }, - "microsoft.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: special;" - }, - "milogin.michigan.gov": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [@#$!~&];" - }, - "mintmobile.com": { - "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; required: special; allowed: [!#$%&()*+:;=@[^_`{}~]];" - }, - "mlb.com": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;" - }, - "mpv.tickets.com": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;" - }, - "museumofflight.org": { - "password-rules": "minlength: 8; maxlength: 15;" - }, - "my.konami.net": { - "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit;" - }, - "myaccess.dmdc.osd.mil": { - "password-rules": "minlength: 9; maxlength: 20; required: lower; required: upper; required: digit; allowed: [-@_#!&$`%*+()./,;~:{}|?>=<^'[]];" - }, - "mygoodtogo.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower, upper, digit;" - }, - "myhealthrecord.com": { - "password-rules": "minlength: 8; maxlength: 20; allowed: lower, upper, digit, [_.!$*=];" - }, - "mysavings.breadfinancial.com": { - "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit; required: [+_%@!$*~];" - }, - "mysedgwick.com": { - "password-rules": "minlength: 8; maxlength: 16; allowed: lower; required: upper; required: digit; required: [@#%^&+=!]; allowed: [-~_$.,;]" - }, - "mysubaru.com": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; allowed: [!#$%()*+,./:;=?@\\^`~];" - }, - "naver.com": { - "password-rules": "minlength: 6; maxlength: 16;" - }, - "nelnet.net": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit, [!@#$&*];" - }, - "netflix.com": { - "password-rules": "minlength: 4; maxlength: 60; required: lower, upper, digit; allowed: special;" - }, - "netgear.com": { - "password-rules": "minlength: 6; maxlength: 128; allowed: lower, upper, digit, [!@#$%^&*()];" - }, - "nowinstock.net": { - "password-rules": "minlength: 6; maxlength: 20; allowed: lower, upper, digit;" - }, - "order.wendys.com": { - "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; allowed: [!#$%&()*+/=?^_{}];" - }, - "ototoy.jp": { - "password-rules": "minlength: 8; allowed: upper,lower,digit,[- .=_];" - }, - "packageconciergeadmin.com": { - "password-rules": "minlength: 4; maxlength: 4; allowed: digit;" - }, - "paypal.com": { - "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower, upper; required: digit, [!@#$%^&*()];" - }, - "payvgm.youraccountadvantage.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: special;" - }, - "pilotflyingj.com": { - "password-rules": "minlength: 7; required: digit; allowed: lower, upper;" - }, - "pixnet.cc": { - "password-rules": "minlength: 4; maxlength: 16; allowed: lower, upper;" - }, - "planetary.org": { - "password-rules": "minlength: 5; maxlength: 20; required: lower; required: upper; required: digit; allowed: ascii-printable;" - }, - "plazapremiumlounge.com": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; allowed: [!#$%&*,@^];" - }, - "portal.edd.ca.gov": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&()*@^];" - }, - "portals.emblemhealth.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&'()*+,./:;<>?@\\^_`{|}~[]];" - }, - "portlandgeneral.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [!#$%&*?@];" - }, - "poste.it": { - "password-rules": "minlength: 8; maxlength: 16; max-consecutive: 2; required: lower; required: upper; required: digit; required: special;" - }, - "posteo.de": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit, [-~!#$%&_+=|(){}[:;\"’<>,.? ]];" - }, - "powells.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [\"!@#$%^&*(){}[]];" - }, - "preferredhotels.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&()*+@^_];" - }, - "premier.ticketek.com.au": { - "password-rules": "minlength: 6; maxlength: 16;" - }, - "premierinn.com": { - "password-rules": "minlength: 8; required: upper; required: digit; allowed: lower;" - }, - "prepaid.bankofamerica.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [!@#$%^&*()+~{}'\";:<>?];" - }, - "prestocard.ca": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit,[!\"#$%&'()*+,<>?@];" - }, - "propelfuels.com": { - "password-rules": "minlength: 6; maxlength: 16;" - }, - "qdosstatusreview.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&@^];" - }, - "questdiagnostics.com": { - "password-rules": "minlength: 8; maxlength: 30; required: upper, lower; required: digit, [!#$%&()*+<>?@^_~];" - }, - "rejsekort.dk": { - "password-rules": "minlength: 7; maxlength: 15; required: lower; required: upper; required: digit;" - }, - "renaud-bray.com": { - "password-rules": "minlength: 8; maxlength: 38; allowed: upper,lower,digit;" - }, - "ring.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!@#$%^&*<>?];" - }, - "riteaid.com": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;" - }, - "robinhood.com": { - "password-rules": "minlength: 10;" - }, - "rogers.com": { - "password-rules": "minlength: 8; required: lower, upper; required: digit; required: [!@#$];" - }, - "ruc.dk": { - "password-rules": "minlength: 6; maxlength: 8; required: lower, upper; required: [-!#%&(){}*+;%/<=>?_];" - }, - "runescape.com": { - "password-rules": "minlength: 5; maxlength: 20; required: lower; required: upper; required: digit;" - }, - "ruten.com.tw": { - "password-rules": "minlength: 6; maxlength: 15; required: lower, upper;" - }, - "salslimo.com": { - "password-rules": "minlength: 8; maxlength: 50; required: upper; required: lower; required: digit; required: [!@#$&*];" - }, - "santahelenasaude.com.br": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [-!@#$%&*_+=<>];" - }, - "santander.de": { - "password-rules": "minlength: 8; maxlength: 12; required: lower, upper; required: digit; allowed: [-!#$%&'()*,.:;=?^{}];" - }, - "sbisec.co.jp": { - "password-rules": "minlength: 10; maxlength: 20; allowed: upper,lower,digit;" - }, - "secure-arborfcu.org": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [!#$%&'()+,.:?@[_`~]];" - }, - "secure.orclinic.com": { - "password-rules": "minlength: 6; maxlength: 15; required: lower; required: digit; allowed: ascii-printable;" - }, - "secure.snnow.ca": { - "password-rules": "minlength: 7; maxlength: 16; required: digit; allowed: lower, upper;" - }, - "sephora.com": { - "password-rules": "minlength: 6; maxlength: 12;" - }, - "serviziconsolari.esteri.it": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;" - }, - "servizioelettriconazionale.it": { - "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; required: [!#$%&*?@^_~];" - }, - "sfwater.org": { - "password-rules": "minlength: 10; maxlength: 30; required: digit; allowed: lower, upper, [!@#$%*()_+^}{:;?.];" - }, - "signin.ea.com": { - "password-rules": "minlength: 8; maxlength: 64; required: lower, upper; required: digit; allowed: [-!@#^&*=+;:];" - }, - "southwest.com": { - "password-rules": "minlength: 8; maxlength: 16; required: upper; required: digit; allowed: lower, [!@#$%^*(),.;:/\\];" - }, - "speedway.com": { - "password-rules": "minlength: 4; maxlength: 8; required: digit;" - }, - "spirit.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [!@#$%^&*()];" - }, - "splunk.com": { - "password-rules": "minlength: 8; maxlength: 64; required: lower; required: upper; required: digit; required: [-!@#$%&*_+=<>];" - }, - "ssa.gov": { - "password-rules": "required: lower; required: upper; required: digit; required: [~!@#$%^&*];" - }, - "store.nintendo.co.uk": { - "password-rules": "minlength: 8; maxlength: 20;" - }, - "store.nvidia.com": { - "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit; required: [-!@#$%^*~:;&><[{}|_+=?]];" - }, - "store.steampowered.com": { - "password-rules": "minlength: 6; required: lower; required: upper; required: digit; allowed: [~!@#$%^&*];" - }, - "successfactors.eu": { - "password-rules": "minlength: 8; maxlength: 18; required: lower; required: upper; required: digit,[-!\"#$%&'()*+,.:;<=>?@[^_`{|}~]];" - }, - "sulamericaseguros.com.br": { - "password-rules": "minlength: 6; maxlength: 6;" - }, - "sunlife.com": { - "password-rules": "minlength: 8; maxlength: 10; required: digit; required: lower, upper;" - }, - "t-mobile.net": { - "password-rules": "minlength: 8; maxlength: 16;" - }, - "target.com": { - "password-rules": "minlength: 8; maxlength: 20; required: lower, upper; required: digit, [-!\"#$%&'()*+,./:;=?@[\\^_`{|}~];" - }, - "tdscpc.gov.in": { - "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [ &',;\"];" - }, - "telekom-dienste.de": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [#$%&()*+,./<=>?@_{|}~];" - }, - "thameswater.co.uk": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;" - }, - "tix.soundrink.com": { - "password-rules": "minlength: 6; maxlength: 16;" - }, - "training.confluent.io": { - "password-rules": "minlength: 6; maxlength: 16; required: lower; required: upper; required: digit; allowed: [!#$%*@^_~];" - }, - "treasurer.mo.gov": { - "password-rules": "minlength: 8; maxlength: 26; required: lower; required: upper; required: digit; required: [!#$&];" - }, - "twitch.tv": { - "password-rules": "minlength: 8; maxlength: 71;" - }, - "twitter.com": { - "password-rules": "minlength: 8;" - }, - "ubisoft.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [-]; required: [!@#$%^&*()+];" - }, - "udel.edu": { - "password-rules": "minlength: 12; maxlength: 30; required: lower; required: upper; required: digit; required: [!@#$%^&*()+];" - }, - "user.ornl.gov": { - "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower, upper; required: digit; allowed: [!#$%./_];" - }, - "usps.com": { - "password-rules": "minlength: 8; maxlength: 50; max-consecutive: 2; required: lower; required: upper; required: digit; allowed: [-!\"#&'()+,./?@];" - }, - "vanguard.com": { - "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: digit;" - }, - "vanguardinvestor.co.uk": { - "password-rules": "minlength: 8; maxlength: 50; required: lower; required: upper; required: digit; required: digit;" - }, - "ventrachicago.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit, [!@#$%^];" - }, - "verizonwireless.com": { - "password-rules": "minlength: 8; maxlength: 20; required: lower, upper; required: digit; allowed: unicode;" - }, - "vetsfirstchoice.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; allowed: [?!@$%^+=&];" - }, - "vince.com": { - "password-rules": "minlength: 8; required: digit; required: lower; required: upper; required: [$%/(){}=?!.,_*|+~#[]];" - }, - "virginmobile.ca": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$@];" - }, - "visa.com": { - "password-rules": "minlength: 6; maxlength: 32;" - }, - "visabenefits-auth.axa-assistance.us": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!\"#$%&()*,.:<>?@^{|}];" - }, - "vivo.com.br": { - "password-rules": "maxlength: 6; max-consecutive: 3; allowed: digit;" - }, - "volaris.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;" - }, - "wa.aaa.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: ascii-printable;" - }, - "walkhighlands.co.uk": { - "password-rules": "minlength: 9; maxlength: 15; required: lower; required: upper; required: digit; allowed: special;" - }, - "walmart.com": { - "password-rules": "allowed: lower, upper, digit, [-(~!@#$%^&*_+=`|(){}[:;\"'<>,.?]];" - }, - "waze.com": { - "password-rules": "minlength: 8; maxlength: 64; required: lower, upper, digit;" - }, - "wccls.org": { - "password-rules": "minlength: 4; maxlength: 16; allowed: lower, upper, digit;" - }, - "web.de": { - "password-rules": "minlength: 8; maxlength: 40; allowed: lower, upper, digit, [-<=>~!|()@#{}$%,.?^'&*_+`:;\"[]];" - }, - "wegmans.com": { - "password-rules": "minlength: 8; required: digit; required: upper,lower; required: [!#$%&*+=?@^];" - }, - "weibo.com": { - "password-rules": "minlength: 6; maxlength: 16;" - }, - "wellsfargo.com": { - "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit;" - }, - "wmata.com": { - "password-rules": "minlength: 8; required: lower, upper; required: digit; required: digit; required: [-!@#$%^&*~/\"()_=+\\|,.?[]];" - }, - "worldstrides.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [-!#$%&*+=?@^_~];" - }, - "wsj.com": { - "password-rules": "minlength: 5; maxlength: 15; required: digit; allowed: lower, upper, [-~!@#$^*_=`|(){}[:;\"'<>,.?]];" - }, - "xfinity.com": { - "password-rules": "minlength: 8; maxlength: 16; required: lower, upper; required: digit;" - }, - "xvoucher.com": { - "password-rules": "minlength: 11; required: upper; required: digit; required: [!@#$%&_];" - }, - "yatra.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&'()+,.:?@[_`~]];" - }, - "zara.com": { - "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" - }, - "zdf.de": { - "password-rules": "minlength: 8; required: upper; required: digit; allowed: lower, special;" - }, - "zoom.us": { - "password-rules": "minlength: 8; maxlength: 32; max-consecutive: 6; required: lower; required: upper; required: digit;" - } -} -},{}],12:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.createDevice = createDevice; -var _config = require("./config.js"); -var _AndroidInterface = require("./DeviceInterface/AndroidInterface.js"); -var _ExtensionInterface = require("./DeviceInterface/ExtensionInterface.js"); -var _AppleDeviceInterface = require("./DeviceInterface/AppleDeviceInterface.js"); -var _AppleOverlayDeviceInterface = require("./DeviceInterface/AppleOverlayDeviceInterface.js"); -var _transports = require("./deviceApiCalls/transports/transports.js"); -var _index = require("../packages/device-api/index.js"); -var _Settings = require("./Settings.js"); -var _WindowsInterface = require("./DeviceInterface/WindowsInterface.js"); -var _WindowsOverlayDeviceInterface = require("./DeviceInterface/WindowsOverlayDeviceInterface.js"); -function createDevice() { - const globalConfig = (0, _config.createGlobalConfig)(); - const transport = (0, _transports.createTransport)(globalConfig); - - /** - * A wrapper around transports to assist in debugging/integrations - * @type {import("../packages/device-api").DeviceApiTransport} - */ - const loggingTransport = { - async send(deviceApiCall) { - console.log('[->outgoing]', 'id:', deviceApiCall.method, deviceApiCall.params || null); - const result = await transport.send(deviceApiCall); - console.log('[<-incoming]', 'id:', deviceApiCall.method, result || null); - return result; + function _parsePasswordRule(input, position) { + console.assert(position >= 0); + console.assert(position < input.length); + console.assert(_isIdentifierCharacter(input[position])); + let length = input.length; + var mayBeIdentifierStartPosition = position; + var [identifier, position] = _parseIdentifier(input, position); + if (!Object.values(RuleName).includes(identifier)) { + return [null, mayBeIdentifierStartPosition, void 0]; } - }; - - // Create the DeviceAPI + Setting - let deviceApi = new _index.DeviceApi(globalConfig.isDDGTestMode ? loggingTransport : transport); - const settings = new _Settings.Settings(globalConfig, deviceApi); - if (globalConfig.isWindows) { - if (globalConfig.isTopFrame) { - return new _WindowsOverlayDeviceInterface.WindowsOverlayDeviceInterface(globalConfig, deviceApi, settings); - } - return new _WindowsInterface.WindowsInterface(globalConfig, deviceApi, settings); - } - if (globalConfig.isDDGApp) { - if (globalConfig.isAndroid) { - return new _AndroidInterface.AndroidInterface(globalConfig, deviceApi, settings); + if (position >= length) { + return [null, position, void 0]; + } + if (input[position] !== PROPERTY_VALUE_START_SENTINEL) { + return [null, position, void 0]; + } + let property = { name: identifier, value: null }; + position = _indexOfNonWhitespaceCharacter(input, position + 1); + if (position >= length || input[position] === PROPERTY_SEPARATOR) { + return [new Rule(property.name, property.value), position, void 0]; } - if (globalConfig.isTopFrame) { - return new _AppleOverlayDeviceInterface.AppleOverlayDeviceInterface(globalConfig, deviceApi, settings); + switch (identifier) { + case RuleName.ALLOWED: + case RuleName.REQUIRED: { + var [propertyValue, position] = _parsePasswordRequiredOrAllowedPropertyValue(input, position); + if (propertyValue) { + property.value = propertyValue; + } + return [new Rule(property.name, property.value), position, void 0]; + } + case RuleName.MAX_CONSECUTIVE: { + var [propertyValue, position] = _parseMaxConsecutivePropertyValue(input, position); + if (propertyValue) { + property.value = propertyValue; + } + return [new Rule(property.name, property.value), position, void 0]; + } + case RuleName.MIN_LENGTH: + case RuleName.MAX_LENGTH: { + var [propertyValue, position] = _parseMinLengthMaxLengthPropertyValue(input, position); + if (propertyValue) { + property.value = propertyValue; + } + return [new Rule(property.name, property.value), position, void 0]; + } } - return new _AppleDeviceInterface.AppleDeviceInterface(globalConfig, deviceApi, settings); + console.assert(false, SHOULD_NOT_BE_REACHED); + return [null, -1, void 0]; } - globalConfig.isExtension = true; - return new _ExtensionInterface.ExtensionInterface(globalConfig, deviceApi, settings); -} - -},{"../packages/device-api/index.js":2,"./DeviceInterface/AndroidInterface.js":13,"./DeviceInterface/AppleDeviceInterface.js":14,"./DeviceInterface/AppleOverlayDeviceInterface.js":15,"./DeviceInterface/ExtensionInterface.js":16,"./DeviceInterface/WindowsInterface.js":18,"./DeviceInterface/WindowsOverlayDeviceInterface.js":19,"./Settings.js":40,"./config.js":54,"./deviceApiCalls/transports/transports.js":62}],13:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.AndroidInterface = void 0; -var _InterfacePrototype = _interopRequireDefault(require("./InterfacePrototype.js")); -var _autofillUtils = require("../autofill-utils.js"); -var _NativeUIController = require("../UI/controllers/NativeUIController.js"); -var _InContextSignup = require("../InContextSignup.js"); -var _deviceApiCalls = require("../deviceApiCalls/__generated__/deviceApiCalls.js"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -class AndroidInterface extends _InterfacePrototype.default { - inContextSignup = new _InContextSignup.InContextSignup(this); - - /** - * @returns {Promise} - */ - async getAlias() { - const { - alias - } = await (0, _autofillUtils.sendAndWaitForAnswer)(async () => { - if (this.inContextSignup.isAvailable()) { - const { - isSignedIn - } = await this.deviceApi.request(new _deviceApiCalls.ShowInContextEmailProtectionSignupPromptCall(null)); - // On Android we can't get the input type data again without - // refreshing the page, so instead we can mutate it now that we - // know the user has Email Protection available. - if (this.globalConfig.availableInputTypes) { - this.globalConfig.availableInputTypes.email = isSignedIn; - } - this.updateForStateChange(); - this.onFinishedAutofill(); - } - return window.EmailInterface.showTooltip(); - }, 'getAliasResponse'); - return alias; + function _parseMinLengthMaxLengthPropertyValue(input, position) { + return _parseInteger(input, position); } - - /** - * @override - */ - createUIController() { - return new _NativeUIController.NativeUIController(); + function _parseMaxConsecutivePropertyValue(input, position) { + return _parseInteger(input, position); } - - /** - * @deprecated use `this.settings.availableInputTypes.email` in the future - * @returns {boolean} - */ - isDeviceSignedIn() { - // on DDG domains, always check via `window.EmailInterface.isSignedIn()` - if (this.globalConfig.isDDGDomain) { - return window.EmailInterface.isSignedIn() === 'true'; + function _parseInteger(input, position) { + console.assert(position >= 0); + console.assert(position < input.length); + if (!_isASCIIDigit(input[position])) { + return [null, position]; } - - // on non-DDG domains, where `availableInputTypes.email` is present, use it - if (typeof this.globalConfig.availableInputTypes?.email === 'boolean') { - return this.globalConfig.availableInputTypes.email; + let length = input.length; + let result = 0; + do { + result = 10 * result + parseInt(input[position], 10); + ++position; + } while (position < length && input[position] !== PROPERTY_SEPARATOR && _isASCIIDigit(input[position])); + if (position >= length || input[position] === PROPERTY_SEPARATOR) { + return [result, position]; } - - // ...on other domains we assume true because the script wouldn't exist otherwise - return true; - } - async setupAutofill() { - await this.inContextSignup.init(); + return [null, position]; } - - /** - * Used by the email web app - * Settings page displays data of the logged in user data - */ - getUserData() { - let userData = null; - try { - userData = JSON.parse(window.EmailInterface.getUserData()); - } catch (e) { - if (this.globalConfig.isDDGTestMode) { - console.error(e); + function _parsePasswordRulesInternal(input) { + let parsedProperties = []; + let length = input.length; + var position = _indexOfNonWhitespaceCharacter(input); + while (position < length) { + if (!_isIdentifierCharacter(input[position])) { + return [parsedProperties, void 0]; } - } - return Promise.resolve(userData); - } - - /** - * Used by the email web app - * Device capabilities determine which functionality is available to the user - */ - getEmailProtectionCapabilities() { - let deviceCapabilities = null; - try { - deviceCapabilities = JSON.parse(window.EmailInterface.getDeviceCapabilities()); - } catch (e) { - if (this.globalConfig.isDDGTestMode) { - console.error(e); + var [parsedProperty, position, message] = _parsePasswordRule(input, position); + if (parsedProperty && parsedProperty.value) { + parsedProperties.push(parsedProperty); + } + position = _indexOfNonWhitespaceCharacter(input, position); + if (position >= length) { + break; } + if (input[position] === PROPERTY_SEPARATOR) { + position = _indexOfNonWhitespaceCharacter(input, position + 1); + if (position >= length) { + return [parsedProperties, void 0]; + } + continue; + } + return [null, message || "Failed to find start of next property: " + input.substr(position)]; } - return Promise.resolve(deviceCapabilities); - } - storeUserData(_ref) { - let { - addUserData: { - token, - userName, - cohort - } - } = _ref; - return window.EmailInterface.storeCredentials(token, userName, cohort); + return [parsedProperties, void 0]; } - - /** - * Used by the email web app - * Provides functionality to log the user out - */ - removeUserData() { - try { - return window.EmailInterface.removeCredentials(); - } catch (e) { - if (this.globalConfig.isDDGTestMode) { - console.error(e); + function parsePasswordRules(input, formatRulesForMinifiedVersion) { + let [passwordRules, maybeMessage] = _parsePasswordRulesInternal(input); + if (!passwordRules) { + throw new ParserError(maybeMessage); + } + if (passwordRules.length === 0) { + throw new ParserError("No valid rules were provided"); + } + let suppressCopyingRequiredToAllowed = formatRulesForMinifiedVersion; + let requiredRules = []; + let newAllowedValues = []; + let minimumMaximumConsecutiveCharacters = null; + let maximumMinLength = 0; + let minimumMaxLength = null; + for (let rule of passwordRules) { + switch (rule.name) { + case RuleName.MAX_CONSECUTIVE: + minimumMaximumConsecutiveCharacters = minimumMaximumConsecutiveCharacters ? Math.min(rule.value, minimumMaximumConsecutiveCharacters) : rule.value; + break; + case RuleName.MIN_LENGTH: + maximumMinLength = Math.max(rule.value, maximumMinLength); + break; + case RuleName.MAX_LENGTH: + minimumMaxLength = minimumMaxLength ? Math.min(rule.value, minimumMaxLength) : rule.value; + break; + case RuleName.REQUIRED: + rule.value = _canonicalizedPropertyValues(rule.value, formatRulesForMinifiedVersion); + requiredRules.push(rule); + if (!suppressCopyingRequiredToAllowed) { + newAllowedValues = newAllowedValues.concat(rule.value); + } + break; + case RuleName.ALLOWED: + newAllowedValues = newAllowedValues.concat(rule.value); + break; } } - } - - /** - * Used by the email web app - * Provides functionality to close the window after in-context sign-up or sign-in - */ - closeEmailProtection() { - this.deviceApi.request(new _deviceApiCalls.CloseEmailProtectionTabCall(null)); - } - addLogoutListener(handler) { - // Only deal with logging out if we're in the email web app - if (!this.globalConfig.isDDGDomain) return; - window.addEventListener('message', e => { - if (this.globalConfig.isDDGDomain && e.data.emailProtectionSignedOut) { - handler(); + let newPasswordRules = []; + if (maximumMinLength > 0) { + newPasswordRules.push(new Rule(RuleName.MIN_LENGTH, maximumMinLength)); + } + if (minimumMaxLength !== null) { + newPasswordRules.push(new Rule(RuleName.MAX_LENGTH, minimumMaxLength)); + } + if (minimumMaximumConsecutiveCharacters !== null) { + newPasswordRules.push(new Rule(RuleName.MAX_CONSECUTIVE, minimumMaximumConsecutiveCharacters)); + } + let sortedRequiredRules = requiredRules.sort(function(a, b) { + const namedCharacterClassOrder = [Identifier.LOWER, Identifier.UPPER, Identifier.DIGIT, Identifier.SPECIAL, Identifier.ASCII_PRINTABLE, Identifier.UNICODE]; + let aIsJustOneNamedCharacterClass = a.value.length === 1 && a.value[0] instanceof NamedCharacterClass; + let bIsJustOneNamedCharacterClass = b.value.length === 1 && b.value[0] instanceof NamedCharacterClass; + if (aIsJustOneNamedCharacterClass && !bIsJustOneNamedCharacterClass) { + return -1; } + if (!aIsJustOneNamedCharacterClass && bIsJustOneNamedCharacterClass) { + return 1; + } + if (aIsJustOneNamedCharacterClass && bIsJustOneNamedCharacterClass) { + let aIndex = namedCharacterClassOrder.indexOf(a.value[0].name); + let bIndex = namedCharacterClassOrder.indexOf(b.value[0].name); + return aIndex - bIndex; + } + return 0; }); - } - - /** Noop */ - firePixel(_pixelParam) {} -} -exports.AndroidInterface = AndroidInterface; - -},{"../InContextSignup.js":34,"../UI/controllers/NativeUIController.js":47,"../autofill-utils.js":52,"../deviceApiCalls/__generated__/deviceApiCalls.js":56,"./InterfacePrototype.js":17}],14:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.AppleDeviceInterface = void 0; -var _InterfacePrototype = _interopRequireDefault(require("./InterfacePrototype.js")); -var _autofillUtils = require("../autofill-utils.js"); -var _HTMLTooltip = require("../UI/HTMLTooltip.js"); -var _HTMLTooltipUIController = require("../UI/controllers/HTMLTooltipUIController.js"); -var _OverlayUIController = require("../UI/controllers/OverlayUIController.js"); -var _index = require("../../packages/device-api/index.js"); -var _additionalDeviceApiCalls = require("../deviceApiCalls/additionalDeviceApiCalls.js"); -var _NativeUIController = require("../UI/controllers/NativeUIController.js"); -var _deviceApiCalls = require("../deviceApiCalls/__generated__/deviceApiCalls.js"); -var _matching = require("../Form/matching.js"); -var _InContextSignup = require("../InContextSignup.js"); -var _ThirdPartyProvider = require("../ThirdPartyProvider.js"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -/** - * @typedef {import('../deviceApiCalls/__generated__/validators-ts').GetAutofillDataRequest} GetAutofillDataRequest - */ - -class AppleDeviceInterface extends _InterfacePrototype.default { - inContextSignup = new _InContextSignup.InContextSignup(this); - - /** @override */ - initialSetupDelayMs = 300; - thirdPartyProvider = new _ThirdPartyProvider.ThirdPartyProvider(this); + newPasswordRules = newPasswordRules.concat(sortedRequiredRules); + newAllowedValues = _canonicalizedPropertyValues(newAllowedValues, suppressCopyingRequiredToAllowed); + if (!suppressCopyingRequiredToAllowed && !newAllowedValues.length) { + newAllowedValues = [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)]; + } + if (newAllowedValues.length) { + newPasswordRules.push(new Rule(RuleName.ALLOWED, newAllowedValues)); + } + return newPasswordRules; + } + + // packages/password/lib/constants.js + var DEFAULT_MIN_LENGTH = 20; + var DEFAULT_MAX_LENGTH = 30; + var DEFAULT_REQUIRED_CHARS = "-!?$&#%"; + var DEFAULT_UNAMBIGUOUS_CHARS = "abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ0123456789"; + var DEFAULT_PASSWORD_RULES = [ + `minlength: ${DEFAULT_MIN_LENGTH}`, + `maxlength: ${DEFAULT_MAX_LENGTH}`, + `required: [${DEFAULT_REQUIRED_CHARS}]`, + `allowed: [${DEFAULT_UNAMBIGUOUS_CHARS}]` + ].join("; "); + var constants2 = { + DEFAULT_MIN_LENGTH, + DEFAULT_MAX_LENGTH, + DEFAULT_PASSWORD_RULES, + DEFAULT_REQUIRED_CHARS, + DEFAULT_UNAMBIGUOUS_CHARS + }; - /** - * The default functionality of this class is to operate as an 'overlay controller' - - * which means it's purpose is to message the native layer about when to open/close the overlay. - * - * There is an additional use-case though, when running on older macOS versions, we just display the - * HTMLTooltip in-page (like the extension does). This is why the `!this.globalConfig.supportsTopFrame` - * check exists below - if we know we don't support the overlay, we fall back to in-page. - * - * @override - * @returns {import("../UI/controllers/UIController.js").UIController} - */ - createUIController() { - if (this.globalConfig.userPreferences?.platform?.name === 'ios') { - return new _NativeUIController.NativeUIController(); + // packages/password/lib/apple.password.js + var defaults = Object.freeze({ + SCAN_SET_ORDER: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-~!@#$%^&*_+=`|(){}[:;\\\"'<>,.?/ ]", + defaultUnambiguousCharacters: "abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ0123456789", + defaultPasswordLength: constants2.DEFAULT_MIN_LENGTH, + defaultPasswordRules: constants2.DEFAULT_PASSWORD_RULES, + defaultRequiredCharacterSets: ["abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "0123456789"], + /** + * @type {typeof window.crypto.getRandomValues | null} + */ + getRandomValues: null + }); + var safeGlobals = {}; + if (typeof window !== "undefined") { + safeGlobals.getRandomValues = window.crypto.getRandomValues.bind(window.crypto); + } + var Password = class { + /** + * @param {Partial} [options] + */ + constructor(options = {}) { + this.options = { + ...defaults, + ...options + }; + return this; } - if (!this.globalConfig.supportsTopFrame) { - const options = { - ..._HTMLTooltip.defaultOptions, - testMode: this.isTestMode() + static get defaults() { + return defaults; + } + /** + * Generates a password from the given input. + * + * Note: This method will throw an error if parsing fails - use with caution + * + * @example + * + * ```javascript + * const password = Password.generateOrThrow("minlength: 20") + * ``` + * @public + * @param {string} inputString + * @param {Partial} [options] + * @throws {ParserError|Error} + * @returns {string} + */ + static generateOrThrow(inputString, options = {}) { + return new Password(options).parse(inputString).generate(); + } + /** + * Generates a password using the default ruleset. + * + * @example + * + * ```javascript + * const password = Password.generateDefault() + * ``` + * + * @public + * @param {Partial} [options] + * @returns {string} + */ + static generateDefault(options = {}) { + return new Password(options).parse(Password.defaults.defaultPasswordRules).generate(); + } + /** + * Convert a ruleset into it's internally-used component pieces. + * + * @param {string} inputString + * @throws {parser.ParserError|Error} + * @returns {{ + * requirements: Requirements; + * parameters: PasswordParameters; + * rules: parser.Rule[], + * get entropy(): number; + * generate: () => string; + * }} + */ + parse(inputString) { + const rules = parsePasswordRules(inputString); + const requirements = this._requirementsFromRules(rules); + if (!requirements) + throw new Error("could not generate requirements for " + JSON.stringify(inputString)); + const parameters = this._passwordGenerationParametersDictionary(requirements); + return { + requirements, + parameters, + rules, + get entropy() { + return Math.log2(parameters.PasswordAllowedCharacters.length ** parameters.NumberOfRequiredRandomCharacters); + }, + generate: () => { + const password = this._generatedPasswordMatchingRequirements(requirements, parameters); + if (password === "") + throw new Error("unreachable"); + return password; + } }; - return new _HTMLTooltipUIController.HTMLTooltipUIController({ - device: this, - tooltipKind: 'modern' - }, options); } - /** - * If we get here, we're just a controller for an overlay + * Given an array of `Rule's`, convert into `Requirements` + * + * @param {parser.Rule[]} passwordRules + * @returns {Requirements | null} */ - return new _OverlayUIController.OverlayUIController({ - remove: async () => this._closeAutofillParent(), - show: async details => this._show(details) - }); - } - - /** - * For now, this could be running - * 1) on iOS - * 2) on macOS + Overlay - * 3) on macOS + in-page HTMLTooltip - * - * @override - * @returns {Promise} - */ - async setupAutofill() { - if (!this.globalConfig.supportsTopFrame) { - await this._getAutofillInitData(); + _requirementsFromRules(passwordRules) { + const requirements = {}; + for (let rule of passwordRules) { + if (rule.name === RuleName.ALLOWED) { + console.assert(!("PasswordAllowedCharacters" in requirements)); + const chars = this._charactersFromCharactersClasses(rule.value); + const scanSet = this._canonicalizedScanSetFromCharacters(chars); + if (scanSet) { + requirements.PasswordAllowedCharacters = scanSet; + } + } else if (rule.name === RuleName.MAX_CONSECUTIVE) { + console.assert(!("PasswordRepeatedCharacterLimit" in requirements)); + requirements.PasswordRepeatedCharacterLimit = rule.value; + } else if (rule.name === RuleName.REQUIRED) { + let requiredCharacters = requirements.PasswordRequiredCharacters; + if (!requiredCharacters) { + requiredCharacters = requirements.PasswordRequiredCharacters = []; + } + requiredCharacters.push(this._canonicalizedScanSetFromCharacters(this._charactersFromCharactersClasses(rule.value))); + } else if (rule.name === RuleName.MIN_LENGTH) { + requirements.PasswordMinLength = rule.value; + } else if (rule.name === RuleName.MAX_LENGTH) { + requirements.PasswordMaxLength = rule.value; + } + } + if (requirements.PasswordAllowedCharacters === this.options.SCAN_SET_ORDER && !requirements.PasswordRequiredCharacters) { + delete requirements.PasswordAllowedCharacters; + } + if (requirements.PasswordRequiredCharacters && requirements.PasswordRequiredCharacters.length === 1 && requirements.PasswordRequiredCharacters[0] === this.options.SCAN_SET_ORDER) { + delete requirements.PasswordRequiredCharacters; + } + return Object.keys(requirements).length ? requirements : null; } - await this.inContextSignup.init(); - const signedIn = await this._checkDeviceSignedIn(); - if (signedIn) { - if (this.globalConfig.isApp) { - await this.getAddresses(); + /** + * @param {number} range + * @returns {number} + */ + _randomNumberWithUniformDistribution(range) { + const getRandomValues = this.options.getRandomValues || safeGlobals.getRandomValues; + const max = Math.floor(2 ** 32 / range) * range; + let x; + do { + x = getRandomValues(new Uint32Array(1))[0]; + } while (x >= max); + return x % range; + } + /** + * @param {number} numberOfRequiredRandomCharacters + * @param {string} allowedCharacters + */ + _classicPassword(numberOfRequiredRandomCharacters, allowedCharacters) { + const length = allowedCharacters.length; + const randomCharArray = Array(numberOfRequiredRandomCharacters); + for (let i = 0; i < numberOfRequiredRandomCharacters; i++) { + const index = this._randomNumberWithUniformDistribution(length); + randomCharArray[i] = allowedCharacters[index]; } + return randomCharArray.join(""); } - } - - /** - * Used by the email web app - * Settings page displays data of the logged in user data - */ - getUserData() { - return this.deviceApi.request((0, _index.createRequest)('emailHandlerGetUserData')); - } - - /** - * Used by the email web app - * Device capabilities determine which functionality is available to the user - */ - getEmailProtectionCapabilities() { - return this.deviceApi.request((0, _index.createRequest)('emailHandlerGetCapabilities')); - } - - /** - */ - async getSelectedCredentials() { - return this.deviceApi.request((0, _index.createRequest)('getSelectedCredentials')); - } - - /** - * The data format provided here for `parentArgs` matches Window now. - * @param {GetAutofillDataRequest} parentArgs - */ - async _showAutofillParent(parentArgs) { - const applePayload = { - ...parentArgs.triggerContext, - serializedInputContext: parentArgs.serializedInputContext - }; - return this.deviceApi.notify((0, _index.createNotification)('showAutofillParent', applePayload)); - } - - /** - * @returns {Promise} - */ - async _closeAutofillParent() { - return this.deviceApi.notify((0, _index.createNotification)('closeAutofillParent', {})); - } - - /** - * @param {GetAutofillDataRequest} details - */ - async _show(details) { - await this._showAutofillParent(details); - this._listenForSelectedCredential(async response => { - if (!response) return; - if ('configType' in response) { - this.selectedDetail(response.data, response.configType); - } else if ('stop' in response) { - await this.onFinishedAutofill(); - } else if ('stateChange' in response) { - await this.updateForStateChange(); - } - }); - } - async refreshData() { - await super.refreshData(); - await this._checkDeviceSignedIn(); - } - async getAddresses() { - if (!this.globalConfig.isApp) return this.getAlias(); - const { - addresses - } = await this.deviceApi.request((0, _index.createRequest)('emailHandlerGetAddresses')); - this.storeLocalAddresses(addresses); - return addresses; - } - async refreshAlias() { - await this.deviceApi.notify((0, _index.createNotification)('emailHandlerRefreshAlias')); - // On macOS we also update the addresses stored locally - if (this.globalConfig.isApp) this.getAddresses(); - } - async _checkDeviceSignedIn() { - const { - isAppSignedIn - } = await this.deviceApi.request((0, _index.createRequest)('emailHandlerCheckAppSignedInStatus')); - this.isDeviceSignedIn = () => !!isAppSignedIn; - return !!isAppSignedIn; - } - storeUserData(_ref) { - let { - addUserData: { - token, - userName, - cohort - } - } = _ref; - return this.deviceApi.notify((0, _index.createNotification)('emailHandlerStoreToken', { - token, - username: userName, - cohort - })); - } - - /** - * Used by the email web app - * Provides functionality to log the user out - */ - removeUserData() { - this.deviceApi.notify((0, _index.createNotification)('emailHandlerRemoveToken')); - } - - /** - * Used by the email web app - * Provides functionality to close the window after in-context sign-up or sign-in - */ - closeEmailProtection() { - this.deviceApi.request(new _deviceApiCalls.CloseEmailProtectionTabCall(null)); - } - - /** - * PM endpoints - */ - - /** - * Gets the init data from the device - * @returns {APIResponse} - */ - async _getAutofillInitData() { - const response = await this.deviceApi.request((0, _index.createRequest)('pmHandlerGetAutofillInitData')); - this.storeLocalData(response.success); - return response; - } - - /** - * Gets credentials ready for autofill - * @param {CredentialsObject['id']} id - the credential id - * @returns {APIResponseSingle} - */ - getAutofillCredentials(id) { - return this.deviceApi.request((0, _index.createRequest)('pmHandlerGetAutofillCredentials', { - id - })); - } - - /** - * Opens the native UI for managing passwords - */ - openManagePasswords() { - return this.deviceApi.notify((0, _index.createNotification)('pmHandlerOpenManagePasswords')); - } - - /** - * Gets a single identity obj once the user requests it - * @param {IdentityObject['id']} id - * @returns {Promise<{success: IdentityObject|undefined}>} - */ - getAutofillIdentity(id) { - const identity = this.getLocalIdentities().find(_ref2 => { - let { - id: identityId - } = _ref2; - return `${identityId}` === `${id}`; - }); - return Promise.resolve({ - success: identity - }); - } - - /** - * Gets a single complete credit card obj once the user requests it - * @param {CreditCardObject['id']} id - * @returns {APIResponse} - */ - getAutofillCreditCard(id) { - return this.deviceApi.request((0, _index.createRequest)('pmHandlerGetCreditCard', { - id - })); - } - getCurrentInputType() { - const topContextData = this.getTopContextData(); - return topContextData?.inputType ? topContextData.inputType : (0, _matching.getInputType)(this.activeForm?.activeInput); - } - - /** - * @returns {Promise} - */ - async getAlias() { - const { - alias - } = await this.deviceApi.request(new _additionalDeviceApiCalls.GetAlias({ - requiresUserPermission: !this.globalConfig.isApp, - shouldConsumeAliasIfProvided: !this.globalConfig.isApp, - isIncontextSignupAvailable: this.inContextSignup.isAvailable() - })); - return alias ? (0, _autofillUtils.formatDuckAddress)(alias) : alias; - } - addLogoutListener(handler) { - // Only deal with logging out if we're in the email web app - if (!this.globalConfig.isDDGDomain) return; - window.addEventListener('message', e => { - if (this.globalConfig.isDDGDomain && e.data.emailProtectionSignedOut) { - handler(); - } - }); - } - async addDeviceListeners() { - this.thirdPartyProvider.init(); - } - - /** @type {any} */ - pollingTimeout = null; - /** - * Poll the native listener until the user has selected a credential. - * Message return types are: - * - 'stop' is returned whenever the message sent doesn't match the native last opened tooltip. - * - This also is triggered when the close event is called and prevents any edge case continued polling. - * - 'ok' is when the user has selected a credential and the value can be injected into the page. - * - 'none' is when the tooltip is open in the native window however hasn't been entered. - * @param {(response: {data:IdentityObject|CreditCardObject|CredentialsObject, configType: string} | {stateChange: boolean} | {stop: boolean} | null) => void} callback - */ - async _listenForSelectedCredential(callback) { - // Prevent two timeouts from happening - const poll = async () => { - clearTimeout(this.pollingTimeout); - const response = await this.getSelectedCredentials(); - switch (response.type) { - case 'none': - // Parent hasn't got a selected credential yet - this.pollingTimeout = setTimeout(() => poll(), 100); - return; - case 'ok': - { - await callback({ - data: response.data, - configType: response.configType - }); - return; + /** + * @param {string} password + * @param {number} consecutiveCharLimit + * @returns {boolean} + */ + _passwordHasNotExceededConsecutiveCharLimit(password, consecutiveCharLimit) { + let longestConsecutiveCharLength = 1; + let firstConsecutiveCharIndex = 0; + let isSequenceAscending; + for (let i = 1; i < password.length; i++) { + const currCharCode = password.charCodeAt(i); + const prevCharCode = password.charCodeAt(i - 1); + if (isSequenceAscending) { + if (isSequenceAscending.valueOf() && currCharCode === prevCharCode + 1 || !isSequenceAscending.valueOf() && currCharCode === prevCharCode - 1) { + continue; } - case 'state': - { - // Inform that state has changed, but continue polling - // e.g. in-context signup has been dismissed - await callback({ - stateChange: true - }); - this.pollingTimeout = setTimeout(() => poll(), 100); - return; + if (currCharCode === prevCharCode + 1) { + firstConsecutiveCharIndex = i - 1; + isSequenceAscending = Boolean(true); + continue; } - case 'stop': - // Parent wants us to stop polling - await callback({ - stop: true - }); + if (currCharCode === prevCharCode - 1) { + firstConsecutiveCharIndex = i - 1; + isSequenceAscending = Boolean(false); + continue; + } + isSequenceAscending = null; + } else if (currCharCode === prevCharCode + 1) { + isSequenceAscending = Boolean(true); + continue; + } else if (currCharCode === prevCharCode - 1) { + isSequenceAscending = Boolean(false); + continue; + } + const currConsecutiveCharLength = i - firstConsecutiveCharIndex; + if (currConsecutiveCharLength > longestConsecutiveCharLength) { + longestConsecutiveCharLength = currConsecutiveCharLength; + } + firstConsecutiveCharIndex = i; } - }; - poll(); - } -} -exports.AppleDeviceInterface = AppleDeviceInterface; - -},{"../../packages/device-api/index.js":2,"../Form/matching.js":33,"../InContextSignup.js":34,"../ThirdPartyProvider.js":41,"../UI/HTMLTooltip.js":45,"../UI/controllers/HTMLTooltipUIController.js":46,"../UI/controllers/NativeUIController.js":47,"../UI/controllers/OverlayUIController.js":48,"../autofill-utils.js":52,"../deviceApiCalls/__generated__/deviceApiCalls.js":56,"../deviceApiCalls/additionalDeviceApiCalls.js":58,"./InterfacePrototype.js":17}],15:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.AppleOverlayDeviceInterface = void 0; -var _AppleDeviceInterface = require("./AppleDeviceInterface.js"); -var _HTMLTooltipUIController = require("../UI/controllers/HTMLTooltipUIController.js"); -var _overlayApi = require("./overlayApi.js"); -var _index = require("../../packages/device-api/index.js"); -/** - * This subclass is designed to separate code that *only* runs inside the - * Overlay into a single place. - * - * It will only run inside the macOS overlay, therefor all code here - * can be viewed as *not* executing within a regular page context. - */ -class AppleOverlayDeviceInterface extends _AppleDeviceInterface.AppleDeviceInterface { - /** - * Mark top frame as not stripping credential data - * @type {boolean} - */ - stripCredentials = false; - - /** - * overlay API helpers - */ - overlay = (0, _overlayApi.overlayApi)(this); - previousX = 0; - previousY = 0; - - /** - * Because we're running inside the Overlay, we always create the HTML - * Tooltip controller. - * - * @override - * @returns {import("../UI/controllers/UIController.js").UIController} - */ - createUIController() { - return new _HTMLTooltipUIController.HTMLTooltipUIController({ - tooltipKind: /** @type {const} */'modern', - device: this - }, { - wrapperClass: 'top-autofill', - tooltipPositionClass: () => '.wrapper { transform: none; }', - setSize: details => this.deviceApi.notify((0, _index.createNotification)('setSize', details)), - remove: async () => this._closeAutofillParent(), - testMode: this.isTestMode() - }); - } - addDeviceListeners() { + if (isSequenceAscending) { + const currConsecutiveCharLength = password.length - firstConsecutiveCharIndex; + if (currConsecutiveCharLength > longestConsecutiveCharLength) { + longestConsecutiveCharLength = currConsecutiveCharLength; + } + } + return longestConsecutiveCharLength <= consecutiveCharLimit; + } /** - * The native side will send a custom event 'mouseMove' to indicate - * that the HTMLTooltip should fake an element being focused. - * - * Note: There's no cleanup required here since the Overlay has a fresh - * page load every time it's opened. - */ - window.addEventListener('mouseMove', event => { - // Don't set focus if the mouse hasn't moved ever - // This is to avoid clickjacking where an attacker puts the pulldown under the cursor - // and tricks the user into clicking - if (!this.previousX && !this.previousY || - // if no previous coords - this.previousX === event.detail.x && this.previousY === event.detail.y // or the mouse hasn't moved - ) { - this.previousX = event.detail.x; - this.previousY = event.detail.y; - return; + * @param {string} password + * @param {number} repeatedCharLimit + * @returns {boolean} + */ + _passwordHasNotExceededRepeatedCharLimit(password, repeatedCharLimit) { + let longestRepeatedCharLength = 1; + let lastRepeatedChar = password.charAt(0); + let lastRepeatedCharIndex = 0; + for (let i = 1; i < password.length; i++) { + const currChar = password.charAt(i); + if (currChar === lastRepeatedChar) { + continue; + } + const currRepeatedCharLength = i - lastRepeatedCharIndex; + if (currRepeatedCharLength > longestRepeatedCharLength) { + longestRepeatedCharLength = currRepeatedCharLength; + } + lastRepeatedChar = currChar; + lastRepeatedCharIndex = i; } - const activeTooltip = this.uiController?.getActiveTooltip?.(); - activeTooltip?.focus(event.detail.x, event.detail.y); - this.previousX = event.detail.x; - this.previousY = event.detail.y; - }); - return super.addDeviceListeners(); - } - - /** - * Since we're running inside the Overlay we can limit what happens here to - * be only things that are needed to power the HTML Tooltip - * - * @override - * @returns {Promise} - */ - async setupAutofill() { - await this._getAutofillInitData(); - await this.inContextSignup.init(); - const signedIn = await this._checkDeviceSignedIn(); - if (signedIn) { - await this.getAddresses(); + return longestRepeatedCharLength <= repeatedCharLimit; } - } - async postInit() { - // setup overlay API pieces - this.overlay.showImmediately(); - super.postInit(); - } - - /** - * In the top-frame scenario we override the base 'selectedDetail'. - * - * This - * - * @override - * @param {IdentityObject|CreditCardObject|CredentialsObject|{email:string, id: string}} data - * @param {string} type - */ - async selectedDetail(data, type) { - return this.overlay.selectedDetail(data, type); - } -} -exports.AppleOverlayDeviceInterface = AppleOverlayDeviceInterface; - -},{"../../packages/device-api/index.js":2,"../UI/controllers/HTMLTooltipUIController.js":46,"./AppleDeviceInterface.js":14,"./overlayApi.js":21}],16:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.ExtensionInterface = void 0; -var _InterfacePrototype = _interopRequireDefault(require("./InterfacePrototype.js")); -var _autofillUtils = require("../autofill-utils.js"); -var _HTMLTooltipUIController = require("../UI/controllers/HTMLTooltipUIController.js"); -var _HTMLTooltip = require("../UI/HTMLTooltip.js"); -var _InContextSignup = require("../InContextSignup.js"); -var _matching = require("../Form/matching.js"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const TOOLTIP_TYPES = { - EmailProtection: 'EmailProtection', - EmailSignup: 'EmailSignup' -}; -class ExtensionInterface extends _InterfacePrototype.default { - /** - * Adding this here since only the extension currently supports this - */ - inContextSignup = new _InContextSignup.InContextSignup(this); - - /** - * @override - */ - createUIController() { - /** @type {import('../UI/HTMLTooltip.js').HTMLTooltipOptions} */ - const htmlTooltipOptions = { - ..._HTMLTooltip.defaultOptions, - css: ``, - testMode: this.isTestMode(), - hasCaret: true - }; - const tooltipKinds = { - [TOOLTIP_TYPES.EmailProtection]: 'legacy', - [TOOLTIP_TYPES.EmailSignup]: 'emailsignup' - }; - const tooltipKind = tooltipKinds[this.getActiveTooltipType()] || tooltipKinds[TOOLTIP_TYPES.EmailProtection]; - return new _HTMLTooltipUIController.HTMLTooltipUIController({ - tooltipKind, - device: this - }, htmlTooltipOptions); - } - getActiveTooltipType() { - if (this.hasLocalAddresses) { - return TOOLTIP_TYPES.EmailProtection; + /** + * @param {string} password + * @param {string[]} requiredCharacterSets + * @returns {boolean} + */ + _passwordContainsRequiredCharacters(password, requiredCharacterSets) { + const requiredCharacterSetsLength = requiredCharacterSets.length; + const passwordLength = password.length; + for (let i = 0; i < requiredCharacterSetsLength; i++) { + const requiredCharacterSet = requiredCharacterSets[i]; + let hasRequiredChar = false; + for (let j = 0; j < passwordLength; j++) { + const char = password.charAt(j); + if (requiredCharacterSet.indexOf(char) !== -1) { + hasRequiredChar = true; + break; + } + } + if (!hasRequiredChar) { + return false; + } + } + return true; } - const inputType = this.activeForm?.activeInput ? (0, _matching.getInputSubtype)(this.activeForm.activeInput) : undefined; - if (this.inContextSignup?.isAvailable(inputType)) { - return TOOLTIP_TYPES.EmailSignup; + /** + * @param {string} string1 + * @param {string} string2 + * @returns {boolean} + */ + _stringsHaveAtLeastOneCommonCharacter(string1, string2) { + const string2Length = string2.length; + for (let i = 0; i < string2Length; i++) { + const char = string2.charAt(i); + if (string1.indexOf(char) !== -1) { + return true; + } + } + return false; } - return null; - } - async resetAutofillUI(callback) { - this.removeAutofillUIFromPage('Resetting autofill.'); - await this.setupAutofill(); - if (callback) await callback(); - this.uiController = this.createUIController(); - await this.postInit(); - } - isDeviceSignedIn() { - return this.hasLocalAddresses; - } - async setupAutofill() { /** - * In the extension, we must resolve `inContextSignup` data as part of setup + * @param {Requirements} requirements + * @returns {PasswordParameters} */ - await this.inContextSignup.init(); - return this.getAddresses(); - } - postInit() { - switch (this.getActiveTooltipType()) { - case TOOLTIP_TYPES.EmailProtection: - { - this._scannerCleanup = this.scanner.init(); - this.addLogoutListener(() => { - this.resetAutofillUI(); - if (this.globalConfig.isDDGDomain) { - (0, _autofillUtils.notifyWebApp)({ - deviceSignedIn: { - value: false - } - }); - } - }); - if (this.activeForm?.activeInput) { - this.attachTooltip({ - form: this.activeForm, - input: this.activeForm?.activeInput, - click: null, - trigger: 'postSignup', - triggerMetaData: { - type: 'transactional' - } - }); + _passwordGenerationParametersDictionary(requirements) { + let minPasswordLength = requirements.PasswordMinLength; + const maxPasswordLength = requirements.PasswordMaxLength; + if (minPasswordLength > maxPasswordLength) { + minPasswordLength = 0; + } + const requiredCharacterArray = requirements.PasswordRequiredCharacters; + let allowedCharacters = requirements.PasswordAllowedCharacters; + let requiredCharacterSets = this.options.defaultRequiredCharacterSets; + if (requiredCharacterArray) { + const mutatedRequiredCharacterSets2 = []; + const requiredCharacterArrayLength = requiredCharacterArray.length; + for (let i = 0; i < requiredCharacterArrayLength; i++) { + const requiredCharacters = requiredCharacterArray[i]; + if (allowedCharacters && this._stringsHaveAtLeastOneCommonCharacter(requiredCharacters, allowedCharacters)) { + mutatedRequiredCharacterSets2.push(requiredCharacters); } - break; } - case TOOLTIP_TYPES.EmailSignup: - { - this._scannerCleanup = this.scanner.init(); - break; + requiredCharacterSets = mutatedRequiredCharacterSets2; + } + let numberOfRequiredRandomCharacters = this.options.defaultPasswordLength; + if (minPasswordLength && minPasswordLength > numberOfRequiredRandomCharacters) { + numberOfRequiredRandomCharacters = minPasswordLength; + } + if (maxPasswordLength && maxPasswordLength < numberOfRequiredRandomCharacters) { + numberOfRequiredRandomCharacters = maxPasswordLength; + } + if (!allowedCharacters) { + allowedCharacters = this.options.defaultUnambiguousCharacters; + } + if (!requiredCharacterSets) { + requiredCharacterSets = this.options.defaultRequiredCharacterSets; + } + if (requiredCharacterSets.length > numberOfRequiredRandomCharacters) { + requiredCharacterSets = []; + } + const requiredCharacterSetsLength = requiredCharacterSets.length; + const mutatedRequiredCharacterSets = []; + const allowedCharactersLength = allowedCharacters.length; + for (let i = 0; i < requiredCharacterSetsLength; i++) { + const requiredCharacterSet = requiredCharacterSets[i]; + let requiredCharacterSetContainsAllowedCharacters = false; + for (let j = 0; j < allowedCharactersLength; j++) { + const character = allowedCharacters.charAt(j); + if (requiredCharacterSet.indexOf(character) !== -1) { + requiredCharacterSetContainsAllowedCharacters = true; + break; + } } - default: - { - // Don't do anyhing if we don't have a tooltip to show - break; + if (requiredCharacterSetContainsAllowedCharacters) { + mutatedRequiredCharacterSets.push(requiredCharacterSet); } + } + requiredCharacterSets = mutatedRequiredCharacterSets; + return { + NumberOfRequiredRandomCharacters: numberOfRequiredRandomCharacters, + PasswordAllowedCharacters: allowedCharacters, + RequiredCharacterSets: requiredCharacterSets + }; } - } - getAddresses() { - return new Promise(resolve => chrome.runtime.sendMessage({ - getAddresses: true - }, data => { - this.storeLocalAddresses(data); - return resolve(data); - })); - } - - /** - * Used by the email web app - * Settings page displays data of the logged in user data - */ - getUserData() { - return new Promise(resolve => chrome.runtime.sendMessage({ - getUserData: true - }, data => resolve(data))); - } - - /** - * Used by the email web app - * Device capabilities determine which functionality is available to the user - */ - getEmailProtectionCapabilities() { - return new Promise(resolve => chrome.runtime.sendMessage({ - getEmailProtectionCapabilities: true - }, data => resolve(data))); - } - refreshAlias() { - return chrome.runtime.sendMessage({ - refreshAlias: true - }, addresses => this.storeLocalAddresses(addresses)); - } - async trySigningIn() { - if (this.globalConfig.isDDGDomain) { - const data = await (0, _autofillUtils.sendAndWaitForAnswer)(_autofillUtils.SIGN_IN_MSG, 'addUserData'); - this.storeUserData(data); + /** + * @param {Requirements | null} requirements + * @param {PasswordParameters} [parameters] + * @returns {string} + */ + _generatedPasswordMatchingRequirements(requirements, parameters) { + requirements = requirements || {}; + parameters = parameters || this._passwordGenerationParametersDictionary(requirements); + const numberOfRequiredRandomCharacters = parameters.NumberOfRequiredRandomCharacters; + const repeatedCharLimit = requirements.PasswordRepeatedCharacterLimit; + const allowedCharacters = parameters.PasswordAllowedCharacters; + const shouldCheckRepeatedCharRequirement = !!repeatedCharLimit; + while (true) { + const password = this._classicPassword(numberOfRequiredRandomCharacters, allowedCharacters); + if (!this._passwordContainsRequiredCharacters(password, parameters.RequiredCharacterSets)) { + continue; + } + if (shouldCheckRepeatedCharRequirement) { + if (repeatedCharLimit !== void 0 && repeatedCharLimit >= 1 && !this._passwordHasNotExceededRepeatedCharLimit(password, repeatedCharLimit)) { + continue; + } + } + const consecutiveCharLimit = requirements.PasswordConsecutiveCharacterLimit; + if (consecutiveCharLimit && consecutiveCharLimit >= 1) { + if (!this._passwordHasNotExceededConsecutiveCharLimit(password, consecutiveCharLimit)) { + continue; + } + } + return password || ""; + } } - } - - /** - * @param {object} message - * @param {object} message.addUserData - * @param {string} message.addUserData.token - * @param {string} message.addUserData.userName - * @param {string} message.addUserData.cohort - */ - storeUserData(message) { - return chrome.runtime.sendMessage(message); - } - - /** - * Used by the email web app - * Provides functionality to log the user out - */ - removeUserData() { - return chrome.runtime.sendMessage({ - removeUserData: true - }); - } - addDeviceListeners() { - // Add contextual menu listeners - let activeEl = null; - document.addEventListener('contextmenu', e => { - activeEl = e.target; - }); - chrome.runtime.onMessage.addListener((message, sender) => { - if (sender.id !== chrome.runtime.id) return; - switch (message.type) { - case 'ddgUserReady': - this.resetAutofillUI(() => this.setupSettingsPage({ - shouldLog: true - })); - break; - case 'contextualAutofill': - (0, _autofillUtils.setValue)(activeEl, (0, _autofillUtils.formatDuckAddress)(message.alias), this.globalConfig); - activeEl.classList.add('ddg-autofilled'); - this.refreshAlias(); - - // If the user changes the alias, remove the decoration - activeEl.addEventListener('input', e => e.target.classList.remove('ddg-autofilled'), { - once: true - }); - break; - default: - break; + /** + * @param {parser.CustomCharacterClass | parser.NamedCharacterClass} characterClass + * @returns {string[]} + */ + _scanSetFromCharacterClass(characterClass) { + if (characterClass instanceof CustomCharacterClass) { + return characterClass.characters; + } + console.assert(characterClass instanceof NamedCharacterClass); + switch (characterClass.name) { + case Identifier.ASCII_PRINTABLE: + case Identifier.UNICODE: + return this.options.SCAN_SET_ORDER.split(""); + case Identifier.DIGIT: + return this.options.SCAN_SET_ORDER.substring(this.options.SCAN_SET_ORDER.indexOf("0"), this.options.SCAN_SET_ORDER.indexOf("9") + 1).split(""); + case Identifier.LOWER: + return this.options.SCAN_SET_ORDER.substring(this.options.SCAN_SET_ORDER.indexOf("a"), this.options.SCAN_SET_ORDER.indexOf("z") + 1).split(""); + case Identifier.SPECIAL: + return this.options.SCAN_SET_ORDER.substring(this.options.SCAN_SET_ORDER.indexOf("-"), this.options.SCAN_SET_ORDER.indexOf("]") + 1).split(""); + case Identifier.UPPER: + return this.options.SCAN_SET_ORDER.substring(this.options.SCAN_SET_ORDER.indexOf("A"), this.options.SCAN_SET_ORDER.indexOf("Z") + 1).split(""); + } + console.assert(false, SHOULD_NOT_BE_REACHED); + return []; + } + /** + * @param {(parser.CustomCharacterClass | parser.NamedCharacterClass)[]} characterClasses + */ + _charactersFromCharactersClasses(characterClasses) { + const output = []; + for (let characterClass of characterClasses) { + output.push(...this._scanSetFromCharacterClass(characterClass)); } - }); - } - addLogoutListener(handler) { - // Make sure there's only one log out listener attached by removing the - // previous logout listener first, if it exists. - if (this._logoutListenerHandler) { - chrome.runtime.onMessage.removeListener(this._logoutListenerHandler); + return output; + } + /** + * @param {string[]} characters + * @returns {string} + */ + _canonicalizedScanSetFromCharacters(characters) { + if (!characters.length) { + return ""; + } + let shadowCharacters = Array.prototype.slice.call(characters); + shadowCharacters.sort((a, b) => this.options.SCAN_SET_ORDER.indexOf(a) - this.options.SCAN_SET_ORDER.indexOf(b)); + let uniqueCharacters = [shadowCharacters[0]]; + for (let i = 1, length = shadowCharacters.length; i < length; ++i) { + if (shadowCharacters[i] === shadowCharacters[i - 1]) { + continue; + } + uniqueCharacters.push(shadowCharacters[i]); + } + return uniqueCharacters.join(""); } + }; - // Cleanup on logout events - this._logoutListenerHandler = (message, sender) => { - if (sender.id === chrome.runtime.id && message.type === 'logout') { - handler(); + // packages/password/index.js + function generate(options = {}) { + try { + if (typeof options?.input === "string") { + return Password.generateOrThrow(options.input); + } + if (typeof options?.domain === "string") { + if (options?.rules) { + const rules = _selectPasswordRules(options.domain, options.rules); + if (rules) { + return Password.generateOrThrow(rules); + } + } } - }; - chrome.runtime.onMessage.addListener(this._logoutListenerHandler); + } catch (e) { + if (options?.onError && typeof options?.onError === "function") { + options.onError(e); + } else { + const isKnownError = e instanceof ParserError || e instanceof HostnameInputError; + if (!isKnownError) { + console.error(e); + } + } + } + return Password.generateDefault(); } -} -exports.ExtensionInterface = ExtensionInterface; - -},{"../Form/matching.js":33,"../InContextSignup.js":34,"../UI/HTMLTooltip.js":45,"../UI/controllers/HTMLTooltipUIController.js":46,"../autofill-utils.js":52,"./InterfacePrototype.js":17}],17:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _autofillUtils = require("../autofill-utils.js"); -var _matching = require("../Form/matching.js"); -var _formatters = require("../Form/formatters.js"); -var _Credentials = require("../InputTypes/Credentials.js"); -var _PasswordGenerator = require("../PasswordGenerator.js"); -var _Scanner = require("../Scanner.js"); -var _config = require("../config.js"); -var _NativeUIController = require("../UI/controllers/NativeUIController.js"); -var _transports = require("../deviceApiCalls/transports/transports.js"); -var _Settings = require("../Settings.js"); -var _index = require("../../packages/device-api/index.js"); -var _deviceApiCalls = require("../deviceApiCalls/__generated__/deviceApiCalls.js"); -var _initFormSubmissionsApi = require("./initFormSubmissionsApi.js"); -var _EmailProtection = require("../EmailProtection.js"); -/** - * @typedef {import('../deviceApiCalls/__generated__/validators-ts').StoreFormData} StoreFormData - */ -/** - * @implements {GlobalConfigImpl} - * @implements {FormExtensionPoints} - * @implements {DeviceExtensionPoints} - */ -class InterfacePrototype { - attempts = 0; - /** @type {import("../Form/Form").Form | null} */ - activeForm = null; - /** @type {import("../UI/HTMLTooltip.js").default | null} */ - currentTooltip = null; - /** @type {number} */ - initialSetupDelayMs = 0; - autopromptFired = false; - - /** @type {PasswordGenerator} */ - passwordGenerator = new _PasswordGenerator.PasswordGenerator(); - emailProtection = new _EmailProtection.EmailProtection(this); - - /** @type {import("../InContextSignup.js").InContextSignup | null} */ - inContextSignup = null; - /** @type {import("../ThirdPartyProvider.js").ThirdPartyProvider | null} */ - thirdPartyProvider = null; - - /** @type {{privateAddress: string, personalAddress: string}} */ - #addresses = { - privateAddress: '', - personalAddress: '' + var HostnameInputError = class extends Error { }; - - /** @type {GlobalConfig} */ - globalConfig; - - /** @type {import('../Scanner').Scanner} */ - scanner; - - /** @type {import("../UI/controllers/UIController.js").UIController | null} */ - uiController; - - /** @type {import("../../packages/device-api").DeviceApi} */ - deviceApi; - - /** @type {boolean} */ - isInitializationStarted; - - /** @type {((reason, ...rest) => void) | null} */ - _scannerCleanup = null; - - /** - * @param {GlobalConfig} config - * @param {import("../../packages/device-api").DeviceApi} deviceApi - * @param {Settings} settings - */ - constructor(config, deviceApi, settings) { - this.globalConfig = config; - this.deviceApi = deviceApi; - this.settings = settings; - this.uiController = null; - this.scanner = (0, _Scanner.createScanner)(this, { - initialDelay: this.initialSetupDelayMs - }); - this.isInitializationStarted = false; + function _selectPasswordRules(inputHostname, rules) { + const hostname = _safeHostname(inputHostname); + if (rules[hostname]) { + return rules[hostname]["password-rules"]; + } + const pieces = hostname.split("."); + while (pieces.length > 1) { + pieces.shift(); + const joined = pieces.join("."); + if (rules[joined]) { + return rules[joined]["password-rules"]; + } + } + return void 0; } - - /** - * Implementors should override this with a UI controller that suits - * their platform. - * - * @returns {import("../UI/controllers/UIController.js").UIController} - */ - createUIController() { - return new _NativeUIController.NativeUIController(); + function _safeHostname(inputHostname) { + if (inputHostname.startsWith("http:") || inputHostname.startsWith("https:")) { + throw new HostnameInputError("invalid input, you can only provide a hostname but you gave a scheme"); + } + if (inputHostname.includes(":")) { + throw new HostnameInputError("invalid input, you can only provide a hostname but you gave a :port"); + } + try { + const asUrl = new URL("https://" + inputHostname); + return asUrl.hostname; + } catch (e) { + throw new HostnameInputError(`could not instantiate a URL from that hostname ${inputHostname}`); + } } - /** - * @param {string} reason - */ - removeAutofillUIFromPage(reason) { - this.uiController?.destroy(); - this._scannerCleanup?.(reason); - } - get hasLocalAddresses() { - return !!(this.#addresses?.privateAddress && this.#addresses?.personalAddress); - } - getLocalAddresses() { - return this.#addresses; - } - storeLocalAddresses(addresses) { - this.#addresses = addresses; - // When we get new duck addresses, add them to the identities list - const identities = this.getLocalIdentities(); - const privateAddressIdentity = identities.find(_ref => { - let { - id - } = _ref; - return id === 'privateAddress'; - }); - // If we had previously stored them, just update the private address - if (privateAddressIdentity) { - privateAddressIdentity.emailAddress = (0, _autofillUtils.formatDuckAddress)(addresses.privateAddress); - } else { - // Otherwise, add both addresses - this.#data.identities = this.addDuckAddressesToIdentities(identities); + // packages/password/rules.json + var rules_default = { + "163.com": { + "password-rules": "minlength: 6; maxlength: 16;" + }, + "1800flowers.com": { + "password-rules": "minlength: 6; required: lower, upper; required: digit;" + }, + "access.service.gov.uk": { + "password-rules": "minlength: 10; required: lower; required: upper; required: digit; required: special;" + }, + "admiral.com": { + "password-rules": "minlength: 8; required: digit; required: [- !\"#$&'()*+,.:;<=>?@[^_`{|}~]]; allowed: lower, upper;" + }, + "ae.com": { + "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit;" + }, + "aetna.com": { + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 2; required: upper; required: digit; allowed: lower, [-_&#@];" + }, + "airasia.com": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;" + }, + "airfrance.com": { + "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit; allowed: [-!#$&+/?@_];" + }, + "airfrance.us": { + "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit; allowed: [-!#$&+/?@_];" + }, + "ajisushionline.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; allowed: [ !#$%&*?@];" + }, + "aliexpress.com": { + "password-rules": "minlength: 6; maxlength: 20; allowed: lower, upper, digit;" + }, + "alliantcreditunion.com": { + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower, upper; required: digit; allowed: [!#$*];" + }, + "allianz.com.br": { + "password-rules": "minlength: 4; maxlength: 4;" + }, + "americanexpress.com": { + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 4; required: lower, upper; required: digit; allowed: [%&_?#=];" + }, + "anatel.gov.br": { + "password-rules": "minlength: 6; maxlength: 15; allowed: lower, upper, digit;" + }, + "ancestry.com": { + "password-rules": "minlength: 8; required: lower, upper; required: digit;" + }, + "angieslist.com": { + "password-rules": "minlength: 6; maxlength: 15;" + }, + "anthem.com": { + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower, upper; required: digit; allowed: [!$*?@|];" + }, + "app.digio.in": { + "password-rules": "minlength: 8; maxlength: 15;" + }, + "app.parkmobile.io": { + "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit; required: [!@#$%^&];" + }, + "apple.com": { + "password-rules": "minlength: 8; maxlength: 63; required: lower; required: upper; required: digit; allowed: ascii-printable;" + }, + "areariservata.bancaetica.it": { + "password-rules": "minlength: 8; maxlength: 10; required: lower; required: upper; required: digit; required: [!#&*+/=@_];" + }, + "artscyclery.com": { + "password-rules": "minlength: 6; maxlength: 19;" + }, + "astonmartinf1.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: special;" + }, + "auth.readymag.com": { + "password-rules": "minlength: 8; maxlength: 128; required: lower; required: upper; allowed: special;" + }, + "autify.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!\"#$%&'()*+,./:;<=>?@[^_`{|}~]];" + }, + "axa.de": { + "password-rules": `minlength: 8; maxlength: 65; required: lower; required: upper; required: digit; allowed: [-!"\xA7$%&/()=?;:_+*'#];` + }, + "baidu.com": { + "password-rules": "minlength: 6; maxlength: 14;" + }, + "bancochile.cl": { + "password-rules": "minlength: 8; maxlength: 8; required: lower; required: upper; required: digit;" + }, + "bankofamerica.com": { + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower; required: upper; required: digit; allowed: [-@#*()+={}/?~;,._];" + }, + "battle.net": { + "password-rules": "minlength: 8; maxlength: 16; required: lower, upper; allowed: digit, special;" + }, + "bcassessment.ca": { + "password-rules": "minlength: 8; maxlength: 14;" + }, + "belkin.com": { + "password-rules": "minlength: 8; required: lower, upper; required: digit; required: [$!@~_,%&];" + }, + "benefitslogin.discoverybenefits.com": { + "password-rules": "minlength: 10; required: upper; required: digit; required: [!#$%&*?@]; allowed: lower;" + }, + "benjerry.com": { + "password-rules": "required: upper; required: upper; required: digit; required: digit; required: special; required: special; allowed: lower;" + }, + "bestbuy.com": { + "password-rules": "minlength: 20; required: lower; required: upper; required: digit; required: special;" + }, + "bhphotovideo.com": { + "password-rules": "maxlength: 15;" + }, + "bilibili.com": { + "password-rules": "maxlength: 16;" + }, + "billerweb.com": { + "password-rules": "minlength: 8; max-consecutive: 2; required: digit; required: upper,lower;" + }, + "biovea.com": { + "password-rules": "maxlength: 19;" + }, + "bitly.com": { + "password-rules": "minlength: 6; required: lower; required: upper; required: digit; required: [`!@#$%^&*()+~{}'\";:<>?]];" + }, + "bloomingdales.com": { + "password-rules": "minlength: 7; maxlength: 16; required: lower, upper; required: digit; required: [`!@#$%^&*()+~{}'\";:<>?]];" + }, + "bluesguitarunleashed.com": { + "password-rules": "allowed: lower, upper, digit, [!$#@];" + }, + "bochk.com": { + "password-rules": "minlength: 8; maxlength: 12; max-consecutive: 3; required: lower; required: upper; required: digit; allowed: [#$%&()*+,.:;<=>?@_];" + }, + "box.com": { + "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: digit;" + }, + "brighthorizons.com": { + "password-rules": "minlength: 8; maxlength: 16;" + }, + "callofduty.com": { + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 2; required: lower, upper; required: digit;" + }, + "capitalone.com": { + "password-rules": "minlength: 8; maxlength: 32; required: lower, upper; required: digit; allowed: [-_./\\@$*&!#];" + }, + "cardbenefitservices.com": { + "password-rules": "minlength: 7; maxlength: 100; required: lower, upper; required: digit;" + }, + "carrefour.it": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&*?@_];" + }, + "cb2.com": { + "password-rules": "minlength: 7; maxlength: 18; required: lower, upper; required: digit;" + }, + "ccs-grp.com": { + "password-rules": "minlength: 8; maxlength: 16; required: digit; required: upper,lower; allowed: [-!#$%&'+./=?\\^_`{|}~];" + }, + "cecredentialtrust.com": { + "password-rules": "minlength: 12; required: lower; required: upper; required: digit; required: [!#$%&*@^];" + }, + "chase.com": { + "password-rules": "minlength: 8; maxlength: 32; max-consecutive: 2; required: lower, upper; required: digit; required: [!#$%+/=@~];" + }, + "cigna.co.uk": { + "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit;" + }, + "citi.com": { + "password-rules": "minlength: 8; maxlength: 64; max-consecutive: 2; required: digit; required: upper; required: lower; required: [-~`!@#$%^&*()_\\/|];" + }, + "claimlookup.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [@#$%^&+=!];" + }, + "claro.com.br": { + "password-rules": "minlength: 8; required: lower; allowed: upper, digit, [-!@#$%&*_+=<>];" + }, + "classmates.com": { + "password-rules": "minlength: 6; maxlength: 20; allowed: lower, upper, digit, [!@#$%^&*];" + }, + "clegc-gckey.gc.ca": { + "password-rules": "minlength: 8; maxlength: 16; required: lower, upper, digit;" + }, + "clien.net": { + "password-rules": "minlength: 5; required: lower, upper; required: digit;" + }, + "cogmembers.org": { + "password-rules": "minlength: 8; maxlength: 14; required: upper; required: digit, allowed: lower;" + }, + "collectivehealth.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" + }, + "comcastpaymentcenter.com": { + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 2;required: lower, upper; required: digit;" + }, + "comed.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [-~!@#$%^&*_+=`|(){}[:;\"'<>,.?/\\]];" + }, + "commerzbank.de": { + "password-rules": "minlength: 5; maxlength: 8; required: lower, upper; required: digit;" + }, + "consorsbank.de": { + "password-rules": "minlength: 5; maxlength: 5; required: lower, upper, digit;" + }, + "consorsfinanz.de": { + "password-rules": "minlength: 6; maxlength: 15; allowed: lower, upper, digit, [-.];" + }, + "costco.com": { + "password-rules": "minlength: 8; maxlength: 20; required: lower, upper; allowed: digit, [-!#$%&'()*+/:;=?@[^_`{|}~]];" + }, + "coursera.com": { + "password-rules": "minlength: 8; maxlength: 72;" + }, + "cox.com": { + "password-rules": "minlength: 8; maxlength: 24; required: digit; required: upper,lower; allowed: [!#$%()*@^];" + }, + "crateandbarrel.com": { + "password-rules": 'minlength: 9; maxlength: 64; required: lower; required: upper; required: digit; required: [!"#$%&()*,.:<>?@^_{|}];' + }, + "cvs.com": { + "password-rules": "minlength: 8; maxlength: 25; required: lower, upper; required: digit; allowed: [!@#$%^&*()];" + }, + "dailymail.co.uk": { + "password-rules": "minlength: 5; maxlength: 15;" + }, + "dan.org": { + "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit; required: [!@$%^&*];" + }, + "danawa.com": { + "password-rules": "minlength: 8; maxlength: 21; required: lower, upper; required: digit; required: [!@$%^&*];" + }, + "darty.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" + }, + "dbs.com.hk": { + "password-rules": "minlength: 8; maxlength: 30; required: lower; required: upper; required: digit;" + }, + "decluttr.com": { + "password-rules": "minlength: 8; maxlength: 45; required: lower; required: upper; required: digit;" + }, + "delta.com": { + "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit;" + }, + "deutsche-bank.de": { + "password-rules": "minlength: 5; maxlength: 5; required: lower, upper, digit;" + }, + "devstore.cn": { + "password-rules": "minlength: 6; maxlength: 12;" + }, + "dickssportinggoods.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&*?@^];" + }, + "dkb.de": { + "password-rules": "minlength: 8; maxlength: 38; required: lower, upper; required: digit; allowed: [-\xE4\xFC\xF6\xC4\xDC\xD6\xDF!$%&/()=?+#,.:];" + }, + "dmm.com": { + "password-rules": "minlength: 4; maxlength: 16; required: lower; required: upper; required: digit;" + }, + "dowjones.com": { + "password-rules": "maxlength: 15;" + }, + "ea.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: special;" + }, + "easycoop.com": { + "password-rules": "minlength: 8; required: upper; required: special; allowed: lower, digit;" + }, + "easyjet.com": { + "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: [-];" + }, + "ebrap.org": { + "password-rules": "minlength: 15; required: lower; required: lower; required: upper; required: upper; required: digit; required: digit; required: [-!@#$%^&*()_+|~=`{}[:\";'?,./.]]; required: [-!@#$%^&*()_+|~=`{}[:\";'?,./.]];" + }, + "ecompanystore.com": { + "password-rules": "minlength: 8; maxlength: 16; max-consecutive: 2; required: lower; required: upper; required: digit; required: [#$%*+.=@^_];" + }, + "eddservices.edd.ca.gov": { + "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit; required: [!@#$%^&*()];" + }, + "edistrict.kerala.gov.in": { + "password-rules": "minlength: 5; maxlength: 15; required: lower; required: upper; required: digit; required: [!@#$];" + }, + "empower-retirement.com": { + "password-rules": "minlength: 8; maxlength: 16;" + }, + "epicgames.com": { + "password-rules": "minlength: 7; required: lower; required: upper; required: digit; required: [-!\"#$%&'()*+,./:;<=>?@[^_`{|}~]];" + }, + "epicmix.com": { + "password-rules": "minlength: 8; maxlength: 16;" + }, + "equifax.com": { + "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; required: [!$*+@];" + }, + "essportal.excelityglobal.com": { + "password-rules": "minlength: 6; maxlength: 8; allowed: lower, upper, digit;" + }, + "ettoday.net": { + "password-rules": "minlength: 6; maxlength: 12;" + }, + "examservice.com.tw": { + "password-rules": "minlength: 6; maxlength: 8;" + }, + "expertflyer.com": { + "password-rules": "minlength: 5; maxlength: 16; required: lower, upper; required: digit;" + }, + "extraspace.com": { + "password-rules": "minlength: 8; maxlength: 20; allowed: lower; required: upper, digit, [!#$%&*?@];" + }, + "ezpassva.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;" + }, + "fc2.com": { + "password-rules": "minlength: 8; maxlength: 16;" + }, + "fccaccessonline.com": { + "password-rules": "minlength: 8; maxlength: 14; max-consecutive: 3; required: lower; required: upper; required: digit; required: [!#$%*^_];" + }, + "fedex.com": { + "password-rules": "minlength: 8; max-consecutive: 3; required: lower; required: upper; required: digit; allowed: [-!@#$%^&*_+=`|(){}[:;,.?]];" + }, + "fidelity.com": { + "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: [-!$%+,./:;=?@^_|]; max-consecutive: 2;" + }, + "flysas.com": { + "password-rules": "minlength: 8; maxlength: 14; required: lower; required: upper; required: digit; required: [-~!@#$%^&_+=`|(){}[:\"'<>,.?]];" + }, + "fnac.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" + }, + "fuelrewards.com": { + "password-rules": "minlength: 8; maxlength: 16; allowed: upper,lower,digit,[!#$%@];" + }, + "gamestop.com": { + "password-rules": "minlength: 8; maxlength: 225; required: lower; required: upper; required: digit; required: [!@#$%];" + }, + "garmin.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" + }, + "getflywheel.com": { + "password-rules": "minlength: 7; maxlength: 72;" + }, + "girlscouts.org": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [$#!];" + }, + "gmx.net": { + "password-rules": "minlength: 8; maxlength: 40; allowed: lower, upper, digit, [-<=>~!|()@#{}$%,.?^'&*_+`:;\"[]];" + }, + "gocurb.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [$%&#*?!@^];" + }, + "google.com": { + "password-rules": `minlength: 8; allowed: lower, upper, digit, [-!"#$%&'()*+,./:;<=>?@[^_{|}~]];` + }, + "guardiananytime.com": { + "password-rules": "minlength: 8; maxlength: 50; max-consecutive: 2; required: lower; required: upper; required: digit, [-~!@#$%^&*_+=`|(){}[:;,.?]];" + }, + "gwl.greatwestlife.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [-!#$%_=+<>];" + }, + "hangseng.com": { + "password-rules": "minlength: 8; maxlength: 30; required: lower; required: upper; required: digit;" + }, + "hawaiianairlines.com": { + "password-rules": "maxlength: 16;" + }, + "hertz-japan.com": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz-kuwait.com": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz-saudi.com": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.at": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.be": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.bh": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.ca": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.ch": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.cn": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.co.ao": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.co.id": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.co.kr": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.co.nz": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.co.th": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.co.uk": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.au": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.bh": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.hk": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.kw": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.mt": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.pl": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.pt": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.sg": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.com.tw": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.cv": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.cz": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.de": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.ee": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.es": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.fi": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.fr": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.hu": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.ie": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.it": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.jo": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.lt": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.nl": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.no": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.nu": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.pl": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.pt": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.qa": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.ru": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.se": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertz.si": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hertzcaribbean.com": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];" + }, + "hetzner.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit, special;" + }, + "hilton.com": { + "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit;" + }, + "hkbea.com": { + "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit;" + }, + "hkexpress.com": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: special;" + }, + "hotels.com": { + "password-rules": "minlength: 6; maxlength: 20; required: digit; required: [-~#@$%&!*_?^]; allowed: lower, upper;" + }, + "hotwire.com": { + "password-rules": "minlength: 6; maxlength: 30; allowed: lower, upper, digit, [-~!@#$%^&*_+=`|(){}[:;\"'<>,.?]];" + }, + "hrblock.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [$#%!];" + }, + "hsbc.com.hk": { + "password-rules": "minlength: 6; maxlength: 30; required: lower; required: upper; required: digit; allowed: ['.@_];" + }, + "hsbc.com.my": { + "password-rules": "minlength: 8; maxlength: 30; required: lower, upper; required: digit; allowed: [-!$*.=?@_'];" + }, + "hypovereinsbank.de": { + "password-rules": 'minlength: 6; maxlength: 10; required: lower, upper, digit; allowed: [!"#$%&()*+:;<=>?@[{}~]];' + }, + "hyresbostader.se": { + "password-rules": "minlength: 6; maxlength: 20; required: lower, upper; required: digit;" + }, + "ichunqiu.com": { + "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit;" + }, + "id.sonyentertainmentnetwork.com": { + "password-rules": "minlength: 8; maxlength: 30; required: lower, upper; required: digit; allowed: [-!@#^&*=+;:];" + }, + "identitytheft.gov": { + "password-rules": "allowed: lower, upper, digit, [!#%&*@^];" + }, + "idestination.info": { + "password-rules": "maxlength: 15;" + }, + "impots.gouv.fr": { + "password-rules": "minlength: 12; maxlength: 128; required: lower; required: digit; allowed: [-!#$%&*+/=?^_'.{|}];" + }, + "indochino.com": { + "password-rules": "minlength: 6; maxlength: 15; required: upper; required: digit; allowed: lower, special;" + }, + "inntopia.travel": { + "password-rules": "minlength: 7; maxlength: 19; required: digit; allowed: upper,lower,[-];" + }, + "internationalsos.com": { + "password-rules": "required: lower; required: upper; required: digit; required: [@#$%^&+=_];" + }, + "irctc.co.in": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [!@#$%^&*()+];" + }, + "irs.gov": { + "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit; required: [!#$%&*@];" + }, + "jal.co.jp": { + "password-rules": "minlength: 8; maxlength: 16;" + }, + "japanpost.jp": { + "password-rules": "minlength: 8; maxlength: 16; required: digit; required: upper,lower;" + }, + "jordancu-onlinebanking.org": { + "password-rules": "minlength: 6; maxlength: 32; allowed: upper, lower, digit,[-!\"#$%&'()*+,.:;<=>?@[^_`{|}~]];" + }, + "keldoc.com": { + "password-rules": "minlength: 12; required: lower; required: upper; required: digit; required: [!@#$%^&*];" + }, + "key.harvard.edu": { + "password-rules": "minlength: 10; maxlength: 100; required: lower; required: upper; required: digit; allowed: [-@_#!&$`%*+()./,;~:{}|?>=<^[']];" + }, + "kfc.ca": { + "password-rules": "minlength: 6; maxlength: 15; required: lower; required: upper; required: digit; required: [!@#$%&?*];" + }, + "klm.com": { + "password-rules": "minlength: 8; maxlength: 12;" + }, + "kundenportal.edeka-smart.de": { + "password-rules": 'minlength: 8; maxlength: 16; required: digit; required: upper, lower; required: [!"\xA7$%&#];' + }, + "la-z-boy.com": { + "password-rules": "minlength: 6; maxlength: 15; required: lower, upper; required: digit;" + }, + "labcorp.com": { + "password-rules": "minlength: 8; maxlength: 20; required: upper; required: lower; required: digit; required: [!@#$%^&*];" + }, + "ladwp.com": { + "password-rules": "minlength: 8; maxlength: 20; required: digit; allowed: lower, upper;" + }, + "launtel.net.au": { + "password-rules": "minlength: 8; required: digit; required: digit; allowed: lower, upper;" + }, + "leetchi.com": { + "password-rules": 'minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&()*+,./:;<>?@"_];' + }, + "lepida.it": { + "password-rules": "minlength: 8; maxlength: 16; max-consecutive: 2; required: lower; required: upper; required: digit; required: [-!\"#$%&'()*+,.:;<=>?@[^_`{|}~]];" + }, + "lg.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [-!#$%&'()*+,.:;=?@[^_{|}~]];" + }, + "linearity.io": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: special;" + }, + "live.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; allowed: [-@_#!&$`%*+()./,;~:{}|?>=<^'[]];" + }, + "lloydsbank.co.uk": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: digit; allowed: upper;" + }, + "lowes.com": { + "password-rules": "minlength: 8; maxlength: 128; max-consecutive: 3; required: lower, upper; required: digit;" + }, + "loyalty.accor.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&=@];" + }, + "lsacsso.b2clogin.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit, [-!#$%&*?@^_];" + }, + "lufthansa.com": { + "password-rules": 'minlength: 8; maxlength: 32; required: lower; required: upper; required: digit; required: [!#$%&()*+,./:;<>?@"_];' + }, + "macys.com": { + "password-rules": "minlength: 7; maxlength: 16; allowed: lower, upper, digit, [~!@#$%^&*+`(){}[:;\"'<>?]];" + }, + "mailbox.org": { + "password-rules": 'minlength: 8; required: lower; required: upper; required: digit; allowed: [-!$"%&/()=*+#.,;:@?{}[]];' + }, + "makemytrip.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [@$!%*#?&];" + }, + "marriott.com": { + "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; allowed: [$!#&@?%=];" + }, + "maybank2u.com.my": { + "password-rules": "minlength: 8; maxlength: 12; max-consecutive: 2; required: lower; required: upper; required: digit; required: [-~!@#$%^&*_+=`|(){}[:;\"'<>,.?];" + }, + "medicare.gov": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [@!$%^*()];" + }, + "member.everbridge.net": { + "password-rules": "minlength: 8; required: lower, upper; required: digit; allowed: [!@#$%^&*()];" + }, + "metlife.com": { + "password-rules": "minlength: 6; maxlength: 20;" + }, + "microsoft.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: special;" + }, + "milogin.michigan.gov": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [@#$!~&];" + }, + "mintmobile.com": { + "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; required: special; allowed: [!#$%&()*+:;=@[^_`{}~]];" + }, + "mlb.com": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;" + }, + "mpv.tickets.com": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;" + }, + "museumofflight.org": { + "password-rules": "minlength: 8; maxlength: 15;" + }, + "my.konami.net": { + "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit;" + }, + "myaccess.dmdc.osd.mil": { + "password-rules": "minlength: 9; maxlength: 20; required: lower; required: upper; required: digit; allowed: [-@_#!&$`%*+()./,;~:{}|?>=<^'[]];" + }, + "mygoodtogo.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower, upper, digit;" + }, + "myhealthrecord.com": { + "password-rules": "minlength: 8; maxlength: 20; allowed: lower, upper, digit, [_.!$*=];" + }, + "mysavings.breadfinancial.com": { + "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit; required: [+_%@!$*~];" + }, + "mysedgwick.com": { + "password-rules": "minlength: 8; maxlength: 16; allowed: lower; required: upper; required: digit; required: [@#%^&+=!]; allowed: [-~_$.,;]" + }, + "mysubaru.com": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; allowed: [!#$%()*+,./:;=?@\\^`~];" + }, + "naver.com": { + "password-rules": "minlength: 6; maxlength: 16;" + }, + "nelnet.net": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit, [!@#$&*];" + }, + "netflix.com": { + "password-rules": "minlength: 4; maxlength: 60; required: lower, upper, digit; allowed: special;" + }, + "netgear.com": { + "password-rules": "minlength: 6; maxlength: 128; allowed: lower, upper, digit, [!@#$%^&*()];" + }, + "nowinstock.net": { + "password-rules": "minlength: 6; maxlength: 20; allowed: lower, upper, digit;" + }, + "order.wendys.com": { + "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; allowed: [!#$%&()*+/=?^_{}];" + }, + "ototoy.jp": { + "password-rules": "minlength: 8; allowed: upper,lower,digit,[- .=_];" + }, + "packageconciergeadmin.com": { + "password-rules": "minlength: 4; maxlength: 4; allowed: digit;" + }, + "paypal.com": { + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower, upper; required: digit, [!@#$%^&*()];" + }, + "payvgm.youraccountadvantage.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: special;" + }, + "pilotflyingj.com": { + "password-rules": "minlength: 7; required: digit; allowed: lower, upper;" + }, + "pixnet.cc": { + "password-rules": "minlength: 4; maxlength: 16; allowed: lower, upper;" + }, + "planetary.org": { + "password-rules": "minlength: 5; maxlength: 20; required: lower; required: upper; required: digit; allowed: ascii-printable;" + }, + "plazapremiumlounge.com": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; allowed: [!#$%&*,@^];" + }, + "portal.edd.ca.gov": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&()*@^];" + }, + "portals.emblemhealth.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&'()*+,./:;<>?@\\^_`{|}~[]];" + }, + "portlandgeneral.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [!#$%&*?@];" + }, + "poste.it": { + "password-rules": "minlength: 8; maxlength: 16; max-consecutive: 2; required: lower; required: upper; required: digit; required: special;" + }, + "posteo.de": { + "password-rules": 'minlength: 8; required: lower; required: upper; required: digit, [-~!#$%&_+=|(){}[:;"\u2019<>,.? ]];' + }, + "powells.com": { + "password-rules": 'minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: ["!@#$%^&*(){}[]];' + }, + "preferredhotels.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&()*+@^_];" + }, + "premier.ticketek.com.au": { + "password-rules": "minlength: 6; maxlength: 16;" + }, + "premierinn.com": { + "password-rules": "minlength: 8; required: upper; required: digit; allowed: lower;" + }, + "prepaid.bankofamerica.com": { + "password-rules": `minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [!@#$%^&*()+~{}'";:<>?];` + }, + "prestocard.ca": { + "password-rules": `minlength: 8; required: lower; required: upper; required: digit,[!"#$%&'()*+,<>?@];` + }, + "propelfuels.com": { + "password-rules": "minlength: 6; maxlength: 16;" + }, + "qdosstatusreview.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&@^];" + }, + "questdiagnostics.com": { + "password-rules": "minlength: 8; maxlength: 30; required: upper, lower; required: digit, [!#$%&()*+<>?@^_~];" + }, + "rejsekort.dk": { + "password-rules": "minlength: 7; maxlength: 15; required: lower; required: upper; required: digit;" + }, + "renaud-bray.com": { + "password-rules": "minlength: 8; maxlength: 38; allowed: upper,lower,digit;" + }, + "ring.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!@#$%^&*<>?];" + }, + "riteaid.com": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;" + }, + "robinhood.com": { + "password-rules": "minlength: 10;" + }, + "rogers.com": { + "password-rules": "minlength: 8; required: lower, upper; required: digit; required: [!@#$];" + }, + "ruc.dk": { + "password-rules": "minlength: 6; maxlength: 8; required: lower, upper; required: [-!#%&(){}*+;%/<=>?_];" + }, + "runescape.com": { + "password-rules": "minlength: 5; maxlength: 20; required: lower; required: upper; required: digit;" + }, + "ruten.com.tw": { + "password-rules": "minlength: 6; maxlength: 15; required: lower, upper;" + }, + "salslimo.com": { + "password-rules": "minlength: 8; maxlength: 50; required: upper; required: lower; required: digit; required: [!@#$&*];" + }, + "santahelenasaude.com.br": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [-!@#$%&*_+=<>];" + }, + "santander.de": { + "password-rules": "minlength: 8; maxlength: 12; required: lower, upper; required: digit; allowed: [-!#$%&'()*,.:;=?^{}];" + }, + "sbisec.co.jp": { + "password-rules": "minlength: 10; maxlength: 20; allowed: upper,lower,digit;" + }, + "secure-arborfcu.org": { + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [!#$%&'()+,.:?@[_`~]];" + }, + "secure.orclinic.com": { + "password-rules": "minlength: 6; maxlength: 15; required: lower; required: digit; allowed: ascii-printable;" + }, + "secure.snnow.ca": { + "password-rules": "minlength: 7; maxlength: 16; required: digit; allowed: lower, upper;" + }, + "sephora.com": { + "password-rules": "minlength: 6; maxlength: 12;" + }, + "serviziconsolari.esteri.it": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;" + }, + "servizioelettriconazionale.it": { + "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; required: [!#$%&*?@^_~];" + }, + "sfwater.org": { + "password-rules": "minlength: 10; maxlength: 30; required: digit; allowed: lower, upper, [!@#$%*()_+^}{:;?.];" + }, + "signin.ea.com": { + "password-rules": "minlength: 8; maxlength: 64; required: lower, upper; required: digit; allowed: [-!@#^&*=+;:];" + }, + "southwest.com": { + "password-rules": "minlength: 8; maxlength: 16; required: upper; required: digit; allowed: lower, [!@#$%^*(),.;:/\\];" + }, + "speedway.com": { + "password-rules": "minlength: 4; maxlength: 8; required: digit;" + }, + "spirit.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [!@#$%^&*()];" + }, + "splunk.com": { + "password-rules": "minlength: 8; maxlength: 64; required: lower; required: upper; required: digit; required: [-!@#$%&*_+=<>];" + }, + "ssa.gov": { + "password-rules": "required: lower; required: upper; required: digit; required: [~!@#$%^&*];" + }, + "store.nintendo.co.uk": { + "password-rules": "minlength: 8; maxlength: 20;" + }, + "store.nvidia.com": { + "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit; required: [-!@#$%^*~:;&><[{}|_+=?]];" + }, + "store.steampowered.com": { + "password-rules": "minlength: 6; required: lower; required: upper; required: digit; allowed: [~!@#$%^&*];" + }, + "successfactors.eu": { + "password-rules": "minlength: 8; maxlength: 18; required: lower; required: upper; required: digit,[-!\"#$%&'()*+,.:;<=>?@[^_`{|}~]];" + }, + "sulamericaseguros.com.br": { + "password-rules": "minlength: 6; maxlength: 6;" + }, + "sunlife.com": { + "password-rules": "minlength: 8; maxlength: 10; required: digit; required: lower, upper;" + }, + "t-mobile.net": { + "password-rules": "minlength: 8; maxlength: 16;" + }, + "target.com": { + "password-rules": "minlength: 8; maxlength: 20; required: lower, upper; required: digit, [-!\"#$%&'()*+,./:;=?@[\\^_`{|}~];" + }, + "tdscpc.gov.in": { + "password-rules": `minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [ &',;"];` + }, + "telekom-dienste.de": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [#$%&()*+,./<=>?@_{|}~];" + }, + "thameswater.co.uk": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;" + }, + "tix.soundrink.com": { + "password-rules": "minlength: 6; maxlength: 16;" + }, + "training.confluent.io": { + "password-rules": "minlength: 6; maxlength: 16; required: lower; required: upper; required: digit; allowed: [!#$%*@^_~];" + }, + "treasurer.mo.gov": { + "password-rules": "minlength: 8; maxlength: 26; required: lower; required: upper; required: digit; required: [!#$&];" + }, + "twitch.tv": { + "password-rules": "minlength: 8; maxlength: 71;" + }, + "twitter.com": { + "password-rules": "minlength: 8;" + }, + "ubisoft.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [-]; required: [!@#$%^&*()+];" + }, + "udel.edu": { + "password-rules": "minlength: 12; maxlength: 30; required: lower; required: upper; required: digit; required: [!@#$%^&*()+];" + }, + "user.ornl.gov": { + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower, upper; required: digit; allowed: [!#$%./_];" + }, + "usps.com": { + "password-rules": `minlength: 8; maxlength: 50; max-consecutive: 2; required: lower; required: upper; required: digit; allowed: [-!"#&'()+,./?@];` + }, + "vanguard.com": { + "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: digit;" + }, + "vanguardinvestor.co.uk": { + "password-rules": "minlength: 8; maxlength: 50; required: lower; required: upper; required: digit; required: digit;" + }, + "ventrachicago.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit, [!@#$%^];" + }, + "verizonwireless.com": { + "password-rules": "minlength: 8; maxlength: 20; required: lower, upper; required: digit; allowed: unicode;" + }, + "vetsfirstchoice.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; allowed: [?!@$%^+=&];" + }, + "vince.com": { + "password-rules": "minlength: 8; required: digit; required: lower; required: upper; required: [$%/(){}=?!.,_*|+~#[]];" + }, + "virginmobile.ca": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$@];" + }, + "visa.com": { + "password-rules": "minlength: 6; maxlength: 32;" + }, + "visabenefits-auth.axa-assistance.us": { + "password-rules": 'minlength: 8; required: lower; required: upper; required: digit; required: [!"#$%&()*,.:<>?@^{|}];' + }, + "vivo.com.br": { + "password-rules": "maxlength: 6; max-consecutive: 3; allowed: digit;" + }, + "volaris.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;" + }, + "wa.aaa.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: ascii-printable;" + }, + "walkhighlands.co.uk": { + "password-rules": "minlength: 9; maxlength: 15; required: lower; required: upper; required: digit; allowed: special;" + }, + "walmart.com": { + "password-rules": "allowed: lower, upper, digit, [-(~!@#$%^&*_+=`|(){}[:;\"'<>,.?]];" + }, + "waze.com": { + "password-rules": "minlength: 8; maxlength: 64; required: lower, upper, digit;" + }, + "wccls.org": { + "password-rules": "minlength: 4; maxlength: 16; allowed: lower, upper, digit;" + }, + "web.de": { + "password-rules": "minlength: 8; maxlength: 40; allowed: lower, upper, digit, [-<=>~!|()@#{}$%,.?^'&*_+`:;\"[]];" + }, + "wegmans.com": { + "password-rules": "minlength: 8; required: digit; required: upper,lower; required: [!#$%&*+=?@^];" + }, + "weibo.com": { + "password-rules": "minlength: 6; maxlength: 16;" + }, + "wellsfargo.com": { + "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit;" + }, + "wmata.com": { + "password-rules": 'minlength: 8; required: lower, upper; required: digit; required: digit; required: [-!@#$%^&*~/"()_=+\\|,.?[]];' + }, + "worldstrides.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [-!#$%&*+=?@^_~];" + }, + "wsj.com": { + "password-rules": "minlength: 5; maxlength: 15; required: digit; allowed: lower, upper, [-~!@#$^*_=`|(){}[:;\"'<>,.?]];" + }, + "xfinity.com": { + "password-rules": "minlength: 8; maxlength: 16; required: lower, upper; required: digit;" + }, + "xvoucher.com": { + "password-rules": "minlength: 11; required: upper; required: digit; required: [!@#$%&_];" + }, + "yatra.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&'()+,.:?@[_`~]];" + }, + "zara.com": { + "password-rules": "minlength: 8; required: lower; required: upper; required: digit;" + }, + "zdf.de": { + "password-rules": "minlength: 8; required: upper; required: digit; allowed: lower, special;" + }, + "zoom.us": { + "password-rules": "minlength: 8; maxlength: 32; max-consecutive: 6; required: lower; required: upper; required: digit;" } - } - - /** @type { PMData } */ - #data = { - credentials: [], - creditCards: [], - identities: [], - topContextData: undefined }; - /** - * @returns {import('../Form/matching').SupportedTypes} - */ - getCurrentInputType() { - throw new Error('Not implemented'); - } - addDuckAddressesToIdentities(identities) { - if (!this.hasLocalAddresses) return identities; - const newIdentities = []; - let { - privateAddress, - personalAddress - } = this.getLocalAddresses(); - privateAddress = (0, _autofillUtils.formatDuckAddress)(privateAddress); - personalAddress = (0, _autofillUtils.formatDuckAddress)(personalAddress); - - // Get the duck addresses in identities - const duckEmailsInIdentities = identities.reduce((duckEmails, _ref2) => { - let { - emailAddress: email - } = _ref2; - return email?.includes(_autofillUtils.ADDRESS_DOMAIN) ? duckEmails.concat(email) : duckEmails; - }, []); + // src/PasswordGenerator.js + var _previous; + var PasswordGenerator = class { + constructor() { + /** @type {string|null} */ + __privateAdd(this, _previous, null); + } + /** @returns {boolean} */ + get generated() { + return __privateGet(this, _previous) !== null; + } + /** @returns {string|null} */ + get password() { + return __privateGet(this, _previous); + } + /** @param {import('../packages/password').GenerateOptions} [params] */ + generate(params = {}) { + if (__privateGet(this, _previous)) { + return __privateGet(this, _previous); + } + __privateSet(this, _previous, generate({ ...params, rules: rules_default })); + return __privateGet(this, _previous); + } + }; + _previous = new WeakMap(); - // Only add the personal duck address to identities if the user hasn't - // already manually added it - if (!duckEmailsInIdentities.includes(personalAddress)) { - newIdentities.push({ - id: 'personalAddress', - emailAddress: personalAddress, - title: 'Block email trackers' + // src/Form/FormAnalyzer.js + var FormAnalyzer = class { + /** + * @param {HTMLElement} form + * @param {HTMLInputElement|HTMLSelectElement} input + * @param {Matching} [matching] + */ + constructor(form, input, matching) { + /** @type HTMLElement */ + __publicField(this, "form"); + /** @type Matching */ + __publicField(this, "matching"); + /** @type {undefined|boolean} */ + __publicField(this, "_isCCForm"); + this.form = form; + this.matching = matching || new Matching(matchingConfiguration); + this.autofillSignal = 0; + this.hybridSignal = 0; + this.signals = []; + this.evaluateElAttributes(input, 1, true); + form ? this.evaluateForm() : this.evaluatePage(); + return this; + } + /** + * Hybrid forms can be used for both login and signup + * @returns {boolean} + */ + get isHybrid() { + const areOtherSignalsWeak = Math.abs(this.autofillSignal) < 10; + return this.hybridSignal > 0 && areOtherSignalsWeak; + } + get isLogin() { + if (this.isHybrid) + return false; + return this.autofillSignal < 0; + } + get isSignup() { + if (this.isHybrid) + return false; + return this.autofillSignal >= 0; + } + /** + * Tilts the scoring towards Signup + * @param {number} strength + * @param {string} signal + * @returns {FormAnalyzer} + */ + increaseSignalBy(strength, signal) { + this.autofillSignal += strength; + this.signals.push(`${signal}: +${strength}`); + return this; + } + /** + * Tilts the scoring towards Login + * @param {number} strength + * @param {string} signal + * @returns {FormAnalyzer} + */ + decreaseSignalBy(strength, signal) { + this.autofillSignal -= strength; + this.signals.push(`${signal}: -${strength}`); + return this; + } + /** + * Increases the probability that this is a hybrid form (can be either login or signup) + * @param {number} strength + * @param {string} signal + * @returns {FormAnalyzer} + */ + increaseHybridSignal(strength, signal) { + this.hybridSignal += strength; + this.signals.push(`${signal} (hybrid): +${strength}`); + return this; + } + /** + * Updates the Login<->Signup signal according to the provided parameters + * @param {object} p + * @param {string} p.string - The string to check + * @param {number} p.strength - Strength of the signal + * @param {string} [p.signalType] - For debugging purposes, we give a name to the signal + * @param {boolean} [p.shouldFlip] - Flips the signals, i.e. when a link points outside. See below + * @param {boolean} [p.shouldCheckUnifiedForm] - Should check for login/signup forms + * @param {boolean} [p.shouldBeConservative] - Should use the conservative signup regex + * @returns {FormAnalyzer} + */ + updateSignal({ + string, + strength, + signalType = "generic", + shouldFlip = false, + shouldCheckUnifiedForm = false, + shouldBeConservative = false + }) { + if (!string || string.length > constants.TEXT_LENGTH_CUTOFF) + return this; + const matchesLogin = safeRegexTest(/current.?password/i, string) || safeRegexTest(this.matching.getDDGMatcherRegex("loginRegex"), string) || safeRegexTest(this.matching.getDDGMatcherRegex("resetPasswordLink"), string); + if (shouldCheckUnifiedForm && matchesLogin && safeRegexTest(this.matching.getDDGMatcherRegex("conservativeSignupRegex"), string)) { + this.increaseHybridSignal(strength, signalType); + return this; + } + const signupRegexToUse = this.matching.getDDGMatcherRegex(shouldBeConservative ? "conservativeSignupRegex" : "signupRegex"); + const matchesSignup = safeRegexTest(/new.?password/i, string) || safeRegexTest(signupRegexToUse, string); + if (shouldFlip) { + if (matchesLogin) + this.increaseSignalBy(strength, signalType); + if (matchesSignup) + this.decreaseSignalBy(strength, signalType); + } else { + if (matchesLogin) + this.decreaseSignalBy(strength, signalType); + if (matchesSignup) + this.increaseSignalBy(strength, signalType); + } + return this; + } + evaluateElAttributes(el, signalStrength = 3, isInput = false) { + Array.from(el.attributes).forEach((attr) => { + if (attr.name === "style") + return; + const attributeString = `${attr.name}=${attr.value}`; + this.updateSignal({ + string: attributeString, + strength: signalStrength, + signalType: `${el.name} attr: ${attributeString}`, + shouldCheckUnifiedForm: isInput + }); }); } - newIdentities.push({ - id: 'privateAddress', - emailAddress: privateAddress, - title: 'Block email trackers & hide address' - }); - return [...identities, ...newIdentities]; - } - - /** - * Stores init data coming from the tooltipHandler - * @param { InboundPMData } data - */ - storeLocalData(data) { - this.storeLocalCredentials(data.credentials); - data.creditCards.forEach(cc => delete cc.cardNumber && delete cc.cardSecurityCode); - // Store the full name as a separate field to simplify autocomplete - const updatedIdentities = data.identities.map(identity => ({ - ...identity, - fullName: (0, _formatters.formatFullName)(identity) - })); - // Add addresses - this.#data.identities = this.addDuckAddressesToIdentities(updatedIdentities); - this.#data.creditCards = data.creditCards; - - // Top autofill only - if (data.serializedInputContext) { - try { - this.#data.topContextData = JSON.parse(data.serializedInputContext); - } catch (e) { - console.error(e); - this.removeTooltip(); + evaluateUrl() { + const path = window.location.pathname; + const matchesLogin = safeRegexTest(this.matching.getDDGMatcherRegex("loginRegex"), path); + const matchesSignup = safeRegexTest(this.matching.getDDGMatcherRegex("conservativeSignupRegex"), path); + if (matchesLogin && matchesSignup) + return; + if (matchesLogin) { + this.decreaseSignalBy(1, "url matches login"); + } + if (matchesSignup) { + this.increaseSignalBy(1, "url matches signup"); + } + } + evaluatePageTitle() { + const pageTitle = document.title; + this.updateSignal({ string: pageTitle, strength: 2, signalType: `page title: ${pageTitle}`, shouldCheckUnifiedForm: true }); + } + evaluatePageHeadings() { + const headings = document.querySelectorAll("h1, h2, h3"); + headings.forEach((heading) => { + const textContent = removeExcessWhitespace(heading.textContent || ""); + this.updateSignal({ + string: textContent, + strength: 0.5, + signalType: `heading: ${textContent}`, + shouldCheckUnifiedForm: true, + shouldBeConservative: true + }); + }); + } + evaluatePage() { + this.evaluatePageTitle(); + this.evaluatePageHeadings(); + const buttons = document.querySelectorAll(this.matching.cssSelector("submitButtonSelector")); + buttons.forEach((button) => { + if (button instanceof HTMLButtonElement) { + if (!button.form && !button.closest("form")) { + this.evaluateElement(button); + this.evaluateElAttributes(button, 0.5); + } + } + }); + } + evaluateElement(el) { + const string = getTextShallow(el); + if (el.matches(this.matching.cssSelector("password"))) { + this.updateSignal({ + string: el.getAttribute("autocomplete") || el.getAttribute("name") || "", + strength: 5, + signalType: `explicit: ${el.getAttribute("autocomplete")}` + }); + return; + } + if (el.matches(this.matching.cssSelector("submitButtonSelector") + ", *[class*=button]")) { + let likelyASubmit = isLikelyASubmitButton(el, this.matching); + let shouldFlip = false; + if (likelyASubmit) { + this.form.querySelectorAll("input[type=submit], button[type=submit]").forEach( + (submit) => { + if (el.type !== "submit" && el !== submit) { + likelyASubmit = false; + } + } + ); + } else { + const thereIsASubmitButton = Boolean(this.form.querySelector("input[type=submit], button[type=submit]")); + const isSocialButton = /facebook|twitter|google|apple/i.test(string); + shouldFlip = thereIsASubmitButton && !isSocialButton; + } + const strength = likelyASubmit ? 20 : 4; + this.updateSignal({ string, strength, signalType: `button: ${string}`, shouldFlip }); + return; + } + if (el instanceof HTMLAnchorElement && el.href && el.getAttribute("href") !== "#" || (el.getAttribute("role") || "").toUpperCase() === "LINK" || el.matches("button[class*=secondary]")) { + let shouldFlip = true; + let strength = 1; + if (safeRegexTest(this.matching.getDDGMatcherRegex("resetPasswordLink"), string)) { + shouldFlip = false; + strength = 3; + } else if (safeRegexTest(this.matching.getDDGMatcherRegex("loginProvidersRegex"), string)) { + shouldFlip = false; + } + this.updateSignal({ string, strength, signalType: `external link: ${string}`, shouldFlip }); + } else { + this.updateSignal({ string, strength: 1, signalType: `generic: ${string}`, shouldCheckUnifiedForm: true }); } } - } + evaluateForm() { + this.evaluateUrl(); + this.evaluatePageTitle(); + this.evaluateElAttributes(this.form); + this.form.querySelectorAll(this.matching.cssSelector("safeUniversalSelector")).forEach((el) => { + const displayValue = window.getComputedStyle(el, null).getPropertyValue("display"); + if (displayValue !== "none") + this.evaluateElement(el); + }); + const relevantFields = this.form.querySelectorAll(this.matching.cssSelector("genericTextField")); + if (relevantFields.length >= 4) { + this.increaseSignalBy(relevantFields.length * 1.5, "many fields: it is probably not a login"); + } + if (this.autofillSignal === 0) { + this.evaluatePageHeadings(); + } + return this; + } + /** + * Tries to infer if it's a credit card form + * @returns {boolean} + */ + isCCForm() { + if (this._isCCForm !== void 0) + return this._isCCForm; + const formEl = this.form; + const ccFieldSelector = this.matching.joinCssSelectors("cc"); + if (!ccFieldSelector) { + this._isCCForm = false; + return this._isCCForm; + } + const hasCCSelectorChild = formEl.matches(ccFieldSelector) || formEl.querySelector(ccFieldSelector); + if (hasCCSelectorChild) { + this._isCCForm = true; + return this._isCCForm; + } + const hasCCAttribute = [...formEl.attributes].some( + ({ name, value }) => safeRegexTest(/(credit|payment).?card/i, `${name}=${value}`) + ); + if (hasCCAttribute) { + this._isCCForm = true; + return this._isCCForm; + } + const textMatches = formEl.textContent?.match(/(credit|payment).?card(.?number)?|ccv|security.?code|cvv|cvc|csc/ig); + const deDupedMatches = new Set(textMatches?.map((match) => match.toLowerCase())); + this._isCCForm = Boolean(textMatches && deDupedMatches.size > 1); + return this._isCCForm; + } + }; + var FormAnalyzer_default = FormAnalyzer; - /** - * Stores credentials locally - * @param {CredentialsObject[]} credentials - */ - storeLocalCredentials(credentials) { - credentials.forEach(cred => delete cred.password); - this.#data.credentials = credentials; - } - getTopContextData() { - return this.#data.topContextData; - } + // src/Form/logo-svg.js + var daxSvg = ` + + + + + + + + + + + + + +`.trim(); + var daxBase64 = `data:image/svg+xml;base64,${window.btoa(daxSvg)}`; + var daxGrayscaleSvg = ` + + + + + + +`.trim(); + var daxGrayscaleBase64 = `data:image/svg+xml;base64,${window.btoa(daxGrayscaleSvg)}`; + + // src/UI/img/ddgPasswordIcon.js + var ddgPasswordIconBase = "data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNS4zMzQgNi42NjdhMiAyIDAgMSAwIDAgNCAyIDIgMCAwIDAgMC00Wm0tLjY2NyAyYS42NjcuNjY3IDAgMSAxIDEuMzMzIDAgLjY2Ny42NjcgMCAwIDEtMS4zMzMgMFoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTQuNjY3IDRhNS4zMzMgNS4zMzMgMCAwIDAtNS4xODggNi41NzhsLTUuMjg0IDUuMjg0YS42NjcuNjY3IDAgMCAwLS4xOTUuNDcxdjNjMCAuMzY5LjI5OC42NjcuNjY3LjY2N2gyLjY2NmMuNzM3IDAgMS4zMzQtLjU5NyAxLjMzNC0xLjMzM1YxOGguNjY2Yy43MzcgMCAxLjMzNC0uNTk3IDEuMzM0LTEuMzMzdi0xLjMzNEgxMmMuMTc3IDAgLjM0Ni0uMDcuNDcxLS4xOTVsLjY4OC0uNjg4QTUuMzMzIDUuMzMzIDAgMSAwIDE0LjY2NyA0Wm0tNCA1LjMzM2E0IDQgMCAxIDEgMi41NTUgMy43MzIuNjY3LjY2NyAwIDAgMC0uNzEzLjE1bC0uNzg1Ljc4NUgxMGEuNjY3LjY2NyAwIDAgMC0uNjY3LjY2N3YySDhhLjY2Ny42NjcgMCAwIDAtLjY2Ny42NjZ2MS4zMzRoLTJ2LTIuMDU4bDUuMzY1LTUuMzY0YS42NjcuNjY3IDAgMCAwIC4xNjMtLjY3NyAzLjk5NiAzLjk5NiAwIDAgMS0uMTk0LTEuMjM1WiIgY2xpcC1ydWxlPSJldmVub2RkIi8+Cjwvc3ZnPgo="; + var ddgPasswordIconFilled = "data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjNzY0MzEwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNS4yNSA2Ljc1YTIgMiAwIDEgMCAwIDQgMiAyIDAgMCAwIDAtNFptLS41IDJhLjUuNSAwIDEgMSAxIDAgLjUuNSAwIDAgMS0xIDBaIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz4KICAgIDxwYXRoIGZpbGw9IiM3NjQzMTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTE0LjYyNSA0YTUuMzc1IDUuMzc1IDAgMCAwLTUuMjQ0IDYuNTU5bC01LjE2MSA1LjE2YS43NS43NSAwIDAgMC0uMjIuNTMxdjNjMCAuNDE0LjMzNi43NS43NS43NWgyLjk5N2MuNTU0IDAgMS4wMDMtLjQ1IDEuMDAzLTEuMDAzVjE4aC45OTdjLjU1NCAwIDEuMDAzLS40NSAxLjAwMy0xLjAwM1YxNS41SDEyYS43NS43NSAwIDAgMCAuNTMtLjIybC43MS0uNzFBNS4zOCA1LjM4IDAgMCAwIDIwIDkuMzc1IDUuMzc1IDUuMzc1IDAgMCAwIDE0LjYyNSA0Wk0xMC43NSA5LjM3NWEzLjg3NSAzLjg3NSAwIDEgMSAyLjU0IDMuNjQuNzUuNzUgMCAwIDAtLjc4OS4xNzNMMTEuNjkgMTRIMTBhLjc1Ljc1IDAgMCAwLS43NS43NXYxLjc1SDhhLjc1Ljc1IDAgMCAwLS43NS43NXYxLjI1SDUuNXYtMS45NGw1LjI0OC01LjI0OGEuNzUuNzUgMCAwIDAgLjE4NC0uNzU4IDMuODcyIDMuODcyIDAgMCAxLS4xODItMS4xNzlaIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz4KPC9zdmc+Cg=="; + var ddgPasswordGenIconBase = "data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjMDAwIiBkPSJNOC4wNDcgNC42MjVDNy45MzcgNC4xMjUgNy44NjIgNCA3LjUgNGMtLjM2MiAwLS40MzguMTI1LS41NDcuNjI1LS4wNjguMzEtLjE3NyAxLjMzOC0uMjUxIDIuMDc3LS43MzguMDc0LTEuNzY3LjE4My0yLjA3Ny4yNTEtLjUuMTEtLjYyNS4xODQtLjYyNS41NDcgMCAuMzYyLjEyNS40MzcuNjI1LjU0Ny4zMS4wNjcgMS4zMzYuMTc3IDIuMDc0LjI1LjA3My43NjcuMTg1IDEuODQyLjI1NCAyLjA3OC4xMS4zNzUuMTg1LjYyNS41NDcuNjI1LjM2MiAwIC40MzgtLjEyNS41NDctLjYyNS4wNjgtLjMxLjE3Ny0xLjMzNi4yNS0yLjA3NC43NjctLjA3MyAxLjg0Mi0uMTg1IDIuMDc4LS4yNTQuMzc1LS4xMS42MjUtLjE4NS42MjUtLjU0NyAwLS4zNjMtLjEyNS0uNDM4LS42MjUtLjU0Ny0uMzEtLjA2OC0xLjMzOS0uMTc3LTIuMDc3LS4yNTEtLjA3NC0uNzM5LS4xODMtMS43NjctLjI1MS0yLjA3N1oiLz4KICAgIDxwYXRoIGZpbGw9IiMwMDAiIGQ9Ik0xNC42ODEgNS4xOTljLS43NjYgMC0xLjQ4Mi4yMS0yLjA5My41NzhhLjYzNi42MzYgMCAwIDEtLjY1NS0xLjA5IDUuMzQgNS4zNCAwIDEgMSAxLjMwMiA5LjcyMmwtLjc3NS43NzZhLjYzNi42MzYgMCAwIDEtLjQ1LjE4NmgtMS4zOTh2MS42NWMwIC40OTMtLjQuODkzLS44OTMuODkzSDguNTc4djEuMTQxYzAgLjQ5NC0uNC44OTMtLjg5NC44OTNINC42MzZBLjYzNi42MzYgMCAwIDEgNCAxOS4zMTNWMTYuMjZjMC0uMTY5LjA2Ny0uMzMuMTg2LS40NWw1LjU2Mi01LjU2MmEuNjM2LjYzNiAwIDEgMSAuOS45bC01LjM3NiA1LjM3NXYyLjE1M2gyLjAzNHYtMS4zOTljMC0uMzUuMjg1LS42MzYuNjM2LS42MzZIOS4zNHYtMS45MDdjMC0uMzUxLjI4NC0uNjM2LjYzNS0uNjM2aDEuNzcxbC44NjQtLjg2M2EuNjM2LjYzNiAwIDAgMSAuNjY4LS4xNDcgNC4wNjkgNC4wNjkgMCAxIDAgMS40MDItNy44OVoiLz4KICAgIDxwYXRoIGZpbGw9IiMwMDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTEzLjYyNSA4LjQ5OWExLjg3NSAxLjg3NSAwIDEgMSAzLjc1IDAgMS44NzUgMS44NzUgMCAwIDEtMy43NSAwWm0xLjg3NS0uNjI1YS42MjUuNjI1IDAgMSAwIDAgMS4yNS42MjUuNjI1IDAgMCAwIDAtMS4yNVoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZmlsbD0iIzAwMCIgZD0iTTQuNjI1IDEyLjEyNWEuNjI1LjYyNSAwIDEgMCAwLTEuMjUuNjI1LjYyNSAwIDAgMCAwIDEuMjVaIi8+Cjwvc3ZnPgo="; + var ddgPasswordGenIconFilled = "data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjNzY0MzEwIiBkPSJNOC4wNDcgNC42MjVDNy45MzcgNC4xMjUgNy44NjIgNCA3LjUgNGMtLjM2MiAwLS40MzguMTI1LS41NDcuNjI1LS4wNjguMzEtLjE3NyAxLjMzOS0uMjUxIDIuMDc3LS43MzguMDc0LTEuNzY3LjE4My0yLjA3Ny4yNTEtLjUuMTEtLjYyNS4xODUtLjYyNS41NDcgMCAuMzYyLjEyNS40MzcuNjI1LjU0Ny4zMS4wNjcgMS4zMzYuMTc3IDIuMDc0LjI1LjA3My43NjcuMTg1IDEuODQyLjI1NCAyLjA3OC4xMS4zNzUuMTg1LjYyNS41NDcuNjI1LjM2MiAwIC40MzgtLjEyNS41NDctLjYyNS4wNjgtLjMxLjE3Ny0xLjMzNi4yNS0yLjA3NC43NjctLjA3MyAxLjg0Mi0uMTg1IDIuMDc4LS4yNTQuMzc1LS4xMS42MjUtLjE4NS42MjUtLjU0NyAwLS4zNjItLjEyNS0uNDM4LS42MjUtLjU0Ny0uMzEtLjA2OC0xLjMzOS0uMTc3LTIuMDc3LS4yNTEtLjA3NC0uNzM4LS4xODMtMS43NjctLjI1MS0yLjA3N1oiLz4KICAgIDxwYXRoIGZpbGw9IiM3NjQzMTAiIGQ9Ik0xNC42ODEgNS4xOTljLS43NjYgMC0xLjQ4Mi4yMTEtMi4wOTMuNTc4YS42MzYuNjM2IDAgMCAxLS42NTUtMS4wOSA1LjM0IDUuMzQgMCAxIDEgMS4zMDIgOS43MjNsLS43NzUuNzc1YS42MzYuNjM2IDAgMCAxLS40NS4xODZoLTEuMzk4djEuNjVjMCAuNDkzLS40Ljg5My0uODkzLjg5M0g4LjU3OHYxLjE0MWMwIC40OTQtLjQuODk0LS44OTQuODk0SDQuNjM2QS42MzYuNjM2IDAgMCAxIDQgMTkuMzEzVjE2LjI2YS42NC42NCAwIDAgMSAuMTg2LS40NWw1LjU2Mi01LjU2MmEuNjM2LjYzNiAwIDEgMSAuOS45bC01LjM3NiA1LjM3NXYyLjE1M2gyLjAzNHYtMS4zOTljMC0uMzUuMjg1LS42MzYuNjM2LS42MzZIOS4zNHYtMS45MDdjMC0uMzUuMjg0LS42MzYuNjM1LS42MzZoMS43NzFsLjg2NC0uODYzYS42MzYuNjM2IDAgMCAxIC42NjgtLjE0NyA0LjA2OSA0LjA2OSAwIDEgMCAxLjQwMi03Ljg5WiIvPgogICAgPHBhdGggZmlsbD0iIzc2NDMxMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTMuNjI1IDguNWExLjg3NSAxLjg3NSAwIDEgMSAzLjc1IDAgMS44NzUgMS44NzUgMCAwIDEtMy43NSAwWm0xLjg3NS0uNjI2YS42MjUuNjI1IDAgMSAwIDAgMS4yNS42MjUuNjI1IDAgMCAwIDAtMS4yNVoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZmlsbD0iIzc2NDMxMCIgZD0iTTQuNjI1IDEyLjEyNWEuNjI1LjYyNSAwIDEgMCAwLTEuMjUuNjI1LjYyNSAwIDAgMCAwIDEuMjVaIi8+Cjwvc3ZnPgo="; + + // src/InputTypes/CreditCard.js + var _data4; + var CreditCardTooltipItem = class { + /** @param {CreditCardObject} data */ + constructor(data) { + /** @type {CreditCardObject} */ + __privateAdd(this, _data4, void 0); + __publicField(this, "id", () => String(__privateGet(this, _data4).id)); + __publicField(this, "labelMedium", (_) => __privateGet(this, _data4).title); + __publicField(this, "labelSmall", (_) => __privateGet(this, _data4).displayNumber); + __privateSet(this, _data4, data); + } + }; + _data4 = new WeakMap(); + + // src/InputTypes/Identity.js + var _data5; + var IdentityTooltipItem = class { + /** @param {IdentityObject} data */ + constructor(data) { + /** @type {IdentityObject} */ + __privateAdd(this, _data5, void 0); + __publicField(this, "id", () => String(__privateGet(this, _data5).id)); + __publicField(this, "labelMedium", (subtype) => { + if (subtype === "addressCountryCode") { + return getCountryDisplayName("en", __privateGet(this, _data5).addressCountryCode || ""); + } + if (__privateGet(this, _data5).id === "privateAddress") { + return "Generate Private Duck Address"; + } + return __privateGet(this, _data5)[subtype]; + }); + __publicField(this, "labelSmall", (_) => { + return __privateGet(this, _data5).title; + }); + __privateSet(this, _data5, data); + } + label(subtype) { + if (__privateGet(this, _data5).id === "privateAddress") { + return __privateGet(this, _data5)[subtype]; + } + return null; + } + }; + _data5 = new WeakMap(); - /** - * @deprecated use `availableInputTypes.credentials` directly instead - * @returns {boolean} - */ - get hasLocalCredentials() { - return this.#data.credentials.length > 0; - } - getLocalCredentials() { - return this.#data.credentials.map(cred => { - const { - password, - ...rest - } = cred; - return rest; - }); - } - /** - * @deprecated use `availableInputTypes.identities` directly instead - * @returns {boolean} - */ - get hasLocalIdentities() { - return this.#data.identities.length > 0; - } - getLocalIdentities() { - return this.#data.identities; - } + // src/Form/inputTypeConfig.js + var getIdentitiesIcon = (input, { device }) => { + if (!canBeInteractedWith(input)) + return ""; + const { isDDGApp, isFirefox, isExtension } = device.globalConfig; + const subtype = getInputSubtype(input); + if (device.inContextSignup?.isAvailable(subtype)) { + if (isDDGApp || isFirefox) { + return daxGrayscaleBase64; + } else if (isExtension) { + return chrome.runtime.getURL("img/logo-small-grayscale.svg"); + } + } + if (subtype === "emailAddress" && device.isDeviceSignedIn()) { + if (isDDGApp || isFirefox) { + return daxBase64; + } else if (isExtension) { + return chrome.runtime.getURL("img/logo-small.svg"); + } + } + return ""; + }; + var getIdentitiesAlternateIcon = (input, { device }) => { + if (!canBeInteractedWith(input)) + return ""; + const { isDDGApp, isFirefox, isExtension } = device.globalConfig; + const subtype = getInputSubtype(input); + const isIncontext = device.inContextSignup?.isAvailable(subtype); + const isEmailProtection = subtype === "emailAddress" && device.isDeviceSignedIn(); + if (isIncontext || isEmailProtection) { + if (isDDGApp || isFirefox) { + return daxBase64; + } else if (isExtension) { + return chrome.runtime.getURL("img/logo-small.svg"); + } + } + return ""; + }; + var canBeInteractedWith = (input) => !input.readOnly && !input.disabled; + var canBeAutofilled = async (input, device) => { + if (!canBeInteractedWith(input)) + return false; + const mainType = getInputMainType(input); + const subtype = getInputSubtype(input); + await device.settings.populateDataIfNeeded({ mainType, subtype }); + const canAutofill = device.settings.canAutofillType({ mainType, subtype }, device.inContextSignup); + return Boolean(canAutofill); + }; + var inputTypeConfig = { + /** @type {CredentialsInputTypeConfig} */ + credentials: { + type: "credentials", + displayName: "passwords", + getIconBase: (input, { device }) => { + if (!canBeInteractedWith(input)) + return ""; + if (device.settings.featureToggles.inlineIcon_credentials) { + const subtype = getInputSubtype(input); + const variant = getInputVariant(input); + if (subtype === "password" && variant === "new") { + return ddgPasswordGenIconBase; + } + return ddgPasswordIconBase; + } + return ""; + }, + getIconFilled: (input, { device }) => { + if (device.settings.featureToggles.inlineIcon_credentials) { + const subtype = getInputSubtype(input); + const variant = getInputVariant(input); + if (subtype === "password" && variant === "new") { + return ddgPasswordGenIconFilled; + } + return ddgPasswordIconFilled; + } + return ""; + }, + getIconAlternate: () => "", + shouldDecorate: async (input, { isLogin, isHybrid, device }) => { + const subtype = getInputSubtype(input); + const variant = getInputVariant(input); + if (device.settings.featureToggles.password_generation) { + if (subtype === "password" && variant === "new") { + return canBeInteractedWith(input); + } + } + if (isLogin || isHybrid || variant === "current") { + return canBeAutofilled(input, device); + } + return false; + }, + dataType: "Credentials", + tooltipItem: (data) => createCredentialsTooltipItem(data) + }, + /** @type {CreditCardsInputTypeConfig} */ + creditCards: { + type: "creditCards", + displayName: "credit cards", + getIconBase: () => "", + getIconFilled: () => "", + getIconAlternate: () => "", + shouldDecorate: async (input, { device }) => { + return canBeAutofilled(input, device); + }, + dataType: "CreditCards", + tooltipItem: (data) => new CreditCardTooltipItem(data) + }, + /** @type {IdentitiesInputTypeConfig} */ + identities: { + type: "identities", + displayName: "identities", + getIconBase: getIdentitiesIcon, + getIconFilled: getIdentitiesIcon, + getIconAlternate: getIdentitiesAlternateIcon, + shouldDecorate: async (input, { device }) => { + return canBeAutofilled(input, device); + }, + dataType: "Identities", + tooltipItem: (data) => new IdentityTooltipItem(data) + }, + /** @type {UnknownInputTypeConfig} */ + unknown: { + type: "unknown", + displayName: "", + getIconBase: () => "", + getIconFilled: () => "", + getIconAlternate: () => "", + shouldDecorate: async () => false, + dataType: "", + tooltipItem: (_data7) => { + throw new Error("unreachable - setting tooltip to unknown field type"); + } + } + }; + var getInputConfig = (input) => { + const inputType = getInputType(input); + return getInputConfigFromType(inputType); + }; + var getInputConfigFromType = (inputType) => { + const inputMainType = getMainTypeFromType(inputType); + return inputTypeConfig[inputMainType]; + }; + var isFieldDecorated = (input) => { + return input.hasAttribute(constants.ATTR_INPUT_TYPE); + }; - /** - * @deprecated use `availableInputTypes.creditCards` directly instead - * @returns {boolean} - */ - get hasLocalCreditCards() { - return this.#data.creditCards.length > 0; - } - /** @return {CreditCardObject[]} */ - getLocalCreditCards() { - return this.#data.creditCards; - } - async startInit() { - if (this.isInitializationStarted) return; - this.alreadyInitialized = true; - await this.settings.refresh(); - this.addDeviceListeners(); - await this.setupAutofill(); - this.uiController = this.createUIController(); + // src/Form/inputStyles.js + var getIcon = (input, form, type = "base") => { + const config = getInputConfig(input); + if (type === "base") { + return config.getIconBase(input, form); + } + if (type === "filled") { + return config.getIconFilled(input, form); + } + if (type === "alternate") { + return config.getIconAlternate(input, form); + } + return ""; + }; + var getBasicStyles = (input, icon) => ({ + // Height must be > 0 to account for fields initially hidden + "background-size": `auto ${input.offsetHeight <= 30 && input.offsetHeight > 0 ? "100%" : "24px"}`, + "background-position": "center right", + "background-repeat": "no-repeat", + "background-origin": "content-box", + "background-image": `url(${icon})`, + "transition": "background 0s" + }); + var getIconStylesBase = (input, form) => { + const icon = getIcon(input, form); + if (!icon) + return {}; + return getBasicStyles(input, icon); + }; + var getIconStylesAlternate = (input, form) => { + const icon = getIcon(input, form, "alternate"); + if (!icon) + return {}; + return { + ...getBasicStyles(input, icon) + }; + }; + var getIconStylesAutofilled = (input, form) => { + const icon = getIcon(input, form, "filled"); + const iconStyle = icon ? getBasicStyles(input, icon) : {}; + return { + ...iconStyle, + "background-color": "#F8F498", + "color": "#333333" + }; + }; - // this is the temporary measure to support windows whilst we still have 'setupAutofill' - // eventually all interfaces will use this - if (!this.settings.enabled) { - return; + // src/Form/Form.js + var { + ATTR_AUTOFILL, + ATTR_INPUT_TYPE: ATTR_INPUT_TYPE2, + MAX_INPUTS_PER_FORM, + MAX_FORM_RESCANS + } = constants; + var Form = class { + /** + * @param {HTMLElement} form + * @param {HTMLInputElement|HTMLSelectElement} input + * @param {import("../DeviceInterface/InterfacePrototype").default} deviceInterface + * @param {import("../Form/matching").Matching} [matching] + * @param {Boolean} [shouldAutoprompt] + */ + constructor(form, input, deviceInterface, matching, shouldAutoprompt = false) { + /** @type {import("../Form/matching").Matching} */ + __publicField(this, "matching"); + /** @type {HTMLElement} */ + __publicField(this, "form"); + /** @type {HTMLInputElement | null} */ + __publicField(this, "activeInput"); + this.form = form; + this.matching = matching || createMatching(); + this.formAnalyzer = new FormAnalyzer_default(form, input, matching); + this.device = deviceInterface; + this.inputs = { + all: /* @__PURE__ */ new Set(), + credentials: /* @__PURE__ */ new Set(), + creditCards: /* @__PURE__ */ new Set(), + identities: /* @__PURE__ */ new Set(), + unknown: /* @__PURE__ */ new Set() + }; + this.touched = /* @__PURE__ */ new Set(); + this.listeners = /* @__PURE__ */ new Set(); + this.activeInput = null; + this.isAutofilling = false; + this.submitHandlerExecuted = false; + this.shouldPromptToStoreData = deviceInterface.settings.featureToggles.credentials_saving; + this.shouldAutoSubmit = this.device.globalConfig.isMobileApp; + this.intObs = new IntersectionObserver((entries) => { + for (const entry of entries) { + if (!entry.isIntersecting) + this.removeTooltip(); + } + }); + this.rescanCount = 0; + this.mutObsConfig = { childList: true, subtree: true }; + this.mutObs = new MutationObserver( + (records) => { + const anythingRemoved = records.some((record) => record.removedNodes.length > 0); + if (anythingRemoved) { + if (!this.form.isConnected) { + this.destroy(); + return; + } + if ([...this.inputs.all].some((input2) => !input2.isConnected)) { + this.mutObs.disconnect(); + window.requestIdleCallback(() => { + this.formAnalyzer = new FormAnalyzer_default(this.form, input, this.matching); + this.recategorizeAllInputs(); + }); + } + } + } + ); + this.initFormListeners(); + this.categorizeInputs(); + this.logFormInfo(); + if (shouldAutoprompt) { + this.promptLoginIfNeeded(); + } } - await this.setupSettingsPage(); - await this.postInit(); - if (this.settings.featureToggles.credentials_saving) { - (0, _initFormSubmissionsApi.initFormSubmissionsApi)(this.scanner.forms, this.scanner.matching); + get isLogin() { + return this.formAnalyzer.isLogin; } - } - async init() { - // bail very early if we can - const isEnabledInitiallyViaConfig = (0, _autofillUtils.autofillEnabled)(this.globalConfig); - if (!isEnabledInitiallyViaConfig) { - return; + get isSignup() { + return this.formAnalyzer.isSignup; } - const handler = async () => { - if (document.readyState === 'complete') { - window.removeEventListener('load', handler); - document.removeEventListener('readystatechange', handler); - await this.startInit(); + get isHybrid() { + return this.formAnalyzer.isHybrid; + } + get isCCForm() { + return this.formAnalyzer.isCCForm(); + } + logFormInfo() { + if (!shouldLog()) + return; + console.log(`Form type: %c${this.getFormType()}`, "font-weight: bold"); + console.log("Signals: ", this.formAnalyzer.signals); + console.log("Wrapping element: ", this.form); + console.log("Inputs: ", this.inputs); + console.log("Submit Buttons: ", this.submitButtons); + } + getFormType() { + if (this.isHybrid) + return `hybrid (hybrid score: ${this.formAnalyzer.hybridSignal}, score: ${this.formAnalyzer.autofillSignal})`; + if (this.isLogin) + return `login (score: ${this.formAnalyzer.autofillSignal}, hybrid score: ${this.formAnalyzer.hybridSignal})`; + if (this.isSignup) + return `signup (score: ${this.formAnalyzer.autofillSignal}, hybrid score: ${this.formAnalyzer.hybridSignal})`; + return "something went wrong"; + } + /** + * Checks if the form element contains the activeElement or the event target + * @return {boolean} + * @param {KeyboardEvent | null} [e] + */ + hasFocus(e) { + return this.form.contains(getActiveElement()) || this.form.contains( + /** @type HTMLElement */ + e?.target + ); + } + submitHandler(via = "unknown") { + if (this.device.globalConfig.isDDGTestMode) { + console.log("Form.submitHandler via:", via, this); } - }; - if (document.readyState === 'complete') { - await this.startInit(); - } else { - window.addEventListener('load', handler); - document.addEventListener('readystatechange', handler); + if (this.submitHandlerExecuted) + return; + const values = this.getValuesReadyForStorage(); + this.device.postSubmit?.(values, this); + this.submitHandlerExecuted = true; } - } - postInit() { - const cleanup = this.scanner.init(); - this.addLogoutListener(() => { - cleanup('Logged out'); - if (this.globalConfig.isDDGDomain) { - (0, _autofillUtils.notifyWebApp)({ - deviceSignedIn: { - value: false - } + /** + * Reads the values from the form without preparing to store them + * @return {InternalDataStorageObject} + */ + getRawValues() { + const formValues = [...this.inputs.credentials, ...this.inputs.identities, ...this.inputs.creditCards].reduce((output, inputEl) => { + const mainType = getInputMainType(inputEl); + const subtype = getInputSubtype(inputEl); + let value = inputEl.value || output[mainType]?.[subtype]; + if (subtype === "addressCountryCode") { + value = inferCountryCodeFromElement(inputEl); + } + if (subtype === "password" && value?.length <= 3) { + value = void 0; + } + if (value) { + output[mainType][subtype] = value; + } + return output; + }, { credentials: {}, creditCards: {}, identities: {} }); + if (!formValues.credentials.username && !formValues.identities.emailAddress) { + const hiddenFields = ( + /** @type [HTMLInputElement] */ + [...this.form.querySelectorAll("input[type=hidden]")] + ); + const probableField = hiddenFields.find((field) => { + const regex = new RegExp("email|" + this.matching.getDDGMatcherRegex("username")?.source); + const attributeText = field.id + " " + field.name; + return safeRegexTest(regex, attributeText); }); + if (probableField?.value) { + formValues.credentials.username = probableField.value; + } else if ( + // If a form has phone + password(s) fields, save the phone as username + formValues.identities.phone && this.inputs.all.size - this.inputs.unknown.size < 4 + ) { + formValues.credentials.username = formValues.identities.phone; + } else { + this.form.querySelectorAll(this.matching.cssSelector("safeUniversalSelector")).forEach((el) => { + const elText = getTextShallow(el); + if (elText.length > 70) + return; + const emailOrUsername = elText.match( + // https://www.emailregex.com/ + /[a-zA-Z\d.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z\d-]+(?:\.[a-zA-Z\d-]+)*/ + )?.[0]; + if (emailOrUsername) { + formValues.credentials.username = emailOrUsername; + } + }); + } } - }); - } - - /** - * @deprecated This was a port from the macOS implementation so the API may not be suitable for all - * @returns {Promise} - */ - async getSelectedCredentials() { - throw new Error('`getSelectedCredentials` not implemented'); - } - isTestMode() { - return this.globalConfig.isDDGTestMode; - } - - /** - * This indicates an item was selected on Desktop, and we should try to autofill - * - * Note: When we're in a top-frame scenario, like on like macOS & Windows in the webview, - * this method gets overridden {@see WindowsOverlayDeviceInterface} {@see AppleOverlayDeviceInterface} - * - * @param {IdentityObject|CreditCardObject|CredentialsObject|{email:string, id: string}} data - * @param {string} type - */ - async selectedDetail(data, type) { - const form = this.activeForm; - if (!form) { - return; + return formValues; } - - // are we autofilling email? - if (type === 'email' && 'email' in data) { - form.autofillEmail(data.email); - } else { - form.autofillData(data, type); + /** + * Return form values ready for storage + * @returns {DataStorageObject} + */ + getValuesReadyForStorage() { + const formValues = this.getRawValues(); + return prepareFormValuesForStorage(formValues); } - const isPrivateAddress = data.id === 'privateAddress'; - /** - * This is desktop only: was it a private address? if so, save it with - * the trigger 'emailProtection' so that native sides can use it + * Determine if the form has values we want to store in the device + * @param {DataStorageObject} [values] + * @return {boolean} */ - if (isPrivateAddress) { - this.refreshAlias(); - if ('emailAddress' in data && data.emailAddress) { - this.emailProtection.storeReceived(data.emailAddress); - - /** @type {DataStorageObject} */ - const formValues = { - credentials: { - username: data.emailAddress, - autogenerated: true - } - }; - this.storeFormData(formValues, 'emailProtection'); + hasValues(values) { + const { credentials, creditCards, identities } = values || this.getValuesReadyForStorage(); + return Boolean(credentials || creditCards || identities); + } + async removeTooltip() { + const tooltip = this.device.isTooltipActive(); + if (this.isAutofilling || !tooltip) { + return; } + await this.device.removeTooltip(); + this.intObs?.disconnect(); } - await this.removeTooltip(); - } - - /** - * Before the DataWebTooltip opens, we collect the data based on the config.type - * @param {InputTypeConfigs} config - * @param {import('../Form/matching').SupportedTypes} inputType - * @param {TopContextData} [data] - * @returns {(CredentialsObject|CreditCardObject|IdentityObject)[]} - */ - dataForAutofill(config, inputType, data) { - const subtype = (0, _matching.getSubtypeFromType)(inputType); - if (config.type === 'identities') { - return this.getLocalIdentities().filter(identity => !!identity[subtype]); - } - if (config.type === 'creditCards') { - return this.getLocalCreditCards(); - } - if (config.type === 'credentials') { - if (data) { - if (Array.isArray(data.credentials) && data.credentials.length > 0) { - return data.credentials; - } else { - return this.getLocalCredentials().filter(cred => !!cred[subtype] || subtype === 'password' || cred.id === _Credentials.PROVIDER_LOCKED); - } + showingTooltip(input) { + this.intObs?.observe(input); + } + removeInputHighlight(input) { + if (!input.classList.contains("ddg-autofilled")) + return; + removeInlineStyles(input, getIconStylesAutofilled(input, this)); + removeInlineStyles(input, { "cursor": "pointer" }); + input.classList.remove("ddg-autofilled"); + this.addAutofillStyles(input); + } + resetIconStylesToInitial() { + const input = this.activeInput; + if (input) { + const initialStyles = getIconStylesBase(input, this); + addInlineStyles(input, initialStyles); } } - return []; - } - - /** - * @param {object} params - * @param {import("../Form/Form").Form} params.form - * @param {HTMLInputElement} params.input - * @param {{ x: number; y: number; } | null} params.click - * @param {import('../deviceApiCalls/__generated__/validators-ts').GetAutofillDataRequest['trigger']} params.trigger - * @param {import('../UI/controllers/UIController.js').AttachArgs["triggerMetaData"]} params.triggerMetaData - */ - attachTooltip(params) { - const { - form, - input, - click, - trigger - } = params; - // Avoid flashing tooltip from background tabs on macOS - if (document.visibilityState !== 'visible' && trigger !== 'postSignup') return; - // Only autoprompt on mobile devices - if (trigger === 'autoprompt' && !this.globalConfig.isMobileApp) return; - // Only fire autoprompt once - if (trigger === 'autoprompt' && this.autopromptFired) return; - form.activeInput = input; - this.activeForm = form; - const inputType = (0, _matching.getInputType)(input); - - /** @type {PosFn} */ - const getPosition = () => { - // In extensions, the tooltip is centered on the Dax icon - const alignLeft = this.globalConfig.isApp || this.globalConfig.isWindows; - return alignLeft ? input.getBoundingClientRect() : (0, _autofillUtils.getDaxBoundingBox)(input); - }; - - // todo: this will be migrated to use NativeUIController soon - if (this.globalConfig.isMobileApp && inputType === 'identities.emailAddress') { - this.getAlias().then(alias => { - if (alias) { - form.autofillEmail(alias); - /** - * We're on mobile here, so we just record the email received. - * Then later in the form submission we can compare the values - */ - this.emailProtection.storeReceived(alias); - } else { - form.activeInput?.focus(); + removeAllHighlights(e, dataType) { + if (e && !e.isTrusted) + return; + this.resetShouldPromptToStoreData(); + this.execOnInputs((input) => this.removeInputHighlight(input), dataType); + } + removeInputDecoration(input) { + removeInlineStyles(input, getIconStylesBase(input, this)); + removeInlineStyles(input, getIconStylesAlternate(input, this)); + input.removeAttribute(ATTR_AUTOFILL); + input.removeAttribute(ATTR_INPUT_TYPE2); + } + removeAllDecorations() { + this.execOnInputs((input) => this.removeInputDecoration(input)); + this.listeners.forEach(({ el, type, fn, opts }) => el.removeEventListener(type, fn, opts)); + } + redecorateAllInputs() { + this.removeAllDecorations(); + this.execOnInputs((input) => { + if (input instanceof HTMLInputElement) { + this.decorateInput(input); } - - // Update data from native-side in case the `getAlias` call - // has included a successful in-context signup - this.updateForStateChange(); - this.onFinishedAutofill(); }); - return; } - - /** @type {TopContextData} */ - const topContextData = { - inputType - }; - - // Allow features to append/change top context data - // for example, generated passwords may get appended here - const processedTopContext = this.preAttachTooltip(topContextData, input, form); - this.uiController?.attach({ - input, - form, - click, - getPosition, - topContextData: processedTopContext, - device: this, - trigger, - triggerMetaData: params.triggerMetaData - }); - if (trigger === 'autoprompt') { - this.autopromptFired = true; + /** + * Removes all scoring attributes from the inputs and deletes them from memory + */ + forgetAllInputs() { + this.execOnInputs((input) => { + input.removeAttribute(ATTR_AUTOFILL); + input.removeAttribute(ATTR_INPUT_TYPE2); + }); + Object.values(this.inputs).forEach((inputSet) => inputSet.clear()); } - } - - /** - * When an item was selected, we then call back to the device - * to fetch the full suite of data needed to complete the autofill - * - * @param {import('../Form/matching').SupportedTypes} inputType - * @param {(CreditCardObject|IdentityObject|CredentialsObject)[]} items - * @param {CreditCardObject['id']|IdentityObject['id']|CredentialsObject['id']} id - */ - onSelect(inputType, items, id) { - id = String(id); - const mainType = (0, _matching.getMainTypeFromType)(inputType); - const subtype = (0, _matching.getSubtypeFromType)(inputType); - if (id === _Credentials.PROVIDER_LOCKED) { - return this.thirdPartyProvider?.askToUnlockProvider(); - } - const matchingData = items.find(item => String(item.id) === id); - if (!matchingData) throw new Error('unreachable (fatal)'); - const dataPromise = (() => { - switch (mainType) { - case 'creditCards': - return this.getAutofillCreditCard(id); - case 'identities': - return this.getAutofillIdentity(id); - case 'credentials': - { - if (_Credentials.AUTOGENERATED_KEY in matchingData) { - const autogeneratedPayload = { - ...matchingData, - username: '' - }; - return Promise.resolve({ - success: autogeneratedPayload - }); - } - return this.getAutofillCredentials(id); - } - default: - throw new Error('unreachable!'); + /** + * Resets our input scoring and starts from scratch + */ + recategorizeAllInputs() { + if (this.rescanCount >= MAX_FORM_RESCANS) { + this.mutObs.disconnect(); + return; } - })(); - - // wait for the data back from the device - dataPromise.then(response => { - if (response) { - const data = response.success || response; - if (mainType === 'identities') { - this.firePixel({ - pixelName: 'autofill_identity', - params: { - fieldType: subtype - } - }); - switch (id) { - case 'personalAddress': - this.firePixel({ - pixelName: 'autofill_personal_address' - }); - break; - case 'privateAddress': - this.firePixel({ - pixelName: 'autofill_private_address' - }); - break; - default: - // Also fire pixel when filling an identity with the personal duck address from an email field - const checks = [subtype === 'emailAddress', this.hasLocalAddresses, data?.emailAddress === (0, _autofillUtils.formatDuckAddress)(this.#addresses.personalAddress)]; - if (checks.every(Boolean)) { - this.firePixel({ - pixelName: 'autofill_personal_address' - }); - } - break; - } + this.rescanCount++; + this.initialScanComplete = false; + this.removeAllDecorations(); + this.forgetAllInputs(); + this.initFormListeners(); + this.categorizeInputs(); + } + resetAllInputs() { + this.execOnInputs((input) => { + setValue(input, "", this.device.globalConfig); + this.removeInputHighlight(input); + }); + if (this.activeInput) + this.activeInput.focus(); + this.matching.clear(); + } + resetShouldPromptToStoreData() { + this.shouldPromptToStoreData = this.device.settings.featureToggles.credentials_saving; + } + dismissTooltip() { + this.removeTooltip(); + } + // This removes all listeners to avoid memory leaks and weird behaviours + destroy() { + this.mutObs.disconnect(); + this.removeAllDecorations(); + this.removeTooltip(); + this.forgetAllInputs(); + this.matching.clear(); + this.intObs = null; + this.device.scanner.forms.delete(this.form); + } + initFormListeners() { + this.addListener(this.form, "input", () => { + if (!this.isAutofilling) { + this.submitHandlerExecuted = false; + this.resetShouldPromptToStoreData(); } - // some platforms do not include a `success` object, why? - return this.selectedDetail(data, mainType); - } else { - return Promise.reject(new Error('none-success response')); + }); + if (this.form instanceof HTMLFormElement && this.form.getRootNode()) { + this.addListener(this.form, "submit", () => { + this.submitHandler("in-form submit handler"); + }, { capture: true }); } - }).catch(e => { - console.error(e); - return this.removeTooltip(); - }); - } - isTooltipActive() { - return this.uiController?.isActive?.() ?? false; - } - removeTooltip() { - return this.uiController?.removeTooltip?.('interface'); - } - onFinishedAutofill() { - // Let input handlers know we've stopped autofilling - this.activeForm?.activeInput?.dispatchEvent(new Event('mouseleave')); - } - async updateForStateChange() { - // Remove decorations before refreshing data to make sure we - // remove the currently set icons - this.activeForm?.removeAllDecorations(); - - // Update for any state that may have changed - await this.refreshData(); - - // Add correct icons and behaviour - this.activeForm?.recategorizeAllInputs(); - } - async refreshData() { - await this.inContextSignup?.refreshData(); - await this.settings.populateData(); - } - async setupSettingsPage() { - let { - shouldLog - } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { - shouldLog: false - }; - if (!this.globalConfig.isDDGDomain) { - return; } - (0, _autofillUtils.notifyWebApp)({ - isApp: this.globalConfig.isApp - }); - if (this.isDeviceSignedIn()) { - let userData; - try { - userData = await this.getUserData(); - } catch (e) {} - let capabilities; - try { - capabilities = await this.getEmailProtectionCapabilities(); - } catch (e) {} - - // Set up listener for web app actions - if (this.globalConfig.isDDGDomain) { - window.addEventListener('message', e => { - if (e.data.removeUserData) { - this.removeUserData(); - } - if (e.data.closeEmailProtection) { - this.closeEmailProtection(); - } - }); + categorizeInputs() { + const selector = this.matching.cssSelector("formInputsSelector"); + if (this.form.matches(selector)) { + this.addInput(this.form); + } else { + let foundInputs = []; + if (this.form instanceof HTMLFormElement) { + foundInputs = [...this.form.elements].filter((el) => el.matches(selector)); + } else { + foundInputs = this.form.querySelectorAll(selector); + } + if (foundInputs.length < MAX_INPUTS_PER_FORM) { + foundInputs.forEach((input) => this.addInput(input)); + } else { + this.device.scanner.stopScanner(`The form has too many inputs (${foundInputs.length}), bailing.`); + return; + } } - const hasUserData = userData && !userData.error && Object.entries(userData).length > 0; - (0, _autofillUtils.notifyWebApp)({ - deviceSignedIn: { - value: true, - shouldLog, - userData: hasUserData ? userData : undefined, - capabilities + this.initialScanComplete = true; + if (this.form !== document.body) { + this.mutObs.observe(this.form, this.mutObsConfig); + } + } + get submitButtons() { + const selector = this.matching.cssSelector("submitButtonSelector"); + const allButtons = ( + /** @type {HTMLElement[]} */ + [...this.form.querySelectorAll(selector)] + ); + return allButtons.filter( + (btn) => isPotentiallyViewable(btn) && isLikelyASubmitButton(btn, this.matching) && buttonMatchesFormType(btn, this) + ); + } + attemptSubmissionIfNeeded() { + if (!this.isLogin || // Only submit login forms + this.submitButtons.length > 1) + return; + let isThereAnEmptyVisibleField = false; + this.execOnInputs((input) => { + if (input.value === "" && isPotentiallyViewable(input)) + isThereAnEmptyVisibleField = true; + }, "all", false); + if (isThereAnEmptyVisibleField) + return; + this.submitButtons.forEach((button) => { + if (isPotentiallyViewable(button)) { + button.click(); } }); - } else { - this.trySigningIn(); } - } - async setupAutofill() {} - - /** @returns {Promise} */ - async getAddresses() { - throw new Error('unimplemented'); - } - - /** @returns {Promise>} */ - getUserData() { - return Promise.resolve(null); - } - - /** @returns {void} */ - removeUserData() {} - - /** @returns {void} */ - closeEmailProtection() {} - - /** @returns {Promise>} */ - getEmailProtectionCapabilities() { - throw new Error('unimplemented'); - } - refreshAlias() {} - async trySigningIn() { - if (this.globalConfig.isDDGDomain) { - if (this.attempts < 10) { - this.attempts++; - const data = await (0, _autofillUtils.sendAndWaitForAnswer)(_autofillUtils.SIGN_IN_MSG, 'addUserData'); - // This call doesn't send a response, so we can't know if it succeeded - this.storeUserData(data); - await this.setupAutofill(); - await this.settings.refresh(); - await this.setupSettingsPage({ - shouldLog: true - }); - await this.postInit(); - } else { - console.warn('max attempts reached, bailing'); + /** + * Executes a function on input elements. Can be limited to certain element types + * @param {(input: HTMLInputElement|HTMLSelectElement) => void} fn + * @param {'all' | SupportedMainTypes} inputType + * @param {boolean} shouldCheckForDecorate + */ + execOnInputs(fn, inputType = "all", shouldCheckForDecorate = true) { + const inputs = this.inputs[inputType]; + for (const input of inputs) { + let canExecute = true; + if (shouldCheckForDecorate) { + canExecute = isFieldDecorated(input); + } + if (canExecute) + fn(input); } } - } - storeUserData(_data) {} - addDeviceListeners() {} - - /** @param {() => void} _fn */ - addLogoutListener(_fn) {} - isDeviceSignedIn() { - return false; - } - /** - * @returns {Promise} - */ - async getAlias() { - return undefined; - } - // PM endpoints - getAccounts() {} - /** - * Gets credentials ready for autofill - * @param {CredentialsObject['id']} id - the credential id - * @returns {Promise} - */ - async getAutofillCredentials(id) { - return this.deviceApi.request(new _deviceApiCalls.GetAutofillCredentialsCall({ - id: String(id) - })); - } - /** @returns {APIResponse} */ - async getAutofillCreditCard(_id) { - throw new Error('getAutofillCreditCard unimplemented'); - } - /** @returns {Promise<{success: IdentityObject|undefined}>} */ - async getAutofillIdentity(_id) { - throw new Error('getAutofillIdentity unimplemented'); - } - openManagePasswords() {} - openManageCreditCards() {} - openManageIdentities() {} - - /** - * @param {StoreFormData} values - * @param {StoreFormData['trigger']} trigger - */ - storeFormData(values, trigger) { - this.deviceApi.notify(new _deviceApiCalls.StoreFormDataCall({ - ...values, - trigger - })); - } - - /** - * `preAttachTooltip` happens just before a tooltip is show - features may want to append some data - * at this point. - * - * For example, if password generation is enabled, this will generate - * a password and send it to the tooltip as though it were a stored credential. - * - * @param {TopContextData} topContextData - * @param {HTMLInputElement} input - * @param {import("../Form/Form").Form} form - */ - preAttachTooltip(topContextData, input, form) { - // A list of checks to determine if we need to generate a password - const checks = [topContextData.inputType === 'credentials.password.new', this.settings.featureToggles.password_generation]; - - // if all checks pass, generate and save a password - if (checks.every(Boolean)) { - const password = this.passwordGenerator.generate({ - input: input.getAttribute('passwordrules'), - domain: window.location.hostname - }); - const rawValues = form.getRawValues(); - const username = rawValues.credentials?.username || rawValues.identities?.emailAddress || ''; - - // append the new credential to the topContextData so that the top autofill can display it - topContextData.credentials = [(0, _Credentials.fromPassword)(password, username)]; + addInput(input) { + if (this.inputs.all.has(input)) + return this; + if (this.inputs.all.size > MAX_INPUTS_PER_FORM) { + this.device.scanner.stopScanner("The form has too many inputs, bailing."); + return this; + } + if (this.initialScanComplete && this.rescanCount < MAX_FORM_RESCANS) { + this.formAnalyzer = new FormAnalyzer_default(this.form, input, this.matching); + this.recategorizeAllInputs(); + return this; + } + if (input.maxLength === 1) + return this; + this.inputs.all.add(input); + const opts = { + isLogin: this.isLogin, + isHybrid: this.isHybrid, + isCCForm: this.isCCForm, + hasCredentials: Boolean(this.device.settings.availableInputTypes.credentials?.username), + supportsIdentitiesAutofill: this.device.settings.featureToggles.inputType_identities + }; + this.matching.setInputType(input, this.form, opts); + const mainInputType = getInputMainType(input); + this.inputs[mainInputType].add(input); + this.decorateInput(input); + return this; } - return topContextData; - } - - /** - * `postAutofill` gives features an opportunity to perform an action directly - * following an autofill. - * - * For example, if a generated password was used, we want to fire a save event. - * - * @param {IdentityObject|CreditCardObject|CredentialsObject} data - * @param {SupportedMainTypes} dataType - * @param {import("../Form/Form").Form} formObj - */ - postAutofill(data, dataType, formObj) { - // If there's an autogenerated password, prompt to save - if (_Credentials.AUTOGENERATED_KEY in data && 'password' in data && - // Don't send message on Android to avoid potential abuse. Data is saved on native confirmation instead. - !this.globalConfig.isAndroid) { - const formValues = formObj.getValuesReadyForStorage(); - if (formValues.credentials?.password === data.password) { - /** @type {StoreFormData} */ - const formData = (0, _Credentials.appendGeneratedKey)(formValues, { - password: data.password + /** + * Adds event listeners and keeps track of them for subsequent removal + * @param {HTMLElement} el + * @param {Event['type']} type + * @param {(Event) => void} fn + * @param {AddEventListenerOptions} [opts] + */ + addListener(el, type, fn, opts) { + el.addEventListener(type, fn, opts); + this.listeners.add({ el, type, fn, opts }); + } + addAutofillStyles(input) { + const initialStyles = getIconStylesBase(input, this); + const activeStyles = getIconStylesAlternate(input, this); + addInlineStyles(input, initialStyles); + return { + onMouseMove: activeStyles, + onMouseLeave: initialStyles + }; + } + /** + * Decorate here means adding listeners and an optional icon + * @param {HTMLInputElement} input + * @returns {Promise} + */ + async decorateInput(input) { + const config = getInputConfig(input); + const shouldDecorate = await config.shouldDecorate(input, this); + if (!shouldDecorate) + return this; + input.setAttribute(ATTR_AUTOFILL, "true"); + const hasIcon = !!config.getIconBase(input, this); + if (hasIcon) { + const { onMouseMove, onMouseLeave } = this.addAutofillStyles(input); + this.addListener(input, "mousemove", (e) => { + if (wasAutofilledByChrome(input)) + return; + if (isEventWithinDax(e, e.target)) { + addInlineStyles(e.target, { + "cursor": "pointer", + ...onMouseMove + }); + } else { + removeInlineStyles(e.target, { "cursor": "pointer" }); + if (!this.device.isTooltipActive()) { + addInlineStyles(e.target, { ...onMouseLeave }); + } + } + }); + this.addListener(input, "mouseleave", (e) => { + removeInlineStyles(e.target, { "cursor": "pointer" }); + if (!this.device.isTooltipActive()) { + addInlineStyles(e.target, { ...onMouseLeave }); + } }); - this.storeFormData(formData, 'passwordGeneration'); } + function getMainClickCoords(e) { + if (!e.isTrusted) + return; + const isMainMouseButton = e.button === 0; + if (!isMainMouseButton) + return; + return { + x: e.clientX, + y: e.clientY + }; + } + function getClickCoords(e, storedClickCoords2) { + if (e.type === "pointerdown") { + return getMainClickCoords( + /** @type {PointerEvent} */ + e + ) || null; + } + const click = storedClickCoords2.get(input); + storedClickCoords2.delete(input); + return click || null; + } + let storedClickCoords = /* @__PURE__ */ new WeakMap(); + let timeout = null; + const handlerLabel = (e) => { + const control = ( + /** @type HTMLElement */ + e.target?.closest("label")?.control + ); + if (!control) + return; + if (e.isTrusted) { + storedClickCoords.set(control, getMainClickCoords(e)); + } + clearTimeout(timeout); + timeout = setTimeout(() => { + storedClickCoords = /* @__PURE__ */ new WeakMap(); + }, 1e3); + }; + const handler = (e) => { + if (this.isAutofilling || this.device.isTooltipActive()) { + return; + } + const isLabel = e.target instanceof HTMLLabelElement; + const input2 = isLabel ? e.target.control : e.target; + if (!input2 || !this.inputs.all.has(input2)) + return; + if (wasAutofilledByChrome(input2)) + return; + if (!canBeInteractedWith(input2)) + return; + const clickCoords = getClickCoords(e, storedClickCoords); + if (e.type === "pointerdown") { + if (!e.isTrusted || !clickCoords) + return; + } + if (this.shouldOpenTooltip(e, input2)) { + const iconClicked = isEventWithinDax(e, input2); + if ((this.device.globalConfig.isMobileApp || this.device.globalConfig.isExtension) && // Avoid the icon capturing clicks on small fields making it impossible to focus + input2.offsetWidth > 50 && iconClicked) { + e.preventDefault(); + e.stopImmediatePropagation(); + input2.blur(); + } + this.touched.add(input2); + this.device.attachTooltip({ + form: this, + input: input2, + click: clickCoords, + trigger: "userInitiated", + triggerMetaData: { + // An 'icon' click is very different to a field click or focus. + // It indicates an explicit opt-in to the feature. + type: iconClicked ? "explicit-opt-in" : "implicit-opt-in" + } + }); + const activeStyles = getIconStylesAlternate(input2, this); + addInlineStyles(input2, activeStyles); + } + }; + if (!(input instanceof HTMLSelectElement)) { + const events = ["pointerdown"]; + if (!this.device.globalConfig.isMobileApp) + events.push("focus"); + input.labels?.forEach((label) => { + if (this.device.globalConfig.isMobileApp) { + this.addListener(label, "pointerdown", handler); + } else { + this.addListener(label, "pointerdown", handlerLabel); + } + }); + events.forEach((ev) => this.addListener(input, ev, handler)); + } + return this; } - if (dataType === 'credentials' && formObj.shouldAutoSubmit) { - formObj.attemptSubmissionIfNeeded(); - } - } - - /** - * `postSubmit` gives features a one-time-only opportunity to perform an - * action directly after a form submission was observed. - * - * Mostly this is about storing data from the form submission, but it can - * also be used like in the case of Password generation, to append additional - * data before it's sent to be saved. - * - * @param {DataStorageObject} values - * @param {import("../Form/Form").Form} form - */ - postSubmit(values, form) { - if (!form.form) return; - if (!form.hasValues(values)) return; - const checks = [form.shouldPromptToStoreData && !form.submitHandlerExecuted, this.passwordGenerator.generated]; - if (checks.some(Boolean)) { - const formData = (0, _Credentials.appendGeneratedKey)(values, { - password: this.passwordGenerator.password, - username: this.emailProtection.lastGenerated - }); - this.storeFormData(formData, 'formSubmission'); + shouldOpenTooltip(e, input) { + if (!isPotentiallyViewable(input)) + return false; + if (isEventWithinDax(e, input)) + return true; + if (this.device.globalConfig.isWindows) + return true; + const subtype = getInputSubtype(input); + const isIncontextSignupAvailable = this.device.inContextSignup?.isAvailable(subtype); + if (this.device.globalConfig.isApp) { + const mainType = getInputMainType(input); + const hasSavedDetails = this.device.settings.canAutofillType({ mainType, subtype }, null); + if (!hasSavedDetails && isIncontextSignupAvailable) + return false; + return true; + } + if (this.device.globalConfig.isExtension || this.device.globalConfig.isMobileApp) { + if (isIncontextSignupAvailable) + return false; + } + return !this.touched.has(input) && !input.classList.contains("ddg-autofilled"); } - } - - /** - * Sends a pixel to be fired on the client side - * @param {import('../deviceApiCalls/__generated__/validators-ts').SendJSPixelParams} pixelParams - */ - firePixel(pixelParams) { - this.deviceApi.notify(new _deviceApiCalls.SendJSPixelCall(pixelParams)); - } - - /** - * This serves as a single place to create a default instance - * of InterfacePrototype that can be useful in testing scenarios - * @param {Partial} [globalConfigOverrides] - * @returns {InterfacePrototype} - */ - static default(globalConfigOverrides) { - const globalConfig = (0, _config.createGlobalConfig)(globalConfigOverrides); - const transport = (0, _transports.createTransport)(globalConfig); - const deviceApi = new _index.DeviceApi(transport); - const settings = _Settings.Settings.default(globalConfig, deviceApi); - return new InterfacePrototype(globalConfig, deviceApi, settings); - } -} -var _default = exports.default = InterfacePrototype; - -},{"../../packages/device-api/index.js":2,"../EmailProtection.js":22,"../Form/formatters.js":26,"../Form/matching.js":33,"../InputTypes/Credentials.js":35,"../PasswordGenerator.js":38,"../Scanner.js":39,"../Settings.js":40,"../UI/controllers/NativeUIController.js":47,"../autofill-utils.js":52,"../config.js":54,"../deviceApiCalls/__generated__/deviceApiCalls.js":56,"../deviceApiCalls/transports/transports.js":62,"./initFormSubmissionsApi.js":20}],18:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.WindowsInterface = void 0; -var _InterfacePrototype = _interopRequireDefault(require("./InterfacePrototype.js")); -var _OverlayUIController = require("../UI/controllers/OverlayUIController.js"); -var _deviceApiCalls = require("../deviceApiCalls/__generated__/deviceApiCalls.js"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -/** - * @typedef {import('../deviceApiCalls/__generated__/validators-ts').GetAutofillDataRequest} GetAutofillDataRequest - */ - -const EMAIL_PROTECTION_LOGOUT_MESSAGE = 'EMAIL_PROTECTION_LOGOUT'; -class WindowsInterface extends _InterfacePrototype.default { - ready = false; - /** @type {AbortController|null} */ - _abortController = null; - async setupAutofill() { - const loggedIn = await this._getIsLoggedIn(); - if (loggedIn) { - await this.getAddresses(); + autofillInput(input, string, dataType) { + if (input instanceof HTMLInputElement && !isPotentiallyViewable(input)) + return; + if (!canBeInteractedWith(input)) + return; + if (dataType !== "creditCards" && // creditCards always override, the others only when we're focusing the input + input.nodeName !== "SELECT" && input.value !== "" && // if the input is not empty + this.activeInput !== input) + return; + if (input.value === string) + return; + const successful = setValue(input, string, this.device.globalConfig); + if (!successful) + return; + input.classList.add("ddg-autofilled"); + addInlineStyles(input, getIconStylesAutofilled(input, this)); + this.touched.add(input); + input.addEventListener("input", (e) => this.removeAllHighlights(e, dataType), { once: true }); } - } - postInit() { - super.postInit(); - this.ready = true; - } - createUIController() { /** - * If we get here, we're just a controller for an overlay + * Autofill method for email protection only + * @param {string} alias + * @param {'all' | SupportedMainTypes} dataType */ - return new _OverlayUIController.OverlayUIController({ - remove: async () => this._closeAutofillParent(), - show: async details => this._show(details) - }); - } - - /** - * @param {GetAutofillDataRequest} details - */ - async _show(details) { - const { - mainType - } = details; - // prevent overlapping listeners - if (this._abortController && !this._abortController.signal.aborted) { - this._abortController.abort(); - } - this._abortController = new AbortController(); - this.deviceApi.request(new _deviceApiCalls.GetAutofillDataCall(details), { - signal: this._abortController.signal - }).then(resp => { - if (!this.activeForm) { - throw new Error('this.currentAttached was absent'); - } - switch (resp.action) { - case 'fill': - { - if (mainType in resp) { - this.activeForm?.autofillData(resp[mainType], mainType); - } else { - throw new Error(`action: "fill" cannot occur because "${mainType}" was missing`); - } - break; + autofillEmail(alias, dataType = "identities") { + this.isAutofilling = true; + this.execOnInputs( + (input) => { + const inputSubtype = getInputSubtype(input); + if (inputSubtype === "emailAddress") { + this.autofillInput(input, alias, dataType); } - case 'focus': - { - this.activeForm?.activeInput?.focus(); - break; + }, + dataType + ); + this.isAutofilling = false; + this.removeTooltip(); + } + autofillData(data, dataType) { + this.isAutofilling = true; + this.execOnInputs((input) => { + const inputSubtype = getInputSubtype(input); + let autofillData = data[inputSubtype]; + if (inputSubtype === "expiration" && input instanceof HTMLInputElement) { + autofillData = getUnifiedExpiryDate(input, data.expirationMonth, data.expirationYear, this); + } + if (inputSubtype === "expirationYear" && input instanceof HTMLInputElement) { + autofillData = formatCCYear(input, autofillData, this); + } + if (inputSubtype === "addressCountryCode") { + autofillData = getCountryName(input, data); + } + if (autofillData) { + const variant = getInputVariant(input); + if (!variant) { + return this.autofillInput(input, autofillData, dataType); } - case 'none': - { - // do nothing - break; + if (variant === "new" && AUTOGENERATED_KEY in data) { + return this.autofillInput(input, autofillData, dataType); } - default: - { - if (this.globalConfig.isDDGTestMode) { - console.warn('unhandled response', resp); - } + if (variant === "current" && !(AUTOGENERATED_KEY in data)) { + return this.autofillInput(input, autofillData, dataType); } - } - return this._closeAutofillParent(); - }).catch(e => { - if (this.globalConfig.isDDGTestMode) { - if (e.name === 'AbortError') { - console.log('Promise Aborted'); - } else { - console.error('Promise Rejected', e); } + }, dataType); + this.isAutofilling = false; + const formValues = this.getValuesReadyForStorage(); + const areAllFormValuesKnown = Object.keys(formValues[dataType] || {}).every((subtype) => formValues[dataType][subtype] === data[subtype]); + if (areAllFormValuesKnown) { + this.shouldPromptToStoreData = false; + this.shouldAutoSubmit = this.device.globalConfig.isMobileApp; + } else { + this.shouldAutoSubmit = false; } - }); - } - - /** - * @returns {Promise} - */ - async _closeAutofillParent() { - return this.deviceApi.notify(new _deviceApiCalls.CloseAutofillParentCall(null)); - } - - /** - * Email Protection calls - */ - - /** - * @returns {Promise} - */ - getEmailProtectionCapabilities() { - return this.deviceApi.request(new _deviceApiCalls.EmailProtectionGetCapabilitiesCall({})); - } - async _getIsLoggedIn() { - const isLoggedIn = await this.deviceApi.request(new _deviceApiCalls.EmailProtectionGetIsLoggedInCall({})); - this.isDeviceSignedIn = () => isLoggedIn; - return isLoggedIn; - } - addLogoutListener(handler) { - // Only deal with logging out if we're in the email web app - if (!this.globalConfig.isDDGDomain) return; - windowsInteropAddEventListener('message', e => { - if (this.globalConfig.isDDGDomain && e.data === EMAIL_PROTECTION_LOGOUT_MESSAGE) { - handler(); - } - }); - } - - /** - * @returns {Promise} - */ - storeUserData(_ref) { - let { - addUserData - } = _ref; - return this.deviceApi.request(new _deviceApiCalls.EmailProtectionStoreUserDataCall(addUserData)); - } - /** - * @returns {Promise} - */ - removeUserData() { - return this.deviceApi.request(new _deviceApiCalls.EmailProtectionRemoveUserDataCall({})); - } - /** - * @returns {Promise} - */ - getUserData() { - return this.deviceApi.request(new _deviceApiCalls.EmailProtectionGetUserDataCall({})); - } - async refreshAlias() { - const addresses = await this.deviceApi.request(new _deviceApiCalls.EmailProtectionRefreshPrivateAddressCall({})); - this.storeLocalAddresses(addresses); - } - async getAddresses() { - const addresses = await this.deviceApi.request(new _deviceApiCalls.EmailProtectionGetAddressesCall({})); - this.storeLocalAddresses(addresses); - return addresses; - } -} -exports.WindowsInterface = WindowsInterface; - -},{"../UI/controllers/OverlayUIController.js":48,"../deviceApiCalls/__generated__/deviceApiCalls.js":56,"./InterfacePrototype.js":17}],19:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.WindowsOverlayDeviceInterface = void 0; -var _InterfacePrototype = _interopRequireDefault(require("./InterfacePrototype.js")); -var _HTMLTooltipUIController = require("../UI/controllers/HTMLTooltipUIController.js"); -var _deviceApiCalls = require("../deviceApiCalls/__generated__/deviceApiCalls.js"); -var _overlayApi = require("./overlayApi.js"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -/** - * This subclass is designed to separate code that *only* runs inside the - * Windows Overlay into a single place. - * - * It has some subtle differences to the macOS version, which is why - * this is another DeviceInterface - */ -class WindowsOverlayDeviceInterface extends _InterfacePrototype.default { - /** - * Mark top frame as not stripping credential data - * @type {boolean} - */ - stripCredentials = false; - - /** - * overlay API helpers - */ - overlay = (0, _overlayApi.overlayApi)(this); - previousScreenX = 0; - previousScreenY = 0; - - /** - * Because we're running inside the Overlay, we always create the HTML - * Tooltip controller. - * - * @override - * @returns {import("../UI/controllers/UIController.js").UIController} - */ - createUIController() { - return new _HTMLTooltipUIController.HTMLTooltipUIController({ - tooltipKind: /** @type {const} */'modern', - device: this - }, { - wrapperClass: 'top-autofill', - tooltipPositionClass: () => '.wrapper { transform: none; }', - setSize: details => this.deviceApi.notify(new _deviceApiCalls.SetSizeCall(details)), - remove: async () => this._closeAutofillParent(), - testMode: this.isTestMode(), - /** - * Note: This is needed because Mutation observer didn't support visibility checks on Windows - */ - checkVisibility: false - }); - } - addDeviceListeners() { + this.device.postAutofill?.(data, dataType, this); + this.removeTooltip(); + } /** - * On Windows (vs. MacOS) we can use the built-in `mousemove` - * event and screen-relative positioning. - * - * Note: There's no cleanup required here since the Overlay has a fresh - * page load every time it's opened. - */ - window.addEventListener('mousemove', event => { - // Don't set focus if the mouse hasn't moved ever - // This is to avoid clickjacking where an attacker puts the pulldown under the cursor - // and tricks the user into clicking - if (!this.previousScreenX && !this.previousScreenY || - // if no previous coords - this.previousScreenX === event.screenX && this.previousScreenY === event.screenY // or the mouse hasn't moved - ) { - this.previousScreenX = event.screenX; - this.previousScreenY = event.screenY; + * Set all inputs of the data type to "touched" + * @param {'all' | SupportedMainTypes} dataType + */ + touchAllInputs(dataType = "all") { + this.execOnInputs( + (input) => this.touched.add(input), + dataType + ); + } + getFirstViableCredentialsInput() { + return [...this.inputs.credentials].find((input) => canBeInteractedWith(input) && isPotentiallyViewable(input)); + } + async promptLoginIfNeeded() { + if (document.visibilityState !== "visible" || !this.isLogin) + return; + const firstCredentialInput = this.getFirstViableCredentialsInput(); + const input = this.activeInput || firstCredentialInput; + if (!input) return; + const mainType = getInputMainType(input); + const subtype = getInputSubtype(input); + await this.device.settings.populateDataIfNeeded({ mainType, subtype }); + if (this.device.settings.canAutofillType({ mainType, subtype }, this.device.inContextSignup)) { + setTimeout(() => { + safeExecute(this.form, () => { + const { x, y, width, height } = this.form.getBoundingClientRect(); + const elHCenter = x + width / 2; + const elVCenter = y + height / 2; + const topMostElementFromPoint = document.elementFromPoint(elHCenter, elVCenter); + if (this.form.contains(topMostElementFromPoint)) { + this.execOnInputs((input2) => { + if (isPotentiallyViewable(input2)) { + this.touched.add(input2); + } + }, "credentials"); + this.device.attachTooltip({ + form: this, + input, + click: null, + trigger: "autoprompt", + triggerMetaData: { + type: "implicit-opt-in" + } + }); + } + }); + }, 200); } - const activeTooltip = this.uiController?.getActiveTooltip?.(); - activeTooltip?.focus(event.x, event.y); - this.previousScreenX = event.screenX; - this.previousScreenY = event.screenY; - }); - return super.addDeviceListeners(); - } - - /** - * @returns {Promise} - */ - async _closeAutofillParent() { - return this.deviceApi.notify(new _deviceApiCalls.CloseAutofillParentCall(null)); - } - - /** - * @returns {Promise} - */ - openManagePasswords() { - return this.deviceApi.notify(new _deviceApiCalls.OpenManagePasswordsCall({})); - } - /** - * @returns {Promise} - */ - openManageCreditCards() { - return this.deviceApi.notify(new _deviceApiCalls.OpenManageCreditCardsCall({})); - } - /** - * @returns {Promise} - */ - openManageIdentities() { - return this.deviceApi.notify(new _deviceApiCalls.OpenManageIdentitiesCall({})); - } - - /** - * Since we're running inside the Overlay we can limit what happens here to - * be only things that are needed to power the HTML Tooltip - * - * @override - * @returns {Promise} - */ - async setupAutofill() { - const loggedIn = await this._getIsLoggedIn(); - if (loggedIn) { - await this.getAddresses(); - } - const response = await this.deviceApi.request(new _deviceApiCalls.GetAutofillInitDataCall(null)); - // @ts-ignore - this.storeLocalData(response); - } - async postInit() { - // setup overlay API pieces - this.overlay.showImmediately(); - super.postInit(); - } - - /** - * In the top-frame scenario, we send a message to the native - * side to indicate a selection. Once received, the native side will store that selection so that a - * subsequence call from main webpage can retrieve it - * - * @override - * @param {IdentityObject|CreditCardObject|CredentialsObject|{email:string, id: string}} data - * @param {string} type - */ - async selectedDetail(data, type) { - return this.overlay.selectedDetail(data, type); - } - - /** - * Email Protection calls - */ - - async _getIsLoggedIn() { - const isLoggedIn = await this.deviceApi.request(new _deviceApiCalls.EmailProtectionGetIsLoggedInCall({})); - this.isDeviceSignedIn = () => isLoggedIn; - return isLoggedIn; - } - async getAddresses() { - const addresses = await this.deviceApi.request(new _deviceApiCalls.EmailProtectionGetAddressesCall({})); - this.storeLocalAddresses(addresses); - return addresses; - } - - /** - * Gets a single identity obj once the user requests it - * @param {Number} id - * @returns {Promise<{success: IdentityObject|undefined}>} - */ - getAutofillIdentity(id) { - const identity = this.getLocalIdentities().find(_ref => { - let { - id: identityId - } = _ref; - return `${identityId}` === `${id}`; - }); - return Promise.resolve({ - success: identity - }); - } -} -exports.WindowsOverlayDeviceInterface = WindowsOverlayDeviceInterface; - -},{"../UI/controllers/HTMLTooltipUIController.js":46,"../deviceApiCalls/__generated__/deviceApiCalls.js":56,"./InterfacePrototype.js":17,"./overlayApi.js":21}],20:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.initFormSubmissionsApi = initFormSubmissionsApi; -var _autofillUtils = require("../autofill-utils.js"); -var _labelUtil = require("../Form/label-util.js"); -/** - * This is a single place to contain all functionality relating to form submission detection - * - * @param {Map} forms - * @param {import("../Form/matching").Matching} matching - */ -function initFormSubmissionsApi(forms, matching) { - /** - * Global submit events - */ - window.addEventListener('submit', e => { - // @ts-ignore - return forms.get(e.target)?.submitHandler('global submit event'); - }, true); - - /** - * Global keydown events - */ - window.addEventListener('keydown', e => { - if (e.key === 'Enter') { - const focusedForm = [...forms.values()].find(form => form.hasFocus(e)); - focusedForm?.submitHandler('global keydown + Enter'); } - }, true); - - /** - * Global pointer down events - * @param {PointerEvent} event - */ - window.addEventListener('pointerdown', event => { - const realTarget = (0, _autofillUtils.pierceShadowTree)(event); - const formsArray = [...forms.values()]; - const matchingForm = formsArray.find(form => { - const btns = [...form.submitButtons]; - // @ts-ignore - if (btns.includes(realTarget)) return true; - - // @ts-ignore - if (btns.find(btn => btn.contains(realTarget))) return true; - }); - matchingForm?.submitHandler('global pointerdown event + matching form'); - if (!matchingForm) { - const selector = matching.cssSelector('submitButtonSelector') + ', a[href="#"], a[href^=javascript], *[onclick], [class*=button i]'; - // check if the click happened on a button - const button = /** @type HTMLElement */realTarget?.closest(selector); - if (!button) return; - - // If the element we've found includes a form it can't be a button, it's a false match - const buttonIsAFalsePositive = formsArray.some(form => button?.contains(form.form)); - if (buttonIsAFalsePositive) return; - const text = (0, _autofillUtils.getTextShallow)(button) || (0, _labelUtil.extractElementStrings)(button).join(' '); - const hasRelevantText = (0, _autofillUtils.safeRegexTest)(matching.getDDGMatcherRegex('submitButtonRegex'), text); - if (hasRelevantText && text.length < 25) { - // check if there's a form with values - const filledForm = formsArray.find(form => form.hasValues()); - if (filledForm && (0, _autofillUtils.buttonMatchesFormType)( /** @type HTMLElement */button, filledForm)) { - filledForm?.submitHandler('global pointerdown event + filled form'); - } - } + }; - // TODO: Temporary hack to support Google signin in different languages - // https://app.asana.com/0/1198964220583541/1201650539303898/f - if ( /** @type HTMLElement */realTarget?.closest('#passwordNext button, #identifierNext button')) { - // check if there's a form with values - const filledForm = formsArray.find(form => form.hasValues()); - filledForm?.submitHandler('global pointerdown event + google escape hatch'); - } + // zod-replacers:./validators.zod.js + var sendJSPixelParamsSchema = null; + var addDebugFlagParamsSchema = null; + var getAutofillCredentialsParamsSchema = null; + var setSizeParamsSchema = null; + var selectedDetailParamsSchema = null; + var setIncontextSignupPermanentlyDismissedAtSchema = null; + var getIncontextSignupDismissedAtSchema = null; + var emailProtectionStoreUserDataParamsSchema = null; + var showInContextEmailProtectionSignupPromptSchema = null; + var getAutofillDataRequestSchema = null; + var getAutofillDataResponseSchema = null; + var storeFormDataSchema = null; + var getAvailableInputTypesResultSchema = null; + var getAutofillInitDataResponseSchema = null; + var getAutofillCredentialsResultSchema = null; + var emailProtectionGetIsLoggedInResultSchema = null; + var emailProtectionGetUserDataResultSchema = null; + var emailProtectionGetCapabilitiesResultSchema = null; + var emailProtectionGetAddressesResultSchema = null; + var emailProtectionRefreshPrivateAddressResultSchema = null; + var getRuntimeConfigurationResponseSchema = null; + var askToUnlockProviderResultSchema = null; + var checkCredentialsProviderStatusResultSchema = null; + + // packages/device-api/lib/device-api-call.js + var DeviceApiCall = class { + /** + * @param {import("zod").infer} data + */ + constructor(data) { + /** @type {string} */ + __publicField(this, "method", "unknown"); + /** + * An optional 'id' - used to indicate if a request requires a response. + * @type {string|null} + */ + __publicField(this, "id", null); + /** @type {Params | null | undefined} */ + __publicField(this, "paramsValidator", null); + /** @type {Result | null | undefined} */ + __publicField(this, "resultValidator", null); + /** @type {import("zod").infer} */ + __publicField(this, "params"); + /** + * This is a carve-out for legacy messages that are not typed yet. + * If you set this to 'true', then the response will not be checked to conform + * to any shape + * @deprecated this is here to aid migration, should be removed ASAP + * @type {boolean} + */ + __publicField(this, "throwOnResultKeysMissing", true); + /** + * New messages should be in a particular format, eg: { success: T }, + * but you can set this to false if you want to access the result as-is, + * without any unwrapping logic + * @deprecated this is here to aid migration, should be removed ASAP + * @type {boolean} + */ + __publicField(this, "unwrapResult", true); + this.params = data; } - }, true); - - /** - * @type {PerformanceObserver} - */ - const observer = new PerformanceObserver(list => { - const formsArray = [...forms.values()]; - const entries = list.getEntries().filter(entry => - // @ts-ignore why does TS not know about `entry.initiatorType`? - ['fetch', 'xmlhttprequest'].includes(entry.initiatorType) && (0, _autofillUtils.safeRegexTest)(/login|sign-in|signin/, entry.name)); - if (!entries.length) return; - const filledForm = formsArray.find(form => form.hasValues()); - const focusedForm = formsArray.find(form => form.hasFocus()); - // If a form is still focused the user is still typing: do nothing - if (focusedForm) return; - filledForm?.submitHandler('performance observer'); - }); - observer.observe({ - entryTypes: ['resource'] - }); -} - -},{"../Form/label-util.js":29,"../autofill-utils.js":52}],21:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.overlayApi = overlayApi; -var _deviceApiCalls = require("../deviceApiCalls/__generated__/deviceApiCalls.js"); -/** - * These are some re-usable parts for handling 'overlays' (like on macOS + Windows) - * - * @param {import("./InterfacePrototype").default} device - */ -function overlayApi(device) { - return { - /** - * When we are inside an 'overlay' - the HTML tooltip will be opened immediately - */ - showImmediately() { - const topContextData = device.getTopContextData(); - if (!topContextData) throw new Error('unreachable, topContextData should be available'); - - // Provide dummy values - const getPosition = () => { - return { - x: 0, - y: 0, - height: 50, - width: 50 - }; - }; - - // Create the tooltip, and set it as active - const tooltip = device.uiController?.createTooltip?.(getPosition, topContextData); - if (tooltip) { - device.uiController?.setActiveTooltip?.(tooltip); - } - }, /** - * @param {IdentityObject|CreditCardObject|CredentialsObject|{email:string, id: string}} data - * @param {string} type - * @returns {Promise} + * @returns {import("zod").infer|undefined} */ - async selectedDetail(data, type) { - let detailsEntries = Object.entries(data).map(_ref => { - let [key, value] = _ref; - return [key, String(value)]; - }); - const entries = Object.fromEntries(detailsEntries); - /** @link {import("../deviceApiCalls/schemas/getAutofillData.result.json")} */ - await device.deviceApi.notify(new _deviceApiCalls.SelectedDetailCall({ - data: entries, - configType: type - })); + validateParams() { + if (this.params === void 0) { + return void 0; + } + this._validate(this.params, this.paramsValidator); + return this.params; } - }; -} - -},{"../deviceApiCalls/__generated__/deviceApiCalls.js":56}],22:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.EmailProtection = void 0; -/** - * Use this as place to store any state or functionality related to Email Protection - */ -class EmailProtection { - /** @type {string|null} */ - #previous = null; - - /** @param {import("./DeviceInterface/InterfacePrototype").default} device */ - constructor(device) { - this.device = device; - } - - /** @returns {string|null} */ - get lastGenerated() { - return this.#previous; - } - - /** - * Store the last received email address - * @param {string} emailAddress - */ - storeReceived(emailAddress) { - this.#previous = emailAddress; - return emailAddress; - } -} -exports.EmailProtection = EmailProtection; - -},{}],23:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.Form = void 0; -var _FormAnalyzer = _interopRequireDefault(require("./FormAnalyzer.js")); -var _autofillUtils = require("../autofill-utils.js"); -var _matching = require("./matching.js"); -var _inputStyles = require("./inputStyles.js"); -var _inputTypeConfig = require("./inputTypeConfig.js"); -var _formatters = require("./formatters.js"); -var _constants = require("../constants.js"); -var _Credentials = require("../InputTypes/Credentials.js"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const { - ATTR_AUTOFILL, - ATTR_INPUT_TYPE, - MAX_INPUTS_PER_FORM, - MAX_FORM_RESCANS -} = _constants.constants; -class Form { - /** @type {import("../Form/matching").Matching} */ - matching; - /** @type {HTMLElement} */ - form; - /** @type {HTMLInputElement | null} */ - activeInput; - /** - * @param {HTMLElement} form - * @param {HTMLInputElement|HTMLSelectElement} input - * @param {import("../DeviceInterface/InterfacePrototype").default} deviceInterface - * @param {import("../Form/matching").Matching} [matching] - * @param {Boolean} [shouldAutoprompt] - */ - constructor(form, input, deviceInterface, matching) { - let shouldAutoprompt = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; - this.form = form; - this.matching = matching || (0, _matching.createMatching)(); - this.formAnalyzer = new _FormAnalyzer.default(form, input, matching); - this.device = deviceInterface; - - /** @type Record<'all' | SupportedMainTypes, Set> */ - this.inputs = { - all: new Set(), - credentials: new Set(), - creditCards: new Set(), - identities: new Set(), - unknown: new Set() - }; - this.touched = new Set(); - this.listeners = new Set(); - this.activeInput = null; - // We set this to true to skip event listeners while we're autofilling - this.isAutofilling = false; - this.submitHandlerExecuted = false; - this.shouldPromptToStoreData = deviceInterface.settings.featureToggles.credentials_saving; - this.shouldAutoSubmit = this.device.globalConfig.isMobileApp; - /** - * @type {IntersectionObserver | null} + * @param {any|null} incoming + * @returns {import("zod").infer} */ - this.intObs = new IntersectionObserver(entries => { - for (const entry of entries) { - if (!entry.isIntersecting) this.removeTooltip(); + validateResult(incoming) { + this._validate(incoming, this.resultValidator); + if (!incoming) { + return incoming; } - }); - this.rescanCount = 0; - this.mutObsConfig = { - childList: true, - subtree: true - }; - this.mutObs = new MutationObserver(records => { - const anythingRemoved = records.some(record => record.removedNodes.length > 0); - if (anythingRemoved) { - // Ensure we destroy the form if it's removed from the DOM - if (!this.form.isConnected) { - this.destroy(); - return; - } - // Must check for inputs because a parent may be removed and not show up in record.removedNodes - if ([...this.inputs.all].some(input => !input.isConnected)) { - // This is re-connected in recategorizeAllInputs, disconnecting here to avoid risk of re-work - this.mutObs.disconnect(); - // If any known input has been removed from the DOM, reanalyze the whole form - window.requestIdleCallback(() => { - this.formAnalyzer = new _FormAnalyzer.default(this.form, input, this.matching); - this.recategorizeAllInputs(); - }); - } + if (!this.unwrapResult) { + return incoming; } - }); - this.initFormListeners(); - this.categorizeInputs(); - this.logFormInfo(); - if (shouldAutoprompt) { - this.promptLoginIfNeeded(); - } - } - get isLogin() { - return this.formAnalyzer.isLogin; - } - get isSignup() { - return this.formAnalyzer.isSignup; - } - get isHybrid() { - return this.formAnalyzer.isHybrid; - } - get isCCForm() { - return this.formAnalyzer.isCCForm(); - } - logFormInfo() { - if (!(0, _autofillUtils.shouldLog)()) return; - console.log(`Form type: %c${this.getFormType()}`, 'font-weight: bold'); - console.log('Signals: ', this.formAnalyzer.signals); - console.log('Wrapping element: ', this.form); - console.log('Inputs: ', this.inputs); - console.log('Submit Buttons: ', this.submitButtons); - } - getFormType() { - if (this.isHybrid) return `hybrid (hybrid score: ${this.formAnalyzer.hybridSignal}, score: ${this.formAnalyzer.autofillSignal})`; - if (this.isLogin) return `login (score: ${this.formAnalyzer.autofillSignal}, hybrid score: ${this.formAnalyzer.hybridSignal})`; - if (this.isSignup) return `signup (score: ${this.formAnalyzer.autofillSignal}, hybrid score: ${this.formAnalyzer.hybridSignal})`; - return 'something went wrong'; - } - - /** - * Checks if the form element contains the activeElement or the event target - * @return {boolean} - * @param {KeyboardEvent | null} [e] - */ - hasFocus(e) { - return this.form.contains((0, _autofillUtils.getActiveElement)()) || this.form.contains( /** @type HTMLElement */e?.target); - } - submitHandler() { - let via = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'unknown'; - if (this.device.globalConfig.isDDGTestMode) { - console.log('Form.submitHandler via:', via, this); - } - if (this.submitHandlerExecuted) return; - const values = this.getValuesReadyForStorage(); - this.device.postSubmit?.(values, this); - - // mark this form as being handled - this.submitHandlerExecuted = true; - } - - /** - * Reads the values from the form without preparing to store them - * @return {InternalDataStorageObject} - */ - getRawValues() { - const formValues = [...this.inputs.credentials, ...this.inputs.identities, ...this.inputs.creditCards].reduce((output, inputEl) => { - const mainType = (0, _matching.getInputMainType)(inputEl); - const subtype = (0, _matching.getInputSubtype)(inputEl); - let value = inputEl.value || output[mainType]?.[subtype]; - if (subtype === 'addressCountryCode') { - value = (0, _formatters.inferCountryCodeFromElement)(inputEl); + if ("data" in incoming) { + console.warn("response had `data` property. Please migrate to `success`"); + return incoming.data; } - // Discard passwords that are shorter than 4 characters - if (subtype === 'password' && value?.length <= 3) { - value = undefined; + if ("success" in incoming) { + return incoming.success; } - if (value) { - output[mainType][subtype] = value; + if ("error" in incoming) { + if (typeof incoming.error.message === "string") { + throw new DeviceApiCallError(`${this.method}: ${incoming.error.message}`); + } } - return output; - }, { - credentials: {}, - creditCards: {}, - identities: {} - }); - if (!formValues.credentials.username && !formValues.identities.emailAddress) { - // If we could find no username, let's search further - const hiddenFields = /** @type [HTMLInputElement] */[...this.form.querySelectorAll('input[type=hidden]')]; - const probableField = hiddenFields.find(field => { - const regex = new RegExp('email|' + this.matching.getDDGMatcherRegex('username')?.source); - const attributeText = field.id + ' ' + field.name; - return (0, _autofillUtils.safeRegexTest)(regex, attributeText); - }); - if (probableField?.value) { - formValues.credentials.username = probableField.value; - } else if ( - // If a form has phone + password(s) fields, save the phone as username - formValues.identities.phone && this.inputs.all.size - this.inputs.unknown.size < 4) { - formValues.credentials.username = formValues.identities.phone; - } else { - // If we still don't have a username, try scanning the form's text for an email address - this.form.querySelectorAll(this.matching.cssSelector('safeUniversalSelector')).forEach(el => { - const elText = (0, _autofillUtils.getTextShallow)(el); - // Ignore long texts to avoid false positives - if (elText.length > 70) return; - const emailOrUsername = elText.match( - // https://www.emailregex.com/ - /[a-zA-Z\d.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z\d-]+(?:\.[a-zA-Z\d-]+)*/)?.[0]; - if (emailOrUsername) { - formValues.credentials.username = emailOrUsername; + if (this.throwOnResultKeysMissing) { + throw new Error("unreachable. Response did not contain `success` or `data`"); + } + return incoming; + } + /** + * @param {any} data + * @param {import("zod").ZodType|undefined|null} [validator] + * @private + */ + _validate(data, validator) { + if (!validator) + return data; + if (validator) { + const result = validator?.safeParse(data); + if (!result) { + throw new Error("unreachable, data failure", data); + } + if (!result.success) { + if ("error" in result) { + this.throwError(result.error.issues); + } else { + console.error("unknown error from validate"); } - }); + } } } - return formValues; - } - - /** - * Return form values ready for storage - * @returns {DataStorageObject} - */ - getValuesReadyForStorage() { - const formValues = this.getRawValues(); - return (0, _formatters.prepareFormValuesForStorage)(formValues); - } - - /** - * Determine if the form has values we want to store in the device - * @param {DataStorageObject} [values] - * @return {boolean} - */ - hasValues(values) { - const { - credentials, - creditCards, - identities - } = values || this.getValuesReadyForStorage(); - return Boolean(credentials || creditCards || identities); - } - async removeTooltip() { - const tooltip = this.device.isTooltipActive(); - if (this.isAutofilling || !tooltip) { - return; + /** + * @param {import('zod').ZodIssue[]} errors + */ + throwError(errors) { + const error = SchemaValidationError.fromZodErrors(errors, this.constructor.name); + throw error; } - await this.device.removeTooltip(); - this.intObs?.disconnect(); - } - showingTooltip(input) { - this.intObs?.observe(input); - } - removeInputHighlight(input) { - if (!input.classList.contains('ddg-autofilled')) return; - (0, _autofillUtils.removeInlineStyles)(input, (0, _inputStyles.getIconStylesAutofilled)(input, this)); - (0, _autofillUtils.removeInlineStyles)(input, { - 'cursor': 'pointer' - }); - input.classList.remove('ddg-autofilled'); - this.addAutofillStyles(input); - } - resetIconStylesToInitial() { - const input = this.activeInput; - if (input) { - const initialStyles = (0, _inputStyles.getIconStylesBase)(input, this); - (0, _autofillUtils.addInlineStyles)(input, initialStyles); + /** + * Use this helper for creating stand-in response messages that are typed correctly. + * + * @examples + * + * ```js + * const msg = new Message(); + * const response = msg.response({}) // <-- This argument will be typed correctly + * ``` + * + * @param {import("zod").infer} response + * @returns {import("zod").infer} + */ + result(response) { + return response; } - } - removeAllHighlights(e, dataType) { - // This ensures we are not removing the highlight ourselves when autofilling more than once - if (e && !e.isTrusted) return; - - // If the user has changed the value, reset shouldPromptToStoreData to initial value - this.resetShouldPromptToStoreData(); - this.execOnInputs(input => this.removeInputHighlight(input), dataType); - } - removeInputDecoration(input) { - (0, _autofillUtils.removeInlineStyles)(input, (0, _inputStyles.getIconStylesBase)(input, this)); - (0, _autofillUtils.removeInlineStyles)(input, (0, _inputStyles.getIconStylesAlternate)(input, this)); - input.removeAttribute(ATTR_AUTOFILL); - input.removeAttribute(ATTR_INPUT_TYPE); - } - removeAllDecorations() { - this.execOnInputs(input => this.removeInputDecoration(input)); - this.listeners.forEach(_ref => { - let { - el, - type, - fn, - opts - } = _ref; - return el.removeEventListener(type, fn, opts); - }); - } - redecorateAllInputs() { - this.removeAllDecorations(); - this.execOnInputs(input => { - if (input instanceof HTMLInputElement) { - this.decorateInput(input); - } - }); - } - - /** - * Removes all scoring attributes from the inputs and deletes them from memory - */ - forgetAllInputs() { - this.execOnInputs(input => { - input.removeAttribute(ATTR_AUTOFILL); - input.removeAttribute(ATTR_INPUT_TYPE); - }); - Object.values(this.inputs).forEach(inputSet => inputSet.clear()); - } - - /** - * Resets our input scoring and starts from scratch - */ - recategorizeAllInputs() { - // If the form mutates too much, disconnect to avoid performance issues - if (this.rescanCount >= MAX_FORM_RESCANS) { - this.mutObs.disconnect(); - return; + /** + * @returns {import("zod").infer} + */ + preResultValidation(response) { + return response; } - this.rescanCount++; - this.initialScanComplete = false; - this.removeAllDecorations(); - this.forgetAllInputs(); - this.initFormListeners(); - this.categorizeInputs(); - } - resetAllInputs() { - this.execOnInputs(input => { - (0, _autofillUtils.setValue)(input, '', this.device.globalConfig); - this.removeInputHighlight(input); - }); - if (this.activeInput) this.activeInput.focus(); - this.matching.clear(); - } - resetShouldPromptToStoreData() { - this.shouldPromptToStoreData = this.device.settings.featureToggles.credentials_saving; - } - dismissTooltip() { - this.removeTooltip(); - } - // This removes all listeners to avoid memory leaks and weird behaviours - destroy() { - this.mutObs.disconnect(); - this.removeAllDecorations(); - this.removeTooltip(); - this.forgetAllInputs(); - this.matching.clear(); - this.intObs = null; - this.device.scanner.forms.delete(this.form); - } - initFormListeners() { - // This ensures we fire the handler again if the form is changed - this.addListener(this.form, 'input', () => { - if (!this.isAutofilling) { - this.submitHandlerExecuted = false; - this.resetShouldPromptToStoreData(); - } - }); - - // If it's a form within a shadow tree, attach the submit listener, because it doesn't bubble outside - if (this.form instanceof HTMLFormElement && this.form.getRootNode()) { - this.addListener(this.form, 'submit', () => { - this.submitHandler('in-form submit handler'); - }, { - capture: true - }); + }; + var DeviceApiCallError = class extends Error { + }; + var SchemaValidationError = class extends Error { + constructor() { + super(...arguments); + /** @type {import("zod").ZodIssue[]} */ + __publicField(this, "validationErrors", []); } - } - categorizeInputs() { - const selector = this.matching.cssSelector('formInputsSelector'); - // If there's no form container and it's just a lonely input field (this.form is an input field) - if (this.form.matches(selector)) { - this.addInput(this.form); - } else { - /** @type {Element[] | NodeList} */ - let foundInputs = []; - if (this.form instanceof HTMLFormElement) { - // For form elements we use .elements to catch fields outside the form itself using the form attribute. - // It also catches all elements when the markup is broken. - // We use .filter to avoid fieldset, button, textarea etc. - foundInputs = [...this.form.elements].filter(el => el.matches(selector)); - } else { - foundInputs = this.form.querySelectorAll(selector); + /** + * @param {import("zod").ZodIssue[]} errors + * @param {string} name + * @returns {SchemaValidationError} + */ + static fromZodErrors(errors, name) { + const heading = `${errors.length} SchemaValidationError(s) errors for ` + name; + function log(issue) { + switch (issue.code) { + case "invalid_literal": + case "invalid_type": { + console.log(`${name}. Path: '${issue.path.join(".")}', Error: '${issue.message}'`); + break; + } + case "invalid_union": { + for (let unionError of issue.unionErrors) { + for (let issue1 of unionError.issues) { + log(issue1); + } + } + break; + } + default: { + console.log(name, "other issue:", issue); + } + } } - if (foundInputs.length < MAX_INPUTS_PER_FORM) { - foundInputs.forEach(input => this.addInput(input)); - } else { - // This is rather extreme, but better safe than sorry - this.device.scanner.stopScanner(`The form has too many inputs (${foundInputs.length}), bailing.`); - return; + for (let error2 of errors) { + log(error2); } + const message = [heading, "please see the details above"].join("\n "); + const error = new SchemaValidationError(message); + error.validationErrors = errors; + return error; } - this.initialScanComplete = true; - - // Observe only if the container isn't the body, to avoid performance overloads - if (this.form !== document.body) { - this.mutObs.observe(this.form, this.mutObsConfig); + }; + function createDeviceApiCall(method, params, paramsValidator = null, resultValidator = null) { + const deviceApiCall = new DeviceApiCall(params); + deviceApiCall.paramsValidator = paramsValidator; + deviceApiCall.resultValidator = resultValidator; + deviceApiCall.method = method; + deviceApiCall.throwOnResultKeysMissing = false; + deviceApiCall.unwrapResult = false; + return deviceApiCall; + } + function createRequest(method, params, id = "n/a", paramsValidator = null, resultValidator = null) { + const call = createDeviceApiCall(method, params, paramsValidator, resultValidator); + call.id = id; + return call; + } + var createNotification = createDeviceApiCall; + function validate(data, validator = null) { + if (validator) { + return validator.parse(data); } - } - get submitButtons() { - const selector = this.matching.cssSelector('submitButtonSelector'); - const allButtons = /** @type {HTMLElement[]} */[...this.form.querySelectorAll(selector)]; - return allButtons.filter(btn => (0, _autofillUtils.isPotentiallyViewable)(btn) && (0, _autofillUtils.isLikelyASubmitButton)(btn, this.matching) && (0, _autofillUtils.buttonMatchesFormType)(btn, this)); - } - attemptSubmissionIfNeeded() { - if (!this.isLogin || - // Only submit login forms - this.submitButtons.length > 1 // Do not submit if we're unsure about the submit button - ) return; - - // check for visible empty fields before attemtping submission - // this is to avoid loops where a captcha keeps failing for the user - let isThereAnEmptyVisibleField = false; - this.execOnInputs(input => { - if (input.value === '' && (0, _autofillUtils.isPotentiallyViewable)(input)) isThereAnEmptyVisibleField = true; - }, 'all', false); - if (isThereAnEmptyVisibleField) return; - - // We're not using .submit() to minimise breakage with client-side forms - this.submitButtons.forEach(button => { - if ((0, _autofillUtils.isPotentiallyViewable)(button)) { - button.click(); - } - }); + return data; } - /** - * Executes a function on input elements. Can be limited to certain element types - * @param {(input: HTMLInputElement|HTMLSelectElement) => void} fn - * @param {'all' | SupportedMainTypes} inputType - * @param {boolean} shouldCheckForDecorate - */ - execOnInputs(fn) { - let inputType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'all'; - let shouldCheckForDecorate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; - const inputs = this.inputs[inputType]; - for (const input of inputs) { - let canExecute = true; - // sometimes we want to execute even if we didn't decorate - if (shouldCheckForDecorate) { - canExecute = (0, _inputTypeConfig.isFieldDecorated)(input); - } - if (canExecute) fn(input); + // packages/device-api/lib/device-api.js + var DeviceApiTransport = class { + /** + * @param {import("./device-api-call.js").DeviceApiCall} _deviceApiCall + * @param {CallOptions} [_options] + * @returns {Promise} + */ + async send(_deviceApiCall, _options) { + return void 0; } - } - addInput(input) { - if (this.inputs.all.has(input)) return this; - - // If the form has too many inputs, destroy everything to avoid performance issues - if (this.inputs.all.size > MAX_INPUTS_PER_FORM) { - this.device.scanner.stopScanner('The form has too many inputs, bailing.'); - return this; + }; + var DeviceApi = class { + /** @param {DeviceApiTransport} transport */ + constructor(transport) { + /** @type {DeviceApiTransport} */ + __publicField(this, "transport"); + this.transport = transport; } - - // When new inputs are added after the initial scan, reanalyze the whole form - if (this.initialScanComplete && this.rescanCount < MAX_FORM_RESCANS) { - this.formAnalyzer = new _FormAnalyzer.default(this.form, input, this.matching); - this.recategorizeAllInputs(); - return this; + /** + * @template {import("./device-api-call").DeviceApiCall} D + * @param {D} deviceApiCall + * @param {CallOptions} [options] + * @returns {Promise['success']>>} + */ + async request(deviceApiCall, options) { + deviceApiCall.validateParams(); + let result = await this.transport.send(deviceApiCall, options); + let processed = deviceApiCall.preResultValidation(result); + return deviceApiCall.validateResult(processed); } + /** + * @template {import("./device-api-call").DeviceApiCall} P + * @param {P} deviceApiCall + * @param {CallOptions} [options] + * @returns {Promise} + */ + async notify(deviceApiCall, options) { + deviceApiCall.validateParams(); + return this.transport.send(deviceApiCall, options); + } + }; - // Nothing to do with 1-character fields - if (input.maxLength === 1) return this; - this.inputs.all.add(input); - const opts = { - isLogin: this.isLogin, - isHybrid: this.isHybrid, - isCCForm: this.isCCForm, - hasCredentials: Boolean(this.device.settings.availableInputTypes.credentials?.username), - supportsIdentitiesAutofill: this.device.settings.featureToggles.inputType_identities - }; - this.matching.setInputType(input, this.form, opts); - const mainInputType = (0, _matching.getInputMainType)(input); - this.inputs[mainInputType].add(input); - this.decorateInput(input); - return this; - } - - /** - * Adds event listeners and keeps track of them for subsequent removal - * @param {HTMLElement} el - * @param {Event['type']} type - * @param {(Event) => void} fn - * @param {AddEventListenerOptions} [opts] - */ - addListener(el, type, fn, opts) { - el.addEventListener(type, fn, opts); - this.listeners.add({ - el, - type, - fn, - opts - }); - } - addAutofillStyles(input) { - const initialStyles = (0, _inputStyles.getIconStylesBase)(input, this); - const activeStyles = (0, _inputStyles.getIconStylesAlternate)(input, this); - (0, _autofillUtils.addInlineStyles)(input, initialStyles); - return { - onMouseMove: activeStyles, - onMouseLeave: initialStyles - }; - } + // src/deviceApiCalls/__generated__/deviceApiCalls.js + var AddDebugFlagCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "addDebugFlag"); + __publicField(this, "paramsValidator", addDebugFlagParamsSchema); + } + }; + var GetAutofillDataCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "getAutofillData"); + __publicField(this, "id", "getAutofillDataResponse"); + __publicField(this, "paramsValidator", getAutofillDataRequestSchema); + __publicField(this, "resultValidator", getAutofillDataResponseSchema); + } + }; + var GetRuntimeConfigurationCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "getRuntimeConfiguration"); + __publicField(this, "id", "getRuntimeConfigurationResponse"); + __publicField(this, "resultValidator", getRuntimeConfigurationResponseSchema); + } + }; + var StoreFormDataCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "storeFormData"); + __publicField(this, "paramsValidator", storeFormDataSchema); + } + }; + var GetAvailableInputTypesCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "getAvailableInputTypes"); + __publicField(this, "id", "getAvailableInputTypesResponse"); + __publicField(this, "resultValidator", getAvailableInputTypesResultSchema); + } + }; + var GetAutofillInitDataCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "getAutofillInitData"); + __publicField(this, "id", "getAutofillInitDataResponse"); + __publicField(this, "resultValidator", getAutofillInitDataResponseSchema); + } + }; + var GetAutofillCredentialsCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "getAutofillCredentials"); + __publicField(this, "id", "getAutofillCredentialsResponse"); + __publicField(this, "paramsValidator", getAutofillCredentialsParamsSchema); + __publicField(this, "resultValidator", getAutofillCredentialsResultSchema); + } + }; + var SetSizeCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "setSize"); + __publicField(this, "paramsValidator", setSizeParamsSchema); + } + }; + var SelectedDetailCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "selectedDetail"); + __publicField(this, "paramsValidator", selectedDetailParamsSchema); + } + }; + var CloseAutofillParentCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "closeAutofillParent"); + } + }; + var AskToUnlockProviderCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "askToUnlockProvider"); + __publicField(this, "id", "askToUnlockProviderResponse"); + __publicField(this, "resultValidator", askToUnlockProviderResultSchema); + } + }; + var CheckCredentialsProviderStatusCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "checkCredentialsProviderStatus"); + __publicField(this, "id", "checkCredentialsProviderStatusResponse"); + __publicField(this, "resultValidator", checkCredentialsProviderStatusResultSchema); + } + }; + var SendJSPixelCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "sendJSPixel"); + __publicField(this, "paramsValidator", sendJSPixelParamsSchema); + } + }; + var SetIncontextSignupPermanentlyDismissedAtCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "setIncontextSignupPermanentlyDismissedAt"); + __publicField(this, "paramsValidator", setIncontextSignupPermanentlyDismissedAtSchema); + } + }; + var GetIncontextSignupDismissedAtCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "getIncontextSignupDismissedAt"); + __publicField(this, "id", "getIncontextSignupDismissedAt"); + __publicField(this, "resultValidator", getIncontextSignupDismissedAtSchema); + } + }; + var OpenManagePasswordsCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "openManagePasswords"); + } + }; + var OpenManageCreditCardsCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "openManageCreditCards"); + } + }; + var OpenManageIdentitiesCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "openManageIdentities"); + } + }; + var EmailProtectionStoreUserDataCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "emailProtectionStoreUserData"); + __publicField(this, "id", "emailProtectionStoreUserDataResponse"); + __publicField(this, "paramsValidator", emailProtectionStoreUserDataParamsSchema); + } + }; + var EmailProtectionRemoveUserDataCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "emailProtectionRemoveUserData"); + } + }; + var EmailProtectionGetIsLoggedInCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "emailProtectionGetIsLoggedIn"); + __publicField(this, "id", "emailProtectionGetIsLoggedInResponse"); + __publicField(this, "resultValidator", emailProtectionGetIsLoggedInResultSchema); + } + }; + var EmailProtectionGetUserDataCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "emailProtectionGetUserData"); + __publicField(this, "id", "emailProtectionGetUserDataResponse"); + __publicField(this, "resultValidator", emailProtectionGetUserDataResultSchema); + } + }; + var EmailProtectionGetCapabilitiesCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "emailProtectionGetCapabilities"); + __publicField(this, "id", "emailProtectionGetCapabilitiesResponse"); + __publicField(this, "resultValidator", emailProtectionGetCapabilitiesResultSchema); + } + }; + var EmailProtectionGetAddressesCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "emailProtectionGetAddresses"); + __publicField(this, "id", "emailProtectionGetAddressesResponse"); + __publicField(this, "resultValidator", emailProtectionGetAddressesResultSchema); + } + }; + var EmailProtectionRefreshPrivateAddressCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "emailProtectionRefreshPrivateAddress"); + __publicField(this, "id", "emailProtectionRefreshPrivateAddressResponse"); + __publicField(this, "resultValidator", emailProtectionRefreshPrivateAddressResultSchema); + } + }; + var StartEmailProtectionSignupCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "startEmailProtectionSignup"); + } + }; + var CloseEmailProtectionTabCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "closeEmailProtectionTab"); + } + }; + var ShowInContextEmailProtectionSignupPromptCall = class extends DeviceApiCall { + constructor() { + super(...arguments); + __publicField(this, "method", "ShowInContextEmailProtectionSignupPrompt"); + __publicField(this, "id", "ShowInContextEmailProtectionSignupPromptResponse"); + __publicField(this, "resultValidator", showInContextEmailProtectionSignupPromptSchema); + } + }; - /** - * Decorate here means adding listeners and an optional icon - * @param {HTMLInputElement} input - * @returns {Promise} - */ - async decorateInput(input) { - const config = (0, _inputTypeConfig.getInputConfig)(input); - const shouldDecorate = await config.shouldDecorate(input, this); - if (!shouldDecorate) return this; - input.setAttribute(ATTR_AUTOFILL, 'true'); - const hasIcon = !!config.getIconBase(input, this); - if (hasIcon) { - const { - onMouseMove, - onMouseLeave - } = this.addAutofillStyles(input); - this.addListener(input, 'mousemove', e => { - if ((0, _autofillUtils.wasAutofilledByChrome)(input)) return; - if ((0, _autofillUtils.isEventWithinDax)(e, e.target)) { - (0, _autofillUtils.addInlineStyles)(e.target, { - 'cursor': 'pointer', - ...onMouseMove - }); - } else { - (0, _autofillUtils.removeInlineStyles)(e.target, { - 'cursor': 'pointer' - }); - // Only overwrite active icon styles if tooltip is closed - if (!this.device.isTooltipActive()) { - (0, _autofillUtils.addInlineStyles)(e.target, { - ...onMouseLeave - }); - } + // src/Scanner.js + var { + MAX_INPUTS_PER_PAGE, + MAX_FORMS_PER_PAGE, + MAX_INPUTS_PER_FORM: MAX_INPUTS_PER_FORM2, + ATTR_INPUT_TYPE: ATTR_INPUT_TYPE3 + } = constants; + var defaultScannerOptions = { + // This buffer size is very large because it's an unexpected edge-case that + // a DOM will be continually modified over and over without ever stopping. If we do see 1000 unique + // new elements in the buffer however then this will prevent the algorithm from never ending. + bufferSize: 50, + // wait for a 500ms window of event silence before performing the scan + debounceTimePeriod: 500, + // how long to wait when performing the initial scan + initialDelay: 0, + // How many inputs is too many on the page. If we detect that there's above + // this maximum, then we don't scan the page. This will prevent slowdowns on + // large pages which are unlikely to require autofill anyway. + maxInputsPerPage: MAX_INPUTS_PER_PAGE, + maxFormsPerPage: MAX_FORMS_PER_PAGE, + maxInputsPerForm: MAX_INPUTS_PER_FORM2 + }; + var DefaultScanner = class { + /** + * @param {import("./DeviceInterface/InterfacePrototype").default} device + * @param {ScannerOptions} options + */ + constructor(device, options) { + /** @type Map */ + __publicField(this, "forms", /* @__PURE__ */ new Map()); + /** @type {any|undefined} the timer to reset */ + __publicField(this, "debounceTimer"); + /** @type {Set} stored changed elements until they can be processed */ + __publicField(this, "changedElements", /* @__PURE__ */ new Set()); + /** @type {ScannerOptions} */ + __publicField(this, "options"); + /** @type {HTMLInputElement | null} */ + __publicField(this, "activeInput", null); + /** @type {boolean} A flag to indicate the whole page will be re-scanned */ + __publicField(this, "rescanAll", false); + /** @type {boolean} Indicates whether we called stopScanning */ + __publicField(this, "stopped", false); + /** @type {import("./Form/matching").Matching} matching */ + __publicField(this, "matching"); + /** + * Watch for changes in the DOM, and enqueue elements to be scanned + * @type {MutationObserver} + */ + __publicField(this, "mutObs", new MutationObserver((mutationList) => { + if (this.rescanAll) { + this.enqueue([]); + return; } - }); - this.addListener(input, 'mouseleave', e => { - (0, _autofillUtils.removeInlineStyles)(e.target, { - 'cursor': 'pointer' - }); - // Only overwrite active icon styles if tooltip is closed - if (!this.device.isTooltipActive()) { - (0, _autofillUtils.addInlineStyles)(e.target, { - ...onMouseLeave - }); + const outgoing = []; + for (const mutationRecord of mutationList) { + if (mutationRecord.type === "childList") { + for (let addedNode of mutationRecord.addedNodes) { + if (!(addedNode instanceof HTMLElement)) + continue; + if (addedNode.nodeName === "DDG-AUTOFILL") + continue; + outgoing.push(addedNode); + } + } } - }); + this.enqueue(outgoing); + })); + this.device = device; + this.matching = createMatching(); + this.options = options; + this.initTimeStamp = Date.now(); } - /** - * @param {PointerEvent} e - * @returns {{ x: number; y: number; } | undefined} + * Determine whether we should fire the credentials autoprompt. This is needed because some sites are blank + * on page load and load scripts asynchronously, so our initial scan didn't set the autoprompt correctly + * @returns {boolean} */ - function getMainClickCoords(e) { - if (!e.isTrusted) return; - const isMainMouseButton = e.button === 0; - if (!isMainMouseButton) return; - return { - x: e.clientX, - y: e.clientY + get shouldAutoprompt() { + return Date.now() - this.initTimeStamp <= 1500; + } + /** + * Call this to scan once and then watch for changes. + * + * Call the returned function to remove listeners. + * @returns {(reason: string, ...rest) => void} + */ + init() { + if (this.device.globalConfig.isExtension) { + this.device.deviceApi.notify(new AddDebugFlagCall({ flag: "autofill" })); + } + window.addEventListener("pointerdown", this, true); + if (!this.device.globalConfig.isMobileApp) { + window.addEventListener("focus", this, true); + } + const delay = this.options.initialDelay; + if (delay === 0) { + window.requestIdleCallback(() => this.scanAndObserve()); + } else { + setTimeout(() => this.scanAndObserve(), delay); + } + return (reason, ...rest) => { + this.stopScanner(reason, ...rest); }; } - /** - * @param {Event} e - * @param {WeakMap} storedClickCoords - * @returns {{ x: number; y: number; } | null} + * Scan the page and begin observing changes + */ + scanAndObserve() { + window.performance?.mark?.("initial_scanner:init:start"); + this.findEligibleInputs(document); + window.performance?.mark?.("initial_scanner:init:end"); + logPerformance("initial_scanner"); + this.mutObs.observe(document.documentElement, { childList: true, subtree: true }); + } + /** + * @param context */ - function getClickCoords(e, storedClickCoords) { - // Get click co-ordinates for pointer events - // We need click coordinates to position the tooltip when the field is in an iframe - if (e.type === 'pointerdown') { - return getMainClickCoords( /** @type {PointerEvent} */e) || null; + findEligibleInputs(context) { + if (this.device.globalConfig.isDDGDomain) { + return this; } - - // Reuse a previous click co-ordinates if they exist for this element - const click = storedClickCoords.get(input); - storedClickCoords.delete(input); - return click || null; + if ("matches" in context && context.matches?.(this.matching.cssSelector("formInputsSelector"))) { + this.addInput(context); + } else { + const inputs = context.querySelectorAll(this.matching.cssSelector("formInputsSelector")); + if (inputs.length > this.options.maxInputsPerPage) { + this.stopScanner(`Too many input fields in the given context (${inputs.length}), stop scanning`, context); + return this; + } + inputs.forEach((input) => this.addInput(input)); + } + return this; } - - // Store the click to a label so we can use the click when the field is focused - // Needed to handle label clicks when the form is in an iframe - let storedClickCoords = new WeakMap(); - let timeout = null; - /** - * @param {PointerEvent} e + * Stops scanning, switches off the mutation observer and clears all forms + * @param {string} reason + * @param {any} rest */ - const handlerLabel = e => { - // Look for e.target OR it's closest parent to be a HTMLLabelElement - const control = /** @type HTMLElement */e.target?.closest('label')?.control; - if (!control) return; - if (e.isTrusted) { - storedClickCoords.set(control, getMainClickCoords(e)); + stopScanner(reason, ...rest) { + this.stopped = true; + if (shouldLog()) { + console.log(reason, ...rest); } - clearTimeout(timeout); - // Remove the stored click if the timer expires - timeout = setTimeout(() => { - storedClickCoords = new WeakMap(); - }, 1000); - }; - const handler = e => { - // Avoid firing multiple times - if (this.isAutofilling || this.device.isTooltipActive()) { + const activeInput = this.device.activeForm?.activeInput; + clearTimeout(this.debounceTimer); + this.changedElements.clear(); + this.mutObs.disconnect(); + window.removeEventListener("pointerdown", this, true); + window.removeEventListener("focus", this, true); + this.forms.forEach((form) => { + form.destroy(); + }); + this.forms.clear(); + activeInput?.focus(); + } + /** + * @param {HTMLElement|HTMLInputElement|HTMLSelectElement} input + * @returns {HTMLFormElement|HTMLElement} + */ + getParentForm(input) { + if (input instanceof HTMLInputElement || input instanceof HTMLSelectElement) { + if (input.form) { + if (this.forms.has(input.form) || // If we've added the form we've already checked that it's not a page wrapper + !isFormLikelyToBeUsedAsPageWrapper(input.form)) { + return input.form; + } + } + } + let traversalLayerCount = 0; + let element = input; + while (traversalLayerCount <= 5 && element.parentElement && element.parentElement !== document.documentElement) { + const siblingForm = element.parentElement?.querySelector("form"); + if (siblingForm && siblingForm !== element) { + return element; + } + element = element.parentElement; + const inputs = element.querySelectorAll(this.matching.cssSelector("formInputsSelector")); + const buttons = element.querySelectorAll(this.matching.cssSelector("submitButtonSelector")); + if (inputs.length > 1 || buttons.length) { + return element; + } + traversalLayerCount++; + } + return input; + } + /** + * @param {HTMLInputElement|HTMLSelectElement} input + */ + addInput(input) { + if (this.stopped) + return; + const parentForm = this.getParentForm(input); + if (parentForm instanceof HTMLFormElement && this.forms.has(parentForm)) { + const foundForm = this.forms.get(parentForm); + if (foundForm && foundForm.inputs.all.size < MAX_INPUTS_PER_FORM2) { + foundForm.addInput(input); + } else { + this.stopScanner("The form has too many inputs, destroying."); + } return; } - - // On mobile, we don't trigger on focus, so here we get the target control on label click - const isLabel = e.target instanceof HTMLLabelElement; - const input = isLabel ? e.target.control : e.target; - if (!input || !this.inputs.all.has(input)) return; - if ((0, _autofillUtils.wasAutofilledByChrome)(input)) return; - if (!(0, _inputTypeConfig.canBeInteractedWith)(input)) return; - const clickCoords = getClickCoords(e, storedClickCoords); - if (e.type === 'pointerdown') { - // Only allow real user clicks with co-ordinates through - if (!e.isTrusted || !clickCoords) return; - } - if (this.shouldOpenTooltip(e, input)) { - const iconClicked = (0, _autofillUtils.isEventWithinDax)(e, input); - // On mobile and extensions we don't trigger the focus event to avoid - // keyboard flashing and conflicts with browsers' own tooltips - if ((this.device.globalConfig.isMobileApp || this.device.globalConfig.isExtension) && - // Avoid the icon capturing clicks on small fields making it impossible to focus - input.offsetWidth > 50 && iconClicked) { - e.preventDefault(); - e.stopImmediatePropagation(); - input.blur(); - } - this.touched.add(input); - this.device.attachTooltip({ - form: this, - input: input, - click: clickCoords, - trigger: 'userInitiated', - triggerMetaData: { - // An 'icon' click is very different to a field click or focus. - // It indicates an explicit opt-in to the feature. - type: iconClicked ? 'explicit-opt-in' : 'implicit-opt-in' - } - }); - const activeStyles = (0, _inputStyles.getIconStylesAlternate)(input, this); - (0, _autofillUtils.addInlineStyles)(input, activeStyles); + if (parentForm.role === "search") + return; + let previouslyFoundParent, childForm; + for (const [formEl] of this.forms) { + if (!formEl.isConnected) { + this.forms.delete(formEl); + continue; + } + if (formEl.contains(parentForm)) { + previouslyFoundParent = formEl; + break; + } + if (parentForm.contains(formEl)) { + childForm = formEl; + break; + } } - }; - if (!(input instanceof HTMLSelectElement)) { - const events = ['pointerdown']; - if (!this.device.globalConfig.isMobileApp) events.push('focus'); - input.labels?.forEach(label => { - if (this.device.globalConfig.isMobileApp) { - // On mobile devices we don't trigger on focus, so we use the click handler here - this.addListener(label, 'pointerdown', handler); + if (previouslyFoundParent) { + if (parentForm instanceof HTMLFormElement && parentForm !== previouslyFoundParent) { + this.forms.delete(previouslyFoundParent); } else { - // Needed to handle label clicks when the form is in an iframe - this.addListener(label, 'pointerdown', handlerLabel); + this.forms.get(previouslyFoundParent)?.addInput(input); } - }); - events.forEach(ev => this.addListener(input, ev, handler)); - } - return this; - } - shouldOpenTooltip(e, input) { - if (!(0, _autofillUtils.isPotentiallyViewable)(input)) return false; - - // Always open if the user has clicked on the Dax icon - if ((0, _autofillUtils.isEventWithinDax)(e, input)) return true; - if (this.device.globalConfig.isWindows) return true; - const subtype = (0, _matching.getInputSubtype)(input); - const isIncontextSignupAvailable = this.device.inContextSignup?.isAvailable(subtype); - if (this.device.globalConfig.isApp) { - const mainType = (0, _matching.getInputMainType)(input); - // Check if, without in-context signup (passed as `null` below), - // we'd have any other items to show. This lets us know if we're - // just showing in-context signup, or with other autofill items. - const hasSavedDetails = this.device.settings.canAutofillType({ - mainType, - subtype - }, null); - - // Don't open the tooltip on input focus whenever it'll only show in-context signup - if (!hasSavedDetails && isIncontextSignupAvailable) return false; - return true; + } else { + if (childForm) { + this.forms.get(childForm)?.destroy(); + this.forms.delete(childForm); + } + if (this.forms.size < this.options.maxFormsPerPage) { + this.forms.set(parentForm, new Form(parentForm, input, this.device, this.matching, this.shouldAutoprompt)); + } else { + this.stopScanner("The page has too many forms, stop adding them."); + } + } } - if (this.device.globalConfig.isExtension || this.device.globalConfig.isMobileApp) { - // Don't open the tooltip on input focus whenever it's showing in-context signup - if (isIncontextSignupAvailable) return false; + /** + * enqueue elements to be re-scanned after the given + * amount of time has elapsed. + * + * @param {(HTMLElement|Document)[]} htmlElements + */ + enqueue(htmlElements) { + if (this.changedElements.size >= this.options.bufferSize) { + this.rescanAll = true; + this.changedElements.clear(); + } else if (!this.rescanAll) { + for (let element of htmlElements) { + this.changedElements.add(element); + } + } + clearTimeout(this.debounceTimer); + this.debounceTimer = setTimeout(() => { + window.performance?.mark?.("scanner:init:start"); + this.processChangedElements(); + this.changedElements.clear(); + this.rescanAll = false; + window.performance?.mark?.("scanner:init:end"); + logPerformance("scanner"); + }, this.options.debounceTimePeriod); } - return !this.touched.has(input) && !input.classList.contains('ddg-autofilled'); - } - autofillInput(input, string, dataType) { - // Do not autofill if it's invisible (select elements can be hidden because of custom implementations) - if (input instanceof HTMLInputElement && !(0, _autofillUtils.isPotentiallyViewable)(input)) return; - // Do not autofill if it's disabled or readonly to avoid potential breakage - if (!(0, _inputTypeConfig.canBeInteractedWith)(input)) return; - - // Don't override values the user provided, unless it's the focused input or we're autofilling creditCards - if (dataType !== 'creditCards' && - // creditCards always override, the others only when we're focusing the input - input.nodeName !== 'SELECT' && input.value !== '' && - // if the input is not empty - this.activeInput !== input // and this is not the active input - ) return; // do not overwrite the value - - // If the value is already there, just return - if (input.value === string) return; - const successful = (0, _autofillUtils.setValue)(input, string, this.device.globalConfig); - if (!successful) return; - input.classList.add('ddg-autofilled'); - (0, _autofillUtils.addInlineStyles)(input, (0, _inputStyles.getIconStylesAutofilled)(input, this)); - this.touched.add(input); - - // If the user changes the value, remove the decoration - input.addEventListener('input', e => this.removeAllHighlights(e, dataType), { - once: true - }); - } - - /** - * Autofill method for email protection only - * @param {string} alias - * @param {'all' | SupportedMainTypes} dataType - */ - autofillEmail(alias) { - let dataType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'identities'; - this.isAutofilling = true; - this.execOnInputs(input => { - const inputSubtype = (0, _matching.getInputSubtype)(input); - if (inputSubtype === 'emailAddress') { - this.autofillInput(input, alias, dataType); - } - }, dataType); - this.isAutofilling = false; - this.removeTooltip(); - } - autofillData(data, dataType) { - this.isAutofilling = true; - this.execOnInputs(input => { - const inputSubtype = (0, _matching.getInputSubtype)(input); - let autofillData = data[inputSubtype]; - if (inputSubtype === 'expiration' && input instanceof HTMLInputElement) { - autofillData = (0, _formatters.getUnifiedExpiryDate)(input, data.expirationMonth, data.expirationYear, this); + /** + * re-scan the changed elements, but only if they + * are still present in the DOM + */ + processChangedElements() { + if (this.rescanAll) { + this.findEligibleInputs(document); + return; } - if (inputSubtype === 'expirationYear' && input instanceof HTMLInputElement) { - autofillData = (0, _formatters.formatCCYear)(input, autofillData, this); + for (let element of this.changedElements) { + if (element.isConnected) { + this.findEligibleInputs(element); + } } - if (inputSubtype === 'addressCountryCode') { - autofillData = (0, _formatters.getCountryName)(input, data); + } + handleEvent(event) { + switch (event.type) { + case "pointerdown": + case "focus": + this.scanShadow(event); + break; } - if (autofillData) { - const variant = (0, _matching.getInputVariant)(input); - if (!variant) { - return this.autofillInput(input, autofillData, dataType); - } - - // Fields with a variant should only be filled when fill is initiated from the same variant. - // This ensures we don't overwrite the current password when filling a - // generated password in password update forms. - if (variant === 'new' && _Credentials.AUTOGENERATED_KEY in data) { - return this.autofillInput(input, autofillData, dataType); - } - if (variant === 'current' && !(_Credentials.AUTOGENERATED_KEY in data)) { - return this.autofillInput(input, autofillData, dataType); - } + } + /** + * Scan clicked input fields, even if they're within a shadow tree + * @param {FocusEvent | PointerEvent} event + */ + scanShadow(event) { + if (this.stopped || !(event.target instanceof Element) || !event.target?.shadowRoot) + return; + window.performance?.mark?.("scan_shadow:init:start"); + const realTarget = pierceShadowTree(event, HTMLInputElement); + if (realTarget instanceof HTMLInputElement && !realTarget.hasAttribute(ATTR_INPUT_TYPE3)) { + this.findEligibleInputs(realTarget.getRootNode()); } - }, dataType); - this.isAutofilling = false; - - // After autofill we check if form values match the data provided… - const formValues = this.getValuesReadyForStorage(); - const areAllFormValuesKnown = Object.keys(formValues[dataType] || {}).every(subtype => formValues[dataType][subtype] === data[subtype]); - if (areAllFormValuesKnown) { - // …if we know all the values do not prompt to store data - this.shouldPromptToStoreData = false; - // reset this to its initial value - this.shouldAutoSubmit = this.device.globalConfig.isMobileApp; - } else { - // …otherwise we will prompt and do not want to autosubmit because the experience is jarring - this.shouldAutoSubmit = false; + window.performance?.mark?.("scan_shadow:init:end"); + logPerformance("scan_shadow"); } - this.device.postAutofill?.(data, dataType, this); - this.removeTooltip(); - } - - /** - * Set all inputs of the data type to "touched" - * @param {'all' | SupportedMainTypes} dataType - */ - touchAllInputs() { - let dataType = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'all'; - this.execOnInputs(input => this.touched.add(input), dataType); - } - getFirstViableCredentialsInput() { - return [...this.inputs.credentials].find(input => (0, _inputTypeConfig.canBeInteractedWith)(input) && (0, _autofillUtils.isPotentiallyViewable)(input)); - } - async promptLoginIfNeeded() { - if (document.visibilityState !== 'visible' || !this.isLogin) return; - const firstCredentialInput = this.getFirstViableCredentialsInput(); - const input = this.activeInput || firstCredentialInput; - if (!input) return; - const mainType = (0, _matching.getInputMainType)(input); - const subtype = (0, _matching.getInputSubtype)(input); - await this.device.settings.populateDataIfNeeded({ - mainType, - subtype + }; + function createScanner(device, scannerOptions) { + return new DefaultScanner(device, { + ...defaultScannerOptions, + ...scannerOptions }); - if (this.device.settings.canAutofillType({ - mainType, - subtype - }, this.device.inContextSignup)) { - // The timeout is needed in case the page shows a cookie prompt with a slight delay - setTimeout(() => { - // safeExecute checks that the element is on screen according to IntersectionObserver - (0, _autofillUtils.safeExecute)(this.form, () => { - const { - x, - y, - width, - height - } = this.form.getBoundingClientRect(); - const elHCenter = x + width / 2; - const elVCenter = y + height / 2; - // This checks that the form is not covered by anything else - const topMostElementFromPoint = document.elementFromPoint(elHCenter, elVCenter); - if (this.form.contains(topMostElementFromPoint)) { - this.execOnInputs(input => { - if ((0, _autofillUtils.isPotentiallyViewable)(input)) { - this.touched.add(input); - } - }, 'credentials'); - this.device.attachTooltip({ - form: this, - input: input, - click: null, - trigger: 'autoprompt', - triggerMetaData: { - type: 'implicit-opt-in' - } - }); - } - }); - }, 200); - } } -} -exports.Form = Form; - -},{"../InputTypes/Credentials.js":35,"../autofill-utils.js":52,"../constants.js":55,"./FormAnalyzer.js":24,"./formatters.js":26,"./inputStyles.js":27,"./inputTypeConfig.js":28,"./matching.js":33}],24:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _matching = require("./matching.js"); -var _constants = require("../constants.js"); -var _compiledMatchingConfig = require("./matching-config/__generated__/compiled-matching-config.js"); -var _autofillUtils = require("../autofill-utils.js"); -class FormAnalyzer { - /** @type HTMLElement */ - form; - /** @type Matching */ - matching; - /** - * @param {HTMLElement} form - * @param {HTMLInputElement|HTMLSelectElement} input - * @param {Matching} [matching] - */ - constructor(form, input, matching) { - this.form = form; - this.matching = matching || new _matching.Matching(_compiledMatchingConfig.matchingConfiguration); + // src/UI/controllers/UIController.js + var UIController = class { /** - * The signal is a continuum where negative values imply login and positive imply signup - * @type {number} + * Implement this method to control what happen when Autofill + * has enough information to 'attach' a tooltip. + * + * @param {AttachArgs} _args + * @returns {void} */ - this.autofillSignal = 0; + attach(_args) { + throw new Error("must implement attach"); + } /** - * A hybrid form can be either a login or a signup, the site uses a single form for both - * @type {number} + * Implement this if your tooltip can be created from positioning + * + topContextData. + * + * For example, in an 'overlay' on macOS/Windows this is needed since + * there's no page information to call 'attach' above. + * + * @param {PosFn} _pos + * @param {TopContextData} _topContextData + * @returns {any | null} */ - this.hybridSignal = 0; - + createTooltip(_pos, _topContextData) { + } /** - * Collects the signals for debugging purposes - * @type {string[]} + * @param {string} _via */ - this.signals = []; - this.evaluateElAttributes(input, 1, true); - form ? this.evaluateForm() : this.evaluatePage(); - return this; - } - - /** - * Hybrid forms can be used for both login and signup - * @returns {boolean} - */ - get isHybrid() { - // When marking for hybrid we also want to ensure other signals are weak - const areOtherSignalsWeak = Math.abs(this.autofillSignal) < 10; - return this.hybridSignal > 0 && areOtherSignalsWeak; - } - get isLogin() { - if (this.isHybrid) return false; - return this.autofillSignal < 0; - } - get isSignup() { - if (this.isHybrid) return false; - return this.autofillSignal >= 0; - } - - /** - * Tilts the scoring towards Signup - * @param {number} strength - * @param {string} signal - * @returns {FormAnalyzer} - */ - increaseSignalBy(strength, signal) { - this.autofillSignal += strength; - this.signals.push(`${signal}: +${strength}`); - return this; - } - - /** - * Tilts the scoring towards Login - * @param {number} strength - * @param {string} signal - * @returns {FormAnalyzer} - */ - decreaseSignalBy(strength, signal) { - this.autofillSignal -= strength; - this.signals.push(`${signal}: -${strength}`); - return this; - } - - /** - * Increases the probability that this is a hybrid form (can be either login or signup) - * @param {number} strength - * @param {string} signal - * @returns {FormAnalyzer} - */ - increaseHybridSignal(strength, signal) { - this.hybridSignal += strength; - this.signals.push(`${signal} (hybrid): +${strength}`); - return this; - } - - /** - * Updates the Login<->Signup signal according to the provided parameters - * @param {object} p - * @param {string} p.string - The string to check - * @param {number} p.strength - Strength of the signal - * @param {string} [p.signalType] - For debugging purposes, we give a name to the signal - * @param {boolean} [p.shouldFlip] - Flips the signals, i.e. when a link points outside. See below - * @param {boolean} [p.shouldCheckUnifiedForm] - Should check for login/signup forms - * @param {boolean} [p.shouldBeConservative] - Should use the conservative signup regex - * @returns {FormAnalyzer} - */ - updateSignal(_ref) { - let { - string, - strength, - signalType = 'generic', - shouldFlip = false, - shouldCheckUnifiedForm = false, - shouldBeConservative = false - } = _ref; - // If the string is empty or too long (noisy) do nothing - if (!string || string.length > _constants.constants.TEXT_LENGTH_CUTOFF) return this; - const matchesLogin = (0, _autofillUtils.safeRegexTest)(/current.?password/i, string) || (0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('loginRegex'), string) || (0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('resetPasswordLink'), string); - - // Check explicitly for unified login/signup forms - if (shouldCheckUnifiedForm && matchesLogin && (0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('conservativeSignupRegex'), string)) { - this.increaseHybridSignal(strength, signalType); - return this; + removeTooltip(_via) { } - const signupRegexToUse = this.matching.getDDGMatcherRegex(shouldBeConservative ? 'conservativeSignupRegex' : 'signupRegex'); - const matchesSignup = (0, _autofillUtils.safeRegexTest)(/new.?password/i, string) || (0, _autofillUtils.safeRegexTest)(signupRegexToUse, string); - - // In some cases a login match means the login is somewhere else, i.e. when a link points outside - if (shouldFlip) { - if (matchesLogin) this.increaseSignalBy(strength, signalType); - if (matchesSignup) this.decreaseSignalBy(strength, signalType); - } else { - if (matchesLogin) this.decreaseSignalBy(strength, signalType); - if (matchesSignup) this.increaseSignalBy(strength, signalType); + /** + * Set the currently open HTMLTooltip instance + * + * @param {import("../HTMLTooltip.js").HTMLTooltip} _tooltip + */ + setActiveTooltip(_tooltip) { } - return this; - } - evaluateElAttributes(el) { - let signalStrength = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 3; - let isInput = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; - Array.from(el.attributes).forEach(attr => { - if (attr.name === 'style') return; - const attributeString = `${attr.name}=${attr.value}`; - this.updateSignal({ - string: attributeString, - strength: signalStrength, - signalType: `${el.name} attr: ${attributeString}`, - shouldCheckUnifiedForm: isInput - }); - }); - } - evaluateUrl() { - const path = window.location.pathname; - const matchesLogin = (0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('loginRegex'), path); - const matchesSignup = (0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('conservativeSignupRegex'), path); - - // If the url matches both, do nothing: the signal is probably confounding - if (matchesLogin && matchesSignup) return; - if (matchesLogin) { - this.decreaseSignalBy(1, 'url matches login'); + /** + * Get the currently open HTMLTooltip instance, if one exists + * + * @returns {import("../HTMLTooltip.js").HTMLTooltip | null} + */ + getActiveTooltip() { + return null; } - if (matchesSignup) { - this.increaseSignalBy(1, 'url matches signup'); + /** + * Indicate whether the controller deems itself 'active' + * + * @returns {boolean} + */ + isActive() { + return false; } - } - evaluatePageTitle() { - const pageTitle = document.title; - this.updateSignal({ - string: pageTitle, - strength: 2, - signalType: `page title: ${pageTitle}`, - shouldCheckUnifiedForm: true - }); - } - evaluatePageHeadings() { - const headings = document.querySelectorAll('h1, h2, h3'); - headings.forEach(heading => { - const textContent = (0, _matching.removeExcessWhitespace)(heading.textContent || ''); - this.updateSignal({ - string: textContent, - strength: 0.5, - signalType: `heading: ${textContent}`, - shouldCheckUnifiedForm: true, - shouldBeConservative: true - }); - }); - } - evaluatePage() { - this.evaluatePageTitle(); - this.evaluatePageHeadings(); - // Check for submit buttons - const buttons = document.querySelectorAll(this.matching.cssSelector('submitButtonSelector')); - buttons.forEach(button => { - // if the button has a form, it's not related to our input, because our input has no form here - if (button instanceof HTMLButtonElement) { - if (!button.form && !button.closest('form')) { - this.evaluateElement(button); - this.evaluateElAttributes(button, 0.5); - } - } - }); - } - evaluateElement(el) { - const string = (0, _autofillUtils.getTextShallow)(el); - if (el.matches(this.matching.cssSelector('password'))) { - // These are explicit signals by the web author, so we weigh them heavily - this.updateSignal({ - string: el.getAttribute('autocomplete') || el.getAttribute('name') || '', - strength: 5, - signalType: `explicit: ${el.getAttribute('autocomplete')}` - }); - return; + /** + * Updates the items in the tooltip based on new data. Currently only supporting credentials. + * @param {CredentialsObject[]} _data + */ + updateItems(_data7) { } + destroy() { + } + }; - // check button contents - if (el.matches(this.matching.cssSelector('submitButtonSelector') + ', *[class*=button]')) { - // If we're confident this is the submit button, it's a stronger signal - let likelyASubmit = (0, _autofillUtils.isLikelyASubmitButton)(el, this.matching); - let shouldFlip = false; - if (likelyASubmit) { - this.form.querySelectorAll('input[type=submit], button[type=submit]').forEach(submit => { - // If there is another element marked as submit and this is not, flip back to false - if (el.type !== 'submit' && el !== submit) { - likelyASubmit = false; - } + // src/UI/controllers/NativeUIController.js + var _passwordStatus; + var NativeUIController = class extends UIController { + constructor() { + super(...arguments); + /** + * Keep track of when passwords were suggested/rejected/accepted etc + * State is kept here because it's specific to the interactions on mobile (eg: NativeUIController) + * + * @type {"default" | "rejected"} + */ + __privateAdd(this, _passwordStatus, "default"); + } + /** + * @param {import('./UIController').AttachArgs} args + */ + attach(args) { + const { form, input, device, trigger, triggerMetaData, topContextData } = args; + const inputType = getInputType(input); + const mainType = getMainTypeFromType(inputType); + const subType = getSubtypeFromType(inputType); + if (mainType === "unknown") { + throw new Error('unreachable, should not be here if (mainType === "unknown")'); + } + if (trigger === "autoprompt") { + window.scrollTo({ + behavior: "smooth", + top: form.form.getBoundingClientRect().top - document.body.getBoundingClientRect().top - 50 }); - } else { - // Here we don't think this is a submit, so if there is another submit in the form, flip the score - const thereIsASubmitButton = Boolean(this.form.querySelector('input[type=submit], button[type=submit]')); - const isSocialButton = /facebook|twitter|google|apple/i.test(string); - shouldFlip = thereIsASubmitButton && !isSocialButton; - } - const strength = likelyASubmit ? 20 : 4; - this.updateSignal({ - string, - strength, - signalType: `button: ${string}`, - shouldFlip + } + let payload = { + inputType, + mainType, + subType, + trigger + }; + if (device.settings.featureToggles.password_generation) { + payload = this.appendGeneratedPassword(topContextData, payload, triggerMetaData); + } + device.deviceApi.request(new GetAutofillDataCall(payload)).then((resp) => { + switch (resp.action) { + case "fill": { + if (mainType in resp) { + form.autofillData(resp[mainType], mainType); + } else { + throw new Error(`action: "fill" cannot occur because "${mainType}" was missing`); + } + break; + } + case "focus": { + form.activeInput?.focus(); + break; + } + case "acceptGeneratedPassword": { + form.autofillData({ + password: topContextData.credentials?.[0].password, + [AUTOGENERATED_KEY]: true + }, mainType); + break; + } + case "rejectGeneratedPassword": { + __privateSet(this, _passwordStatus, "rejected"); + form.touchAllInputs("credentials"); + form.activeInput?.focus(); + break; + } + default: { + if (args.device.isTestMode()) { + console.warn("response not handled", resp); + } + } + } + }).catch((e) => { + console.error("NativeTooltip::device.getAutofillData(payload)"); + console.error(e); }); - return; } - // if an external link matches one of the regexes, we assume the match is not pertinent to the current form - if (el instanceof HTMLAnchorElement && el.href && el.getAttribute('href') !== '#' || (el.getAttribute('role') || '').toUpperCase() === 'LINK' || el.matches('button[class*=secondary]')) { - let shouldFlip = true; - let strength = 1; - // Don't flip forgotten password links - if ((0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('resetPasswordLink'), string)) { - shouldFlip = false; - strength = 3; - } else if ((0, _autofillUtils.safeRegexTest)(this.matching.getDDGMatcherRegex('loginProvidersRegex'), string)) { - // Don't flip login providers links - shouldFlip = false; - } - this.updateSignal({ - string, - strength, - signalType: `external link: ${string}`, - shouldFlip - }); - } else { - // any other case - this.updateSignal({ - string, - strength: 1, - signalType: `generic: ${string}`, - shouldCheckUnifiedForm: true - }); + /** + * If a password exists in `topContextData`, we can append it to the outgoing data + * in a way that native platforms can easily understand. + * + * @param {TopContextData} topContextData + * @param {import('../../deviceApiCalls/__generated__/validators-ts.js').GetAutofillDataRequest} outgoingData + * @param {import('../../UI/controllers/UIController.js').AttachArgs['triggerMetaData']} triggerMetaData + * @return {import('../../deviceApiCalls/__generated__/validators-ts.js').GetAutofillDataRequest} + */ + appendGeneratedPassword(topContextData, outgoingData, triggerMetaData) { + const autoGeneratedCredential = topContextData.credentials?.find((credential) => credential.autogenerated); + if (!autoGeneratedCredential?.password) { + return outgoingData; + } + function suggestPassword() { + if (!autoGeneratedCredential?.password) + throw new Error("unreachable"); + return { + ...outgoingData, + generatedPassword: { + value: autoGeneratedCredential.password, + username: autoGeneratedCredential.username + } + }; + } + if (triggerMetaData.type === "explicit-opt-in") { + return suggestPassword(); + } + if (triggerMetaData.type === "implicit-opt-in" && __privateGet(this, _passwordStatus) !== "rejected") { + return suggestPassword(); + } + return outgoingData; } - } - evaluateForm() { - // Check page url - this.evaluateUrl(); - - // Check page title - this.evaluatePageTitle(); - - // Check form attributes - this.evaluateElAttributes(this.form); - - // Check form contents (noisy elements are skipped with the safeUniversalSelector) - this.form.querySelectorAll(this.matching.cssSelector('safeUniversalSelector')).forEach(el => { - // Check if element is not hidden. Note that we can't use offsetHeight - // nor intersectionObserver, because the element could be outside the - // viewport or its parent hidden - const displayValue = window.getComputedStyle(el, null).getPropertyValue('display'); - if (displayValue !== 'none') this.evaluateElement(el); - }); + }; + _passwordStatus = new WeakMap(); - // A form with many fields is unlikely to be a login form - const relevantFields = this.form.querySelectorAll(this.matching.cssSelector('genericTextField')); - if (relevantFields.length >= 4) { - this.increaseSignalBy(relevantFields.length * 1.5, 'many fields: it is probably not a login'); + // packages/messaging/webkit.js + var WebkitMessagingTransport = class { + /** + * @param {WebkitMessagingConfig} config + */ + constructor(config) { + /** @type {WebkitMessagingConfig} */ + __publicField(this, "config"); + __publicField(this, "globals"); + /** + * @type {{name: string, length: number}} + */ + __publicField(this, "algoObj", { name: "AES-GCM", length: 256 }); + this.config = config; + this.globals = captureGlobals(); + if (!this.config.hasModernWebkitAPI) { + this.captureWebkitHandlers(this.config.webkitMessageHandlerNames); + } } - - // If we can't decide at this point, try reading page headings - if (this.autofillSignal === 0) { - this.evaluatePageHeadings(); + /** + * Sends message to the webkit layer (fire and forget) + * @param {String} handler + * @param {*} data + * @internal + */ + wkSend(handler, data = {}) { + if (!(handler in this.globals.window.webkit.messageHandlers)) { + throw new MissingHandler(`Missing webkit handler: '${handler}'`, handler); + } + const outgoing = { + ...data, + messageHandling: { ...data.messageHandling, secret: this.config.secret } + }; + if (!this.config.hasModernWebkitAPI) { + if (!(handler in this.globals.capturedWebkitHandlers)) { + throw new MissingHandler(`cannot continue, method ${handler} not captured on macos < 11`, handler); + } else { + return this.globals.capturedWebkitHandlers[handler](outgoing); + } + } + return this.globals.window.webkit.messageHandlers[handler].postMessage?.(outgoing); } - return this; - } - - /** @type {undefined|boolean} */ - _isCCForm = undefined; - /** - * Tries to infer if it's a credit card form - * @returns {boolean} - */ - isCCForm() { - if (this._isCCForm !== undefined) return this._isCCForm; - const formEl = this.form; - const ccFieldSelector = this.matching.joinCssSelectors('cc'); - if (!ccFieldSelector) { - this._isCCForm = false; - return this._isCCForm; + /** + * Sends message to the webkit layer and waits for the specified response + * @param {String} handler + * @param {*} data + * @returns {Promise<*>} + * @internal + */ + async wkSendAndWait(handler, data = {}) { + if (this.config.hasModernWebkitAPI) { + const response = await this.wkSend(handler, data); + return this.globals.JSONparse(response || "{}"); + } + try { + const randMethodName = this.createRandMethodName(); + const key = await this.createRandKey(); + const iv = this.createRandIv(); + const { ciphertext, tag } = await new this.globals.Promise((resolve) => { + this.generateRandomMethod(randMethodName, resolve); + data.messageHandling = new SecureMessagingParams({ + methodName: randMethodName, + secret: this.config.secret, + key: this.globals.Arrayfrom(key), + iv: this.globals.Arrayfrom(iv) + }); + this.wkSend(handler, data); + }); + const cipher = new this.globals.Uint8Array([...ciphertext, ...tag]); + const decrypted = await this.decrypt(cipher, key, iv); + return this.globals.JSONparse(decrypted || "{}"); + } catch (e) { + if (e instanceof MissingHandler) { + throw e; + } else { + console.error("decryption failed", e); + console.error(e); + return { error: e }; + } + } } - const hasCCSelectorChild = formEl.matches(ccFieldSelector) || formEl.querySelector(ccFieldSelector); - // If the form contains one of the specific selectors, we have high confidence - if (hasCCSelectorChild) { - this._isCCForm = true; - return this._isCCForm; + /** + * @param {string} name + * @param {Record} [data] + */ + notify(name, data = {}) { + this.wkSend(name, data); } - - // Read form attributes to find a signal - const hasCCAttribute = [...formEl.attributes].some(_ref2 => { - let { - name, - value - } = _ref2; - return (0, _autofillUtils.safeRegexTest)(/(credit|payment).?card/i, `${name}=${value}`); - }); - if (hasCCAttribute) { - this._isCCForm = true; - return this._isCCForm; + /** + * @param {string} name + * @param {Record} [data] + */ + request(name, data = {}) { + return this.wkSendAndWait(name, data); } - - // Match form textContent against common cc fields (includes hidden labels) - const textMatches = formEl.textContent?.match(/(credit|payment).?card(.?number)?|ccv|security.?code|cvv|cvc|csc/ig); - // De-dupe matches to avoid counting the same element more than once - const deDupedMatches = new Set(textMatches?.map(match => match.toLowerCase())); - - // We check for more than one to minimise false positives - this._isCCForm = Boolean(textMatches && deDupedMatches.size > 1); - return this._isCCForm; - } -} -var _default = exports.default = FormAnalyzer; - -},{"../autofill-utils.js":52,"../constants.js":55,"./matching-config/__generated__/compiled-matching-config.js":31,"./matching.js":33}],25:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.COUNTRY_NAMES_TO_CODES = exports.COUNTRY_CODES_TO_NAMES = void 0; -/** - * Country names object using 2-letter country codes to reference country name - * Derived from the Intl.DisplayNames implementation - * @source https://stackoverflow.com/a/70517921/1948947 - */ -const COUNTRY_CODES_TO_NAMES = exports.COUNTRY_CODES_TO_NAMES = { - AC: 'Ascension Island', - AD: 'Andorra', - AE: 'United Arab Emirates', - AF: 'Afghanistan', - AG: 'Antigua & Barbuda', - AI: 'Anguilla', - AL: 'Albania', - AM: 'Armenia', - AN: 'Curaçao', - AO: 'Angola', - AQ: 'Antarctica', - AR: 'Argentina', - AS: 'American Samoa', - AT: 'Austria', - AU: 'Australia', - AW: 'Aruba', - AX: 'Åland Islands', - AZ: 'Azerbaijan', - BA: 'Bosnia & Herzegovina', - BB: 'Barbados', - BD: 'Bangladesh', - BE: 'Belgium', - BF: 'Burkina Faso', - BG: 'Bulgaria', - BH: 'Bahrain', - BI: 'Burundi', - BJ: 'Benin', - BL: 'St. Barthélemy', - BM: 'Bermuda', - BN: 'Brunei', - BO: 'Bolivia', - BQ: 'Caribbean Netherlands', - BR: 'Brazil', - BS: 'Bahamas', - BT: 'Bhutan', - BU: 'Myanmar (Burma)', - BV: 'Bouvet Island', - BW: 'Botswana', - BY: 'Belarus', - BZ: 'Belize', - CA: 'Canada', - CC: 'Cocos (Keeling) Islands', - CD: 'Congo - Kinshasa', - CF: 'Central African Republic', - CG: 'Congo - Brazzaville', - CH: 'Switzerland', - CI: 'Côte d’Ivoire', - CK: 'Cook Islands', - CL: 'Chile', - CM: 'Cameroon', - CN: 'China mainland', - CO: 'Colombia', - CP: 'Clipperton Island', - CR: 'Costa Rica', - CS: 'Serbia', - CU: 'Cuba', - CV: 'Cape Verde', - CW: 'Curaçao', - CX: 'Christmas Island', - CY: 'Cyprus', - CZ: 'Czechia', - DD: 'Germany', - DE: 'Germany', - DG: 'Diego Garcia', - DJ: 'Djibouti', - DK: 'Denmark', - DM: 'Dominica', - DO: 'Dominican Republic', - DY: 'Benin', - DZ: 'Algeria', - EA: 'Ceuta & Melilla', - EC: 'Ecuador', - EE: 'Estonia', - EG: 'Egypt', - EH: 'Western Sahara', - ER: 'Eritrea', - ES: 'Spain', - ET: 'Ethiopia', - EU: 'European Union', - EZ: 'Eurozone', - FI: 'Finland', - FJ: 'Fiji', - FK: 'Falkland Islands', - FM: 'Micronesia', - FO: 'Faroe Islands', - FR: 'France', - FX: 'France', - GA: 'Gabon', - GB: 'United Kingdom', - GD: 'Grenada', - GE: 'Georgia', - GF: 'French Guiana', - GG: 'Guernsey', - GH: 'Ghana', - GI: 'Gibraltar', - GL: 'Greenland', - GM: 'Gambia', - GN: 'Guinea', - GP: 'Guadeloupe', - GQ: 'Equatorial Guinea', - GR: 'Greece', - GS: 'So. Georgia & So. Sandwich Isl.', - GT: 'Guatemala', - GU: 'Guam', - GW: 'Guinea-Bissau', - GY: 'Guyana', - HK: 'Hong Kong', - HM: 'Heard & McDonald Islands', - HN: 'Honduras', - HR: 'Croatia', - HT: 'Haiti', - HU: 'Hungary', - HV: 'Burkina Faso', - IC: 'Canary Islands', - ID: 'Indonesia', - IE: 'Ireland', - IL: 'Israel', - IM: 'Isle of Man', - IN: 'India', - IO: 'Chagos Archipelago', - IQ: 'Iraq', - IR: 'Iran', - IS: 'Iceland', - IT: 'Italy', - JE: 'Jersey', - JM: 'Jamaica', - JO: 'Jordan', - JP: 'Japan', - KE: 'Kenya', - KG: 'Kyrgyzstan', - KH: 'Cambodia', - KI: 'Kiribati', - KM: 'Comoros', - KN: 'St. Kitts & Nevis', - KP: 'North Korea', - KR: 'South Korea', - KW: 'Kuwait', - KY: 'Cayman Islands', - KZ: 'Kazakhstan', - LA: 'Laos', - LB: 'Lebanon', - LC: 'St. Lucia', - LI: 'Liechtenstein', - LK: 'Sri Lanka', - LR: 'Liberia', - LS: 'Lesotho', - LT: 'Lithuania', - LU: 'Luxembourg', - LV: 'Latvia', - LY: 'Libya', - MA: 'Morocco', - MC: 'Monaco', - MD: 'Moldova', - ME: 'Montenegro', - MF: 'St. Martin', - MG: 'Madagascar', - MH: 'Marshall Islands', - MK: 'North Macedonia', - ML: 'Mali', - MM: 'Myanmar (Burma)', - MN: 'Mongolia', - MO: 'Macao', - MP: 'Northern Mariana Islands', - MQ: 'Martinique', - MR: 'Mauritania', - MS: 'Montserrat', - MT: 'Malta', - MU: 'Mauritius', - MV: 'Maldives', - MW: 'Malawi', - MX: 'Mexico', - MY: 'Malaysia', - MZ: 'Mozambique', - NA: 'Namibia', - NC: 'New Caledonia', - NE: 'Niger', - NF: 'Norfolk Island', - NG: 'Nigeria', - NH: 'Vanuatu', - NI: 'Nicaragua', - NL: 'Netherlands', - NO: 'Norway', - NP: 'Nepal', - NR: 'Nauru', - NU: 'Niue', - NZ: 'New Zealand', - OM: 'Oman', - PA: 'Panama', - PE: 'Peru', - PF: 'French Polynesia', - PG: 'Papua New Guinea', - PH: 'Philippines', - PK: 'Pakistan', - PL: 'Poland', - PM: 'St. Pierre & Miquelon', - PN: 'Pitcairn Islands', - PR: 'Puerto Rico', - PS: 'Palestinian Territories', - PT: 'Portugal', - PW: 'Palau', - PY: 'Paraguay', - QA: 'Qatar', - QO: 'Outlying Oceania', - RE: 'Réunion', - RH: 'Zimbabwe', - RO: 'Romania', - RS: 'Serbia', - RU: 'Russia', - RW: 'Rwanda', - SA: 'Saudi Arabia', - SB: 'Solomon Islands', - SC: 'Seychelles', - SD: 'Sudan', - SE: 'Sweden', - SG: 'Singapore', - SH: 'St. Helena', - SI: 'Slovenia', - SJ: 'Svalbard & Jan Mayen', - SK: 'Slovakia', - SL: 'Sierra Leone', - SM: 'San Marino', - SN: 'Senegal', - SO: 'Somalia', - SR: 'Suriname', - SS: 'South Sudan', - ST: 'São Tomé & Príncipe', - SU: 'Russia', - SV: 'El Salvador', - SX: 'Sint Maarten', - SY: 'Syria', - SZ: 'Eswatini', - TA: 'Tristan da Cunha', - TC: 'Turks & Caicos Islands', - TD: 'Chad', - TF: 'French Southern Territories', - TG: 'Togo', - TH: 'Thailand', - TJ: 'Tajikistan', - TK: 'Tokelau', - TL: 'Timor-Leste', - TM: 'Turkmenistan', - TN: 'Tunisia', - TO: 'Tonga', - TP: 'Timor-Leste', - TR: 'Turkey', - TT: 'Trinidad & Tobago', - TV: 'Tuvalu', - TW: 'Taiwan', - TZ: 'Tanzania', - UA: 'Ukraine', - UG: 'Uganda', - UK: 'United Kingdom', - UM: 'U.S. Outlying Islands', - UN: 'United Nations', - US: 'United States', - UY: 'Uruguay', - UZ: 'Uzbekistan', - VA: 'Vatican City', - VC: 'St. Vincent & Grenadines', - VD: 'Vietnam', - VE: 'Venezuela', - VG: 'British Virgin Islands', - VI: 'U.S. Virgin Islands', - VN: 'Vietnam', - VU: 'Vanuatu', - WF: 'Wallis & Futuna', - WS: 'Samoa', - XA: 'Pseudo-Accents', - XB: 'Pseudo-Bidi', - XK: 'Kosovo', - YD: 'Yemen', - YE: 'Yemen', - YT: 'Mayotte', - YU: 'Serbia', - ZA: 'South Africa', - ZM: 'Zambia', - ZR: 'Congo - Kinshasa', - ZW: 'Zimbabwe', - ZZ: 'Unknown Region' -}; - -/** - * Country names object using country name to reference 2-letter country codes - * Derived from the solution above with - * Object.fromEntries(Object.entries(COUNTRY_CODES_TO_NAMES).map(entry => [entry[1], entry[0]])) - */ -const COUNTRY_NAMES_TO_CODES = exports.COUNTRY_NAMES_TO_CODES = { - 'Ascension Island': 'AC', - Andorra: 'AD', - 'United Arab Emirates': 'AE', - Afghanistan: 'AF', - 'Antigua & Barbuda': 'AG', - Anguilla: 'AI', - Albania: 'AL', - Armenia: 'AM', - 'Curaçao': 'CW', - Angola: 'AO', - Antarctica: 'AQ', - Argentina: 'AR', - 'American Samoa': 'AS', - Austria: 'AT', - Australia: 'AU', - Aruba: 'AW', - 'Åland Islands': 'AX', - Azerbaijan: 'AZ', - 'Bosnia & Herzegovina': 'BA', - Barbados: 'BB', - Bangladesh: 'BD', - Belgium: 'BE', - 'Burkina Faso': 'HV', - Bulgaria: 'BG', - Bahrain: 'BH', - Burundi: 'BI', - Benin: 'DY', - 'St. Barthélemy': 'BL', - Bermuda: 'BM', - Brunei: 'BN', - Bolivia: 'BO', - 'Caribbean Netherlands': 'BQ', - Brazil: 'BR', - Bahamas: 'BS', - Bhutan: 'BT', - 'Myanmar (Burma)': 'MM', - 'Bouvet Island': 'BV', - Botswana: 'BW', - Belarus: 'BY', - Belize: 'BZ', - Canada: 'CA', - 'Cocos (Keeling) Islands': 'CC', - 'Congo - Kinshasa': 'ZR', - 'Central African Republic': 'CF', - 'Congo - Brazzaville': 'CG', - Switzerland: 'CH', - 'Côte d’Ivoire': 'CI', - 'Cook Islands': 'CK', - Chile: 'CL', - Cameroon: 'CM', - 'China mainland': 'CN', - Colombia: 'CO', - 'Clipperton Island': 'CP', - 'Costa Rica': 'CR', - Serbia: 'YU', - Cuba: 'CU', - 'Cape Verde': 'CV', - 'Christmas Island': 'CX', - Cyprus: 'CY', - Czechia: 'CZ', - Germany: 'DE', - 'Diego Garcia': 'DG', - Djibouti: 'DJ', - Denmark: 'DK', - Dominica: 'DM', - 'Dominican Republic': 'DO', - Algeria: 'DZ', - 'Ceuta & Melilla': 'EA', - Ecuador: 'EC', - Estonia: 'EE', - Egypt: 'EG', - 'Western Sahara': 'EH', - Eritrea: 'ER', - Spain: 'ES', - Ethiopia: 'ET', - 'European Union': 'EU', - Eurozone: 'EZ', - Finland: 'FI', - Fiji: 'FJ', - 'Falkland Islands': 'FK', - Micronesia: 'FM', - 'Faroe Islands': 'FO', - France: 'FX', - Gabon: 'GA', - 'United Kingdom': 'UK', - Grenada: 'GD', - Georgia: 'GE', - 'French Guiana': 'GF', - Guernsey: 'GG', - Ghana: 'GH', - Gibraltar: 'GI', - Greenland: 'GL', - Gambia: 'GM', - Guinea: 'GN', - Guadeloupe: 'GP', - 'Equatorial Guinea': 'GQ', - Greece: 'GR', - 'So. Georgia & So. Sandwich Isl.': 'GS', - Guatemala: 'GT', - Guam: 'GU', - 'Guinea-Bissau': 'GW', - Guyana: 'GY', - 'Hong Kong': 'HK', - 'Heard & McDonald Islands': 'HM', - Honduras: 'HN', - Croatia: 'HR', - Haiti: 'HT', - Hungary: 'HU', - 'Canary Islands': 'IC', - Indonesia: 'ID', - Ireland: 'IE', - Israel: 'IL', - 'Isle of Man': 'IM', - India: 'IN', - 'Chagos Archipelago': 'IO', - Iraq: 'IQ', - Iran: 'IR', - Iceland: 'IS', - Italy: 'IT', - Jersey: 'JE', - Jamaica: 'JM', - Jordan: 'JO', - Japan: 'JP', - Kenya: 'KE', - Kyrgyzstan: 'KG', - Cambodia: 'KH', - Kiribati: 'KI', - Comoros: 'KM', - 'St. Kitts & Nevis': 'KN', - 'North Korea': 'KP', - 'South Korea': 'KR', - Kuwait: 'KW', - 'Cayman Islands': 'KY', - Kazakhstan: 'KZ', - Laos: 'LA', - Lebanon: 'LB', - 'St. Lucia': 'LC', - Liechtenstein: 'LI', - 'Sri Lanka': 'LK', - Liberia: 'LR', - Lesotho: 'LS', - Lithuania: 'LT', - Luxembourg: 'LU', - Latvia: 'LV', - Libya: 'LY', - Morocco: 'MA', - Monaco: 'MC', - Moldova: 'MD', - Montenegro: 'ME', - 'St. Martin': 'MF', - Madagascar: 'MG', - 'Marshall Islands': 'MH', - 'North Macedonia': 'MK', - Mali: 'ML', - Mongolia: 'MN', - Macao: 'MO', - 'Northern Mariana Islands': 'MP', - Martinique: 'MQ', - Mauritania: 'MR', - Montserrat: 'MS', - Malta: 'MT', - Mauritius: 'MU', - Maldives: 'MV', - Malawi: 'MW', - Mexico: 'MX', - Malaysia: 'MY', - Mozambique: 'MZ', - Namibia: 'NA', - 'New Caledonia': 'NC', - Niger: 'NE', - 'Norfolk Island': 'NF', - Nigeria: 'NG', - Vanuatu: 'VU', - Nicaragua: 'NI', - Netherlands: 'NL', - Norway: 'NO', - Nepal: 'NP', - Nauru: 'NR', - Niue: 'NU', - 'New Zealand': 'NZ', - Oman: 'OM', - Panama: 'PA', - Peru: 'PE', - 'French Polynesia': 'PF', - 'Papua New Guinea': 'PG', - Philippines: 'PH', - Pakistan: 'PK', - Poland: 'PL', - 'St. Pierre & Miquelon': 'PM', - 'Pitcairn Islands': 'PN', - 'Puerto Rico': 'PR', - 'Palestinian Territories': 'PS', - Portugal: 'PT', - Palau: 'PW', - Paraguay: 'PY', - Qatar: 'QA', - 'Outlying Oceania': 'QO', - 'Réunion': 'RE', - Zimbabwe: 'ZW', - Romania: 'RO', - Russia: 'SU', - Rwanda: 'RW', - 'Saudi Arabia': 'SA', - 'Solomon Islands': 'SB', - Seychelles: 'SC', - Sudan: 'SD', - Sweden: 'SE', - Singapore: 'SG', - 'St. Helena': 'SH', - Slovenia: 'SI', - 'Svalbard & Jan Mayen': 'SJ', - Slovakia: 'SK', - 'Sierra Leone': 'SL', - 'San Marino': 'SM', - Senegal: 'SN', - Somalia: 'SO', - Suriname: 'SR', - 'South Sudan': 'SS', - 'São Tomé & Príncipe': 'ST', - 'El Salvador': 'SV', - 'Sint Maarten': 'SX', - Syria: 'SY', - Eswatini: 'SZ', - 'Tristan da Cunha': 'TA', - 'Turks & Caicos Islands': 'TC', - Chad: 'TD', - 'French Southern Territories': 'TF', - Togo: 'TG', - Thailand: 'TH', - Tajikistan: 'TJ', - Tokelau: 'TK', - 'Timor-Leste': 'TP', - Turkmenistan: 'TM', - Tunisia: 'TN', - Tonga: 'TO', - Turkey: 'TR', - 'Trinidad & Tobago': 'TT', - Tuvalu: 'TV', - Taiwan: 'TW', - Tanzania: 'TZ', - Ukraine: 'UA', - Uganda: 'UG', - 'U.S. Outlying Islands': 'UM', - 'United Nations': 'UN', - 'United States': 'US', - Uruguay: 'UY', - Uzbekistan: 'UZ', - 'Vatican City': 'VA', - 'St. Vincent & Grenadines': 'VC', - Vietnam: 'VN', - Venezuela: 'VE', - 'British Virgin Islands': 'VG', - 'U.S. Virgin Islands': 'VI', - 'Wallis & Futuna': 'WF', - Samoa: 'WS', - 'Pseudo-Accents': 'XA', - 'Pseudo-Bidi': 'XB', - Kosovo: 'XK', - Yemen: 'YE', - Mayotte: 'YT', - 'South Africa': 'ZA', - Zambia: 'ZM', - 'Unknown Region': 'ZZ' -}; - -},{}],26:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.prepareFormValuesForStorage = exports.inferCountryCodeFromElement = exports.getUnifiedExpiryDate = exports.getMMAndYYYYFromString = exports.getCountryName = exports.getCountryDisplayName = exports.formatPhoneNumber = exports.formatFullName = exports.formatCCYear = void 0; -var _matching = require("./matching.js"); -var _countryNames = require("./countryNames.js"); -// Matches strings like mm/yy, mm-yyyy, mm-aa, 12 / 2024 -const DATE_SEPARATOR_REGEX = /\b((.)\2{1,3}|\d+)(?\s?[/\s.\-_—–]\s?)((.)\5{1,3}|\d+)\b/i; -// Matches 4 non-digit repeated characters (YYYY or AAAA) or 4 digits (2022) -const FOUR_DIGIT_YEAR_REGEX = /(\D)\1{3}|\d{4}/i; - -/** - * Format the cc year to best adapt to the input requirements (YY vs YYYY) - * @param {HTMLInputElement} input - * @param {string} year - * @param {import("./Form").Form} form - * @returns {string} - */ -const formatCCYear = (input, year, form) => { - const selector = form.matching.cssSelector('formInputsSelector'); - if (input.maxLength === 4 || (0, _matching.checkPlaceholderAndLabels)(input, FOUR_DIGIT_YEAR_REGEX, form.form, selector)) return year; - return `${Number(year) - 2000}`; -}; - -/** - * Get a unified expiry date with separator - * @param {HTMLInputElement} input - * @param {string} month - * @param {string} year - * @param {import("./Form").Form} form - * @returns {string} - */ -exports.formatCCYear = formatCCYear; -const getUnifiedExpiryDate = (input, month, year, form) => { - const formattedYear = formatCCYear(input, year, form); - const paddedMonth = `${month}`.padStart(2, '0'); - const cssSelector = form.matching.cssSelector('formInputsSelector'); - const separator = (0, _matching.matchInPlaceholderAndLabels)(input, DATE_SEPARATOR_REGEX, form.form, cssSelector)?.groups?.separator || '/'; - return `${paddedMonth}${separator}${formattedYear}`; -}; -exports.getUnifiedExpiryDate = getUnifiedExpiryDate; -const formatFullName = _ref => { - let { - firstName = '', - middleName = '', - lastName = '' - } = _ref; - return `${firstName} ${middleName ? middleName + ' ' : ''}${lastName}`.trim(); -}; - -/** - * Tries to look up a human-readable country name from the country code - * @param {string} locale - * @param {string} addressCountryCode - * @return {string} - Returns the country code if we can't find a name - */ -exports.formatFullName = formatFullName; -const getCountryDisplayName = (locale, addressCountryCode) => { - try { - const regionNames = new Intl.DisplayNames([locale], { - type: 'region' - }); - // Adding this ts-ignore to prevent having to change this implementation. - // @ts-ignore - return regionNames.of(addressCountryCode); - } catch (e) { - return _countryNames.COUNTRY_CODES_TO_NAMES[addressCountryCode] || addressCountryCode; - } -}; - -/** - * Tries to infer the element locale or returns 'en' - * @param {HTMLInputElement | HTMLSelectElement} el - * @return {string | 'en'} - */ -exports.getCountryDisplayName = getCountryDisplayName; -const inferElementLocale = el => el.lang || el.form?.lang || document.body.lang || document.documentElement.lang || 'en'; - -/** - * Tries to format the country code into a localised country name - * @param {HTMLInputElement | HTMLSelectElement} el - * @param {{addressCountryCode?: string}} options - */ -const getCountryName = function (el) { - let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - const { - addressCountryCode - } = options; - if (!addressCountryCode) return ''; - - // Try to infer the field language or fallback to en - const elLocale = inferElementLocale(el); - const localisedCountryName = getCountryDisplayName(elLocale, addressCountryCode); - - // If it's a select el we try to find a suitable match to autofill - if (el.nodeName === 'SELECT') { - const englishCountryName = getCountryDisplayName('en', addressCountryCode); - // This regex matches both the localised and English country names - const countryNameRegex = new RegExp(String.raw`${localisedCountryName.replace(/ /g, '.?')}|${englishCountryName.replace(/ /g, '.?')}`, 'i'); - const countryCodeRegex = new RegExp(String.raw`\b${addressCountryCode}\b`, 'i'); - - // We check the country code first because it's more accurate - if (el instanceof HTMLSelectElement) { - for (const option of el.options) { - if (countryCodeRegex.test(option.value)) { - return option.value; + /** + * Generate a random method name and adds it to the global scope + * The native layer will use this method to send the response + * @param {string | number} randomMethodName + * @param {Function} callback + */ + generateRandomMethod(randomMethodName, callback) { + this.globals.ObjectDefineProperty(this.globals.window, randomMethodName, { + enumerable: false, + // configurable, To allow for deletion later + configurable: true, + writable: false, + /** + * @param {any[]} args + */ + value: (...args) => { + callback(...args); + delete this.globals.window[randomMethodName]; } - } - for (const option of el.options) { - if (countryNameRegex.test(option.value) || countryNameRegex.test(option.innerText)) return option.value; - } + }); } - } - return localisedCountryName; -}; - -/** - * Try to get a map of localised country names to code, or falls back to the English map - * @param {HTMLInputElement | HTMLSelectElement} el - */ -exports.getCountryName = getCountryName; -const getLocalisedCountryNamesToCodes = el => { - if (typeof Intl.DisplayNames !== 'function') return _countryNames.COUNTRY_NAMES_TO_CODES; - - // Try to infer the field language or fallback to en - const elLocale = inferElementLocale(el); - return Object.fromEntries(Object.entries(_countryNames.COUNTRY_CODES_TO_NAMES).map(_ref2 => { - let [code] = _ref2; - return [getCountryDisplayName(elLocale, code), code]; - })); -}; - -/** - * Try to infer a country code from an element we identified as identities.addressCountryCode - * @param {HTMLInputElement | HTMLSelectElement} el - * @return {string} - */ -const inferCountryCodeFromElement = el => { - if (_countryNames.COUNTRY_CODES_TO_NAMES[el.value]) return el.value; - if (_countryNames.COUNTRY_NAMES_TO_CODES[el.value]) return _countryNames.COUNTRY_NAMES_TO_CODES[el.value]; - const localisedCountryNamesToCodes = getLocalisedCountryNamesToCodes(el); - if (localisedCountryNamesToCodes[el.value]) return localisedCountryNamesToCodes[el.value]; - if (el instanceof HTMLSelectElement) { - const selectedText = el.selectedOptions[0]?.text; - if (_countryNames.COUNTRY_CODES_TO_NAMES[selectedText]) return selectedText; - if (_countryNames.COUNTRY_NAMES_TO_CODES[selectedText]) return localisedCountryNamesToCodes[selectedText]; - if (localisedCountryNamesToCodes[selectedText]) return localisedCountryNamesToCodes[selectedText]; - } - return ''; -}; - -/** - * Gets separate expiration month and year from a single string - * @param {string} expiration - * @return {{expirationYear: string, expirationMonth: string}} - */ -exports.inferCountryCodeFromElement = inferCountryCodeFromElement; -const getMMAndYYYYFromString = expiration => { - /** @type {string[]} */ - const values = expiration.match(/(\d+)/g) || []; - return values?.reduce((output, current) => { - if (Number(current) > 12) { - output.expirationYear = current.padStart(4, '20'); - } else { - output.expirationMonth = current.padStart(2, '0'); + randomString() { + return "" + this.globals.getRandomValues(new this.globals.Uint32Array(1))[0]; + } + createRandMethodName() { + return "_" + this.randomString(); + } + /** + * @returns {Promise} + */ + async createRandKey() { + const key = await this.globals.generateKey(this.algoObj, true, ["encrypt", "decrypt"]); + const exportedKey = await this.globals.exportKey("raw", key); + return new this.globals.Uint8Array(exportedKey); + } + /** + * @returns {Uint8Array} + */ + createRandIv() { + return this.globals.getRandomValues(new this.globals.Uint8Array(12)); } - return output; - }, { - expirationYear: '', - expirationMonth: '' - }); -}; - -/** - * @param {InternalDataStorageObject} credentials - * @return {boolean} - */ -exports.getMMAndYYYYFromString = getMMAndYYYYFromString; -const shouldStoreCredentials = _ref3 => { - let { - credentials - } = _ref3; - return Boolean(credentials.password); -}; - -/** - * @param {InternalDataStorageObject} credentials - * @return {boolean} - */ -const shouldStoreIdentities = _ref4 => { - let { - identities - } = _ref4; - return Boolean((identities.firstName || identities.fullName) && identities.addressStreet && identities.addressCity); -}; - -/** - * @param {InternalDataStorageObject} credentials - * @return {boolean} - */ -const shouldStoreCreditCards = _ref5 => { - let { - creditCards - } = _ref5; - if (!creditCards.cardNumber) return false; - if (creditCards.cardSecurityCode) return true; - // Some forms (Amazon) don't have the cvv, so we still save if there's the expiration - if (creditCards.expiration) return true; - // Expiration can also be two separate values - return Boolean(creditCards.expirationYear && creditCards.expirationMonth); -}; - -/** - * Removes formatting characters from phone numbers, only leaves digits and the + sign - * @param {String} phone - * @returns {String} - */ -const formatPhoneNumber = phone => phone.replaceAll(/[^0-9|+]/g, ''); - -/** - * Formats form data into an object to send to the device for storage - * If values are insufficient for a complete entry, they are discarded - * @param {InternalDataStorageObject} formValues - * @return {DataStorageObject} - */ -exports.formatPhoneNumber = formatPhoneNumber; -const prepareFormValuesForStorage = formValues => { - /** @type {Partial} */ - let { - credentials, - identities, - creditCards - } = formValues; - - // If we have an identity name but not a card name, copy it over there - if (!creditCards.cardName && (identities?.fullName || identities?.firstName)) { - creditCards.cardName = identities?.fullName || formatFullName(identities); - } - - /** Fixes for credentials **/ - // Don't store if there isn't enough data - if (shouldStoreCredentials(formValues)) { - // If we don't have a username to match a password, let's see if the email is available - if (credentials.password && !credentials.username && identities.emailAddress) { - credentials.username = identities.emailAddress; - } - } else { - credentials = undefined; - } - - /** Fixes for identities **/ - // Don't store if there isn't enough data - if (shouldStoreIdentities(formValues)) { - if (identities.fullName) { - // when forms have both first/last and fullName we keep the individual values and drop the fullName - if (!(identities.firstName && identities.lastName)) { - // If the fullname can be easily split into two, we'll store it as first and last - const nameParts = identities.fullName.trim().split(/\s+/); - if (nameParts.length === 2) { - identities.firstName = nameParts[0]; - identities.lastName = nameParts[1]; - } else { - // If we can't split it, just store it as first name - identities.firstName = identities.fullName; + /** + * @param {BufferSource} ciphertext + * @param {BufferSource} key + * @param {Uint8Array} iv + * @returns {Promise} + */ + async decrypt(ciphertext, key, iv) { + const cryptoKey = await this.globals.importKey("raw", key, "AES-GCM", false, ["decrypt"]); + const algo = { name: "AES-GCM", iv }; + let decrypted = await this.globals.decrypt(algo, cryptoKey, ciphertext); + let dec = new this.globals.TextDecoder(); + return dec.decode(decrypted); + } + /** + * When required (such as on macos 10.x), capture the `postMessage` method on + * each webkit messageHandler + * + * @param {string[]} handlerNames + */ + captureWebkitHandlers(handlerNames) { + const handlers = window.webkit.messageHandlers; + if (!handlers) + throw new MissingHandler("window.webkit.messageHandlers was absent", "all"); + for (let webkitMessageHandlerName of handlerNames) { + if (typeof handlers[webkitMessageHandlerName]?.postMessage === "function") { + const original = handlers[webkitMessageHandlerName]; + const bound = handlers[webkitMessageHandlerName].postMessage?.bind(original); + this.globals.capturedWebkitHandlers[webkitMessageHandlerName] = bound; + delete handlers[webkitMessageHandlerName].postMessage; } } - delete identities.fullName; } - if (identities.phone) { - identities.phone = formatPhoneNumber(identities.phone); + }; + var WebkitMessagingConfig = class { + /** + * @param {object} params + * @param {boolean} params.hasModernWebkitAPI + * @param {string[]} params.webkitMessageHandlerNames + * @param {string} params.secret + */ + constructor(params) { + this.hasModernWebkitAPI = params.hasModernWebkitAPI; + this.webkitMessageHandlerNames = params.webkitMessageHandlerNames; + this.secret = params.secret; } - } else { - identities = undefined; - } - - /** Fixes for credit cards **/ - // Don't store if there isn't enough data - if (shouldStoreCreditCards(formValues)) { - if (creditCards.expiration) { - const { - expirationMonth, - expirationYear - } = getMMAndYYYYFromString(creditCards.expiration); - creditCards.expirationMonth = expirationMonth; - creditCards.expirationYear = expirationYear; - delete creditCards.expiration; - } - creditCards.expirationYear = creditCards.expirationYear?.padStart(4, '20'); - if (creditCards.cardNumber) { - creditCards.cardNumber = creditCards.cardNumber.replace(/\D/g, ''); - } - } else { - creditCards = undefined; - } - return { - credentials, - identities, - creditCards }; -}; -exports.prepareFormValuesForStorage = prepareFormValuesForStorage; - -},{"./countryNames.js":25,"./matching.js":33}],27:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.getIconStylesBase = exports.getIconStylesAutofilled = exports.getIconStylesAlternate = void 0; -var _inputTypeConfig = require("./inputTypeConfig.js"); -/** - * Returns the css-ready base64 encoding of the icon for the given input - * @param {HTMLInputElement} input - * @param {import("./Form").Form} form - * @param {'base' | 'filled' | 'alternate'} type - * @return {string} - */ -const getIcon = function (input, form) { - let type = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'base'; - const config = (0, _inputTypeConfig.getInputConfig)(input); - if (type === 'base') { - return config.getIconBase(input, form); - } - if (type === 'filled') { - return config.getIconFilled(input, form); - } - if (type === 'alternate') { - return config.getIconAlternate(input, form); + var SecureMessagingParams = class { + /** + * @param {object} params + * @param {string} params.methodName + * @param {string} params.secret + * @param {number[]} params.key + * @param {number[]} params.iv + */ + constructor(params) { + this.methodName = params.methodName; + this.secret = params.secret; + this.key = params.key; + this.iv = params.iv; + } + }; + function captureGlobals() { + return { + window, + // Methods must be bound to their interface, otherwise they throw Illegal invocation + encrypt: window.crypto.subtle.encrypt.bind(window.crypto.subtle), + decrypt: window.crypto.subtle.decrypt.bind(window.crypto.subtle), + generateKey: window.crypto.subtle.generateKey.bind(window.crypto.subtle), + exportKey: window.crypto.subtle.exportKey.bind(window.crypto.subtle), + importKey: window.crypto.subtle.importKey.bind(window.crypto.subtle), + getRandomValues: window.crypto.getRandomValues.bind(window.crypto), + TextEncoder, + TextDecoder, + Uint8Array, + Uint16Array, + Uint32Array, + JSONstringify: window.JSON.stringify, + JSONparse: window.JSON.parse, + Arrayfrom: window.Array.from, + Promise: window.Promise, + ObjectDefineProperty: window.Object.defineProperty, + addEventListener: window.addEventListener.bind(window), + /** @type {Record} */ + capturedWebkitHandlers: {} + }; } - return ''; -}; - -/** - * Returns an object with styles to be applied inline - * @param {HTMLInputElement} input - * @param {String} icon - * @return {Object} - */ -const getBasicStyles = (input, icon) => ({ - // Height must be > 0 to account for fields initially hidden - 'background-size': `auto ${input.offsetHeight <= 30 && input.offsetHeight > 0 ? '100%' : '24px'}`, - 'background-position': 'center right', - 'background-repeat': 'no-repeat', - 'background-origin': 'content-box', - 'background-image': `url(${icon})`, - 'transition': 'background 0s' -}); - -/** - * Get inline styles for the injected icon, base state - * @param {HTMLInputElement} input - * @param {import("./Form").Form} form - * @return {Object} - */ -const getIconStylesBase = (input, form) => { - const icon = getIcon(input, form); - if (!icon) return {}; - return getBasicStyles(input, icon); -}; - -/** - * Get inline styles for the injected icon, alternate state - * @param {HTMLInputElement} input - * @param {import("./Form").Form} form - * @return {Object} - */ -exports.getIconStylesBase = getIconStylesBase; -const getIconStylesAlternate = (input, form) => { - const icon = getIcon(input, form, 'alternate'); - if (!icon) return {}; - return { - ...getBasicStyles(input, icon) - }; -}; -/** - * Get inline styles for the injected icon, autofilled state - * @param {HTMLInputElement} input - * @param {import("./Form").Form} form - * @return {Object} - */ -exports.getIconStylesAlternate = getIconStylesAlternate; -const getIconStylesAutofilled = (input, form) => { - const icon = getIcon(input, form, 'filled'); - const iconStyle = icon ? getBasicStyles(input, icon) : {}; - return { - ...iconStyle, - 'background-color': '#F8F498', - 'color': '#333333' - }; -}; -exports.getIconStylesAutofilled = getIconStylesAutofilled; - -},{"./inputTypeConfig.js":28}],28:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.isFieldDecorated = exports.getInputConfigFromType = exports.getInputConfig = exports.canBeInteractedWith = void 0; -var _logoSvg = require("./logo-svg.js"); -var ddgPasswordIcons = _interopRequireWildcard(require("../UI/img/ddgPasswordIcon.js")); -var _matching = require("./matching.js"); -var _Credentials = require("../InputTypes/Credentials.js"); -var _CreditCard = require("../InputTypes/CreditCard.js"); -var _Identity = require("../InputTypes/Identity.js"); -var _constants = require("../constants.js"); -function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } -function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } -/** - * Get the icon for the identities (currently only Dax for emails) - * @param {HTMLInputElement} input - * @param {import("./Form").Form} form - * @return {string} - */ -const getIdentitiesIcon = (input, _ref) => { - let { - device - } = _ref; - if (!canBeInteractedWith(input)) return ''; - - // In Firefox web_accessible_resources could leak a unique user identifier, so we avoid it here - const { - isDDGApp, - isFirefox, - isExtension - } = device.globalConfig; - const subtype = (0, _matching.getInputSubtype)(input); - if (device.inContextSignup?.isAvailable(subtype)) { - if (isDDGApp || isFirefox) { - return _logoSvg.daxGrayscaleBase64; - } else if (isExtension) { - return chrome.runtime.getURL('img/logo-small-grayscale.svg'); + // packages/messaging/messaging.js + var Messaging = class { + /** + * @param {WebkitMessagingConfig} config + */ + constructor(config) { + this.transport = getTransport(config); } - } - if (subtype === 'emailAddress' && device.isDeviceSignedIn()) { - if (isDDGApp || isFirefox) { - return _logoSvg.daxBase64; - } else if (isExtension) { - return chrome.runtime.getURL('img/logo-small.svg'); + /** + * Send a 'fire-and-forget' message. + * @throws {Error} + * {@link MissingHandler} + * + * @example + * + * ``` + * const messaging = new Messaging(config) + * messaging.notify("foo", {bar: "baz"}) + * ``` + * @param {string} name + * @param {Record} [data] + */ + notify(name, data = {}) { + this.transport.notify(name, data); } - } - return ''; -}; - -/** - * Get the alternate icon for the identities (currently only Dax for emails) - * @param {HTMLInputElement} input - * @param {import("./Form").Form} form - * @return {string} - */ -const getIdentitiesAlternateIcon = (input, _ref2) => { - let { - device - } = _ref2; - if (!canBeInteractedWith(input)) return ''; - - // In Firefox web_accessible_resources could leak a unique user identifier, so we avoid it here - const { - isDDGApp, - isFirefox, - isExtension - } = device.globalConfig; - const subtype = (0, _matching.getInputSubtype)(input); - const isIncontext = device.inContextSignup?.isAvailable(subtype); - const isEmailProtection = subtype === 'emailAddress' && device.isDeviceSignedIn(); - if (isIncontext || isEmailProtection) { - if (isDDGApp || isFirefox) { - return _logoSvg.daxBase64; - } else if (isExtension) { - return chrome.runtime.getURL('img/logo-small.svg'); + /** + * Send a request, and wait for a response + * @throws {Error} + * {@link MissingHandler} + * + * @example + * ``` + * const messaging = new Messaging(config) + * const response = await messaging.request("foo", {bar: "baz"}) + * ``` + * + * @param {string} name + * @param {Record} [data] + * @return {Promise} + */ + request(name, data = {}) { + return this.transport.request(name, data); } - } - return ''; -}; - -/** - * Checks whether a field is readonly or disabled - * @param {HTMLInputElement} input - * @return {boolean} - */ -const canBeInteractedWith = input => !input.readOnly && !input.disabled; - -/** - * Checks if the input can be decorated and we have the needed data - * @param {HTMLInputElement} input - * @param {import("../DeviceInterface/InterfacePrototype").default} device - * @returns {Promise} - */ -exports.canBeInteractedWith = canBeInteractedWith; -const canBeAutofilled = async (input, device) => { - if (!canBeInteractedWith(input)) return false; - const mainType = (0, _matching.getInputMainType)(input); - const subtype = (0, _matching.getInputSubtype)(input); - await device.settings.populateDataIfNeeded({ - mainType, - subtype - }); - const canAutofill = device.settings.canAutofillType({ - mainType, - subtype - }, device.inContextSignup); - return Boolean(canAutofill); -}; - -/** - * A map of config objects. These help by centralising here some complexity - * @type {InputTypeConfig} - */ -const inputTypeConfig = { - /** @type {CredentialsInputTypeConfig} */ - credentials: { - type: 'credentials', - displayName: 'passwords', - getIconBase: (input, _ref3) => { - let { - device - } = _ref3; - if (!canBeInteractedWith(input)) return ''; - if (device.settings.featureToggles.inlineIcon_credentials) { - const subtype = (0, _matching.getInputSubtype)(input); - const variant = (0, _matching.getInputVariant)(input); - if (subtype === 'password' && variant === 'new') { - return ddgPasswordIcons.ddgPasswordGenIconBase; - } - return ddgPasswordIcons.ddgPasswordIconBase; - } - return ''; - }, - getIconFilled: (input, _ref4) => { - let { - device - } = _ref4; - if (device.settings.featureToggles.inlineIcon_credentials) { - const subtype = (0, _matching.getInputSubtype)(input); - const variant = (0, _matching.getInputVariant)(input); - if (subtype === 'password' && variant === 'new') { - return ddgPasswordIcons.ddgPasswordGenIconFilled; - } - return ddgPasswordIcons.ddgPasswordIconFilled; - } - return ''; - }, - getIconAlternate: () => '', - shouldDecorate: async (input, _ref5) => { - let { - isLogin, - isHybrid, - device - } = _ref5; - const subtype = (0, _matching.getInputSubtype)(input); - const variant = (0, _matching.getInputVariant)(input); - - // Check first for password generation and the password.new scoring - if (device.settings.featureToggles.password_generation) { - if (subtype === 'password' && variant === 'new') { - return canBeInteractedWith(input); - } - } - - // if we are on a 'login' page, check if we have data to autofill the field - if (isLogin || isHybrid || variant === 'current') { - return canBeAutofilled(input, device); - } - return false; - }, - dataType: 'Credentials', - tooltipItem: data => (0, _Credentials.createCredentialsTooltipItem)(data) - }, - /** @type {CreditCardsInputTypeConfig} */ - creditCards: { - type: 'creditCards', - displayName: 'credit cards', - getIconBase: () => '', - getIconFilled: () => '', - getIconAlternate: () => '', - shouldDecorate: async (input, _ref6) => { - let { - device - } = _ref6; - return canBeAutofilled(input, device); - }, - dataType: 'CreditCards', - tooltipItem: data => new _CreditCard.CreditCardTooltipItem(data) - }, - /** @type {IdentitiesInputTypeConfig} */ - identities: { - type: 'identities', - displayName: 'identities', - getIconBase: getIdentitiesIcon, - getIconFilled: getIdentitiesIcon, - getIconAlternate: getIdentitiesAlternateIcon, - shouldDecorate: async (input, _ref7) => { - let { - device - } = _ref7; - return canBeAutofilled(input, device); - }, - dataType: 'Identities', - tooltipItem: data => new _Identity.IdentityTooltipItem(data) - }, - /** @type {UnknownInputTypeConfig} */ - unknown: { - type: 'unknown', - displayName: '', - getIconBase: () => '', - getIconFilled: () => '', - getIconAlternate: () => '', - shouldDecorate: async () => false, - dataType: '', - tooltipItem: _data => { - throw new Error('unreachable - setting tooltip to unknown field type'); + }; + function getTransport(config) { + if (config instanceof WebkitMessagingConfig) { + return new WebkitMessagingTransport(config); } + throw new Error("unreachable"); } -}; - -/** - * Retrieves configs from an input el - * @param {HTMLInputElement} input - * @returns {InputTypeConfigs} - */ -const getInputConfig = input => { - const inputType = (0, _matching.getInputType)(input); - return getInputConfigFromType(inputType); -}; - -/** - * Retrieves configs from an input type - * @param {import('./matching').SupportedTypes} inputType - * @returns {InputTypeConfigs} - */ -exports.getInputConfig = getInputConfig; -const getInputConfigFromType = inputType => { - const inputMainType = (0, _matching.getMainTypeFromType)(inputType); - return inputTypeConfig[inputMainType]; -}; - -/** - * Given an input field checks wheter it was previously decorated - * @param {HTMLInputElement} input - * @returns {Boolean} - */ -exports.getInputConfigFromType = getInputConfigFromType; -const isFieldDecorated = input => { - return input.hasAttribute(_constants.constants.ATTR_INPUT_TYPE); -}; -exports.isFieldDecorated = isFieldDecorated; - -},{"../InputTypes/Credentials.js":35,"../InputTypes/CreditCard.js":36,"../InputTypes/Identity.js":37,"../UI/img/ddgPasswordIcon.js":50,"../constants.js":55,"./logo-svg.js":30,"./matching.js":33}],29:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.extractElementStrings = exports.EXCLUDED_TAGS = void 0; -var _matching = require("./matching.js"); -const EXCLUDED_TAGS = exports.EXCLUDED_TAGS = ['BR', 'SCRIPT', 'NOSCRIPT', 'OPTION', 'STYLE']; - -/** - * Extract all strings of an element's children to an array. - * "element.textContent" is a string which is merged of all children nodes, - * which can cause issues with things like script tags etc. - * - * @param {Element} element - * A DOM element to be extracted. - * @returns {string[]} - * All strings in an element. - */ -const extractElementStrings = element => { - const strings = new Set(); - const _extractElementStrings = el => { - if (EXCLUDED_TAGS.includes(el.tagName)) { - return; + var MissingHandler = class extends Error { + /** + * @param {string} message + * @param {string} handlerName + */ + constructor(message, handlerName) { + super(message); + this.handlerName = handlerName; } + }; - // only take the string when it's an explicit text node - if (el.nodeType === el.TEXT_NODE || !el.childNodes.length) { - let trimmedText = (0, _matching.removeExcessWhitespace)(el.textContent); - if (trimmedText) { - strings.add(trimmedText); - } - return; + // src/deviceApiCalls/transports/apple.transport.js + var AppleTransport = class extends DeviceApiTransport { + /** @param {GlobalConfig} globalConfig */ + constructor(globalConfig) { + super(); + this.config = globalConfig; + const webkitConfig = new WebkitMessagingConfig({ + hasModernWebkitAPI: this.config.hasModernWebkitAPI, + webkitMessageHandlerNames: this.config.webkitMessageHandlerNames, + secret: this.config.secret + }); + this.messaging = new Messaging(webkitConfig); } - for (let node of el.childNodes) { - let nodeType = node.nodeType; - if (nodeType !== node.ELEMENT_NODE && nodeType !== node.TEXT_NODE) { - continue; + async send(deviceApiCall) { + try { + if (deviceApiCall.id) { + return await this.messaging.request(deviceApiCall.method, deviceApiCall.params || void 0); + } else { + return this.messaging.notify(deviceApiCall.method, deviceApiCall.params || void 0); + } + } catch (e) { + if (e instanceof MissingHandler) { + if (this.config.isDDGTestMode) { + console.log("MissingWebkitHandler error for:", deviceApiCall.method); + } + throw new Error("unimplemented handler: " + deviceApiCall.method); + } else { + throw e; + } } - _extractElementStrings(node); } }; - _extractElementStrings(element); - return [...strings]; -}; -exports.extractElementStrings = extractElementStrings; - -},{"./matching.js":33}],30:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.daxGrayscaleBase64 = exports.daxBase64 = void 0; -const daxSvg = ` - - - - - - - - - - - - - -`.trim(); -const daxBase64 = exports.daxBase64 = `data:image/svg+xml;base64,${window.btoa(daxSvg)}`; -const daxGrayscaleSvg = ` - - - - - - -`.trim(); -const daxGrayscaleBase64 = exports.daxGrayscaleBase64 = `data:image/svg+xml;base64,${window.btoa(daxGrayscaleSvg)}`; - -},{}],31:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.matchingConfiguration = void 0; -/* DO NOT EDIT, this file was generated by scripts/precompile-regexes.js */ -/** @type {MatchingConfiguration} */ -const matchingConfiguration = exports.matchingConfiguration = { - matchers: { - fields: { - unknown: { - type: 'unknown', - strategies: { - ddgMatcher: 'unknown' - } - }, - emailAddress: { - type: 'emailAddress', - strategies: { - cssSelector: 'emailAddress', - ddgMatcher: 'emailAddress', - vendorRegex: 'email' - } - }, - password: { - type: 'password', - strategies: { - cssSelector: 'password', - ddgMatcher: 'password' - } - }, - username: { - type: 'username', - strategies: { - cssSelector: 'username', - ddgMatcher: 'username' - } - }, - firstName: { - type: 'firstName', - strategies: { - cssSelector: 'firstName', - ddgMatcher: 'firstName', - vendorRegex: 'given-name' + // src/deviceApiCalls/transports/android.transport.js + var AndroidTransport = class extends DeviceApiTransport { + /** @param {GlobalConfig} globalConfig */ + constructor(globalConfig) { + super(); + /** @type {GlobalConfig} */ + __publicField(this, "config"); + this.config = globalConfig; + if (this.config.isDDGTestMode) { + if (typeof window.BrowserAutofill?.getAutofillData !== "function") { + console.warn("window.BrowserAutofill.getAutofillData missing"); } - }, - middleName: { - type: 'middleName', - strategies: { - cssSelector: 'middleName', - ddgMatcher: 'middleName', - vendorRegex: 'additional-name' - } - }, - lastName: { - type: 'lastName', - strategies: { - cssSelector: 'lastName', - ddgMatcher: 'lastName', - vendorRegex: 'family-name' - } - }, - fullName: { - type: 'fullName', - strategies: { - cssSelector: 'fullName', - ddgMatcher: 'fullName', - vendorRegex: 'name' - } - }, - phone: { - type: 'phone', - strategies: { - cssSelector: 'phone', - ddgMatcher: 'phone', - vendorRegex: 'tel' - } - }, - addressStreet: { - type: 'addressStreet', - strategies: { - cssSelector: 'addressStreet', - ddgMatcher: 'addressStreet', - vendorRegex: 'address-line1' - } - }, - addressStreet2: { - type: 'addressStreet2', - strategies: { - cssSelector: 'addressStreet2', - ddgMatcher: 'addressStreet2', - vendorRegex: 'address-line2' - } - }, - addressCity: { - type: 'addressCity', - strategies: { - cssSelector: 'addressCity', - ddgMatcher: 'addressCity', - vendorRegex: 'address-level2' + if (typeof window.BrowserAutofill?.storeFormData !== "function") { + console.warn("window.BrowserAutofill.storeFormData missing"); } - }, - addressProvince: { - type: 'addressProvince', - strategies: { - cssSelector: 'addressProvince', - ddgMatcher: 'addressProvince', - vendorRegex: 'address-level1' + } + } + /** + * @param {import("../../../packages/device-api").DeviceApiCall} deviceApiCall + * @returns {Promise} + */ + async send(deviceApiCall) { + if (deviceApiCall instanceof GetRuntimeConfigurationCall) { + return androidSpecificRuntimeConfiguration(this.config); + } + if (deviceApiCall instanceof GetAvailableInputTypesCall) { + return androidSpecificAvailableInputTypes(this.config); + } + if (deviceApiCall instanceof GetIncontextSignupDismissedAtCall) { + window.BrowserAutofill.getIncontextSignupDismissedAt(JSON.stringify(deviceApiCall.params)); + return waitForResponse(deviceApiCall.id, this.config); + } + if (deviceApiCall instanceof SetIncontextSignupPermanentlyDismissedAtCall) { + return window.BrowserAutofill.setIncontextSignupPermanentlyDismissedAt(JSON.stringify(deviceApiCall.params)); + } + if (deviceApiCall instanceof StartEmailProtectionSignupCall) { + return window.BrowserAutofill.startEmailProtectionSignup(JSON.stringify(deviceApiCall.params)); + } + if (deviceApiCall instanceof CloseEmailProtectionTabCall) { + return window.BrowserAutofill.closeEmailProtectionTab(JSON.stringify(deviceApiCall.params)); + } + if (deviceApiCall instanceof ShowInContextEmailProtectionSignupPromptCall) { + window.BrowserAutofill.showInContextEmailProtectionSignupPrompt(JSON.stringify(deviceApiCall.params)); + return waitForResponse(deviceApiCall.id, this.config); + } + if (deviceApiCall instanceof GetAutofillDataCall) { + window.BrowserAutofill.getAutofillData(JSON.stringify(deviceApiCall.params)); + return waitForResponse(deviceApiCall.id, this.config); + } + if (deviceApiCall instanceof StoreFormDataCall) { + return window.BrowserAutofill.storeFormData(JSON.stringify(deviceApiCall.params)); + } + throw new Error("android: not implemented: " + deviceApiCall.method); + } + }; + function waitForResponse(expectedResponse, config) { + return new Promise((resolve) => { + const handler = (e) => { + if (!config.isDDGTestMode) { + if (e.origin !== "") { + return; + } } - }, - addressPostalCode: { - type: 'addressPostalCode', - strategies: { - cssSelector: 'addressPostalCode', - ddgMatcher: 'addressPostalCode', - vendorRegex: 'postal-code' + if (!e.data) { + return; } - }, - addressCountryCode: { - type: 'addressCountryCode', - strategies: { - cssSelector: 'addressCountryCode', - ddgMatcher: 'addressCountryCode', - vendorRegex: 'country' + if (typeof e.data !== "string") { + if (config.isDDGTestMode) { + console.log("\u274C event.data was not a string. Expected a string so that it can be JSON parsed"); + } + return; } - }, - birthdayDay: { - type: 'birthdayDay', - strategies: { - cssSelector: 'birthdayDay', - ddgMatcher: 'birthdayDay' + try { + let data = JSON.parse(e.data); + if (data.type === expectedResponse) { + window.removeEventListener("message", handler); + return resolve(data); + } + if (config.isDDGTestMode) { + console.log(`\u274C event.data.type was '${data.type}', which didnt match '${expectedResponse}'`, JSON.stringify(data)); + } + } catch (e2) { + window.removeEventListener("message", handler); + if (config.isDDGTestMode) { + console.log("\u274C Could not JSON.parse the response"); + } } - }, - birthdayMonth: { - type: 'birthdayMonth', - strategies: { - cssSelector: 'birthdayMonth', - ddgMatcher: 'birthdayMonth' + }; + window.addEventListener("message", handler); + }); + } + function androidSpecificRuntimeConfiguration(globalConfig) { + if (!globalConfig.userPreferences) { + throw new Error("globalConfig.userPreferences not supported yet on Android"); + } + return { + success: { + // @ts-ignore + contentScope: globalConfig.contentScope, + // @ts-ignore + userPreferences: globalConfig.userPreferences, + // @ts-ignore + userUnprotectedDomains: globalConfig.userUnprotectedDomains, + // @ts-ignore + availableInputTypes: globalConfig.availableInputTypes + } + }; + } + function androidSpecificAvailableInputTypes(globalConfig) { + if (!globalConfig.availableInputTypes) { + throw new Error("globalConfig.availableInputTypes not supported yet on Android"); + } + return { + success: globalConfig.availableInputTypes + }; + } + + // zod-replacers:./deviceApiCalls/__generated__/validators.zod.js + var autofillSettingsSchema = null; + var providerStatusUpdatedSchema = null; + + // src/Settings.js + var _Settings = class { + /** + * @param {GlobalConfig} config + * @param {DeviceApi} deviceApi + */ + constructor(config, deviceApi) { + /** @type {GlobalConfig} */ + __publicField(this, "globalConfig"); + /** @type {DeviceApi} */ + __publicField(this, "deviceApi"); + /** @type {AutofillFeatureToggles | null} */ + __publicField(this, "_featureToggles", null); + /** @type {AvailableInputTypes | null} */ + __publicField(this, "_availableInputTypes", null); + /** @type {RuntimeConfiguration | null | undefined} */ + __publicField(this, "_runtimeConfiguration", null); + /** @type {boolean | null} */ + __publicField(this, "_enabled", null); + this.deviceApi = deviceApi; + this.globalConfig = config; + } + /** + * Feature toggles are delivered as part of the Runtime Configuration - a flexible design that + * allows data per user + remote config to be accessed together. + * + * Once we access the Runtime Configuration, we then extract the autofill-specific settings via + * `runtimeConfig.userPreferences.features.autofill.settings` and validate that separately. + * + * The 2-step validation occurs because RuntimeConfiguration will be coming from a shared library + * and does not know about the shape of Autofill specific settings. + * + * @returns {Promise} + */ + async getFeatureToggles() { + try { + const runtimeConfig = await this._getRuntimeConfiguration(); + const autofillSettings = validate(runtimeConfig.userPreferences?.features?.autofill?.settings, autofillSettingsSchema); + return autofillSettings.featureToggles; + } catch (e) { + if (this.globalConfig.isDDGTestMode) { + console.log("isDDGTestMode: getFeatureToggles: \u274C", e); } - }, - birthdayYear: { - type: 'birthdayYear', - strategies: { - cssSelector: 'birthdayYear', - ddgMatcher: 'birthdayYear' + return _Settings.defaults.featureToggles; + } + } + /** + * If the platform in question is happy to derive it's 'enabled' state from the RuntimeConfiguration, + * then they should use this. Currently only Windows supports this, but we aim to move all platforms to + * support this going forward. + * @returns {Promise} + */ + async getEnabled() { + try { + const runtimeConfig = await this._getRuntimeConfiguration(); + const enabled = autofillEnabled(runtimeConfig); + return enabled; + } catch (e) { + if (this.globalConfig.isDDGTestMode) { + console.log("isDDGTestMode: getEnabled: \u274C", e); } - }, - cardName: { - type: 'cardName', - strategies: { - cssSelector: 'cardName', - ddgMatcher: 'cardName', - vendorRegex: 'cc-name' + return null; + } + } + /** + * Get runtime configuration, but only once. + * + * Some platforms may be reading this directly from inlined variables, whilst others + * may make a DeviceApiCall. + * + * Currently, it's only read once - but we should be open to the idea that we may need + * this to be called multiple times in the future. + * + * @returns {Promise} + * @throws + * @private + */ + async _getRuntimeConfiguration() { + if (this._runtimeConfiguration) + return this._runtimeConfiguration; + const runtimeConfig = await this.deviceApi.request(new GetRuntimeConfigurationCall(null)); + this._runtimeConfiguration = runtimeConfig; + return this._runtimeConfiguration; + } + /** + * Available Input Types are boolean indicators to represent which input types the + * current **user** has data available for. + * + * @returns {Promise} + */ + async getAvailableInputTypes() { + try { + if (this.globalConfig.isTopFrame) { + return _Settings.defaults.availableInputTypes; } - }, - cardNumber: { - type: 'cardNumber', - strategies: { - cssSelector: 'cardNumber', - ddgMatcher: 'cardNumber', - vendorRegex: 'cc-number' + return await this.deviceApi.request(new GetAvailableInputTypesCall(null)); + } catch (e) { + if (this.globalConfig.isDDGTestMode) { + console.log("isDDGTestMode: getAvailableInputTypes: \u274C", e); } - }, - cardSecurityCode: { - type: 'cardSecurityCode', - strategies: { - cssSelector: 'cardSecurityCode', - ddgMatcher: 'cardSecurityCode' + return _Settings.defaults.availableInputTypes; + } + } + /** + * To 'refresh' settings means to re-call APIs to determine new state. This may + * only occur once per page, but it must be done before any page scanning/decorating can happen + * + * @returns {Promise<{ + * availableInputTypes: AvailableInputTypes, + * featureToggles: AutofillFeatureToggles, + * enabled: boolean | null + * }>} + */ + async refresh() { + this.setEnabled(await this.getEnabled()); + this.setFeatureToggles(await this.getFeatureToggles()); + this.setAvailableInputTypes(await this.getAvailableInputTypes()); + if (typeof this.enabled === "boolean") { + if (!this.enabled) { + return _Settings.defaults; } + } + return { + featureToggles: this.featureToggles, + availableInputTypes: this.availableInputTypes, + enabled: this.enabled + }; + } + /** + * Checks if input type is one which we can't autofill + * @param {{ + * mainType: SupportedMainTypes + * subtype: import('./Form/matching.js').SupportedSubTypes | "unknown" + * }} types + * @returns {boolean} + */ + isTypeUnavailable({ mainType, subtype }) { + if (mainType === "unknown") + return true; + if (!this.featureToggles[`inputType_${mainType}`] && subtype !== "emailAddress") { + return true; + } + return false; + } + /** + * Requests data from remote + * @returns {Promise<>} + */ + async populateData() { + const availableInputTypesFromRemote = await this.getAvailableInputTypes(); + this.setAvailableInputTypes(availableInputTypesFromRemote); + } + /** + * Requests data from remote if not available + * @param {{ + * mainType: SupportedMainTypes + * subtype: import('./Form/matching.js').SupportedSubTypes | "unknown" + * }} types + * @returns {Promise} + */ + async populateDataIfNeeded({ mainType, subtype }) { + if (this.isTypeUnavailable({ mainType, subtype })) + return false; + if (this.availableInputTypes?.[mainType] === void 0) { + await this.populateData(); + return true; + } + return false; + } + /** + * Checks if items will show in the autofill menu, including in-context signup. + * Triggers side-effect if input types is not already available. + * @param {{ + * mainType: SupportedMainTypes + * subtype: import('./Form/matching.js').SupportedSubTypes | "unknown" + * }} types + * @param {import("./InContextSignup.js").InContextSignup?} inContextSignup + * @returns {boolean} + */ + canAutofillType({ mainType, subtype }, inContextSignup) { + if (this.isTypeUnavailable({ mainType, subtype })) + return false; + const isEmailProtectionEnabled = this.featureToggles.emailProtection && this.availableInputTypes.email; + if (subtype === "emailAddress" && isEmailProtectionEnabled) { + return true; + } + if (inContextSignup?.isAvailable(subtype)) { + return true; + } + if (subtype === "fullName") { + return Boolean(this.availableInputTypes.identities?.firstName || this.availableInputTypes.identities?.lastName); + } + if (subtype === "expiration") { + return Boolean(this.availableInputTypes.creditCards?.expirationMonth || this.availableInputTypes.creditCards?.expirationYear); + } + return Boolean(this.availableInputTypes[mainType]?.[subtype]); + } + /** @returns {AutofillFeatureToggles} */ + get featureToggles() { + if (this._featureToggles === null) + throw new Error("feature toggles accessed before being set"); + return this._featureToggles; + } + /** @param {AutofillFeatureToggles} input */ + setFeatureToggles(input) { + this._featureToggles = input; + } + /** @returns {AvailableInputTypes} */ + get availableInputTypes() { + if (this._availableInputTypes === null) + throw new Error("available input types accessed before being set"); + return this._availableInputTypes; + } + /** @param {AvailableInputTypes} value */ + setAvailableInputTypes(value) { + this._availableInputTypes = { ...this._availableInputTypes, ...value }; + } + static default(globalConfig, deviceApi) { + const settings = new _Settings(globalConfig, deviceApi); + settings.setFeatureToggles(_Settings.defaults.featureToggles); + settings.setAvailableInputTypes(_Settings.defaults.availableInputTypes); + return settings; + } + /** @returns {boolean|null} */ + get enabled() { + return this._enabled; + } + /** + * @param {boolean|null} enabled + */ + setEnabled(enabled) { + this._enabled = enabled; + } + }; + var Settings = _Settings; + __publicField(Settings, "defaults", { + /** @type {AutofillFeatureToggles} */ + featureToggles: { + credentials_saving: false, + password_generation: false, + emailProtection: false, + emailProtection_incontext_signup: false, + inputType_identities: false, + inputType_credentials: false, + inputType_creditCards: false, + inlineIcon_credentials: false + }, + /** @type {AvailableInputTypes} */ + availableInputTypes: { + credentials: { + username: false, + password: false }, - expirationMonth: { - type: 'expirationMonth', - strategies: { - cssSelector: 'expirationMonth', - ddgMatcher: 'expirationMonth', - vendorRegex: 'cc-exp-month' - } + identities: { + firstName: false, + middleName: false, + lastName: false, + birthdayDay: false, + birthdayMonth: false, + birthdayYear: false, + addressStreet: false, + addressStreet2: false, + addressCity: false, + addressProvince: false, + addressPostalCode: false, + addressCountryCode: false, + phone: false, + emailAddress: false }, - expirationYear: { - type: 'expirationYear', - strategies: { - cssSelector: 'expirationYear', - ddgMatcher: 'expirationYear', - vendorRegex: 'cc-exp-year' - } + creditCards: { + cardName: false, + cardSecurityCode: false, + expirationMonth: false, + expirationYear: false, + cardNumber: false }, - expiration: { - type: 'expiration', - strategies: { - cssSelector: 'expiration', - ddgMatcher: 'expiration', - vendorRegex: 'cc-exp' - } - } - }, - lists: { - unknown: ['unknown'], - emailAddress: ['emailAddress'], - password: ['password'], - username: ['username'], - cc: ['cardName', 'cardNumber', 'cardSecurityCode', 'expirationMonth', 'expirationYear', 'expiration'], - id: ['firstName', 'middleName', 'lastName', 'fullName', 'phone', 'addressStreet', 'addressStreet2', 'addressCity', 'addressProvince', 'addressPostalCode', 'addressCountryCode', 'birthdayDay', 'birthdayMonth', 'birthdayYear'] - } - }, - strategies: { - cssSelector: { - selectors: { - genericTextField: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month])', - submitButtonSelector: 'input[type=submit], input[type=button], input[type=image], button:not([role=switch]):not([role=link]), [role=button], a[href="#"][id*=button i], a[href="#"][id*=btn i]', - formInputsSelector: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month]),[autocomplete=username],select', - safeUniversalSelector: '*:not(select):not(option):not(script):not(noscript):not(style):not(br)', - emailAddress: 'input:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]), input[type=""][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([type=tel]), input[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]):not([name*=code i]), input:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]), input[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=""][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=email], input[type=text][aria-label*=email i]:not([aria-label*=search i]), input:not([type])[aria-label*=email i]:not([aria-label*=search i]), input[name=username][type=email], input[autocomplete=username][type=email], input[autocomplete=username][placeholder*=email i], input[autocomplete=email],input[name="mail_tel" i],input[value=email i]', - username: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month])[autocomplete^=user i],input[name=username i],input[name="loginId" i],input[name="userid" i],input[id="userid" i],input[name="user_id" i],input[name="user-id" i],input[id="login-id" i],input[id="login_id" i],input[id="loginid" i],input[name="login" i],input[name=accountname i],input[autocomplete=username i],input[name*=accountid i],input[name="j_username" i],input[id="j_username" i],input[name="uwinid" i],input[name="livedoor_id" i],input[name="ssousername" i],input[name="j_userlogin_pwd" i],input[name="user[login]" i],input[name="user" i],input[name$="_username" i],input[id="lmSsoinput" i],input[name="account_subdomain" i],input[name="masterid" i],input[name="tridField" i],input[id="signInName" i],input[id="w3c_accountsbundle_accountrequeststep1_login" i],input[id="username" i],input[name="_user" i],input[name="login_username" i],input[name^="login-user-account" i],input[id="loginusuario" i],input[name="usuario" i],input[id="UserLoginFormUsername" i],input[id="nw_username" i],input[can-field="accountName"],input[placeholder^="username" i]', - password: 'input[type=password]:not([autocomplete*=cc]):not([autocomplete=one-time-code]):not([name*=answer i]):not([name*=mfa i]):not([name*=tin i]):not([name*=card i]):not([name*=cvv i]),input.js-cloudsave-phrase', - cardName: 'input[autocomplete="cc-name" i], input[autocomplete="ccname" i], input[name="ccname" i], input[name="cc-name" i], input[name="ppw-accountHolderName" i], input[id*=cardname i], input[id*=card-name i], input[id*=card_name i]', - cardNumber: 'input[autocomplete="cc-number" i], input[autocomplete="ccnumber" i], input[autocomplete="cardnumber" i], input[autocomplete="card-number" i], input[name="ccnumber" i], input[name="cc-number" i], input[name*=card i][name*=number i], input[name*=cardnumber i], input[id*=cardnumber i], input[id*=card-number i], input[id*=card_number i]', - cardSecurityCode: 'input[autocomplete="cc-csc" i], input[autocomplete="csc" i], input[autocomplete="cc-cvc" i], input[autocomplete="cvc" i], input[name="cvc" i], input[name="cc-cvc" i], input[name="cc-csc" i], input[name="csc" i], input[name*=security i][name*=code i]', - expirationMonth: '[autocomplete="cc-exp-month" i], [autocomplete="cc_exp_month" i], [name="ccmonth" i], [name="ppw-expirationDate_month" i], [name=cardExpiryMonth i], [name*=ExpDate_Month i], [name*=expiration i][name*=month i], [id*=expiration i][id*=month i], [name*=cc-exp-month i], [name*="card_exp-month" i], [name*=cc_exp_month i], [id*=cc-month i]', - expirationYear: '[autocomplete="cc-exp-year" i], [autocomplete="cc_exp_year" i], [name="ccyear" i], [name="ppw-expirationDate_year" i], [name=cardExpiryYear i], [name*=ExpDate_Year i], [name*=expiration i][name*=year i], [id*=expiration i][id*=year i], [name*="cc-exp-year" i], [name*="card_exp-year" i], [name*=cc_exp_year i], [id*=cc-year i]', - expiration: '[autocomplete="cc-exp" i], [name="cc-exp" i], [name="exp-date" i], [name="expirationDate" i], input[id*=expiration i]', - firstName: '[name*=fname i], [autocomplete*=given-name i], [name*=firstname i], [autocomplete*=firstname i], [name*=first-name i], [autocomplete*=first-name i], [name*=first_name i], [autocomplete*=first_name i], [name*=givenname i], [autocomplete*=givenname i], [name*=given-name i], [name*=given_name i], [autocomplete*=given_name i], [name*=forename i], [autocomplete*=forename i]', - middleName: '[name*=mname i], [autocomplete*=additional-name i], [name*=middlename i], [autocomplete*=middlename i], [name*=middle-name i], [autocomplete*=middle-name i], [name*=middle_name i], [autocomplete*=middle_name i], [name*=additionalname i], [autocomplete*=additionalname i], [name*=additional-name i], [name*=additional_name i], [autocomplete*=additional_name i]', - lastName: '[name=lname], [autocomplete*=family-name i], [name*=lastname i], [autocomplete*=lastname i], [name*=last-name i], [autocomplete*=last-name i], [name*=last_name i], [autocomplete*=last_name i], [name*=familyname i], [autocomplete*=familyname i], [name*=family-name i], [name*=family_name i], [autocomplete*=family_name i], [name*=surname i], [autocomplete*=surname i]', - fullName: '[autocomplete=name], [name*=fullname i], [autocomplete*=fullname i], [name*=full-name i], [autocomplete*=full-name i], [name*=full_name i], [autocomplete*=full_name i], [name*=your-name i], [autocomplete*=your-name i]', - phone: '[name*=phone i]:not([name*=extension i]):not([name*=type i]):not([name*=country i]), [name*=mobile i]:not([name*=type i]), [autocomplete=tel], [autocomplete="tel-national"], [placeholder*="phone number" i]', - addressStreet: '[name=address i], [autocomplete=street-address i], [autocomplete=address-line1 i], [name=street i], [name=ppw-line1 i], [name*=addressLine1 i]', - addressStreet2: '[name=address2 i], [autocomplete=address-line2 i], [name=ppw-line2 i], [name*=addressLine2 i]', - addressCity: '[name=city i], [autocomplete=address-level2 i], [name=ppw-city i], [name*=addressCity i]', - addressProvince: '[name=province i], [name=state i], [autocomplete=address-level1 i]', - addressPostalCode: '[name=zip i], [name=zip2 i], [name=postal i], [autocomplete=postal-code i], [autocomplete=zip-code i], [name*=postalCode i], [name*=zipcode i]', - addressCountryCode: '[name=country i], [autocomplete=country i], [name*=countryCode i], [name*=country-code i], [name*=countryName i], [name*=country-name i],select.idms-address-country', - birthdayDay: '[name=bday-day i], [name*=birthday_day i], [name*=birthday-day i], [name=date_of_birth_day i], [name=date-of-birth-day i], [name^=birthdate_d i], [name^=birthdate-d i], [aria-label="birthday" i][placeholder="day" i]', - birthdayMonth: '[name=bday-month i], [name*=birthday_month i], [name*=birthday-month i], [name=date_of_birth_month i], [name=date-of-birth-month i], [name^=birthdate_m i], [name^=birthdate-m i], select[name="mm" i]', - birthdayYear: '[name=bday-year i], [name*=birthday_year i], [name*=birthday-year i], [name=date_of_birth_year i], [name=date-of-birth-year i], [name^=birthdate_y i], [name^=birthdate-y i], [aria-label="birthday" i][placeholder="year" i]' - } - }, - ddgMatcher: { - matchers: { - unknown: { - match: /search|filter|subject|title|captcha|mfa|2fa|(two|2).?factor|one-time|otp|cerca|filtr|oggetto|titolo|(due|2|più).?fattori|suche|filtern|betreff|zoeken|filter|onderwerp|titel|chercher|filtrer|objet|titre|authentification multifacteur|double authentification|à usage unique|busca|busqueda|filtra|dos pasos|un solo uso|sök|filter|ämne|multifaktorsautentisering|tvåfaktorsautentisering|två.?faktor|engångs/iu, - skip: /phone|mobile|email|password/iu - }, - emailAddress: { - match: /.mail\b|apple.?id|posta elettronica|e.?mailadres|correo electr|correo-e|^correo$|\be.?post|e.?postadress/iu, - skip: /phone|(first.?|last.?)name|number|code/iu, - forceUnknown: /search|filter|subject|title|\btab\b|otp/iu - }, - password: { - match: /password|passwort|kennwort|wachtwoord|mot de passe|clave|contraseña|lösenord/iu, - skip: /email|one-time|error|hint/iu, - forceUnknown: /captcha|mfa|2fa|two factor|otp|pin/iu - }, - newPassword: { - match: /new|re.?(enter|type)|repeat|update|reset\b/iu - }, - currentPassword: { - match: /current|old|previous|expired|existing/iu - }, - username: { - match: /(user|account|online.?id|log(i|o)n|net)((.)?(name|i.?d.?|log(i|o)n).?)?(.?((or|\/).+|\*|:)( required)?)?$|(nome|id|login).?utente|(nome|id) (dell.)?account|codice cliente|nutzername|anmeldename|gebruikersnaam|nom d.utilisateur|identifiant|pseudo|usuari|cuenta|identificador|apodo|\bdni\b|\bnie\b| del? documento|documento de identidad|användarnamn|kontonamn|användar-id/iu, - skip: /phone/iu, - forceUnknown: /search|policy|choose a user\b/iu - }, - cardName: { - match: /(card.*name|name.*card)|(card.*holder|holder.*card)|(card.*owner|owner.*card)/iu - }, - cardNumber: { - match: /card.*number|number.*card/iu, - skip: /phone/iu, - forceUnknown: /plus/iu - }, - cardSecurityCode: { - match: /security.?code|card.?verif|cvv|csc|cvc|cv2|card id/iu - }, - expirationMonth: { - match: /(card|\bcc\b)?.?(exp(iry|iration)?)?.?(month|\bmm\b(?![.\s/-]yy))/iu, - skip: /mm[/\s.\-_—–]|check/iu - }, - expirationYear: { - match: /(card|\bcc\b)?.?(exp(iry|iration)?)?.?(year|yy)/iu, - skip: /mm[/\s.\-_—–]|check/iu - }, - expiration: { - match: /(\bmm\b|\b\d\d\b)[/\s.\-_—–](\byy|\bjj|\baa|\b\d\d)|\bexp|\bvalid(idity| through| until)/iu, - skip: /invalid|^dd\/|check/iu - }, - firstName: { - match: /(first|given|fore).?name|\bnome/iu, - skip: /last|cognome|completo/iu - }, - middleName: { - match: /(middle|additional).?name/iu - }, - lastName: { - match: /(last|family|sur)[^i]?name|cognome/iu, - skip: /first|\bnome/iu - }, - fullName: { - match: /^(full.?|whole\s|first.*last\s|real\s|contact.?)?name\b|\bnome/iu, - forceUnknown: /company|org|item/iu - }, - phone: { - match: /phone|mobile|telefono|cellulare/iu, - skip: /code|pass|country/iu, - forceUnknown: /ext|type|otp/iu - }, - addressStreet: { - match: /address/iu, - forceUnknown: /\bip\b|duck|web|url/iu, - skip: /address.*(2|two|3|three)|email|log.?in|sign.?in|civico/iu - }, - addressStreet2: { - match: /address.*(2|two)|apartment|\bapt\b|\bflat\b|\bline.*(2|two)/iu, - forceUnknown: /\bip\b|duck/iu, - skip: /email|log.?in|sign.?in/iu - }, - addressCity: { - match: /city|town|città|comune/iu, - skip: /\bzip\b|\bcap\b/iu, - forceUnknown: /vatican/iu - }, - addressProvince: { - match: /state|province|region|county|provincia|regione/iu, - forceUnknown: /united/iu, - skip: /country/iu - }, - addressPostalCode: { - match: /\bzip\b|postal\b|post.?code|\bcap\b|codice postale/iu - }, - addressCountryCode: { - match: /country|\bnation\b|nazione|paese/iu - }, - birthdayDay: { - match: /(birth.*day|day.*birth)/iu, - skip: /month|year/iu - }, - birthdayMonth: { - match: /(birth.*month|month.*birth)/iu, - skip: /year/iu - }, - birthdayYear: { - match: /(birth.*year|year.*birth)/iu - }, - loginRegex: { - match: /sign(ing)?.?[io]n(?!g)|log.?[io]n|log.?out|unsubscri|(forgot(ten)?|reset) (your )?password|password (forgotten|lost)|mfa-submit-form|unlock|logged in as|entra|accedi|accesso|resetta password|password dimenticata|dimenticato la password|recuper[ao] password|(ein|aus)loggen|anmeld(eformular|ung|efeld)|abmelden|passwort (vergessen|verloren)|zugang| zugangsformular|einwahl|inloggen|se (dé)?connecter|(dé)?connexion|récupérer ((mon|ton|votre|le) )?mot de passe|mot de passe (oublié|perdu)|clave(?! su)|olvidó su (clave|contraseña)|.*sesión|conect(arse|ado)|conéctate|acce(de|so)|entrar|logga (in|ut)|avprenumerera|avregistrera|glömt lösenord|återställ lösenord/iu - }, - signupRegex: { - match: /sign(ing)?.?up|join|\bregist(er|ration)|newsletter|\bsubscri(be|ption)|contact|create|start|enroll|settings|preferences|profile|update|checkout|purchase|buy|order|schedule|estimate|request|new.?customer|(confirm|re.?(type|enter)|repeat) password|password confirm|iscri(viti|zione)|registra(ti|zione)|(?:nuovo|crea(?:zione)?) account|contatt(?:ac)i|sottoscriv|sottoscrizione|compra|acquist(a|o)|ordin[aeio]|richie(?:di|sta)|(?:conferma|ripeti) password|inizia|nuovo cliente|impostazioni|preferenze|profilo|aggiorna|paga|registrier(ung|en)|profil (anlegen|erstellen)| nachrichten|verteiler|neukunde|neuer (kunde|benutzer|nutzer)|passwort wiederholen|anmeldeseite|nieuwsbrief|aanmaken|profiel|s.inscrire|inscription|s.abonner|créer|préférences|profil|mise à jour|payer|ach(eter|at)| nouvel utilisateur|(confirmer|réessayer) ((mon|ton|votre|le) )?mot de passe|regis(trarse|tro)|regístrate|inscr(ibirse|ipción|íbete)|solicitar|crea(r cuenta)?|nueva cuenta|nuevo (cliente|usuario)|preferencias|perfil|lista de correo|registrer(a|ing)|(nytt|öppna) konto|nyhetsbrev|prenumer(era|ation)|kontakt|skapa|starta|inställningar|min (sida|kundvagn)|uppdatera|till kassan|gäst|köp|beställ|schemalägg|ny kund|(repetera|bekräfta) lösenord/iu - }, - conservativeSignupRegex: { - match: /sign.?up|join|register|enroll|(create|new).+account|newsletter|subscri(be|ption)|settings|preferences|profile|update|iscri(viti|zione)|registra(ti|zione)|(?:nuovo|crea(?:zione)?) account|contatt(?:ac)?i|sottoscriv|sottoscrizione|impostazioni|preferenze|aggiorna|anmeld(en|ung)|registrier(en|ung)|neukunde|neuer (kunde|benutzer|nutzer)|registreren|eigenschappen|profiel|bijwerken|s.inscrire|inscription|s.abonner|abonnement|préférences|profil|créer un compte|regis(trarse|tro)|regístrate|inscr(ibirse|ipción|íbete)|crea(r cuenta)?|nueva cuenta|nuevo (cliente|usuario)|preferencias|perfil|lista de correo|registrer(a|ing)|(nytt|öppna) konto|nyhetsbrev|prenumer(era|ation)|kontakt|skapa|starta|inställningar|min (sida|kundvagn)|uppdatera/iu + email: false + }, + /** @type {boolean | null} */ + enabled: null + }); + + // src/deviceApiCalls/transports/extension.transport.js + var ExtensionTransport = class extends DeviceApiTransport { + /** @param {GlobalConfig} globalConfig */ + constructor(globalConfig) { + super(); + this.config = globalConfig; + } + async send(deviceApiCall) { + if (deviceApiCall instanceof GetRuntimeConfigurationCall) { + return deviceApiCall.result(await extensionSpecificRuntimeConfiguration(this)); + } + if (deviceApiCall instanceof GetAvailableInputTypesCall) { + return deviceApiCall.result(await extensionSpecificGetAvailableInputTypes()); + } + if (deviceApiCall instanceof SetIncontextSignupPermanentlyDismissedAtCall) { + return deviceApiCall.result(await extensionSpecificSetIncontextSignupPermanentlyDismissedAtCall(deviceApiCall.params)); + } + if (deviceApiCall instanceof GetIncontextSignupDismissedAtCall) { + return deviceApiCall.result(await extensionSpecificGetIncontextSignupDismissedAt()); + } + if (deviceApiCall instanceof SendJSPixelCall) { + return deviceApiCall.result(await extensionSpecificSendPixel(deviceApiCall.params)); + } + if (deviceApiCall instanceof AddDebugFlagCall) { + return deviceApiCall.result(await extensionSpecificAddDebugFlag(deviceApiCall.params)); + } + if (deviceApiCall instanceof CloseAutofillParentCall || deviceApiCall instanceof StartEmailProtectionSignupCall) { + return; + } + console.error("Send not implemented for " + deviceApiCall.method); + } + }; + async function extensionSpecificRuntimeConfiguration(deviceApi) { + const contentScope = await getContentScopeConfig(); + const emailProtectionEnabled = isAutofillEnabledFromProcessedConfig(contentScope); + const incontextSignupEnabled = isIncontextSignupEnabledFromProcessedConfig(contentScope); + return { + success: { + // @ts-ignore + contentScope, + // @ts-ignore + userPreferences: { + features: { + autofill: { + settings: { + featureToggles: { + ...Settings.defaults.featureToggles, + emailProtection: emailProtectionEnabled, + emailProtection_incontext_signup: incontextSignupEnabled + } + } + } + } }, - resetPasswordLink: { - match: /(forgot(ten)?|reset|don't remember) (your )?password|password forgotten|password dimenticata|reset(?:ta) password|recuper[ao] password|(vergessen|verloren|verlegt|wiederherstellen) passwort|wachtwoord (vergeten|reset)|(oublié|récupérer) ((mon|ton|votre|le) )?mot de passe|mot de passe (oublié|perdu)|re(iniciar|cuperar) (contraseña|clave)|olvid(ó su|aste tu|é mi) (contraseña|clave)|recordar( su)? (contraseña|clave)|glömt lösenord|återställ lösenord/iu + // @ts-ignore + userUnprotectedDomains: deviceApi.config?.userUnprotectedDomains || [] + } + }; + } + async function extensionSpecificGetAvailableInputTypes() { + const contentScope = await getContentScopeConfig(); + const emailProtectionEnabled = isAutofillEnabledFromProcessedConfig(contentScope); + return { + success: { + ...Settings.defaults.availableInputTypes, + email: emailProtectionEnabled + } + }; + } + async function getContentScopeConfig() { + return new Promise((resolve) => { + chrome.runtime.sendMessage( + { + registeredTempAutofillContentScript: true, + documentUrl: window.location.href }, - loginProvidersRegex: { - match: / with | con | mit | met | avec /iu + (response) => { + if (response && "site" in response) { + resolve(response); + } + } + ); + }); + } + async function extensionSpecificSendPixel(params) { + return new Promise((resolve) => { + chrome.runtime.sendMessage( + { + messageType: "sendJSPixel", + options: params }, - submitButtonRegex: { - match: /submit|send|confirm|save|continue|next|sign|log.?([io])n|buy|purchase|check.?out|subscribe|donate|update|\bset\b|invia|conferma|salva|continua|entra|acced|accesso|compra|paga|sottoscriv|registra|dona|senden|\bja\b|bestätigen|weiter|nächste|kaufen|bezahlen|spenden|versturen|verzenden|opslaan|volgende|koop|kopen|voeg toe|aanmelden|envoyer|confirmer|sauvegarder|continuer|suivant|signer|connexion|acheter|payer|s.abonner|donner|enviar|confirmar|registrarse|continuar|siguiente|comprar|donar|skicka|bekräfta|spara|fortsätt|nästa|logga in|köp|handla|till kassan|registrera|donera/iu + () => { + resolve(true); + } + ); + }); + } + async function extensionSpecificAddDebugFlag(params) { + return new Promise((resolve) => { + chrome.runtime.sendMessage( + { + messageType: "addDebugFlag", + options: params }, - submitButtonUnlikelyRegex: { - match: /facebook|twitter|google|apple|cancel|show|toggle|reveal|hide|print|back|already|annulla|mostra|nascondi|stampa|indietro|già|abbrechen|passwort|zeigen|verbergen|drucken|zurück|annuleer|wachtwoord|toon|vorige|annuler|mot de passe|montrer|cacher|imprimer|retour|déjà|anular|cancelar|imprimir|cerrar|avbryt|lösenord|visa|dölj|skirv ut|tillbaka|redan/iu - } - } - }, - vendorRegex: { - rules: { - email: /((^e-?mail$)|(^email-?address$))|(e.?mail|courriel|correo.*electr(o|ó)nico|メールアドレス|электронной.?почты|邮件|邮箱|電郵地址|ഇ-മെയില്‍|ഇലക്ട്രോണിക്.?മെയിൽ|ایمیل|پست.*الکترونیک|ईमेल|इलॅक्ट्रॉनिक.?मेल|(\b|_)eposta(\b|_)|(?:이메일|전자.?우편|[ee]-?mail)(.?주소)?)/iu, - tel: /((^phone$)|(^mobile$)|(^mobile-?phone$)|(^tel$)|(^telephone$)|(^phone-?number$))|(phone|mobile|contact.?number|telefonnummer|telefono|teléfono|telfixe|電話|telefone|telemovel|телефон|मोबाइल|(\b|_|\*)telefon(\b|_|\*)|电话|മൊബൈല്‍|(?:전화|핸드폰|휴대폰|휴대전화)(?:.?번호)?)/iu, - organization: /((^company$)|(^company-?name$)|(^organization$)|(^organization-?name$))|(company|business|organization|organisation|empresa|societe|société|ragione.?sociale|会社|название.?компании|单位|公司|شرکت|회사|직장)/iu, - 'street-address': /((^address$)|(^street-?address$)|(^addr$)|(^street$)|(^mailing-?addr(ess)?$)|(^billing-?addr(ess)?$)|(^mail-?addr(ess)?$)|(^bill-?addr(ess)?$))|(streetaddress|street-address)/iu, - 'address-line1': /(addrline1|address_1)|((^address-?1$)|(^address-?line-?1$)|(^addr-?1$)|(^street-?1$))|(^address$|address[_-]?line[_-]?(1|one)|address1|addr1|street|(?:shipping|billing)address$|strasse|straße|hausnummer|housenumber|house.?name|direccion|dirección|adresse|indirizzo|^住所$|住所1|адрес|地址|(\b|_)adres(?! (başlığı(nız)?|tarifi))(\b|_)|^주소.?$|주소.?1)/iu, - 'address-line2': /(addrline2|address_2)|((^address-?2$)|(^address-?line-?2$)|(^addr-?2$)|(^street-?2$))|(address[_-]?line(2|two)|address2|addr2|street|suite|unit(?!e)|adresszusatz|ergänzende.?angaben|direccion2|colonia|adicional|addresssuppl|complementnom|appartement|indirizzo2|住所2|complemento|addrcomplement|улица|地址2|주소.?2)/iu, - 'address-line3': /(addrline3|address_3)|((^address-?3$)|(^address-?line-?3$)|(^addr-?3$)|(^street-?3$))|(address[_-]?line(3|three)|address3|addr3|street|suite|unit(?!e)|adresszusatz|ergänzende.?angaben|direccion3|colonia|adicional|addresssuppl|complementnom|appartement|indirizzo3|住所3|complemento|addrcomplement|улица|地址3|주소.?3)/iu, - 'address-level2': /((^city$)|(^town$)|(^address-?level-?2$)|(^address-?city$)|(^address-?town$))|(city|town|\bort\b|stadt|suburb|ciudad|provincia|localidad|poblacion|ville|commune|localit(a|à)|citt(a|à)|市区町村|cidade|город|市|分區|شهر|शहर|ग्राम|गाँव|നഗരം|ഗ്രാമം|((\b|_|\*)([i̇ii̇]l[cç]e(miz|niz)?)(\b|_|\*))|^시[^도·・]|시[·・]?군[·・]?구)/iu, - 'address-level1': /(land)|((^state$)|(^province$)|(^provence$)|(^address-?level-?1$)|(^address-?state$)|(^address-?province$))|(county|region|province|county|principality|都道府県|estado|provincia|область|省|地區|സംസ്ഥാനം|استان|राज्य|((\b|_|\*)(eyalet|[şs]ehir|[i̇ii̇]limiz|kent)(\b|_|\*))|^시[·・]?도)/iu, - 'postal-code': /((^postal$)|(^zip$)|(^zip2$)|(^zip-?code$)|(^postal-?code$)|(^post-?code$)|(^address-?zip$)|(^address-?postal$)|(^address-?code$)|(^address-?postal-?code$)|(^address-?zip-?code$))|(zip|postal|post.*code|pcode|pin.?code|postleitzahl|\bcp\b|\bcdp\b|\bcap\b|郵便番号|codigo|codpos|\bcep\b|почтовый.?индекс|पिन.?कोड|പിന്‍കോഡ്|邮政编码|邮编|郵遞區號|(\b|_)posta kodu(\b|_)|우편.?번호)/iu, - country: /((^country$)|(^country-?code$)|(^country-?name$)|(^address-?country$)|(^address-?country-?name$)|(^address-?country-?code$))|(country|countries|país|pais|(\b|_)land(\b|_)(?!.*(mark.*))|国家|국가|나라|(\b|_)(ülke|ulce|ulke)(\b|_)|کشور)/iu, - 'cc-name': /(accountholdername|titulaire)|(cc-?name|card-?name|cardholder-?name|cardholder|(^nom$))|(card.?(?:holder|owner)|name.*(\b)?on(\b)?.*card|(?:card|cc).?name|cc.?full.?name|karteninhaber|nombre.*tarjeta|nom.*carte|nome.*cart|名前|имя.*карты|信用卡开户名|开户名|持卡人姓名|持卡人姓名)/iu, - name: /((^name$)|full-?name|your-?name)|(^name|full.?name|your.?name|customer.?name|bill.?name|ship.?name|name.*first.*last|firstandlastname|nombre.*y.*apellidos|^nom(?!bre)\b|お名前|氏名|^nome|نام.*نام.*خانوادگی|姓名|(\b|_|\*)ad[ı]? soyad[ı]?(\b|_|\*)|성명)/iu, - 'given-name': /((^f-?name$)|(^first-?name$)|(^given-?name$)|(^first-?n$))|(first.*name|initials|fname|first$|given.*name|vorname|nombre|forename|prénom|prenom|名|\bnome|имя|نام|이름|പേര്|(\b|_|\*)(isim|ad|ad(i|ı|iniz|ınız)?)(\b|_|\*)|नाम)/iu, - 'additional-name': /(apellido.?materno|lastlastname)|((^m-?name$)|(^middle-?name$)|(^additional-?name$)|(^middle-?initial$)|(^middle-?n$)|(^middle-?i$))|(middle.*name|mname|middle$|middle.*initial|m\.i\.|mi$|\bmi\b)/iu, - 'family-name': /((^l-?name$)|(^last-?name$)|(^s-?name$)|(^surname$)|(^family-?name$)|(^family-?n$)|(^last-?n$))|(last.*name|lname|surname|last$|secondname|family.*name|nachname|apellidos?|famille|^nom(?!bre)|cognome|姓|apelidos|surename|sobrenome|фамилия|نام.*خانوادگی|उपनाम|മറുപേര്|(\b|_|\*)(soyisim|soyad(i|ı|iniz|ınız)?)(\b|_|\*)|\b성(?:[^명]|\b))/iu, - 'cc-number': /((cc|kk)nr)|(cc-?number|cc-?num|card-?number|card-?num|(^number$)|(^cc$)|cc-?no|card-?no|(^credit-?card$)|numero-?carte|(^carte$)|(^carte-?credit$)|num-?carte|cb-?num)|((add)?(?:card|cc|acct).?(?:number|#|no|num|field)|カード番号|номер.*карты|信用卡号|信用卡号码|信用卡卡號|카드|(numero|número|numéro)(?!.*(document|fono|phone|réservation)))/iu, - 'cc-exp-month': /((cc|kk)month)|((^exp-?month$)|(^cc-?exp-?month$)|(^cc-?month$)|(^card-?month$)|(^cc-?mo$)|(^card-?mo$)|(^exp-?mo$)|(^card-?exp-?mo$)|(^cc-?exp-?mo$)|(^card-?expiration-?month$)|(^expiration-?month$)|(^cc-?mm$)|(^cc-?m$)|(^card-?mm$)|(^card-?m$)|(^card-?exp-?mm$)|(^cc-?exp-?mm$)|(^exp-?mm$)|(^exp-?m$)|(^expire-?month$)|(^expire-?mo$)|(^expiry-?month$)|(^expiry-?mo$)|(^card-?expire-?month$)|(^card-?expire-?mo$)|(^card-?expiry-?month$)|(^card-?expiry-?mo$)|(^mois-?validite$)|(^mois-?expiration$)|(^m-?validite$)|(^m-?expiration$)|(^expiry-?date-?field-?month$)|(^expiration-?date-?month$)|(^expiration-?date-?mm$)|(^exp-?mon$)|(^validity-?mo$)|(^exp-?date-?mo$)|(^cb-?date-?mois$)|(^date-?m$))|(gueltig|gültig|monat|fecha|date.*exp|scadenza|有効期限|validade|срок действия карты|月)/iu, - 'cc-exp-year': /((cc|kk)year)|((^exp-?year$)|(^cc-?exp-?year$)|(^cc-?year$)|(^card-?year$)|(^cc-?yr$)|(^card-?yr$)|(^exp-?yr$)|(^card-?exp-?yr$)|(^cc-?exp-?yr$)|(^card-?expiration-?year$)|(^expiration-?year$)|(^cc-?yy$)|(^cc-?y$)|(^card-?yy$)|(^card-?y$)|(^card-?exp-?yy$)|(^cc-?exp-?yy$)|(^exp-?yy$)|(^exp-?y$)|(^cc-?yyyy$)|(^card-?yyyy$)|(^card-?exp-?yyyy$)|(^cc-?exp-?yyyy$)|(^expire-?year$)|(^expire-?yr$)|(^expiry-?year$)|(^expiry-?yr$)|(^card-?expire-?year$)|(^card-?expire-?yr$)|(^card-?expiry-?year$)|(^card-?expiry-?yr$)|(^an-?validite$)|(^an-?expiration$)|(^annee-?validite$)|(^annee-?expiration$)|(^expiry-?date-?field-?year$)|(^expiration-?date-?year$)|(^cb-?date-?ann$)|(^expiration-?date-?yy$)|(^expiration-?date-?yyyy$)|(^validity-?year$)|(^exp-?date-?year$)|(^date-?y$))|(ablaufdatum|gueltig|gültig|jahr|fecha|scadenza|有効期限|validade|срок действия карты|年|有效期)/iu, - 'cc-exp': /((^cc-?exp$)|(^card-?exp$)|(^cc-?expiration$)|(^card-?expiration$)|(^cc-?ex$)|(^card-?ex$)|(^card-?expire$)|(^card-?expiry$)|(^validite$)|(^expiration$)|(^expiry$)|mm-?yy|mm-?yyyy|yy-?mm|yyyy-?mm|expiration-?date|payment-?card-?expiration|(^payment-?cc-?date$))|(expir|exp.*date|^expfield$|gueltig|gültig|fecha|date.*exp|scadenza|有効期限|validade|срок действия карты)/iu, - 'cc-type': /(type|kartenmarke)|((^cc-?type$)|(^card-?type$)|(^card-?brand$)|(^cc-?brand$)|(^cb-?type$))/iu - }, - ruleSets: [{ - 'address-line1': 'addrline1|address_1', - 'address-line2': 'addrline2|address_2', - 'address-line3': 'addrline3|address_3', - 'address-level1': 'land', - 'additional-name': 'apellido.?materno|lastlastname', - 'cc-name': 'accountholdername|titulaire', - 'cc-number': '(cc|kk)nr', - 'cc-exp-month': '(cc|kk)month', - 'cc-exp-year': '(cc|kk)year', - 'cc-type': 'type|kartenmarke' - }, { - email: '(^e-?mail$)|(^email-?address$)', - tel: '(^phone$)|(^mobile$)|(^mobile-?phone$)|(^tel$)|(^telephone$)|(^phone-?number$)', - organization: '(^company$)|(^company-?name$)|(^organization$)|(^organization-?name$)', - 'street-address': '(^address$)|(^street-?address$)|(^addr$)|(^street$)|(^mailing-?addr(ess)?$)|(^billing-?addr(ess)?$)|(^mail-?addr(ess)?$)|(^bill-?addr(ess)?$)', - 'address-line1': '(^address-?1$)|(^address-?line-?1$)|(^addr-?1$)|(^street-?1$)', - 'address-line2': '(^address-?2$)|(^address-?line-?2$)|(^addr-?2$)|(^street-?2$)', - 'address-line3': '(^address-?3$)|(^address-?line-?3$)|(^addr-?3$)|(^street-?3$)', - 'address-level2': '(^city$)|(^town$)|(^address-?level-?2$)|(^address-?city$)|(^address-?town$)', - 'address-level1': '(^state$)|(^province$)|(^provence$)|(^address-?level-?1$)|(^address-?state$)|(^address-?province$)', - 'postal-code': '(^postal$)|(^zip$)|(^zip2$)|(^zip-?code$)|(^postal-?code$)|(^post-?code$)|(^address-?zip$)|(^address-?postal$)|(^address-?code$)|(^address-?postal-?code$)|(^address-?zip-?code$)', - country: '(^country$)|(^country-?code$)|(^country-?name$)|(^address-?country$)|(^address-?country-?name$)|(^address-?country-?code$)', - name: '(^name$)|full-?name|your-?name', - 'given-name': '(^f-?name$)|(^first-?name$)|(^given-?name$)|(^first-?n$)', - 'additional-name': '(^m-?name$)|(^middle-?name$)|(^additional-?name$)|(^middle-?initial$)|(^middle-?n$)|(^middle-?i$)', - 'family-name': '(^l-?name$)|(^last-?name$)|(^s-?name$)|(^surname$)|(^family-?name$)|(^family-?n$)|(^last-?n$)', - 'cc-name': 'cc-?name|card-?name|cardholder-?name|cardholder|(^nom$)', - 'cc-number': 'cc-?number|cc-?num|card-?number|card-?num|(^number$)|(^cc$)|cc-?no|card-?no|(^credit-?card$)|numero-?carte|(^carte$)|(^carte-?credit$)|num-?carte|cb-?num', - 'cc-exp': '(^cc-?exp$)|(^card-?exp$)|(^cc-?expiration$)|(^card-?expiration$)|(^cc-?ex$)|(^card-?ex$)|(^card-?expire$)|(^card-?expiry$)|(^validite$)|(^expiration$)|(^expiry$)|mm-?yy|mm-?yyyy|yy-?mm|yyyy-?mm|expiration-?date|payment-?card-?expiration|(^payment-?cc-?date$)', - 'cc-exp-month': '(^exp-?month$)|(^cc-?exp-?month$)|(^cc-?month$)|(^card-?month$)|(^cc-?mo$)|(^card-?mo$)|(^exp-?mo$)|(^card-?exp-?mo$)|(^cc-?exp-?mo$)|(^card-?expiration-?month$)|(^expiration-?month$)|(^cc-?mm$)|(^cc-?m$)|(^card-?mm$)|(^card-?m$)|(^card-?exp-?mm$)|(^cc-?exp-?mm$)|(^exp-?mm$)|(^exp-?m$)|(^expire-?month$)|(^expire-?mo$)|(^expiry-?month$)|(^expiry-?mo$)|(^card-?expire-?month$)|(^card-?expire-?mo$)|(^card-?expiry-?month$)|(^card-?expiry-?mo$)|(^mois-?validite$)|(^mois-?expiration$)|(^m-?validite$)|(^m-?expiration$)|(^expiry-?date-?field-?month$)|(^expiration-?date-?month$)|(^expiration-?date-?mm$)|(^exp-?mon$)|(^validity-?mo$)|(^exp-?date-?mo$)|(^cb-?date-?mois$)|(^date-?m$)', - 'cc-exp-year': '(^exp-?year$)|(^cc-?exp-?year$)|(^cc-?year$)|(^card-?year$)|(^cc-?yr$)|(^card-?yr$)|(^exp-?yr$)|(^card-?exp-?yr$)|(^cc-?exp-?yr$)|(^card-?expiration-?year$)|(^expiration-?year$)|(^cc-?yy$)|(^cc-?y$)|(^card-?yy$)|(^card-?y$)|(^card-?exp-?yy$)|(^cc-?exp-?yy$)|(^exp-?yy$)|(^exp-?y$)|(^cc-?yyyy$)|(^card-?yyyy$)|(^card-?exp-?yyyy$)|(^cc-?exp-?yyyy$)|(^expire-?year$)|(^expire-?yr$)|(^expiry-?year$)|(^expiry-?yr$)|(^card-?expire-?year$)|(^card-?expire-?yr$)|(^card-?expiry-?year$)|(^card-?expiry-?yr$)|(^an-?validite$)|(^an-?expiration$)|(^annee-?validite$)|(^annee-?expiration$)|(^expiry-?date-?field-?year$)|(^expiration-?date-?year$)|(^cb-?date-?ann$)|(^expiration-?date-?yy$)|(^expiration-?date-?yyyy$)|(^validity-?year$)|(^exp-?date-?year$)|(^date-?y$)', - 'cc-type': '(^cc-?type$)|(^card-?type$)|(^card-?brand$)|(^cc-?brand$)|(^cb-?type$)' - }, { - email: 'e.?mail|courriel|correo.*electr(o|ó)nico|メールアドレス|Электронной.?Почты|邮件|邮箱|電郵地址|ഇ-മെയില്‍|ഇലക്ട്രോണിക്.?മെയിൽ|ایمیل|پست.*الکترونیک|ईमेल|इलॅक्ट्रॉनिक.?मेल|(\\b|_)eposta(\\b|_)|(?:이메일|전자.?우편|[Ee]-?mail)(.?주소)?', - tel: 'phone|mobile|contact.?number|telefonnummer|telefono|teléfono|telfixe|電話|telefone|telemovel|телефон|मोबाइल|(\\b|_|\\*)telefon(\\b|_|\\*)|电话|മൊബൈല്‍|(?:전화|핸드폰|휴대폰|휴대전화)(?:.?번호)?', - organization: 'company|business|organization|organisation|empresa|societe|société|ragione.?sociale|会社|название.?компании|单位|公司|شرکت|회사|직장', - 'street-address': 'streetaddress|street-address', - 'address-line1': '^address$|address[_-]?line[_-]?(1|one)|address1|addr1|street|(?:shipping|billing)address$|strasse|straße|hausnummer|housenumber|house.?name|direccion|dirección|adresse|indirizzo|^住所$|住所1|Адрес|地址|(\\b|_)adres(?! (başlığı(nız)?|tarifi))(\\b|_)|^주소.?$|주소.?1', - 'address-line2': 'address[_-]?line(2|two)|address2|addr2|street|suite|unit(?!e)|adresszusatz|ergänzende.?angaben|direccion2|colonia|adicional|addresssuppl|complementnom|appartement|indirizzo2|住所2|complemento|addrcomplement|Улица|地址2|주소.?2', - 'address-line3': 'address[_-]?line(3|three)|address3|addr3|street|suite|unit(?!e)|adresszusatz|ergänzende.?angaben|direccion3|colonia|adicional|addresssuppl|complementnom|appartement|indirizzo3|住所3|complemento|addrcomplement|Улица|地址3|주소.?3', - 'address-level2': 'city|town|\\bort\\b|stadt|suburb|ciudad|provincia|localidad|poblacion|ville|commune|localit(a|à)|citt(a|à)|市区町村|cidade|Город|市|分區|شهر|शहर|ग्राम|गाँव|നഗരം|ഗ്രാമം|((\\b|_|\\*)([İii̇]l[cç]e(miz|niz)?)(\\b|_|\\*))|^시[^도·・]|시[·・]?군[·・]?구', - 'address-level1': 'county|region|province|county|principality|都道府県|estado|provincia|область|省|地區|സംസ്ഥാനം|استان|राज्य|((\\b|_|\\*)(eyalet|[şs]ehir|[İii̇]limiz|kent)(\\b|_|\\*))|^시[·・]?도', - 'postal-code': 'zip|postal|post.*code|pcode|pin.?code|postleitzahl|\\bcp\\b|\\bcdp\\b|\\bcap\\b|郵便番号|codigo|codpos|\\bcep\\b|Почтовый.?Индекс|पिन.?कोड|പിന്‍കോഡ്|邮政编码|邮编|郵遞區號|(\\b|_)posta kodu(\\b|_)|우편.?번호', - country: 'country|countries|país|pais|(\\b|_)land(\\b|_)(?!.*(mark.*))|国家|국가|나라|(\\b|_)(ülke|ulce|ulke)(\\b|_)|کشور', - 'cc-name': 'card.?(?:holder|owner)|name.*(\\b)?on(\\b)?.*card|(?:card|cc).?name|cc.?full.?name|karteninhaber|nombre.*tarjeta|nom.*carte|nome.*cart|名前|Имя.*карты|信用卡开户名|开户名|持卡人姓名|持卡人姓名', - name: '^name|full.?name|your.?name|customer.?name|bill.?name|ship.?name|name.*first.*last|firstandlastname|nombre.*y.*apellidos|^nom(?!bre)\\b|お名前|氏名|^nome|نام.*نام.*خانوادگی|姓名|(\\b|_|\\*)ad[ı]? soyad[ı]?(\\b|_|\\*)|성명', - 'given-name': 'first.*name|initials|fname|first$|given.*name|vorname|nombre|forename|prénom|prenom|名|\\bnome|Имя|نام|이름|പേര്|(\\b|_|\\*)(isim|ad|ad(i|ı|iniz|ınız)?)(\\b|_|\\*)|नाम', - 'additional-name': 'middle.*name|mname|middle$|middle.*initial|m\\.i\\.|mi$|\\bmi\\b', - 'family-name': 'last.*name|lname|surname|last$|secondname|family.*name|nachname|apellidos?|famille|^nom(?!bre)|cognome|姓|apelidos|surename|sobrenome|Фамилия|نام.*خانوادگی|उपनाम|മറുപേര്|(\\b|_|\\*)(soyisim|soyad(i|ı|iniz|ınız)?)(\\b|_|\\*)|\\b성(?:[^명]|\\b)', - 'cc-number': '(add)?(?:card|cc|acct).?(?:number|#|no|num|field)|カード番号|Номер.*карты|信用卡号|信用卡号码|信用卡卡號|카드|(numero|número|numéro)(?!.*(document|fono|phone|réservation))', - 'cc-exp-month': 'gueltig|gültig|monat|fecha|date.*exp|scadenza|有効期限|validade|Срок действия карты|月', - 'cc-exp-year': 'ablaufdatum|gueltig|gültig|jahr|fecha|scadenza|有効期限|validade|Срок действия карты|年|有效期', - 'cc-exp': 'expir|exp.*date|^expfield$|gueltig|gültig|fecha|date.*exp|scadenza|有効期限|validade|Срок действия карты' - }] - } + () => { + resolve(true); + } + ); + }); } -}; - -},{}],32:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.logMatching = logMatching; -exports.logUnmatched = logUnmatched; -var _autofillUtils = require("../autofill-utils.js"); -var _matching = require("./matching.js"); -/** - * Logs out matching details when debug flag is active - * @param {HTMLInputElement | HTMLSelectElement} el - * @param {MatchingResult} matchingResult - */ -function logMatching(el, matchingResult) { - if (!(0, _autofillUtils.shouldLog)()) return; - const fieldIdentifier = getInputIdentifier(el); - console.group(fieldIdentifier); - console.log(el); - const { - strategyName, - matchedString, - matchedFrom, - matcherType - } = matchingResult; - const verb = getVerb(matchingResult); - let stringToLog = `${verb} for "${matcherType}" with "${strategyName}"`; - if (matchedString && matchedFrom) { - stringToLog += `\nString: "${matchedString}"\nSource: "${matchedFrom}"`; + async function extensionSpecificGetIncontextSignupDismissedAt() { + return new Promise((resolve) => { + chrome.runtime.sendMessage( + { + messageType: "getIncontextSignupDismissedAt" + }, + (response) => { + resolve(response); + } + ); + }); } - console.log(stringToLog); - console.groupEnd(); -} - -/** - * Helper to form the correct string based on matching result type - * @param {MatchingResult} matchingResult - * @return {string} - */ -function getVerb(matchingResult) { - if (matchingResult.matched) return 'Matched'; - if (matchingResult.proceed === false) return 'Matched forceUnknown'; - if (matchingResult.skip) return 'Skipped'; - return ''; -} - -/** - * Returns a human-friendly name to identify a single input field - * @param {HTMLInputElement | HTMLSelectElement} el - * @returns {string} - */ -function getInputIdentifier(el) { - const label = (0, _matching.getExplicitLabelsText)(el); - const placeholder = el instanceof HTMLInputElement && el.placeholder ? `${el.placeholder}` : ''; - const name = el.name ? `${el.name}` : ''; - const id = el.id ? `#${el.id}` : ''; - return 'Field: ' + (label || placeholder || name || id); -} - -/** - * Logs info when a field was not matched by the algo - * @param el - * @param allStrings - */ -function logUnmatched(el, allStrings) { - if (!(0, _autofillUtils.shouldLog)()) return; - const fieldIdentifier = getInputIdentifier(el); - console.group(fieldIdentifier); - console.log(el); - const stringToLog = 'Field not matched.'; - console.log(stringToLog, allStrings); - console.groupEnd(); -} - -},{"../autofill-utils.js":52,"./matching.js":33}],33:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.checkPlaceholderAndLabels = exports.Matching = void 0; -exports.createMatching = createMatching; -exports.getInputMainType = exports.getExplicitLabelsText = void 0; -exports.getInputSubtype = getInputSubtype; -exports.getInputType = getInputType; -exports.getInputVariant = getInputVariant; -exports.getMainTypeFromType = getMainTypeFromType; -exports.getRelatedText = void 0; -exports.getSubtypeFromType = getSubtypeFromType; -exports.getVariantFromType = getVariantFromType; -exports.removeExcessWhitespace = exports.matchInPlaceholderAndLabels = void 0; -var _constants = require("../constants.js"); -var _labelUtil = require("./label-util.js"); -var _compiledMatchingConfig = require("./matching-config/__generated__/compiled-matching-config.js"); -var _matchingUtils = require("./matching-utils.js"); -var _autofillUtils = require("../autofill-utils.js"); -const { - TEXT_LENGTH_CUTOFF, - ATTR_INPUT_TYPE -} = _constants.constants; - -/** @type {{[K in keyof MatcherLists]?: { minWidth: number }} } */ -const dimensionBounds = { - emailAddress: { - minWidth: 35 + async function extensionSpecificSetIncontextSignupPermanentlyDismissedAtCall(params) { + return new Promise((resolve) => { + chrome.runtime.sendMessage( + { + messageType: "setIncontextSignupPermanentlyDismissedAt", + options: params + }, + () => { + resolve(true); + } + ); + }); } -}; - -/** - * An abstraction around the concept of classifying input fields. - * - * The only state this class keeps is derived from the passed-in MatchingConfiguration. - */ -class Matching { - /** @type {MatchingConfiguration} */ - #config; - - /** @type {CssSelectorConfiguration['selectors']} */ - #cssSelectors; - - /** @type {Record} */ - #ddgMatchers; - - /** - * This acts as an internal cache for the larger vendorRegexes - * @type {VendorRegexConfiguration['rules']} - */ - #vendorRegexRules; - - /** @type {MatcherLists} */ - #matcherLists; - /** @type {Array} */ - #defaultStrategyOrder = ['cssSelector', 'ddgMatcher', 'vendorRegex']; - - /** @type {Record} */ - activeElementStrings = { - nameAttr: '', - labelText: '', - placeholderAttr: '', - relatedText: '', - id: '' + // src/deviceApiCalls/transports/windows.transport.js + var WindowsTransport = class extends DeviceApiTransport { + async send(deviceApiCall, options) { + if (deviceApiCall.id) { + return windowsTransport(deviceApiCall, options).withResponse(deviceApiCall.id); + } + return windowsTransport(deviceApiCall, options); + } }; - - /** - * @param {MatchingConfiguration} config - */ - constructor(config) { - this.#config = config; - this.#vendorRegexRules = this.#config.strategies.vendorRegex.rules; - this.#cssSelectors = this.#config.strategies.cssSelector.selectors; - this.#ddgMatchers = this.#config.strategies.ddgMatcher.matchers; - this.#matcherLists = { - unknown: [], - cc: [], - id: [], - password: [], - username: [], - emailAddress: [] + function windowsTransport(deviceApiCall, options) { + windowsInteropPostMessage({ + Feature: "Autofill", + Name: deviceApiCall.method, + Data: deviceApiCall.params + }); + return { + /** + * Sends a message and returns a Promise that resolves with the response + * @param responseId + * @returns {Promise<*>} + */ + withResponse(responseId) { + return waitForWindowsResponse(responseId, options); + } }; - - /** - * Convert the raw config data into actual references. - * - * For example this takes `email: ["email"]` and creates - * - * `email: [{type: "email", strategies: {cssSelector: "email", ... etc}]` - */ - for (let [listName, matcherNames] of Object.entries(this.#config.matchers.lists)) { - for (let fieldName of matcherNames) { - if (!this.#matcherLists[listName]) { - this.#matcherLists[listName] = []; - } - this.#matcherLists[listName].push(this.#config.matchers.fields[fieldName]); + } + function waitForWindowsResponse(responseId, options) { + return new Promise((resolve, reject) => { + if (options?.signal?.aborted) { + return reject(new DOMException("Aborted", "AbortError")); } - } + let teardown; + const handler = (event) => { + if (!event.data) { + console.warn("data absent from message"); + return; + } + if (event.data.type === responseId) { + teardown(); + resolve(event.data); + } + }; + const abortHandler = () => { + teardown(); + reject(new DOMException("Aborted", "AbortError")); + }; + windowsInteropAddEventListener("message", handler); + options?.signal?.addEventListener("abort", abortHandler); + teardown = () => { + windowsInteropRemoveEventListener("message", handler); + options?.signal?.removeEventListener("abort", abortHandler); + }; + }); } - /** - * @param {HTMLInputElement|HTMLSelectElement} input - * @param {HTMLElement} formEl - */ - setActiveElementStrings(input, formEl) { - this.activeElementStrings = this.getElementStrings(input, formEl); + // src/deviceApiCalls/transports/transports.js + function createTransport(globalConfig) { + if (typeof globalConfig.userPreferences?.platform?.name === "string") { + switch (globalConfig.userPreferences?.platform?.name) { + case "ios": + case "macos": + return new AppleTransport(globalConfig); + case "android": + return new AndroidTransport(globalConfig); + default: + throw new Error("selectSender unimplemented!"); + } + } + if (globalConfig.isWindows) { + return new WindowsTransport(); + } + if (globalConfig.isDDGApp) { + if (globalConfig.isAndroid) { + return new AndroidTransport(globalConfig); + } + throw new Error("unreachable, createTransport"); + } + return new ExtensionTransport(globalConfig); + } + + // src/DeviceInterface/initFormSubmissionsApi.js + function initFormSubmissionsApi(forms, matching) { + window.addEventListener("submit", (e) => { + return forms.get(e.target)?.submitHandler("global submit event"); + }, true); + window.addEventListener("keydown", (e) => { + if (e.key === "Enter") { + const focusedForm = [...forms.values()].find((form) => form.hasFocus(e)); + focusedForm?.submitHandler("global keydown + Enter"); + } + }, true); + window.addEventListener("pointerdown", (event) => { + const realTarget = pierceShadowTree(event); + const formsArray = [...forms.values()]; + const matchingForm = formsArray.find( + (form) => { + const btns = [...form.submitButtons]; + if (btns.includes(realTarget)) + return true; + if (btns.find((btn) => btn.contains(realTarget))) + return true; + } + ); + matchingForm?.submitHandler("global pointerdown event + matching form"); + if (!matchingForm) { + const selector = matching.cssSelector("submitButtonSelector") + ', a[href="#"], a[href^=javascript], *[onclick], [class*=button i]'; + const button = ( + /** @type HTMLElement */ + realTarget?.closest(selector) + ); + if (!button) + return; + const buttonIsAFalsePositive = formsArray.some((form) => button?.contains(form.form)); + if (buttonIsAFalsePositive) + return; + const text = getTextShallow(button) || extractElementStrings(button).join(" "); + const hasRelevantText = safeRegexTest(matching.getDDGMatcherRegex("submitButtonRegex"), text); + if (hasRelevantText && text.length < 25) { + const filledForm = formsArray.find((form) => form.hasValues()); + if (filledForm && buttonMatchesFormType( + /** @type HTMLElement */ + button, + filledForm + )) { + filledForm?.submitHandler("global pointerdown event + filled form"); + } + } + if ( + /** @type HTMLElement */ + realTarget?.closest("#passwordNext button, #identifierNext button") + ) { + const filledForm = formsArray.find((form) => form.hasValues()); + filledForm?.submitHandler("global pointerdown event + google escape hatch"); + } + } + }, true); + const observer = new PerformanceObserver((list) => { + const formsArray = [...forms.values()]; + const entries = list.getEntries().filter( + (entry) => ( + // @ts-ignore why does TS not know about `entry.initiatorType`? + ["fetch", "xmlhttprequest"].includes(entry.initiatorType) && safeRegexTest(/login|sign-in|signin/, entry.name) + ) + ); + if (!entries.length) + return; + const filledForm = formsArray.find((form) => form.hasValues()); + const focusedForm = formsArray.find((form) => form.hasFocus()); + if (focusedForm) + return; + filledForm?.submitHandler("performance observer"); + }); + observer.observe({ entryTypes: ["resource"] }); } - /** - * Try to access a 'vendor regex' by name - * @param {string} regexName - * @returns {RegExp | undefined} - */ - vendorRegex(regexName) { - const match = this.#vendorRegexRules[regexName]; - if (!match) { - console.warn('Vendor Regex not found for', regexName); - return undefined; + // src/EmailProtection.js + var _previous2; + var EmailProtection = class { + /** @param {import("./DeviceInterface/InterfacePrototype").default} device */ + constructor(device) { + /** @type {string|null} */ + __privateAdd(this, _previous2, null); + this.device = device; } - return match; - } - - /** - * Strategies can have different lookup names. This returns the correct one - * @param {MatcherTypeNames} matcherName - * @param {StrategyNames} vendorRegex - * @returns {MatcherTypeNames} - */ - getStrategyLookupByType(matcherName, vendorRegex) { - return this.#config.matchers.fields[matcherName]?.strategies[vendorRegex]; - } - - /** - * Try to access a 'css selector' by name from configuration - * @param {RequiredCssSelectors | string} selectorName - * @returns {string}; - */ - cssSelector(selectorName) { - const match = this.#cssSelectors[selectorName]; - if (!match) { - console.warn('CSS selector not found for %s, using a default value', selectorName); - return ''; + /** @returns {string|null} */ + get lastGenerated() { + return __privateGet(this, _previous2); } - return match; - } - - /** - * Try to access a 'ddg matcher' by name from configuration - * @param {MatcherTypeNames | string} matcherName - * @returns {DDGMatcher | undefined} - */ - ddgMatcher(matcherName) { - const match = this.#ddgMatchers[matcherName]; - if (!match) { - console.warn('DDG matcher not found for', matcherName); - return undefined; + /** + * Store the last received email address + * @param {string} emailAddress + */ + storeReceived(emailAddress) { + __privateSet(this, _previous2, emailAddress); + return emailAddress; } - return match; - } + }; + _previous2 = new WeakMap(); - /** - * Returns the RegExp for the given matcherName, with proper flags - * @param {AllDDGMatcherNames} matcherName - * @returns {RegExp|undefined} - */ - getDDGMatcherRegex(matcherName) { - const matcher = this.ddgMatcher(matcherName); - if (!matcher || !matcher.match) { - console.warn('DDG matcher has unexpected format'); - return undefined; + // src/DeviceInterface/InterfacePrototype.js + var _addresses, _data6; + var _InterfacePrototype = class { + /** + * @param {GlobalConfig} config + * @param {import("../../packages/device-api").DeviceApi} deviceApi + * @param {Settings} settings + */ + constructor(config, deviceApi, settings) { + __publicField(this, "attempts", 0); + /** @type {import("../Form/Form").Form | null} */ + __publicField(this, "activeForm", null); + /** @type {import("../UI/HTMLTooltip.js").default | null} */ + __publicField(this, "currentTooltip", null); + /** @type {number} */ + __publicField(this, "initialSetupDelayMs", 0); + __publicField(this, "autopromptFired", false); + /** @type {PasswordGenerator} */ + __publicField(this, "passwordGenerator", new PasswordGenerator()); + __publicField(this, "emailProtection", new EmailProtection(this)); + /** @type {import("../InContextSignup.js").InContextSignup | null} */ + __publicField(this, "inContextSignup", null); + /** @type {import("../ThirdPartyProvider.js").ThirdPartyProvider | null} */ + __publicField(this, "thirdPartyProvider", null); + /** @type {{privateAddress: string, personalAddress: string}} */ + __privateAdd(this, _addresses, { + privateAddress: "", + personalAddress: "" + }); + /** @type {GlobalConfig} */ + __publicField(this, "globalConfig"); + /** @type {import('../Scanner').Scanner} */ + __publicField(this, "scanner"); + /** @type {import("../UI/controllers/UIController.js").UIController | null} */ + __publicField(this, "uiController"); + /** @type {import("../../packages/device-api").DeviceApi} */ + __publicField(this, "deviceApi"); + /** @type {boolean} */ + __publicField(this, "isInitializationStarted"); + /** @type {((reason, ...rest) => void) | null} */ + __publicField(this, "_scannerCleanup", null); + /** @type { PMData } */ + __privateAdd(this, _data6, { + credentials: [], + creditCards: [], + identities: [], + topContextData: void 0 + }); + this.globalConfig = config; + this.deviceApi = deviceApi; + this.settings = settings; + this.uiController = null; + this.scanner = createScanner(this, { + initialDelay: this.initialSetupDelayMs + }); + this.isInitializationStarted = false; } - return matcher?.match; - } - - /** - * Try to access a list of matchers by name - these are the ones collected in the constructor - * @param {keyof MatcherLists} listName - * @return {Matcher[]} - */ - matcherList(listName) { - const matcherList = this.#matcherLists[listName]; - if (!matcherList) { - console.warn('MatcherList not found for ', listName); - return []; + /** + * Implementors should override this with a UI controller that suits + * their platform. + * + * @returns {import("../UI/controllers/UIController.js").UIController} + */ + createUIController() { + return new NativeUIController(); } - return matcherList; - } - - /** - * Convert a list of matchers into a single CSS selector. - * - * This will consider all matchers in the list and if it - * contains a CSS Selector it will be added to the final output - * - * @param {keyof MatcherLists} listName - * @returns {string | undefined} - */ - joinCssSelectors(listName) { - const matcherList = this.matcherList(listName); - if (!matcherList) { - console.warn('Matcher list not found for', listName); - return undefined; + /** + * @param {string} reason + */ + removeAutofillUIFromPage(reason) { + this.uiController?.destroy(); + this._scannerCleanup?.(reason); + } + get hasLocalAddresses() { + return !!(__privateGet(this, _addresses)?.privateAddress && __privateGet(this, _addresses)?.personalAddress); + } + getLocalAddresses() { + return __privateGet(this, _addresses); + } + storeLocalAddresses(addresses) { + __privateSet(this, _addresses, addresses); + const identities = this.getLocalIdentities(); + const privateAddressIdentity = identities.find(({ id }) => id === "privateAddress"); + if (privateAddressIdentity) { + privateAddressIdentity.emailAddress = formatDuckAddress(addresses.privateAddress); + } else { + __privateGet(this, _data6).identities = this.addDuckAddressesToIdentities(identities); + } + } + /** + * @returns {import('../Form/matching').SupportedTypes} + */ + getCurrentInputType() { + throw new Error("Not implemented"); + } + addDuckAddressesToIdentities(identities) { + if (!this.hasLocalAddresses) + return identities; + const newIdentities = []; + let { privateAddress, personalAddress } = this.getLocalAddresses(); + privateAddress = formatDuckAddress(privateAddress); + personalAddress = formatDuckAddress(personalAddress); + const duckEmailsInIdentities = identities.reduce( + (duckEmails, { emailAddress: email }) => email?.includes(ADDRESS_DOMAIN) ? duckEmails.concat(email) : duckEmails, + [] + ); + if (!duckEmailsInIdentities.includes(personalAddress)) { + newIdentities.push({ + id: "personalAddress", + emailAddress: personalAddress, + title: "Block email trackers" + }); + } + newIdentities.push({ + id: "privateAddress", + emailAddress: privateAddress, + title: "Block email trackers & hide address" + }); + return [...identities, ...newIdentities]; } - /** - * @type {string[]} + * Stores init data coming from the tooltipHandler + * @param { InboundPMData } data */ - const selectors = []; - for (let matcher of matcherList) { - if (matcher.strategies.cssSelector) { - const css = this.cssSelector(matcher.strategies.cssSelector); - if (css) { - selectors.push(css); + storeLocalData(data) { + this.storeLocalCredentials(data.credentials); + data.creditCards.forEach((cc) => delete cc.cardNumber && delete cc.cardSecurityCode); + const updatedIdentities = data.identities.map((identity) => ({ + ...identity, + fullName: formatFullName(identity) + })); + __privateGet(this, _data6).identities = this.addDuckAddressesToIdentities(updatedIdentities); + __privateGet(this, _data6).creditCards = data.creditCards; + if (data.serializedInputContext) { + try { + __privateGet(this, _data6).topContextData = JSON.parse(data.serializedInputContext); + } catch (e) { + console.error(e); + this.removeTooltip(); } } } - return selectors.join(', '); - } - - /** - * Returns true if the field is visible and large enough - * @param {keyof MatcherLists} matchedType - * @param {HTMLInputElement} input - * @returns {boolean} - */ - isInputLargeEnough(matchedType, input) { - const expectedDimensionBounds = dimensionBounds[matchedType]; - if (!expectedDimensionBounds) return true; - const width = input.offsetWidth; - const height = input.offsetHeight; - - // Ignore hidden elements as we can't determine their dimensions - const isHidden = height === 0 && width === 0; - if (isHidden) return true; - return width >= expectedDimensionBounds.minWidth; - } - - /** - * Tries to infer the input type for an input - * - * @param {HTMLInputElement|HTMLSelectElement} input - * @param {HTMLElement} formEl - * @param {SetInputTypeOpts} [opts] - * @returns {SupportedTypes} - */ - inferInputType(input, formEl) { - let opts = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - const presetType = getInputType(input); - if (presetType !== 'unknown') { - return presetType; - } - this.setActiveElementStrings(input, formEl); - if (this.subtypeFromMatchers('unknown', input)) return 'unknown'; - - // For CC forms we run aggressive matches, so we want to make sure we only - // run them on actual CC forms to avoid false positives and expensive loops - if (opts.isCCForm) { - const subtype = this.subtypeFromMatchers('cc', input); - if (subtype && isValidCreditCardSubtype(subtype)) { - return `creditCards.${subtype}`; - } - } - if (input instanceof HTMLInputElement) { - if (this.subtypeFromMatchers('password', input)) { - // Any other input type is likely a false match - // Arguably "text" should be as well, but it can be used for password reveal fields - if (['password', 'text'].includes(input.type) && input.name !== 'email' && - // pcsretirement.com, improper use of the for attribute - input.name !== 'Username') { - return this.inferPasswordVariant(input, opts); - } - } - if (this.subtypeFromMatchers('emailAddress', input)) { - if (!this.isInputLargeEnough('emailAddress', input)) { - if ((0, _autofillUtils.shouldLog)()) { - console.log('Field matched for Email Address, but discarded because too small when scanned'); - } - return 'unknown'; - } - if (opts.isLogin || opts.isHybrid) { - // TODO: Bring this support back in the future - // https://app.asana.com/0/1198964220583541/1204686960531034/f - // Show identities when supported and there are no credentials - // if (opts.supportsIdentitiesAutofill && !opts.hasCredentials) { - // return 'identities.emailAddress' - // } - - return 'credentials.username'; - } - - // TODO: Temporary hack to support Google signin in different languages - // https://app.asana.com/0/1198964220583541/1201650539303898/f - if (window.location.href.includes('https://accounts.google.com/v3/signin/identifier') && input.matches('[type=email][autocomplete=username]')) { - return 'credentials.username'; - } - return 'identities.emailAddress'; - } - if (this.subtypeFromMatchers('username', input)) { - return 'credentials.username'; - } + /** + * Stores credentials locally + * @param {CredentialsObject[]} credentials + */ + storeLocalCredentials(credentials) { + credentials.forEach((cred) => delete cred.password); + __privateGet(this, _data6).credentials = credentials; } - const idSubtype = this.subtypeFromMatchers('id', input); - if (idSubtype && isValidIdentitiesSubtype(idSubtype)) { - return `identities.${idSubtype}`; + getTopContextData() { + return __privateGet(this, _data6).topContextData; } - (0, _matchingUtils.logUnmatched)(input, this.activeElementStrings); - return 'unknown'; - } - - /** - * @typedef {{ - * isLogin?: boolean, - * isHybrid?: boolean, - * isCCForm?: boolean, - * hasCredentials?: boolean, - * supportsIdentitiesAutofill?: boolean - * }} SetInputTypeOpts - */ - - /** - * Sets the input type as a data attribute to the element and returns it - * @param {HTMLInputElement} input - * @param {HTMLElement} formEl - * @param {SetInputTypeOpts} [opts] - * @returns {SupportedSubTypes | string} - */ - setInputType(input, formEl) { - let opts = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - const type = this.inferInputType(input, formEl, opts); - input.setAttribute(ATTR_INPUT_TYPE, type); - return type; - } - - /** - * Tries to infer input subtype, with checks in decreasing order of reliability - * @param {keyof MatcherLists} listName - * @param {HTMLInputElement|HTMLSelectElement} el - * @return {MatcherTypeNames|undefined} - */ - subtypeFromMatchers(listName, el) { - const matchers = this.matcherList(listName); - /** - * Loop through each strategy in order + * @deprecated use `availableInputTypes.credentials` directly instead + * @returns {boolean} */ - for (let strategyName of this.#defaultStrategyOrder) { - let result; - /** - * Now loop through each matcher in the list. - */ - for (let matcher of matchers) { - /** - * for each `strategyName` (such as cssSelector), check - * if the current matcher implements it. - */ - const lookup = matcher.strategies[strategyName]; - /** - * Sometimes a matcher may not implement the current strategy, - * so we skip it - */ - if (!lookup) continue; - - /** - * Now perform the matching - */ - if (strategyName === 'cssSelector') { - result = this.execCssSelector(lookup, el); - } - if (strategyName === 'ddgMatcher') { - result = this.execDDGMatcher(lookup); - } - if (strategyName === 'vendorRegex') { - result = this.execVendorRegex(lookup); - } - - /** - * If there's a match, return the matcher type. - * - * So, for example if 'username' had a `cssSelector` implemented, and - * it matched the current element, then we'd return 'username' - */ - if (result?.matched) { - (0, _matchingUtils.logMatching)(el, result); - return matcher.type; - } - - /** - * If a matcher wants to prevent all future matching on this element, - * it would return { matched: false, proceed: false } - */ - if (!result?.matched && result?.proceed === false) { - (0, _matchingUtils.logMatching)(el, result); - // If we get here, do not allow subsequent strategies to continue - return undefined; - } - } - if (result?.skip) { - (0, _matchingUtils.logMatching)(el, result); - break; - } - } - return undefined; - } - - /** - * Returns the password type string including the variant - * @param {HTMLInputElement} input - * @param opts - * @returns {'credentials.password.new'|'credentials.password.current'} - */ - inferPasswordVariant(input, opts) { - // Check attributes first - // This is done mainly to ensure coverage for all languages, since attributes are usually in English - const attrsToCheck = [input.autocomplete, input.name, input.id]; - if (opts.isSignup && attrsToCheck.some(str => (0, _autofillUtils.safeRegexTest)(/new.?password|password.?new/i, str))) { - return 'credentials.password.new'; + get hasLocalCredentials() { + return __privateGet(this, _data6).credentials.length > 0; } - if ((opts.isLogin || opts.isHybrid) && attrsToCheck.some(str => (0, _autofillUtils.safeRegexTest)(/(current|old|previous).?password|password.?(current|old|previous)/i, str))) { - return 'credentials.password.current'; + getLocalCredentials() { + return __privateGet(this, _data6).credentials.map((cred) => { + const { password, ...rest } = cred; + return rest; + }); } - - // Check strings using the usual DDG matcher - const newPasswordMatch = this.execDDGMatcher('newPassword'); - if (newPasswordMatch.matched) { - return 'credentials.password.new'; + /** + * @deprecated use `availableInputTypes.identities` directly instead + * @returns {boolean} + */ + get hasLocalIdentities() { + return __privateGet(this, _data6).identities.length > 0; } - const currentPasswordMatch = this.execDDGMatcher('currentPassword'); - if (currentPasswordMatch.matched) { - return 'credentials.password.current'; + getLocalIdentities() { + return __privateGet(this, _data6).identities; } - - // Otherwise, rely on the passed form type - if (opts.isLogin || opts.isHybrid) { - return 'credentials.password.current'; + /** + * @deprecated use `availableInputTypes.creditCards` directly instead + * @returns {boolean} + */ + get hasLocalCreditCards() { + return __privateGet(this, _data6).creditCards.length > 0; } - return 'credentials.password.new'; - } - - /** - * CSS selector matching just leverages the `.matches` method on elements - * - * @param {MatcherTypeNames} lookup - * @param {HTMLInputElement|HTMLSelectElement} el - * @returns {MatchingResult} - */ - execCssSelector(lookup, el) { - const selector = this.cssSelector(lookup); - return { - matched: el.matches(selector), - strategyName: 'cssSelector', - matcherType: lookup - }; - } - - /** - * A DDG Matcher can have a `match` regex along with a `not` regex. This is done - * to allow it to be driven by configuration as it avoids needing to invoke custom functions. - * - * todo: maxDigits was added as an edge-case when converting this over to be declarative, but I'm - * unsure if it's actually needed. It's not urgent, but we should consider removing it if that's the case - * - * @param {MatcherTypeNames} lookup - * @returns {MatchingResult} - */ - execDDGMatcher(lookup) { - /** @type {MatchingResult} */ - const defaultResult = { - matched: false, - strategyName: 'ddgMatcher', - matcherType: lookup - }; - const ddgMatcher = this.ddgMatcher(lookup); - if (!ddgMatcher || !ddgMatcher.match) { - return defaultResult; + /** @return {CreditCardObject[]} */ + getLocalCreditCards() { + return __privateGet(this, _data6).creditCards; } - let matchRexExp = this.getDDGMatcherRegex(lookup); - if (!matchRexExp) { - return defaultResult; + async startInit() { + if (this.isInitializationStarted) + return; + this.alreadyInitialized = true; + await this.settings.refresh(); + this.addDeviceListeners(); + await this.setupAutofill(); + this.uiController = this.createUIController(); + if (!this.settings.enabled) { + return; + } + await this.setupSettingsPage(); + await this.postInit(); + if (this.settings.featureToggles.credentials_saving) { + initFormSubmissionsApi(this.scanner.forms, this.scanner.matching); + } } - let requiredScore = ['match', 'forceUnknown', 'maxDigits'].filter(ddgMatcherProp => ddgMatcherProp in ddgMatcher).length; - - /** @type {MatchableStrings[]} */ - const matchableStrings = ddgMatcher.matchableStrings || ['labelText', 'placeholderAttr', 'relatedText']; - for (let stringName of matchableStrings) { - let elementString = this.activeElementStrings[stringName]; - if (!elementString) continue; - - // Scoring to ensure all DDG tests are valid - let score = 0; - - /** @type {MatchingResult} */ - const result = { - ...defaultResult, - matchedString: elementString, - matchedFrom: stringName - }; - - // If a negated regex was provided, ensure it does not match - // If it DOES match - then we need to prevent any future strategies from continuing - if (ddgMatcher.forceUnknown) { - let notRegex = ddgMatcher.forceUnknown; - if (!notRegex) { - return { - ...result, - matched: false - }; + async init() { + const isEnabledInitiallyViaConfig = autofillEnabled(this.globalConfig); + if (!isEnabledInitiallyViaConfig) { + return; + } + const handler = async () => { + if (document.readyState === "complete") { + window.removeEventListener("load", handler); + document.removeEventListener("readystatechange", handler); + await this.startInit(); } - if ((0, _autofillUtils.safeRegexTest)(notRegex, elementString)) { - return { - ...result, - matched: false, - proceed: false - }; - } else { - // All good here, increment the score - score++; + }; + if (document.readyState === "complete") { + await this.startInit(); + } else { + window.addEventListener("load", handler); + document.addEventListener("readystatechange", handler); + } + } + postInit() { + const cleanup = this.scanner.init(); + this.addLogoutListener(() => { + cleanup("Logged out"); + if (this.globalConfig.isDDGDomain) { + notifyWebApp({ deviceSignedIn: { value: false } }); } + }); + } + /** + * @deprecated This was a port from the macOS implementation so the API may not be suitable for all + * @returns {Promise} + */ + async getSelectedCredentials() { + throw new Error("`getSelectedCredentials` not implemented"); + } + isTestMode() { + return this.globalConfig.isDDGTestMode; + } + /** + * This indicates an item was selected on Desktop, and we should try to autofill + * + * Note: When we're in a top-frame scenario, like on like macOS & Windows in the webview, + * this method gets overridden {@see WindowsOverlayDeviceInterface} {@see AppleOverlayDeviceInterface} + * + * @param {IdentityObject|CreditCardObject|CredentialsObject|{email:string, id: string}} data + * @param {string} type + */ + async selectedDetail(data, type) { + const form = this.activeForm; + if (!form) { + return; } - if (ddgMatcher.skip) { - let skipRegex = ddgMatcher.skip; - if (!skipRegex) { - return { - ...result, - matched: false + if (type === "email" && "email" in data) { + form.autofillEmail(data.email); + } else { + form.autofillData(data, type); + } + const isPrivateAddress = data.id === "privateAddress"; + if (isPrivateAddress) { + this.refreshAlias(); + if ("emailAddress" in data && data.emailAddress) { + this.emailProtection.storeReceived(data.emailAddress); + const formValues = { + credentials: { + username: data.emailAddress, + autogenerated: true + } }; + this.storeFormData(formValues, "emailProtection"); } - if ((0, _autofillUtils.safeRegexTest)(skipRegex, elementString)) { - return { - ...result, - matched: false, - skip: true - }; + } + await this.removeTooltip(); + } + /** + * Before the DataWebTooltip opens, we collect the data based on the config.type + * @param {InputTypeConfigs} config + * @param {import('../Form/matching').SupportedTypes} inputType + * @param {TopContextData} [data] + * @returns {(CredentialsObject|CreditCardObject|IdentityObject)[]} + */ + dataForAutofill(config, inputType, data) { + const subtype = getSubtypeFromType(inputType); + if (config.type === "identities") { + return this.getLocalIdentities().filter((identity) => !!identity[subtype]); + } + if (config.type === "creditCards") { + return this.getLocalCreditCards(); + } + if (config.type === "credentials") { + if (data) { + if (Array.isArray(data.credentials) && data.credentials.length > 0) { + return data.credentials; + } else { + return this.getLocalCredentials().filter((cred) => !!cred[subtype] || subtype === "password" || cred.id === PROVIDER_LOCKED); + } } } - - // if the `match` regex fails, moves onto the next string - if (!(0, _autofillUtils.safeRegexTest)(matchRexExp, elementString)) { - continue; + return []; + } + /** + * @param {object} params + * @param {import("../Form/Form").Form} params.form + * @param {HTMLInputElement} params.input + * @param {{ x: number; y: number; } | null} params.click + * @param {import('../deviceApiCalls/__generated__/validators-ts').GetAutofillDataRequest['trigger']} params.trigger + * @param {import('../UI/controllers/UIController.js').AttachArgs["triggerMetaData"]} params.triggerMetaData + */ + attachTooltip(params) { + const { + form, + input, + click, + trigger + } = params; + if (document.visibilityState !== "visible" && trigger !== "postSignup") + return; + if (trigger === "autoprompt" && !this.globalConfig.isMobileApp) + return; + if (trigger === "autoprompt" && this.autopromptFired) + return; + form.activeInput = input; + this.activeForm = form; + const inputType = getInputType(input); + const getPosition = () => { + const alignLeft = this.globalConfig.isApp || this.globalConfig.isWindows; + return alignLeft ? input.getBoundingClientRect() : getDaxBoundingBox(input); + }; + if (this.globalConfig.isMobileApp && inputType === "identities.emailAddress") { + this.getAlias().then((alias) => { + if (alias) { + form.autofillEmail(alias); + this.emailProtection.storeReceived(alias); + } else { + form.activeInput?.focus(); + } + this.updateForStateChange(); + this.onFinishedAutofill(); + }); + return; } - - // Otherwise, increment the score - score++; - - // If a 'maxDigits' rule was provided, validate it - if (ddgMatcher.maxDigits) { - const digitLength = elementString.replace(/[^0-9]/g, '').length; - if (digitLength > ddgMatcher.maxDigits) { - return { - ...result, - matched: false - }; + const topContextData = { + inputType + }; + const processedTopContext = this.preAttachTooltip(topContextData, input, form); + this.uiController?.attach({ + input, + form, + click, + getPosition, + topContextData: processedTopContext, + device: this, + trigger, + triggerMetaData: params.triggerMetaData + }); + if (trigger === "autoprompt") { + this.autopromptFired = true; + } + } + /** + * When an item was selected, we then call back to the device + * to fetch the full suite of data needed to complete the autofill + * + * @param {import('../Form/matching').SupportedTypes} inputType + * @param {(CreditCardObject|IdentityObject|CredentialsObject)[]} items + * @param {CreditCardObject['id']|IdentityObject['id']|CredentialsObject['id']} id + */ + onSelect(inputType, items, id) { + id = String(id); + const mainType = getMainTypeFromType(inputType); + const subtype = getSubtypeFromType(inputType); + if (id === PROVIDER_LOCKED) { + return this.thirdPartyProvider?.askToUnlockProvider(); + } + const matchingData = items.find((item) => String(item.id) === id); + if (!matchingData) + throw new Error("unreachable (fatal)"); + const dataPromise = (() => { + switch (mainType) { + case "creditCards": + return this.getAutofillCreditCard(id); + case "identities": + return this.getAutofillIdentity(id); + case "credentials": { + if (AUTOGENERATED_KEY in matchingData) { + const autogeneratedPayload = { ...matchingData, username: "" }; + return Promise.resolve({ success: autogeneratedPayload }); + } + return this.getAutofillCredentials(id); + } + default: + throw new Error("unreachable!"); + } + })(); + dataPromise.then((response) => { + if (response) { + const data = response.success || response; + if (mainType === "identities") { + this.firePixel({ pixelName: "autofill_identity", params: { fieldType: subtype } }); + switch (id) { + case "personalAddress": + this.firePixel({ pixelName: "autofill_personal_address" }); + break; + case "privateAddress": + this.firePixel({ pixelName: "autofill_private_address" }); + break; + default: + const checks = [ + subtype === "emailAddress", + this.hasLocalAddresses, + data?.emailAddress === formatDuckAddress(__privateGet(this, _addresses).personalAddress) + ]; + if (checks.every(Boolean)) { + this.firePixel({ pixelName: "autofill_personal_address" }); + } + break; + } + } + return this.selectedDetail(data, mainType); } else { - score++; + return Promise.reject(new Error("none-success response")); } - } - if (score === requiredScore) { - return { - ...result, - matched: true - }; - } + }).catch((e) => { + console.error(e); + return this.removeTooltip(); + }); } - return defaultResult; - } - - /** - * If we get here, a firefox/vendor regex was given and we can execute it on the element - * strings - * @param {MatcherTypeNames} lookup - * @return {MatchingResult} - */ - execVendorRegex(lookup) { - /** @type {MatchingResult} */ - const defaultResult = { - matched: false, - strategyName: 'vendorRegex', - matcherType: lookup - }; - const regex = this.vendorRegex(lookup); - if (!regex) { - return defaultResult; + isTooltipActive() { + return this.uiController?.isActive?.() ?? false; } - /** @type {MatchableStrings[]} */ - const stringsToMatch = ['placeholderAttr', 'nameAttr', 'labelText', 'id', 'relatedText']; - for (let stringName of stringsToMatch) { - let elementString = this.activeElementStrings[stringName]; - if (!elementString) continue; - if ((0, _autofillUtils.safeRegexTest)(regex, elementString)) { - return { - ...defaultResult, - matched: true, - matchedString: elementString, - matchedFrom: stringName - }; - } + removeTooltip() { + return this.uiController?.removeTooltip?.("interface"); } - return defaultResult; - } - - /** - * Yield strings in the order in which they should be checked against. - * - * Note: some strategies may not want to accept all strings, which is - * where `matchableStrings` helps. It defaults to when you see below but can - * be overridden. - * - * For example, `nameAttr` is first, since this has the highest chance of matching - * and then the rest are in decreasing order of value vs cost - * - * A generator function is used here to prevent any potentially expensive - * lookups occurring if they are rare. For example if 90% of all matching never needs - * to look at the output from `relatedText`, then the cost of computing it will be avoided. - * - * @param {HTMLInputElement|HTMLSelectElement} el - * @param {HTMLElement} form - * @returns {Record} - */ - _elementStringCache = new WeakMap(); - getElementStrings(el, form) { - if (this._elementStringCache.has(el)) { - return this._elementStringCache.get(el); + onFinishedAutofill() { + this.activeForm?.activeInput?.dispatchEvent(new Event("mouseleave")); } - const explicitLabelsText = getExplicitLabelsText(el); - - /** @type {Record} */ - const next = { - nameAttr: el.name, - labelText: explicitLabelsText, - placeholderAttr: el.placeholder || '', - id: el.id, - relatedText: explicitLabelsText ? '' : getRelatedText(el, form, this.cssSelector('formInputsSelector')) - }; - this._elementStringCache.set(el, next); - return next; - } - clear() { - this._elementStringCache = new WeakMap(); - } - - /** - * Only used for testing - * @param {HTMLInputElement|HTMLSelectElement} input - * @param {HTMLElement} form - * @returns {Matching} - */ - forInput(input, form) { - this.setActiveElementStrings(input, form); - return this; - } - - /** - * @type {MatchingConfiguration} - */ - static emptyConfig = { - matchers: { - lists: {}, - fields: {} - }, - strategies: { - 'vendorRegex': { - rules: {}, - ruleSets: [] - }, - 'ddgMatcher': { - matchers: {} - }, - 'cssSelector': { - selectors: {} - } + async updateForStateChange() { + this.activeForm?.removeAllDecorations(); + await this.refreshData(); + this.activeForm?.recategorizeAllInputs(); } - }; -} - -/** - * @returns {SupportedTypes} - */ -exports.Matching = Matching; -function getInputType(input) { - const attr = input?.getAttribute(ATTR_INPUT_TYPE); - if (isValidSupportedType(attr)) { - return attr; - } - return 'unknown'; -} - -/** - * Retrieves the main type - * @param {SupportedTypes | string} type - * @returns {SupportedMainTypes} - */ -function getMainTypeFromType(type) { - const mainType = type.split('.')[0]; - switch (mainType) { - case 'credentials': - case 'creditCards': - case 'identities': - return mainType; - } - return 'unknown'; -} - -/** - * Retrieves the input main type - * @param {HTMLInputElement} input - * @returns {SupportedMainTypes} - */ -const getInputMainType = input => getMainTypeFromType(getInputType(input)); - -/** @typedef {supportedIdentitiesSubtypes[number]} SupportedIdentitiesSubTypes */ -exports.getInputMainType = getInputMainType; -const supportedIdentitiesSubtypes = /** @type {const} */['emailAddress', 'firstName', 'middleName', 'lastName', 'fullName', 'phone', 'addressStreet', 'addressStreet2', 'addressCity', 'addressProvince', 'addressPostalCode', 'addressCountryCode', 'birthdayDay', 'birthdayMonth', 'birthdayYear']; - -/** - * @param {SupportedTypes | any} supportedType - * @returns {supportedType is SupportedIdentitiesSubTypes} - */ -function isValidIdentitiesSubtype(supportedType) { - return supportedIdentitiesSubtypes.includes(supportedType); -} - -/** @typedef {supportedCreditCardSubtypes[number]} SupportedCreditCardSubTypes */ -const supportedCreditCardSubtypes = /** @type {const} */['cardName', 'cardNumber', 'cardSecurityCode', 'expirationMonth', 'expirationYear', 'expiration']; - -/** - * @param {SupportedTypes | any} supportedType - * @returns {supportedType is SupportedCreditCardSubTypes} - */ -function isValidCreditCardSubtype(supportedType) { - return supportedCreditCardSubtypes.includes(supportedType); -} - -/** @typedef {supportedCredentialsSubtypes[number]} SupportedCredentialsSubTypes */ -const supportedCredentialsSubtypes = /** @type {const} */['password', 'password.new', 'password.current', 'username']; - -/** @typedef {supportedVariants[number]} SupportedVariants */ -const supportedVariants = /** @type {const} */['new', 'current']; - -/** - * @param {SupportedTypes | any} supportedType - * @returns {supportedType is SupportedCredentialsSubTypes} - */ -function isValidCredentialsSubtype(supportedType) { - return supportedCredentialsSubtypes.includes(supportedType); -} - -/** @typedef {SupportedIdentitiesSubTypes | SupportedCreditCardSubTypes | SupportedCredentialsSubTypes} SupportedSubTypes */ - -/** @typedef {`identities.${SupportedIdentitiesSubTypes}` | `creditCards.${SupportedCreditCardSubTypes}` | `credentials.${SupportedCredentialsSubTypes}` | 'unknown'} SupportedTypes */ -const supportedTypes = [...supportedIdentitiesSubtypes.map(type => `identities.${type}`), ...supportedCreditCardSubtypes.map(type => `creditCards.${type}`), ...supportedCredentialsSubtypes.map(type => `credentials.${type}`)]; - -/** - * Retrieves the subtype - * @param {SupportedTypes | string} type - * @returns {SupportedSubTypes | 'unknown'} - */ -function getSubtypeFromType(type) { - const subType = type?.split('.')[1]; - const validType = isValidSubtype(subType); - return validType ? subType : 'unknown'; -} - -/** - * Retrieves the variant - * @param {SupportedTypes | string} type - * @returns {SupportedVariants | ''} - */ -function getVariantFromType(type) { - const variant = type?.split('.')[2]; - const validVariant = isValidVariant(variant); - return validVariant ? variant : ''; -} - -/** - * @param {SupportedSubTypes | any} supportedSubType - * @returns {supportedSubType is SupportedSubTypes} - */ -function isValidSubtype(supportedSubType) { - return isValidIdentitiesSubtype(supportedSubType) || isValidCreditCardSubtype(supportedSubType) || isValidCredentialsSubtype(supportedSubType); -} - -/** - * @param {SupportedTypes | any} supportedType - * @returns {supportedType is SupportedTypes} - */ -function isValidSupportedType(supportedType) { - return supportedTypes.includes(supportedType); -} - -/** - * @param {SupportedVariants | any} supportedVariant - * @returns {supportedVariant is SupportedVariants} - */ -function isValidVariant(supportedVariant) { - return supportedVariants.includes(supportedVariant); -} - -/** - * Retrieves the input subtype - * @param {HTMLInputElement|Element} input - * @returns {SupportedSubTypes | 'unknown'} - */ -function getInputSubtype(input) { - const type = getInputType(input); - return getSubtypeFromType(type); -} - -/** - * Retrieves the input variant - * @param {HTMLInputElement|Element} input - * @returns {SupportedVariants | ''} - */ -function getInputVariant(input) { - const type = getInputType(input); - return getVariantFromType(type); -} - -/** - * Remove whitespace of more than 2 in a row and trim the string - * @param {string | null} string - * @return {string} - */ -const removeExcessWhitespace = function () { - let string = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; - string = string?.trim() || ''; - // The length check is extra safety to avoid trimming strings that would be discarded anyway - if (!string || string.length > TEXT_LENGTH_CUTOFF + 50) return ''; - return string.replace(/\n/g, ' ').replace(/\s{2,}/g, ' '); -}; - -/** - * Get text from all explicit labels - * @param {HTMLInputElement|HTMLSelectElement} el - * @return {string} - */ -exports.removeExcessWhitespace = removeExcessWhitespace; -const getExplicitLabelsText = el => { - const labelTextCandidates = []; - for (let label of el.labels || []) { - labelTextCandidates.push(...(0, _labelUtil.extractElementStrings)(label)); - } - if (el.hasAttribute('aria-label')) { - labelTextCandidates.push(removeExcessWhitespace(el.getAttribute('aria-label'))); - } - - // Try to access another element if it was marked as the label for this input/select - const ariaLabelAttr = removeExcessWhitespace(el.getAttribute('aria-labelled') || el.getAttribute('aria-labelledby')); - if (ariaLabelAttr) { - const labelledByElement = document.getElementById(ariaLabelAttr); - if (labelledByElement) { - labelTextCandidates.push(...(0, _labelUtil.extractElementStrings)(labelledByElement)); + async refreshData() { + await this.inContextSignup?.refreshData(); + await this.settings.populateData(); + } + async setupSettingsPage({ shouldLog: shouldLog2 } = { shouldLog: false }) { + if (!this.globalConfig.isDDGDomain) { + return; + } + notifyWebApp({ isApp: this.globalConfig.isApp }); + if (this.isDeviceSignedIn()) { + let userData; + try { + userData = await this.getUserData(); + } catch (e) { + } + let capabilities; + try { + capabilities = await this.getEmailProtectionCapabilities(); + } catch (e) { + } + if (this.globalConfig.isDDGDomain) { + window.addEventListener("message", (e) => { + if (e.data.removeUserData) { + this.removeUserData(); + } + if (e.data.closeEmailProtection) { + this.closeEmailProtection(); + } + }); + } + const hasUserData = userData && !userData.error && Object.entries(userData).length > 0; + notifyWebApp({ + deviceSignedIn: { + value: true, + shouldLog: shouldLog2, + userData: hasUserData ? userData : void 0, + capabilities + } + }); + } else { + this.trySigningIn(); + } } - } - - // Labels with long text are likely to be noisy and lead to false positives - const filteredLabels = labelTextCandidates.filter(string => string.length < 65); - if (filteredLabels.length > 0) { - return filteredLabels.join(' '); - } - return ''; -}; - -/** - * Tries to get a relevant previous Element sibling, excluding certain tags - * @param {Element} el - * @returns {Element|null} - */ -exports.getExplicitLabelsText = getExplicitLabelsText; -const recursiveGetPreviousElSibling = el => { - const previousEl = el.previousElementSibling; - if (!previousEl) return null; - - // Skip elements with no childNodes - if (_labelUtil.EXCLUDED_TAGS.includes(previousEl.tagName)) { - return recursiveGetPreviousElSibling(previousEl); - } - return previousEl; -}; - -/** - * Get all text close to the input (useful when no labels are defined) - * @param {HTMLInputElement|HTMLSelectElement} el - * @param {HTMLElement} form - * @param {string} cssSelector - * @return {string} - */ -const getRelatedText = (el, form, cssSelector) => { - let scope = getLargestMeaningfulContainer(el, form, cssSelector); - - // If we didn't find a container, try looking for an adjacent label - if (scope === el) { - let previousEl = recursiveGetPreviousElSibling(el); - if (previousEl instanceof HTMLElement) { - scope = previousEl; + async setupAutofill() { } - // If there is still no meaningful container return empty string - if (scope === el || scope instanceof HTMLSelectElement) { - if (el.previousSibling instanceof Text) { - return removeExcessWhitespace(el.previousSibling.textContent); - } - return ''; + /** @returns {Promise} */ + async getAddresses() { + throw new Error("unimplemented"); } - } - - // If there is still no meaningful container return empty string - if (scope === el || scope instanceof HTMLSelectElement) { - if (el.previousSibling instanceof Text) { - return removeExcessWhitespace(el.previousSibling.textContent); + /** @returns {Promise>} */ + getUserData() { + return Promise.resolve(null); } - return ''; - } - let trimmedText = ''; - const label = scope.querySelector('label'); - if (label) { - // Try searching for a label first - trimmedText = (0, _autofillUtils.getTextShallow)(label); - } else { - // If the container has a select element, remove its contents to avoid noise - trimmedText = (0, _labelUtil.extractElementStrings)(scope).join(' '); - } - - // If the text is longer than n chars it's too noisy and likely to yield false positives, so return '' - if (trimmedText.length < TEXT_LENGTH_CUTOFF) return trimmedText; - return ''; -}; - -/** - * Find a container for the input field that won't contain other inputs (useful to get elements related to the field) - * @param {HTMLElement} el - * @param {HTMLElement} form - * @param {string} cssSelector - * @return {HTMLElement} - */ -exports.getRelatedText = getRelatedText; -const getLargestMeaningfulContainer = (el, form, cssSelector) => { - /* TODO: there could be more than one select el for the same label, in that case we should - change how we compute the container */ - const parentElement = el.parentElement; - if (!parentElement || el === form || !cssSelector) return el; - const inputsInParentsScope = parentElement.querySelectorAll(cssSelector); - // To avoid noise, ensure that our input is the only in scope - if (inputsInParentsScope.length === 1) { - return getLargestMeaningfulContainer(parentElement, form, cssSelector); - } - return el; -}; - -/** - * Find a regex match for a given input - * @param {HTMLInputElement} input - * @param {RegExp} regex - * @param {HTMLElement} form - * @param {string} cssSelector - * @returns {RegExpMatchArray|null} - */ -const matchInPlaceholderAndLabels = (input, regex, form, cssSelector) => { - return input.placeholder?.match(regex) || getExplicitLabelsText(input).match(regex) || getRelatedText(input, form, cssSelector).match(regex); -}; - -/** - * Check if a given input matches a regex - * @param {HTMLInputElement} input - * @param {RegExp} regex - * @param {HTMLElement} form - * @param {string} cssSelector - * @returns {boolean} - */ -exports.matchInPlaceholderAndLabels = matchInPlaceholderAndLabels; -const checkPlaceholderAndLabels = (input, regex, form, cssSelector) => { - return !!matchInPlaceholderAndLabels(input, regex, form, cssSelector); -}; - -/** - * Factory for instances of Matching - * - * @return {Matching} - */ -exports.checkPlaceholderAndLabels = checkPlaceholderAndLabels; -function createMatching() { - return new Matching(_compiledMatchingConfig.matchingConfiguration); -} - -},{"../autofill-utils.js":52,"../constants.js":55,"./label-util.js":29,"./matching-config/__generated__/compiled-matching-config.js":31,"./matching-utils.js":32}],34:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.InContextSignup = void 0; -var _deviceApiCalls = require("./deviceApiCalls/__generated__/deviceApiCalls.js"); -var _autofillUtils = require("./autofill-utils.js"); -class InContextSignup { - /** - * @param {import("./DeviceInterface/InterfacePrototype").default} device - */ - constructor(device) { - this.device = device; - } - async init() { - await this.refreshData(); - this.addNativeAccessibleGlobalFunctions(); - } - addNativeAccessibleGlobalFunctions() { - if (!this.device.globalConfig.hasModernWebkitAPI) return; - try { - // Set up a function which can be called from the native layer after completed sign-up or sign-in. - Object.defineProperty(window, 'openAutofillAfterClosingEmailProtectionTab', { - enumerable: false, - configurable: false, - writable: false, - value: () => { - this.openAutofillTooltip(); + /** @returns {void} */ + removeUserData() { + } + /** @returns {void} */ + closeEmailProtection() { + } + /** @returns {Promise>} */ + getEmailProtectionCapabilities() { + throw new Error("unimplemented"); + } + refreshAlias() { + } + async trySigningIn() { + if (this.globalConfig.isDDGDomain) { + if (this.attempts < 10) { + this.attempts++; + const data = await sendAndWaitForAnswer(SIGN_IN_MSG, "addUserData"); + this.storeUserData(data); + await this.setupAutofill(); + await this.settings.refresh(); + await this.setupSettingsPage({ shouldLog: true }); + await this.postInit(); + } else { + console.warn("max attempts reached, bailing"); } - }); - } catch (e) { - // Ignore if function can't be set up, it's a UX enhancement not a critical flow + } } - } - async refreshData() { - const incontextSignupDismissedAt = await this.device.deviceApi.request(new _deviceApiCalls.GetIncontextSignupDismissedAtCall(null)); - this.permanentlyDismissedAt = incontextSignupDismissedAt.permanentlyDismissedAt; - this.isInstalledRecently = incontextSignupDismissedAt.isInstalledRecently; - } - async openAutofillTooltip() { - // Make sure we're working with the latest data - await this.device.refreshData(); - - // Make sure the tooltip is closed before we try to open it - await this.device.uiController?.removeTooltip('stateChange'); - - // Make sure the input doesn't have focus so we can focus on it again - const activeInput = this.device.activeForm?.activeInput; - activeInput?.blur(); - - // Select the active input to open the tooltip - const selectActiveInput = () => { - activeInput?.focus(); - }; - if (document.hasFocus()) { - selectActiveInput(); - } else { - document.addEventListener('visibilitychange', () => { - selectActiveInput(); - }, { - once: true - }); + storeUserData(_data7) { } - } - isPermanentlyDismissed() { - return Boolean(this.permanentlyDismissedAt); - } - isOnValidDomain() { - // Only show in-context signup if we've high confidence that the page is - // not internally hosted or an intranet - return (0, _autofillUtils.isValidTLD)() && !(0, _autofillUtils.isLocalNetwork)(); - } - isAllowedByDevice() { - if (typeof this.isInstalledRecently === 'boolean') { - return this.isInstalledRecently; - } else { - // Don't restrict in-context signup based on recent installation - // if the device hasn't provided a clear indication - return true; + addDeviceListeners() { } - } - - /** - * @param {import('./Form/matching.js').SupportedSubTypes | "unknown"} [inputType] - * @returns {boolean} - */ - isAvailable() { - let inputType = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'emailAddress'; - const isEmailInput = inputType === 'emailAddress'; - const isEmailProtectionEnabled = !!this.device.settings?.featureToggles.emailProtection; - const isIncontextSignupEnabled = !!this.device.settings?.featureToggles.emailProtection_incontext_signup; - const isNotAlreadyLoggedIn = !this.device.isDeviceSignedIn(); - const isNotDismissed = !this.isPermanentlyDismissed(); - const isOnExpectedPage = this.device.globalConfig.isTopFrame || this.isOnValidDomain(); - const isAllowedByDevice = this.isAllowedByDevice(); - return isEmailInput && isEmailProtectionEnabled && isIncontextSignupEnabled && isNotAlreadyLoggedIn && isNotDismissed && isOnExpectedPage && isAllowedByDevice; - } - onIncontextSignup() { - this.device.deviceApi.notify(new _deviceApiCalls.StartEmailProtectionSignupCall({})); - this.device.firePixel({ - pixelName: 'incontext_primary_cta' - }); - } - onIncontextSignupDismissed() { - let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { - shouldHideTooltip: true - }; - if (options.shouldHideTooltip) { - this.device.removeAutofillUIFromPage('Email Protection in-context signup dismissed.'); - this.device.deviceApi.notify(new _deviceApiCalls.CloseAutofillParentCall(null)); - } - this.permanentlyDismissedAt = new Date().getTime(); - this.device.deviceApi.notify(new _deviceApiCalls.SetIncontextSignupPermanentlyDismissedAtCall({ - value: this.permanentlyDismissedAt - })); - this.device.firePixel({ - pixelName: 'incontext_dismiss_persisted' - }); - } - - // In-context signup can be closed when displayed as a stand-alone tooltip, e.g. extension - onIncontextSignupClosed() { - this.device.activeForm?.dismissTooltip(); - this.device.firePixel({ - pixelName: 'incontext_close_x' - }); - } -} -exports.InContextSignup = InContextSignup; - -},{"./autofill-utils.js":52,"./deviceApiCalls/__generated__/deviceApiCalls.js":56}],35:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.PROVIDER_LOCKED = exports.AUTOGENERATED_KEY = void 0; -exports.appendGeneratedKey = appendGeneratedKey; -exports.createCredentialsTooltipItem = createCredentialsTooltipItem; -exports.fromPassword = fromPassword; -var _autofillUtils = require("../autofill-utils.js"); -const AUTOGENERATED_KEY = exports.AUTOGENERATED_KEY = 'autogenerated'; -const PROVIDER_LOCKED = exports.PROVIDER_LOCKED = 'provider_locked'; - -/** - * @implements {TooltipItemRenderer} - */ -class CredentialsTooltipItem { - /** @type {CredentialsObject} */ - #data; - /** @param {CredentialsObject} data */ - constructor(data) { - this.#data = data; - } - id = () => String(this.#data.id); - labelMedium = _subtype => { - if (this.#data.username) { - return this.#data.username; + /** @param {() => void} _fn */ + addLogoutListener(_fn) { } - if (this.#data.origin?.url) { - return `Password for ${(0, _autofillUtils.truncateFromMiddle)(this.#data.origin.url)}`; + isDeviceSignedIn() { + return false; } - return ''; - }; - labelSmall = _subtype => { - if (this.#data.origin?.url) { - return (0, _autofillUtils.truncateFromMiddle)(this.#data.origin.url); + /** + * @returns {Promise} + */ + async getAlias() { + return void 0; } - return '•••••••••••••••'; - }; - credentialsProvider = () => this.#data.credentialsProvider; -} - -/** - * @implements {TooltipItemRenderer} - */ -class AutoGeneratedCredential { - /** @type {CredentialsObject} */ - #data; - /** @param {CredentialsObject} data */ - constructor(data) { - this.#data = data; - } - id = () => String(this.#data.id); - label = _subtype => this.#data.password; - labelMedium = _subtype => 'Generated password'; - labelSmall = _subtype => 'Password will be saved for this website'; -} - -/** - * Generate a stand-in 'CredentialsObject' from a - * given (generated) password. - * - * @param {string} password - * @param {string} username - * @returns {CredentialsObject} - */ -function fromPassword(password, username) { - return { - [AUTOGENERATED_KEY]: true, - password, - username - }; -} - -/** - * @implements TooltipItemRenderer - */ -class ProviderLockedItem { - /** @type {CredentialsObject} */ - #data; - /** @param {CredentialsObject} data */ - constructor(data) { - this.#data = data; - } - id = () => String(this.#data.id); - labelMedium = _subtype => 'Bitwarden is locked'; - labelSmall = _subtype => 'Unlock your vault to access credentials or generate passwords'; - credentialsProvider = () => this.#data.credentialsProvider; -} - -/** - * If the locally generated/stored password or username ends up being the same - * as submitted in a subsequent form submission - then we mark the - * credentials as 'autogenerated' so that the native layer can decide - * how to process it - * - * @param {DataStorageObject} data - * @param {object} [autofilledFields] - * @param {string|null|undefined} [autofilledFields.username] - if present, it's the last username generated by something like email Protection - * @param {string|null|undefined} [autofilledFields.password] - if present, it's the last generated password - * - */ -function appendGeneratedKey(data) { - let autofilledFields = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - let autogenerated = false; - - // does the current password match the most recently generated one? - if (autofilledFields.password && data.credentials?.password === autofilledFields.password) { - autogenerated = true; - } - - // does the current username match a recently generated one? (eg: email protection) - if (autofilledFields.username && data.credentials?.username === autofilledFields.username) { - autogenerated = true; - } - - // if neither username nor password were generated, don't alter the outgoing data - if (!autogenerated) return data; - - // if we get here, we're confident that something was generated + filled - // so we mark the credential as 'autogenerated' for the benefit of native implementations - return { - ...data, - credentials: { - ...data.credentials, - [AUTOGENERATED_KEY]: true + // PM endpoints + getAccounts() { } - }; -} - -/** - * Factory for creating a TooltipItemRenderer - * - * @param {CredentialsObject} data - * @returns {TooltipItemRenderer} - */ -function createCredentialsTooltipItem(data) { - if (data.id === PROVIDER_LOCKED) { - return new ProviderLockedItem(data); - } - if (AUTOGENERATED_KEY in data && data.password) { - return new AutoGeneratedCredential(data); - } - return new CredentialsTooltipItem(data); -} - -},{"../autofill-utils.js":52}],36:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.CreditCardTooltipItem = void 0; -/** - * @implements {TooltipItemRenderer} - */ -class CreditCardTooltipItem { - /** @type {CreditCardObject} */ - #data; - /** @param {CreditCardObject} data */ - constructor(data) { - this.#data = data; - } - id = () => String(this.#data.id); - labelMedium = _ => this.#data.title; - labelSmall = _ => this.#data.displayNumber; -} -exports.CreditCardTooltipItem = CreditCardTooltipItem; - -},{}],37:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.IdentityTooltipItem = void 0; -var _formatters = require("../Form/formatters.js"); -/** - * @implements {TooltipItemRenderer} - */ -class IdentityTooltipItem { - /** @type {IdentityObject} */ - #data; - /** @param {IdentityObject} data */ - constructor(data) { - this.#data = data; - } - id = () => String(this.#data.id); - labelMedium = subtype => { - if (subtype === 'addressCountryCode') { - return (0, _formatters.getCountryDisplayName)('en', this.#data.addressCountryCode || ''); + /** + * Gets credentials ready for autofill + * @param {CredentialsObject['id']} id - the credential id + * @returns {Promise} + */ + async getAutofillCredentials(id) { + return this.deviceApi.request(new GetAutofillCredentialsCall({ id: String(id) })); } - if (this.#data.id === 'privateAddress') { - return 'Generate Private Duck Address'; + /** @returns {APIResponse} */ + async getAutofillCreditCard(_id) { + throw new Error("getAutofillCreditCard unimplemented"); } - return this.#data[subtype]; - }; - label(subtype) { - if (this.#data.id === 'privateAddress') { - return this.#data[subtype]; + /** @returns {Promise<{success: IdentityObject|undefined}>} */ + async getAutofillIdentity(_id) { + throw new Error("getAutofillIdentity unimplemented"); } - return null; - } - labelSmall = _ => { - return this.#data.title; - }; -} -exports.IdentityTooltipItem = IdentityTooltipItem; - -},{"../Form/formatters.js":26}],38:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.PasswordGenerator = void 0; -var _index = require("../packages/password/index.js"); -var _rules = _interopRequireDefault(require("../packages/password/rules.json")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -/** - * Create a password once and reuse it. - */ -class PasswordGenerator { - /** @type {string|null} */ - #previous = null; - - /** @returns {boolean} */ - get generated() { - return this.#previous !== null; - } - - /** @returns {string|null} */ - get password() { - return this.#previous; - } - - /** @param {import('../packages/password').GenerateOptions} [params] */ - generate() { - let params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - if (this.#previous) { - return this.#previous; - } - this.#previous = (0, _index.generate)({ - ...params, - rules: _rules.default - }); - return this.#previous; - } -} -exports.PasswordGenerator = PasswordGenerator; - -},{"../packages/password/index.js":7,"../packages/password/rules.json":11}],39:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.createScanner = createScanner; -var _Form = require("./Form/Form.js"); -var _constants = require("./constants.js"); -var _matching = require("./Form/matching.js"); -var _autofillUtils = require("./autofill-utils.js"); -var _deviceApiCalls = require("./deviceApiCalls/__generated__/deviceApiCalls.js"); -const { - MAX_INPUTS_PER_PAGE, - MAX_FORMS_PER_PAGE, - MAX_INPUTS_PER_FORM, - ATTR_INPUT_TYPE -} = _constants.constants; - -/** - * @typedef {{ - * forms: Map; - * init(): (reason, ...rest)=> void; - * enqueue(elements: (HTMLElement|Document)[]): void; - * findEligibleInputs(context): Scanner; - * matching: import("./Form/matching").Matching; - * options: ScannerOptions; - * stopScanner: (reason: string, ...rest: any) => void; - * }} Scanner - * - * @typedef {{ - * initialDelay: number, - * bufferSize: number, - * debounceTimePeriod: number, - * maxInputsPerPage: number, - * maxFormsPerPage: number, - * maxInputsPerForm: number - * }} ScannerOptions - */ - -/** @type {ScannerOptions} */ -const defaultScannerOptions = { - // This buffer size is very large because it's an unexpected edge-case that - // a DOM will be continually modified over and over without ever stopping. If we do see 1000 unique - // new elements in the buffer however then this will prevent the algorithm from never ending. - bufferSize: 50, - // wait for a 500ms window of event silence before performing the scan - debounceTimePeriod: 500, - // how long to wait when performing the initial scan - initialDelay: 0, - // How many inputs is too many on the page. If we detect that there's above - // this maximum, then we don't scan the page. This will prevent slowdowns on - // large pages which are unlikely to require autofill anyway. - maxInputsPerPage: MAX_INPUTS_PER_PAGE, - maxFormsPerPage: MAX_FORMS_PER_PAGE, - maxInputsPerForm: MAX_INPUTS_PER_FORM -}; - -/** - * This allows: - * 1) synchronous DOM scanning + mutations - via `createScanner(device).findEligibleInputs(document)` - * 2) or, as above + a debounced mutation observer to re-run the scan after the given time - */ -class DefaultScanner { - /** @type Map */ - forms = new Map(); - /** @type {any|undefined} the timer to reset */ - debounceTimer; - /** @type {Set} stored changed elements until they can be processed */ - changedElements = new Set(); - /** @type {ScannerOptions} */ - options; - /** @type {HTMLInputElement | null} */ - activeInput = null; - /** @type {boolean} A flag to indicate the whole page will be re-scanned */ - rescanAll = false; - /** @type {boolean} Indicates whether we called stopScanning */ - stopped = false; - /** @type {import("./Form/matching").Matching} matching */ - matching; - - /** - * @param {import("./DeviceInterface/InterfacePrototype").default} device - * @param {ScannerOptions} options - */ - constructor(device, options) { - this.device = device; - this.matching = (0, _matching.createMatching)(); - this.options = options; - /** @type {number} A timestamp of the */ - this.initTimeStamp = Date.now(); - } - - /** - * Determine whether we should fire the credentials autoprompt. This is needed because some sites are blank - * on page load and load scripts asynchronously, so our initial scan didn't set the autoprompt correctly - * @returns {boolean} - */ - get shouldAutoprompt() { - return Date.now() - this.initTimeStamp <= 1500; - } - - /** - * Call this to scan once and then watch for changes. - * - * Call the returned function to remove listeners. - * @returns {(reason: string, ...rest) => void} - */ - init() { - var _this = this; - if (this.device.globalConfig.isExtension) { - this.device.deviceApi.notify(new _deviceApiCalls.AddDebugFlagCall({ - flag: 'autofill' - })); + openManagePasswords() { } - - // Add the shadow DOM listener. Handlers in handleEvent - window.addEventListener('pointerdown', this, true); - // We don't listen for focus events on mobile, they can cause keyboard flashing - if (!this.device.globalConfig.isMobileApp) { - window.addEventListener('focus', this, true); - } - const delay = this.options.initialDelay; - // if the delay is zero, (chrome/firefox etc) then use `requestIdleCallback` - if (delay === 0) { - window.requestIdleCallback(() => this.scanAndObserve()); - } else { - // otherwise, use the delay time to defer the initial scan - setTimeout(() => this.scanAndObserve(), delay); + openManageCreditCards() { } - return function (reason) { - for (var _len = arguments.length, rest = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - rest[_key - 1] = arguments[_key]; - } - _this.stopScanner(reason, ...rest); - }; - } - - /** - * Scan the page and begin observing changes - */ - scanAndObserve() { - window.performance?.mark?.('initial_scanner:init:start'); - this.findEligibleInputs(document); - window.performance?.mark?.('initial_scanner:init:end'); - (0, _autofillUtils.logPerformance)('initial_scanner'); - this.mutObs.observe(document.documentElement, { - childList: true, - subtree: true - }); - } - - /** - * @param context - */ - findEligibleInputs(context) { - // Avoid autofill on Email Protection web app - if (this.device.globalConfig.isDDGDomain) { - return this; + openManageIdentities() { } - if ('matches' in context && context.matches?.(this.matching.cssSelector('formInputsSelector'))) { - this.addInput(context); - } else { - const inputs = context.querySelectorAll(this.matching.cssSelector('formInputsSelector')); - if (inputs.length > this.options.maxInputsPerPage) { - this.stopScanner(`Too many input fields in the given context (${inputs.length}), stop scanning`, context); - return this; - } - inputs.forEach(input => this.addInput(input)); + /** + * @param {StoreFormData} values + * @param {StoreFormData['trigger']} trigger + */ + storeFormData(values, trigger) { + this.deviceApi.notify(new StoreFormDataCall({ ...values, trigger })); } - return this; - } - - /** - * Stops scanning, switches off the mutation observer and clears all forms - * @param {string} reason - * @param {any} rest - */ - stopScanner(reason) { - this.stopped = true; - if ((0, _autofillUtils.shouldLog)()) { - for (var _len2 = arguments.length, rest = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { - rest[_key2 - 1] = arguments[_key2]; + /** + * `preAttachTooltip` happens just before a tooltip is show - features may want to append some data + * at this point. + * + * For example, if password generation is enabled, this will generate + * a password and send it to the tooltip as though it were a stored credential. + * + * @param {TopContextData} topContextData + * @param {HTMLInputElement} input + * @param {import("../Form/Form").Form} form + */ + preAttachTooltip(topContextData, input, form) { + const checks = [ + topContextData.inputType === "credentials.password.new", + this.settings.featureToggles.password_generation + ]; + if (checks.every(Boolean)) { + const password = this.passwordGenerator.generate({ + input: input.getAttribute("passwordrules"), + domain: window.location.hostname + }); + const rawValues = form.getRawValues(); + const username = rawValues.credentials?.username || rawValues.identities?.emailAddress || ""; + topContextData.credentials = [fromPassword(password, username)]; } - console.log(reason, ...rest); + return topContextData; } - const activeInput = this.device.activeForm?.activeInput; - - // remove Dax, listeners, timers, and observers - clearTimeout(this.debounceTimer); - this.changedElements.clear(); - this.mutObs.disconnect(); - window.removeEventListener('pointerdown', this, true); - window.removeEventListener('focus', this, true); - this.forms.forEach(form => { - form.destroy(); - }); - this.forms.clear(); - - // Bring the user back to the input they were interacting with - activeInput?.focus(); - } - - /** - * @param {HTMLElement|HTMLInputElement|HTMLSelectElement} input - * @returns {HTMLFormElement|HTMLElement} - */ - getParentForm(input) { - if (input instanceof HTMLInputElement || input instanceof HTMLSelectElement) { - if (input.form) { - // Use input.form unless it encloses most of the DOM - // In that case we proceed to identify more precise wrappers - if (this.forms.has(input.form) || - // If we've added the form we've already checked that it's not a page wrapper - !(0, _autofillUtils.isFormLikelyToBeUsedAsPageWrapper)(input.form)) { - return input.form; + /** + * `postAutofill` gives features an opportunity to perform an action directly + * following an autofill. + * + * For example, if a generated password was used, we want to fire a save event. + * + * @param {IdentityObject|CreditCardObject|CredentialsObject} data + * @param {SupportedMainTypes} dataType + * @param {import("../Form/Form").Form} formObj + */ + postAutofill(data, dataType, formObj) { + if (AUTOGENERATED_KEY in data && "password" in data && // Don't send message on Android to avoid potential abuse. Data is saved on native confirmation instead. + !this.globalConfig.isAndroid) { + const formValues = formObj.getValuesReadyForStorage(); + if (formValues.credentials?.password === data.password) { + const formData = appendGeneratedKey(formValues, { password: data.password }); + this.storeFormData(formData, "passwordGeneration"); } } + if (dataType === "credentials" && formObj.shouldAutoSubmit) { + formObj.attemptSubmissionIfNeeded(); + } } - /** - * Max number of nodes we want to traverse upwards, critical to avoid enclosing large portions of the DOM - * @type {number} - */ - let traversalLayerCount = 0; - let element = input; - // traverse the DOM to search for related inputs - while (traversalLayerCount <= 5 && element.parentElement && element.parentElement !== document.documentElement) { - // Avoid overlapping containers or forms - const siblingForm = element.parentElement?.querySelector('form'); - if (siblingForm && siblingForm !== element) { - return element; - } - element = element.parentElement; - const inputs = element.querySelectorAll(this.matching.cssSelector('formInputsSelector')); - const buttons = element.querySelectorAll(this.matching.cssSelector('submitButtonSelector')); - // If we find a button or another input, we assume that's our form - if (inputs.length > 1 || buttons.length) { - // found related input, return common ancestor - return element; - } - traversalLayerCount++; - } - return input; - } - - /** - * @param {HTMLInputElement|HTMLSelectElement} input - */ - addInput(input) { - if (this.stopped) return; - const parentForm = this.getParentForm(input); - if (parentForm instanceof HTMLFormElement && this.forms.has(parentForm)) { - const foundForm = this.forms.get(parentForm); - // We've met the form, add the input provided it's below the max input limit - if (foundForm && foundForm.inputs.all.size < MAX_INPUTS_PER_FORM) { - foundForm.addInput(input); - } else { - this.stopScanner('The form has too many inputs, destroying.'); + * `postSubmit` gives features a one-time-only opportunity to perform an + * action directly after a form submission was observed. + * + * Mostly this is about storing data from the form submission, but it can + * also be used like in the case of Password generation, to append additional + * data before it's sent to be saved. + * + * @param {DataStorageObject} values + * @param {import("../Form/Form").Form} form + */ + postSubmit(values, form) { + if (!form.form) + return; + if (!form.hasValues(values)) + return; + const checks = [ + form.shouldPromptToStoreData && !form.submitHandlerExecuted, + this.passwordGenerator.generated + ]; + if (checks.some(Boolean)) { + const formData = appendGeneratedKey(values, { + password: this.passwordGenerator.password, + username: this.emailProtection.lastGenerated + }); + this.storeFormData(formData, "formSubmission"); } - return; } + /** + * Sends a pixel to be fired on the client side + * @param {import('../deviceApiCalls/__generated__/validators-ts').SendJSPixelParams} pixelParams + */ + firePixel(pixelParams) { + this.deviceApi.notify(new SendJSPixelCall(pixelParams)); + } + /** + * This serves as a single place to create a default instance + * of InterfacePrototype that can be useful in testing scenarios + * @param {Partial} [globalConfigOverrides] + * @returns {InterfacePrototype} + */ + static default(globalConfigOverrides) { + const globalConfig = createGlobalConfig(globalConfigOverrides); + const transport = createTransport(globalConfig); + const deviceApi = new DeviceApi(transport); + const settings = Settings.default(globalConfig, deviceApi); + return new _InterfacePrototype(globalConfig, deviceApi, settings); + } + }; + var InterfacePrototype = _InterfacePrototype; + _addresses = new WeakMap(); + _data6 = new WeakMap(); + var InterfacePrototype_default = InterfacePrototype; - // Do not add explicitly search forms - if (parentForm.role === 'search') return; - - // Check if the forms we've seen are either disconnected, - // or are parent/child of the currently-found form - let previouslyFoundParent, childForm; - for (const [formEl] of this.forms) { - // Remove disconnected forms to avoid leaks - if (!formEl.isConnected) { - this.forms.delete(formEl); - continue; - } - if (formEl.contains(parentForm)) { - previouslyFoundParent = formEl; - break; - } - if (parentForm.contains(formEl)) { - childForm = formEl; - break; + // src/InContextSignup.js + var InContextSignup = class { + /** + * @param {import("./DeviceInterface/InterfacePrototype").default} device + */ + constructor(device) { + this.device = device; + } + async init() { + await this.refreshData(); + this.addNativeAccessibleGlobalFunctions(); + } + addNativeAccessibleGlobalFunctions() { + if (!this.device.globalConfig.hasModernWebkitAPI) + return; + try { + Object.defineProperty(window, "openAutofillAfterClosingEmailProtectionTab", { + enumerable: false, + configurable: false, + writable: false, + value: () => { + this.openAutofillTooltip(); + } + }); + } catch (e) { } } - if (previouslyFoundParent) { - if (parentForm instanceof HTMLFormElement && parentForm !== previouslyFoundParent) { - // If we had a prior parent but this is an explicit form, the previous was a false positive - this.forms.delete(previouslyFoundParent); + async refreshData() { + const incontextSignupDismissedAt = await this.device.deviceApi.request(new GetIncontextSignupDismissedAtCall(null)); + this.permanentlyDismissedAt = incontextSignupDismissedAt.permanentlyDismissedAt; + this.isInstalledRecently = incontextSignupDismissedAt.isInstalledRecently; + } + async openAutofillTooltip() { + await this.device.refreshData(); + await this.device.uiController?.removeTooltip("stateChange"); + const activeInput = this.device.activeForm?.activeInput; + activeInput?.blur(); + const selectActiveInput = () => { + activeInput?.focus(); + }; + if (document.hasFocus()) { + selectActiveInput(); } else { - // If we've already met the form or a descendant, add the input - this.forms.get(previouslyFoundParent)?.addInput(input); + document.addEventListener("visibilitychange", () => { + selectActiveInput(); + }, { once: true }); } - } else { - // if this form is an ancestor of an existing form, remove that before adding this - if (childForm) { - this.forms.get(childForm)?.destroy(); - this.forms.delete(childForm); - } - - // Only add the form if below the limit of forms per page - if (this.forms.size < this.options.maxFormsPerPage) { - this.forms.set(parentForm, new _Form.Form(parentForm, input, this.device, this.matching, this.shouldAutoprompt)); + } + isPermanentlyDismissed() { + return Boolean(this.permanentlyDismissedAt); + } + isOnValidDomain() { + return isValidTLD() && !isLocalNetwork(); + } + isAllowedByDevice() { + if (typeof this.isInstalledRecently === "boolean") { + return this.isInstalledRecently; } else { - this.stopScanner('The page has too many forms, stop adding them.'); + return true; } } - } - - /** - * enqueue elements to be re-scanned after the given - * amount of time has elapsed. - * - * @param {(HTMLElement|Document)[]} htmlElements - */ - enqueue(htmlElements) { - // if the buffer limit is reached, stop trying to track elements and process body instead. - if (this.changedElements.size >= this.options.bufferSize) { - this.rescanAll = true; - this.changedElements.clear(); - } else if (!this.rescanAll) { - // otherwise keep adding each element to the queue - for (let element of htmlElements) { - this.changedElements.add(element); - } + /** + * @param {import('./Form/matching.js').SupportedSubTypes | "unknown"} [inputType] + * @returns {boolean} + */ + isAvailable(inputType = "emailAddress") { + const isEmailInput = inputType === "emailAddress"; + const isEmailProtectionEnabled = !!this.device.settings?.featureToggles.emailProtection; + const isIncontextSignupEnabled = !!this.device.settings?.featureToggles.emailProtection_incontext_signup; + const isNotAlreadyLoggedIn = !this.device.isDeviceSignedIn(); + const isNotDismissed = !this.isPermanentlyDismissed(); + const isOnExpectedPage = this.device.globalConfig.isTopFrame || this.isOnValidDomain(); + const isAllowedByDevice = this.isAllowedByDevice(); + return isEmailInput && isEmailProtectionEnabled && isIncontextSignupEnabled && isNotAlreadyLoggedIn && isNotDismissed && isOnExpectedPage && isAllowedByDevice; + } + onIncontextSignup() { + this.device.deviceApi.notify(new StartEmailProtectionSignupCall({})); + this.device.firePixel({ pixelName: "incontext_primary_cta" }); + } + onIncontextSignupDismissed(options = { shouldHideTooltip: true }) { + if (options.shouldHideTooltip) { + this.device.removeAutofillUIFromPage("Email Protection in-context signup dismissed."); + this.device.deviceApi.notify(new CloseAutofillParentCall(null)); + } + this.permanentlyDismissedAt = (/* @__PURE__ */ new Date()).getTime(); + this.device.deviceApi.notify(new SetIncontextSignupPermanentlyDismissedAtCall({ value: this.permanentlyDismissedAt })); + this.device.firePixel({ pixelName: "incontext_dismiss_persisted" }); + } + // In-context signup can be closed when displayed as a stand-alone tooltip, e.g. extension + onIncontextSignupClosed() { + this.device.activeForm?.dismissTooltip(); + this.device.firePixel({ pixelName: "incontext_close_x" }); } - clearTimeout(this.debounceTimer); - this.debounceTimer = setTimeout(() => { - window.performance?.mark?.('scanner:init:start'); - this.processChangedElements(); - this.changedElements.clear(); - this.rescanAll = false; - window.performance?.mark?.('scanner:init:end'); - (0, _autofillUtils.logPerformance)('scanner'); - }, this.options.debounceTimePeriod); - } + }; - /** - * re-scan the changed elements, but only if they - * are still present in the DOM - */ - processChangedElements() { - if (this.rescanAll) { - this.findEligibleInputs(document); - return; + // src/DeviceInterface/AndroidInterface.js + var AndroidInterface = class extends InterfacePrototype_default { + constructor() { + super(...arguments); + __publicField(this, "inContextSignup", new InContextSignup(this)); + } + /** + * @returns {Promise} + */ + async getAlias() { + const { alias } = await sendAndWaitForAnswer(async () => { + if (this.inContextSignup.isAvailable()) { + const { isSignedIn } = await this.deviceApi.request(new ShowInContextEmailProtectionSignupPromptCall(null)); + if (this.globalConfig.availableInputTypes) { + this.globalConfig.availableInputTypes.email = isSignedIn; + } + this.updateForStateChange(); + this.onFinishedAutofill(); + } + return window.EmailInterface.showTooltip(); + }, "getAliasResponse"); + return alias; } - for (let element of this.changedElements) { - if (element.isConnected) { - this.findEligibleInputs(element); + /** + * @override + */ + createUIController() { + return new NativeUIController(); + } + /** + * @deprecated use `this.settings.availableInputTypes.email` in the future + * @returns {boolean} + */ + isDeviceSignedIn() { + if (this.globalConfig.isDDGDomain) { + return window.EmailInterface.isSignedIn() === "true"; } + if (typeof this.globalConfig.availableInputTypes?.email === "boolean") { + return this.globalConfig.availableInputTypes.email; + } + return true; } - } - - /** - * Watch for changes in the DOM, and enqueue elements to be scanned - * @type {MutationObserver} - */ - mutObs = new MutationObserver(mutationList => { - /** @type {HTMLElement[]} */ - if (this.rescanAll) { - // quick version if buffer full - this.enqueue([]); - return; + async setupAutofill() { + await this.inContextSignup.init(); } - const outgoing = []; - for (const mutationRecord of mutationList) { - if (mutationRecord.type === 'childList') { - for (let addedNode of mutationRecord.addedNodes) { - if (!(addedNode instanceof HTMLElement)) continue; - if (addedNode.nodeName === 'DDG-AUTOFILL') continue; - outgoing.push(addedNode); + /** + * Used by the email web app + * Settings page displays data of the logged in user data + */ + getUserData() { + let userData = null; + try { + userData = JSON.parse(window.EmailInterface.getUserData()); + } catch (e) { + if (this.globalConfig.isDDGTestMode) { + console.error(e); } } + return Promise.resolve(userData); } - this.enqueue(outgoing); - }); - handleEvent(event) { - switch (event.type) { - case 'pointerdown': - case 'focus': - this.scanShadow(event); - break; + /** + * Used by the email web app + * Device capabilities determine which functionality is available to the user + */ + getEmailProtectionCapabilities() { + let deviceCapabilities = null; + try { + deviceCapabilities = JSON.parse(window.EmailInterface.getDeviceCapabilities()); + } catch (e) { + if (this.globalConfig.isDDGTestMode) { + console.error(e); + } + } + return Promise.resolve(deviceCapabilities); } - } - - /** - * Scan clicked input fields, even if they're within a shadow tree - * @param {FocusEvent | PointerEvent} event - */ - scanShadow(event) { - // If the scanner is stopped or there's no shadow root, just return - if (this.stopped || !(event.target instanceof Element) || !event.target?.shadowRoot) return; - window.performance?.mark?.('scan_shadow:init:start'); - const realTarget = (0, _autofillUtils.pierceShadowTree)(event, HTMLInputElement); - - // If it's an input we haven't already scanned, scan the whole shadow tree - if (realTarget instanceof HTMLInputElement && !realTarget.hasAttribute(ATTR_INPUT_TYPE)) { - this.findEligibleInputs(realTarget.getRootNode()); + storeUserData({ addUserData: { token, userName, cohort } }) { + return window.EmailInterface.storeCredentials(token, userName, cohort); } - window.performance?.mark?.('scan_shadow:init:end'); - (0, _autofillUtils.logPerformance)('scan_shadow'); - } -} - -/** - * @param {import("./DeviceInterface/InterfacePrototype").default} device - * @param {Partial} [scannerOptions] - * @returns {Scanner} - */ -function createScanner(device, scannerOptions) { - return new DefaultScanner(device, { - ...defaultScannerOptions, - ...scannerOptions - }); -} - -},{"./Form/Form.js":23,"./Form/matching.js":33,"./autofill-utils.js":52,"./constants.js":55,"./deviceApiCalls/__generated__/deviceApiCalls.js":56}],40:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.Settings = void 0; -var _index = require("../packages/device-api/index.js"); -var _deviceApiCalls = require("./deviceApiCalls/__generated__/deviceApiCalls.js"); -var _validatorsZod = require("./deviceApiCalls/__generated__/validators.zod.js"); -var _autofillUtils = require("./autofill-utils.js"); -/** - * Some Type helpers to prevent duplication - * @typedef {import("./deviceApiCalls/__generated__/validators-ts").AutofillFeatureToggles} AutofillFeatureToggles - * @typedef {import("./deviceApiCalls/__generated__/validators-ts").AvailableInputTypes} AvailableInputTypes - * @typedef {import("./deviceApiCalls/__generated__/validators-ts").RuntimeConfiguration} RuntimeConfiguration - * @typedef {import("../packages/device-api").DeviceApi} DeviceApi - */ - -/** - * The Settings class encapsulates the concept of 1) feature toggles + 2) available input types. - * - * 1) Feature toggles are boolean flags that can represent a device's capabilities. That may be user-toggled - * or not, we don't make that distinction. - * - * 2) Available Input Types are indicators to whether the given platform can provide data for a given type. - * For example, a user might have credentials saved for https://example.com, so when the page loads, but **before** - * we can decorate any fields, we determine this first. - */ -class Settings { - /** @type {GlobalConfig} */ - globalConfig; - /** @type {DeviceApi} */ - deviceApi; - /** @type {AutofillFeatureToggles | null} */ - _featureToggles = null; - /** @type {AvailableInputTypes | null} */ - _availableInputTypes = null; - /** @type {RuntimeConfiguration | null | undefined} */ - _runtimeConfiguration = null; - /** @type {boolean | null} */ - _enabled = null; - - /** - * @param {GlobalConfig} config - * @param {DeviceApi} deviceApi - */ - constructor(config, deviceApi) { - this.deviceApi = deviceApi; - this.globalConfig = config; - } - - /** - * Feature toggles are delivered as part of the Runtime Configuration - a flexible design that - * allows data per user + remote config to be accessed together. - * - * Once we access the Runtime Configuration, we then extract the autofill-specific settings via - * `runtimeConfig.userPreferences.features.autofill.settings` and validate that separately. - * - * The 2-step validation occurs because RuntimeConfiguration will be coming from a shared library - * and does not know about the shape of Autofill specific settings. - * - * @returns {Promise} - */ - async getFeatureToggles() { - try { - const runtimeConfig = await this._getRuntimeConfiguration(); - const autofillSettings = (0, _index.validate)(runtimeConfig.userPreferences?.features?.autofill?.settings, _validatorsZod.autofillSettingsSchema); - return autofillSettings.featureToggles; - } catch (e) { - // these are the fallbacks for when a platform hasn't implemented the calls above. - if (this.globalConfig.isDDGTestMode) { - console.log('isDDGTestMode: getFeatureToggles: ❌', e); + /** + * Used by the email web app + * Provides functionality to log the user out + */ + removeUserData() { + try { + return window.EmailInterface.removeCredentials(); + } catch (e) { + if (this.globalConfig.isDDGTestMode) { + console.error(e); + } } - return Settings.defaults.featureToggles; } - } - - /** - * If the platform in question is happy to derive it's 'enabled' state from the RuntimeConfiguration, - * then they should use this. Currently only Windows supports this, but we aim to move all platforms to - * support this going forward. - * @returns {Promise} - */ - async getEnabled() { - try { - const runtimeConfig = await this._getRuntimeConfiguration(); - const enabled = (0, _autofillUtils.autofillEnabled)(runtimeConfig); - return enabled; - } catch (e) { - // these are the fallbacks for when a platform hasn't implemented the calls above. (like on android) - if (this.globalConfig.isDDGTestMode) { - console.log('isDDGTestMode: getEnabled: ❌', e); - } - return null; + /** + * Used by the email web app + * Provides functionality to close the window after in-context sign-up or sign-in + */ + closeEmailProtection() { + this.deviceApi.request(new CloseEmailProtectionTabCall(null)); } - } - - /** - * Get runtime configuration, but only once. - * - * Some platforms may be reading this directly from inlined variables, whilst others - * may make a DeviceApiCall. - * - * Currently, it's only read once - but we should be open to the idea that we may need - * this to be called multiple times in the future. - * - * @returns {Promise} - * @throws - * @private - */ - async _getRuntimeConfiguration() { - if (this._runtimeConfiguration) return this._runtimeConfiguration; - const runtimeConfig = await this.deviceApi.request(new _deviceApiCalls.GetRuntimeConfigurationCall(null)); - this._runtimeConfiguration = runtimeConfig; - return this._runtimeConfiguration; - } - - /** - * Available Input Types are boolean indicators to represent which input types the - * current **user** has data available for. - * - * @returns {Promise} - */ - async getAvailableInputTypes() { - try { - // This info is not needed in the topFrame, so we avoid calling the native app - if (this.globalConfig.isTopFrame) { - return Settings.defaults.availableInputTypes; - } - return await this.deviceApi.request(new _deviceApiCalls.GetAvailableInputTypesCall(null)); - } catch (e) { - if (this.globalConfig.isDDGTestMode) { - console.log('isDDGTestMode: getAvailableInputTypes: ❌', e); - } - return Settings.defaults.availableInputTypes; + addLogoutListener(handler) { + if (!this.globalConfig.isDDGDomain) + return; + window.addEventListener("message", (e) => { + if (this.globalConfig.isDDGDomain && e.data.emailProtectionSignedOut) { + handler(); + } + }); } - } + /** Noop */ + firePixel(_pixelParam) { + } + }; - /** - * To 'refresh' settings means to re-call APIs to determine new state. This may - * only occur once per page, but it must be done before any page scanning/decorating can happen - * - * @returns {Promise<{ - * availableInputTypes: AvailableInputTypes, - * featureToggles: AutofillFeatureToggles, - * enabled: boolean | null - * }>} - */ - async refresh() { - this.setEnabled(await this.getEnabled()); - this.setFeatureToggles(await this.getFeatureToggles()); - this.setAvailableInputTypes(await this.getAvailableInputTypes()); + // src/UI/styles/autofill-tooltip-styles.css + var autofill_tooltip_styles_default = ":root {\n color-scheme: light dark;\n}\n\n.wrapper *, .wrapper *::before, .wrapper *::after {\n box-sizing: border-box;\n}\n.wrapper {\n position: fixed;\n top: 0;\n left: 0;\n padding: 0;\n font-family: 'DDG_ProximaNova', 'Proxima Nova', system-ui, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',\n 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;\n -webkit-font-smoothing: antialiased;\n z-index: 2147483647;\n}\n.wrapper--data {\n font-family: 'SF Pro Text', system-ui, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',\n 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;\n}\n:not(.top-autofill) .tooltip {\n position: absolute;\n width: 300px;\n max-width: calc(100vw - 25px);\n transform: translate(-1000px, -1000px);\n z-index: 2147483647;\n}\n.tooltip--data, #topAutofill {\n background-color: rgba(242, 240, 240, 1);\n -webkit-backdrop-filter: blur(40px);\n backdrop-filter: blur(40px);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip--data, #topAutofill {\n background: rgb(100, 98, 102, .9);\n }\n}\n.tooltip--data {\n padding: 6px;\n font-size: 13px;\n line-height: 14px;\n width: 315px;\n max-height: 290px;\n overflow-y: auto;\n}\n.top-autofill .tooltip--data {\n min-height: 100vh;\n}\n.tooltip--data.tooltip--incontext-signup {\n width: 360px;\n}\n:not(.top-autofill) .tooltip--data {\n top: 100%;\n left: 100%;\n border: 0.5px solid rgba(255, 255, 255, 0.2);\n border-radius: 6px;\n box-shadow: 0 10px 20px rgba(0, 0, 0, 0.32);\n}\n@media (prefers-color-scheme: dark) {\n :not(.top-autofill) .tooltip--data {\n border: 1px solid rgba(255, 255, 255, 0.2);\n }\n}\n:not(.top-autofill) .tooltip--email {\n top: calc(100% + 6px);\n right: calc(100% - 48px);\n padding: 8px;\n border: 1px solid #D0D0D0;\n border-radius: 10px;\n background-color: #FFFFFF;\n font-size: 14px;\n line-height: 1.3;\n color: #333333;\n box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);\n}\n.tooltip--email__caret {\n position: absolute;\n transform: translate(-1000px, -1000px);\n z-index: 2147483647;\n}\n.tooltip--email__caret::before,\n.tooltip--email__caret::after {\n content: \"\";\n width: 0;\n height: 0;\n border-left: 10px solid transparent;\n border-right: 10px solid transparent;\n display: block;\n border-bottom: 8px solid #D0D0D0;\n position: absolute;\n right: -28px;\n}\n.tooltip--email__caret::before {\n border-bottom-color: #D0D0D0;\n top: -1px;\n}\n.tooltip--email__caret::after {\n border-bottom-color: #FFFFFF;\n top: 0px;\n}\n\n/* Buttons */\n.tooltip__button {\n display: flex;\n width: 100%;\n padding: 8px 8px 8px 0px;\n font-family: inherit;\n color: inherit;\n background: transparent;\n border: none;\n border-radius: 6px;\n}\n.tooltip__button.currentFocus,\n.wrapper:not(.top-autofill) .tooltip__button:hover {\n background-color: #3969EF;\n color: #FFFFFF;\n}\n\n/* Data autofill tooltip specific */\n.tooltip__button--data {\n position: relative;\n min-height: 48px;\n flex-direction: row;\n justify-content: flex-start;\n font-size: inherit;\n font-weight: 500;\n line-height: 16px;\n text-align: left;\n border-radius: 3px;\n}\n.tooltip--data__item-container {\n max-height: 220px;\n overflow: auto;\n}\n.tooltip__button--data:first-child {\n margin-top: 0;\n}\n.tooltip__button--data:last-child {\n margin-bottom: 0;\n}\n.tooltip__button--data::before {\n content: '';\n flex-shrink: 0;\n display: block;\n width: 32px;\n height: 32px;\n margin: 0 8px;\n background-size: 20px 20px;\n background-repeat: no-repeat;\n background-position: center center;\n}\n#provider_locked::after {\n position: absolute;\n content: '';\n flex-shrink: 0;\n display: block;\n width: 32px;\n height: 32px;\n margin: 0 8px;\n background-size: 11px 13px;\n background-repeat: no-repeat;\n background-position: right bottom;\n}\n.tooltip__button--data.currentFocus:not(.tooltip__button--data--bitwarden)::before,\n.wrapper:not(.top-autofill) .tooltip__button--data:not(.tooltip__button--data--bitwarden):hover::before {\n filter: invert(100%);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip__button--data:not(.tooltip__button--data--bitwarden)::before,\n .tooltip__button--data:not(.tooltip__button--data--bitwarden)::before {\n filter: invert(100%);\n opacity: .9;\n }\n}\n.tooltip__button__text-container {\n margin: auto 0;\n}\n.label {\n display: block;\n font-weight: 400;\n letter-spacing: -0.25px;\n color: rgba(0,0,0,.8);\n font-size: 13px;\n line-height: 1;\n}\n.label + .label {\n margin-top: 2px;\n}\n.label.label--medium {\n font-weight: 500;\n letter-spacing: -0.25px;\n color: rgba(0,0,0,.9);\n}\n.label.label--small {\n font-size: 11px;\n font-weight: 400;\n letter-spacing: 0.06px;\n color: rgba(0,0,0,0.6);\n}\n@media (prefers-color-scheme: dark) {\n .tooltip--data .label {\n color: #ffffff;\n }\n .tooltip--data .label--medium {\n color: #ffffff;\n }\n .tooltip--data .label--small {\n color: #cdcdcd;\n }\n}\n.tooltip__button.currentFocus .label,\n.wrapper:not(.top-autofill) .tooltip__button:hover .label {\n color: #FFFFFF;\n}\n\n.tooltip__button--manage {\n font-size: 13px;\n padding: 5px 9px;\n border-radius: 3px;\n margin: 0;\n}\n\n/* Icons */\n.tooltip__button--data--credentials::before,\n.tooltip__button--data--credentials__current::before {\n background-size: 28px 28px;\n background-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNS4zMzQgNi42NjdhMiAyIDAgMSAwIDAgNCAyIDIgMCAwIDAgMC00Wm0tLjY2NyAyYS42NjcuNjY3IDAgMSAxIDEuMzMzIDAgLjY2Ny42NjcgMCAwIDEtMS4zMzMgMFoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTQuNjY3IDRhNS4zMzMgNS4zMzMgMCAwIDAtNS4xODggNi41NzhsLTUuMjg0IDUuMjg0YS42NjcuNjY3IDAgMCAwLS4xOTUuNDcxdjNjMCAuMzY5LjI5OC42NjcuNjY3LjY2N2gyLjY2NmMuNzM3IDAgMS4zMzQtLjU5NyAxLjMzNC0xLjMzM1YxOGguNjY2Yy43MzcgMCAxLjMzNC0uNTk3IDEuMzM0LTEuMzMzdi0xLjMzNEgxMmMuMTc3IDAgLjM0Ni0uMDcuNDcxLS4xOTVsLjY4OC0uNjg4QTUuMzMzIDUuMzMzIDAgMSAwIDE0LjY2NyA0Wm0tNCA1LjMzM2E0IDQgMCAxIDEgMi41NTUgMy43MzIuNjY3LjY2NyAwIDAgMC0uNzEzLjE1bC0uNzg1Ljc4NUgxMGEuNjY3LjY2NyAwIDAgMC0uNjY3LjY2N3YySDhhLjY2Ny42NjcgMCAwIDAtLjY2Ny42NjZ2MS4zMzRoLTJ2LTIuMDU4bDUuMzY1LTUuMzY0YS42NjcuNjY3IDAgMCAwIC4xNjMtLjY3NyAzLjk5NiAzLjk5NiAwIDAgMS0uMTk0LTEuMjM1WiIgY2xpcC1ydWxlPSJldmVub2RkIi8+Cjwvc3ZnPgo=');\n}\n.tooltip__button--data--credentials__new::before {\n background-size: 28px 28px;\n background-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBmaWxsPSIjMDAwIiBkPSJNOC4wNDcgNC42MjVDNy45MzcgNC4xMjUgNy44NjIgNCA3LjUgNGMtLjM2MiAwLS40MzguMTI1LS41NDcuNjI1LS4wNjguMzEtLjE3NyAxLjMzOC0uMjUxIDIuMDc3LS43MzguMDc0LTEuNzY3LjE4My0yLjA3Ny4yNTEtLjUuMTEtLjYyNS4xODQtLjYyNS41NDcgMCAuMzYyLjEyNS40MzcuNjI1LjU0Ny4zMS4wNjcgMS4zMzYuMTc3IDIuMDc0LjI1LjA3My43NjcuMTg1IDEuODQyLjI1NCAyLjA3OC4xMS4zNzUuMTg1LjYyNS41NDcuNjI1LjM2MiAwIC40MzgtLjEyNS41NDctLjYyNS4wNjgtLjMxLjE3Ny0xLjMzNi4yNS0yLjA3NC43NjctLjA3MyAxLjg0Mi0uMTg1IDIuMDc4LS4yNTQuMzc1LS4xMS42MjUtLjE4NS42MjUtLjU0NyAwLS4zNjMtLjEyNS0uNDM4LS42MjUtLjU0Ny0uMzEtLjA2OC0xLjMzOS0uMTc3LTIuMDc3LS4yNTEtLjA3NC0uNzM5LS4xODMtMS43NjctLjI1MS0yLjA3N1oiLz4KICAgIDxwYXRoIGZpbGw9IiMwMDAiIGQ9Ik0xNC42ODEgNS4xOTljLS43NjYgMC0xLjQ4Mi4yMS0yLjA5My41NzhhLjYzNi42MzYgMCAwIDEtLjY1NS0xLjA5IDUuMzQgNS4zNCAwIDEgMSAxLjMwMiA5LjcyMmwtLjc3NS43NzZhLjYzNi42MzYgMCAwIDEtLjQ1LjE4NmgtMS4zOTh2MS42NWMwIC40OTMtLjQuODkzLS44OTMuODkzSDguNTc4djEuMTQxYzAgLjQ5NC0uNC44OTMtLjg5NC44OTNINC42MzZBLjYzNi42MzYgMCAwIDEgNCAxOS4zMTNWMTYuMjZjMC0uMTY5LjA2Ny0uMzMuMTg2LS40NWw1LjU2Mi01LjU2MmEuNjM2LjYzNiAwIDEgMSAuOS45bC01LjM3NiA1LjM3NXYyLjE1M2gyLjAzNHYtMS4zOTljMC0uMzUuMjg1LS42MzYuNjM2LS42MzZIOS4zNHYtMS45MDdjMC0uMzUxLjI4NC0uNjM2LjYzNS0uNjM2aDEuNzcxbC44NjQtLjg2M2EuNjM2LjYzNiAwIDAgMSAuNjY4LS4xNDcgNC4wNjkgNC4wNjkgMCAxIDAgMS40MDItNy44OVoiLz4KICAgIDxwYXRoIGZpbGw9IiMwMDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTEzLjYyNSA4LjQ5OWExLjg3NSAxLjg3NSAwIDEgMSAzLjc1IDAgMS44NzUgMS44NzUgMCAwIDEtMy43NSAwWm0xLjg3NS0uNjI1YS42MjUuNjI1IDAgMSAwIDAgMS4yNS42MjUuNjI1IDAgMCAwIDAtMS4yNVoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZmlsbD0iIzAwMCIgZD0iTTQuNjI1IDEyLjEyNWEuNjI1LjYyNSAwIDEgMCAwLTEuMjUuNjI1LjYyNSAwIDAgMCAwIDEuMjVaIi8+Cjwvc3ZnPgo=');\n}\n.tooltip__button--data--creditCards::before {\n background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBkPSJNNSA5Yy0uNTUyIDAtMSAuNDQ4LTEgMXYyYzAgLjU1Mi40NDggMSAxIDFoM2MuNTUyIDAgMS0uNDQ4IDEtMXYtMmMwLS41NTItLjQ0OC0xLTEtMUg1eiIgZmlsbD0iIzAwMCIvPgogICAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xIDZjMC0yLjIxIDEuNzktNCA0LTRoMTRjMi4yMSAwIDQgMS43OSA0IDR2MTJjMCAyLjIxLTEuNzkgNC00IDRINWMtMi4yMSAwLTQtMS43OS00LTRWNnptNC0yYy0xLjEwNSAwLTIgLjg5NS0yIDJ2OWgxOFY2YzAtMS4xMDUtLjg5NS0yLTItMkg1em0wIDE2Yy0xLjEwNSAwLTItLjg5NS0yLTJoMThjMCAxLjEwNS0uODk1IDItMiAySDV6IiBmaWxsPSIjMDAwIi8+Cjwvc3ZnPgo=');\n}\n.tooltip__button--data--identities::before {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTEyIDIxYzIuMTQzIDAgNC4xMTEtLjc1IDUuNjU3LTItLjYyNi0uNTA2LTEuMzE4LS45MjctMi4wNi0xLjI1LTEuMS0uNDgtMi4yODUtLjczNS0zLjQ4Ni0uNzUtMS4yLS4wMTQtMi4zOTIuMjExLTMuNTA0LjY2NC0uODE3LjMzMy0xLjU4Ljc4My0yLjI2NCAxLjMzNiAxLjU0NiAxLjI1IDMuNTE0IDIgNS42NTcgMnptNC4zOTctNS4wODNjLjk2Ny40MjIgMS44NjYuOTggMi42NzIgMS42NTVDMjAuMjc5IDE2LjAzOSAyMSAxNC4xMDQgMjEgMTJjMC00Ljk3LTQuMDMtOS05LTlzLTkgNC4wMy05IDljMCAyLjEwNC43MjIgNC4wNCAxLjkzMiA1LjU3Mi44NzQtLjczNCAxLjg2LTEuMzI4IDIuOTIxLTEuNzYgMS4zNi0uNTU0IDIuODE2LS44MyA0LjI4My0uODExIDEuNDY3LjAxOCAyLjkxNi4zMyA0LjI2LjkxNnpNMTIgMjNjNi4wNzUgMCAxMS00LjkyNSAxMS0xMVMxOC4wNzUgMSAxMiAxIDEgNS45MjUgMSAxMnM0LjkyNSAxMSAxMSAxMXptMy0xM2MwIDEuNjU3LTEuMzQzIDMtMyAzcy0zLTEuMzQzLTMtMyAxLjM0My0zIDMtMyAzIDEuMzQzIDMgM3ptMiAwYzAgMi43NjEtMi4yMzkgNS01IDVzLTUtMi4yMzktNS01IDIuMjM5LTUgNS01IDUgMi4yMzkgNSA1eiIgZmlsbD0iIzAwMCIvPgo8L3N2Zz4=');\n}\n.tooltip__button--data--credentials.tooltip__button--data--bitwarden::before,\n.tooltip__button--data--credentials__current.tooltip__button--data--bitwarden::before {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiByeD0iOCIgZmlsbD0iIzE3NUREQyIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTE4LjU2OTYgNS40MzM1NUMxOC41MDg0IDUuMzc0NDIgMTguNDM0NyA1LjMyNzYzIDE4LjM1MzEgNS4yOTYxMUMxOC4yNzE1IDUuMjY0NiAxOC4xODM3IDUuMjQ5MDQgMTguMDk1MyA1LjI1MDQxSDUuOTIxOTFDNS44MzMyNiA1LjI0NzI5IDUuNzQ0OTMgNS4yNjIwNSA1LjY2MzA0IDUuMjkzNjdDNS41ODExNSA1LjMyNTI5IDUuNTA3NjUgNS4zNzMwMiA1LjQ0NzYyIDUuNDMzNTVDNS4zMjE3IDUuNTUwMTMgNS4yNTA2NSA1LjcwODE1IDUuMjUgNS44NzMxVjEzLjM4MjFDNS4yNTMzNiAxMy45NTM1IDUuMzc0MDggMTQuNTE5MSA1LjYwNTcyIDE1LjA0ODdDNS44MTkzMSAxNS41NzI4IDYuMTEyMDcgMTYuMDY2MSA2LjQ3NTI0IDE2LjUxMzlDNi44NDIgMTYuOTY4MyA3LjI1OTI5IDE3LjM4NTcgNy43MjAyNSAxNy43NTkzQzguMTQwNTMgMTguMTI1NiA4LjU4OTcxIDE4LjQ2MjMgOS4wNjQwNyAxOC43NjY2QzkuNDU5MzEgMTkuMDIzIDkuOTEzODMgMTkuMjc5NCAxMC4zNDg2IDE5LjUxNzVDMTAuNzgzNCAxOS43NTU2IDExLjA5OTYgMTkuOTIwNCAxMS4yNzc0IDE5Ljk5MzdDMTEuNDU1MyAyMC4wNjY5IDExLjYxMzQgMjAuMTQwMiAxMS43MTIyIDIwLjE5NTFDMTEuNzk5MiAyMC4yMzEzIDExLjg5MzUgMjAuMjUgMTEuOTg4OCAyMC4yNUMxMi4wODQyIDIwLjI1IDEyLjE3ODUgMjAuMjMxMyAxMi4yNjU1IDIwLjE5NTFDMTIuNDIxMiAyMC4xMzYzIDEyLjU3MjkgMjAuMDY5IDEyLjcyIDE5Ljk5MzdDMTIuNzcxMSAxOS45Njc0IDEyLjgzMzUgMTkuOTM2NiAxMi45MDY5IDE5LjkwMDRDMTMuMDg5MSAxOS44MTA1IDEzLjMzODggMTkuNjg3MiAxMy42NDg5IDE5LjUxNzVDMTQuMDgzNiAxOS4yNzk0IDE0LjUxODQgMTkuMDIzIDE0LjkzMzQgMTguNzY2NkMxNS40MDQgMTguNDU3NyAxNS44NTI4IDE4LjEyMTIgMTYuMjc3MiAxNy43NTkzQzE2LjczMzEgMTcuMzgwOSAxNy4xNDk5IDE2Ljk2NCAxNy41MjIyIDE2LjUxMzlDMTcuODc4IDE2LjA2MTcgMTguMTcwMiAxNS41NjkzIDE4LjM5MTcgMTUuMDQ4N0MxOC42MjM0IDE0LjUxOTEgMTguNzQ0MSAxMy45NTM1IDE4Ljc0NzQgMTMuMzgyMVY1Ljg3MzFDMTguNzU1NyA1Ljc5MjE0IDE4Ljc0MzkgNS43MTA1IDE4LjcxMzEgNS42MzQzNUMxOC42ODIzIDUuNTU4MiAxOC42MzMyIDUuNDg5NTQgMTguNTY5NiA1LjQzMzU1Wk0xNy4wMDg0IDEzLjQ1NTNDMTcuMDA4NCAxNi4xODQyIDEyLjAwODYgMTguNTI4NSAxMi4wMDg2IDE4LjUyODVWNi44NjIwOUgxNy4wMDg0VjEzLjQ1NTNaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K');\n}\n#provider_locked:after {\n background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTEiIGhlaWdodD0iMTMiIHZpZXdCb3g9IjAgMCAxMSAxMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEgNy42MDA1N1Y3LjYwMjVWOS41MjI1QzEgMTAuMDgwMSAxLjIyMTUxIDEwLjYxNDkgMS42MTU4MSAxMS4wMDkyQzIuMDEwMSAxMS40MDM1IDIuNTQ0ODggMTEuNjI1IDMuMTAyNSAxMS42MjVINy4yNzI1QzcuNTQ4NjEgMTEuNjI1IDcuODIyMDEgMTEuNTcwNiA4LjA3NzA5IDExLjQ2NUM4LjMzMjE4IDExLjM1OTMgOC41NjM5NiAxMS4yMDQ0IDguNzU5MTkgMTEuMDA5MkM4Ljk1NDQzIDEwLjgxNCA5LjEwOTMgMTAuNTgyMiA5LjIxNDk2IDEwLjMyNzFDOS4zMjA2MiAxMC4wNzIgOS4zNzUgOS43OTg2MSA5LjM3NSA5LjUyMjVMOS4zNzUgNy42MDI1TDkuMzc1IDcuNjAwNTdDOS4zNzQxNSA3LjE2MTMxIDkuMjM1NzQgNi43MzMzNSA4Ljk3OTIyIDYuMzc2NzhDOC44NzY4MyA2LjIzNDQ2IDguNzU3NjggNi4xMDYzNyA4LjYyNSA1Ljk5NDg5VjUuMTg3NUM4LjYyNSA0LjI3NTgyIDguMjYyODQgMy40MDE0OCA3LjYxODE4IDIuNzU2ODJDNi45NzM1MiAyLjExMjE2IDYuMDk5MTggMS43NSA1LjE4NzUgMS43NUM0LjI3NTgyIDEuNzUgMy40MDE0OCAyLjExMjE2IDIuNzU2ODIgMi43NTY4MkMyLjExMjE2IDMuNDAxNDggMS43NSA0LjI3NTgyIDEuNzUgNS4xODc1VjUuOTk0ODlDMS42MTczMiA2LjEwNjM3IDEuNDk4MTcgNi4yMzQ0NiAxLjM5NTc4IDYuMzc2NzhDMS4xMzkyNiA2LjczMzM1IDEuMDAwODUgNy4xNjEzMSAxIDcuNjAwNTdaTTQuOTY4NyA0Ljk2ODdDNS4wMjY5NCA0LjkxMDQ3IDUuMTA1MzIgNC44NzY5OSA1LjE4NzUgNC44NzUwN0M1LjI2OTY4IDQuODc2OTkgNS4zNDgwNiA0LjkxMDQ3IDUuNDA2MyA0Ljk2ODdDNS40NjU0MiA1LjAyNzgzIDUuNDk5MDQgNS4xMDc3NCA1LjUgNS4xOTEzVjUuNUg0Ljg3NVY1LjE5MTNDNC44NzU5NiA1LjEwNzc0IDQuOTA5NTggNS4wMjc4MyA0Ljk2ODcgNC45Njg3WiIgZmlsbD0iIzIyMjIyMiIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIyIi8+Cjwvc3ZnPgo=');\n}\n\nhr {\n display: block;\n margin: 5px 9px;\n border: none; /* reset the border */\n border-top: 1px solid rgba(0,0,0,.1);\n}\n\nhr:first-child {\n display: none;\n}\n\n@media (prefers-color-scheme: dark) {\n hr {\n border-top: 1px solid rgba(255,255,255,.2);\n }\n}\n\n#privateAddress {\n align-items: flex-start;\n}\n#personalAddress::before,\n#privateAddress::before,\n#incontextSignup::before,\n#personalAddress.currentFocus::before,\n#personalAddress:hover::before,\n#privateAddress.currentFocus::before,\n#privateAddress:hover::before {\n filter: none;\n /* This is the same icon as `daxBase64` in `src/Form/logo-svg.js` */\n background-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMTI4IDEyOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0ibTY0IDEyOGMzNS4zNDYgMCA2NC0yOC42NTQgNjQtNjRzLTI4LjY1NC02NC02NC02NC02NCAyOC42NTQtNjQgNjQgMjguNjU0IDY0IDY0IDY0eiIgZmlsbD0iI2RlNTgzMyIgZmlsbC1ydWxlPSJldmVub2RkIi8+CiAgICA8cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Im03MyAxMTEuNzVjMC0uNS4xMjMtLjYxNC0xLjQ2Ni0zLjc4Mi00LjIyNC04LjQ1OS04LjQ3LTIwLjM4NC02LjU0LTI4LjA3NS4zNTMtMS4zOTctMy45NzgtNTEuNzQ0LTcuMDQtNTMuMzY1LTMuNDAyLTEuODEzLTcuNTg4LTQuNjktMTEuNDE4LTUuMzMtMS45NDMtLjMxLTQuNDktLjE2NC02LjQ4Mi4xMDUtLjM1My4wNDctLjM2OC42ODMtLjAzLjc5OCAxLjMwOC40NDMgMi44OTUgMS4yMTIgMy44MyAyLjM3NS4xNzguMjItLjA2LjU2Ni0uMzQyLjU3Ny0uODgyLjAzMi0yLjQ4Mi40MDItNC41OTMgMi4xOTUtLjI0NC4yMDctLjA0MS41OTIuMjczLjUzIDQuNTM2LS44OTcgOS4xNy0uNDU1IDExLjkgMi4wMjcuMTc3LjE2LjA4NC40NS0uMTQ3LjUxMi0yMy42OTQgNi40NC0xOS4wMDMgMjcuMDUtMTIuNjk2IDUyLjM0NCA1LjYxOSAyMi41MyA3LjczMyAyOS43OTIgOC40IDMyLjAwNGEuNzE4LjcxOCAwIDAgMCAuNDIzLjQ2N2M4LjE1NiAzLjI0OCAyNS45MjggMy4zOTIgMjUuOTI4LTIuMTMyeiIgZmlsbD0iI2RkZCIgZmlsbC1ydWxlPSJldmVub2RkIi8+CiAgICA8cGF0aCBkPSJtNzYuMjUgMTE2LjVjLTIuODc1IDEuMTI1LTguNSAxLjYyNS0xMS43NSAxLjYyNS00Ljc2NCAwLTExLjYyNS0uNzUtMTQuMTI1LTEuODc1LTEuNTQ0LTQuNzUxLTYuMTY0LTE5LjQ4LTEwLjcyNy0zOC4xODVsLS40NDctMS44MjctLjAwNC0uMDE1Yy01LjQyNC0yMi4xNTctOS44NTUtNDAuMjUzIDE0LjQyNy00NS45MzguMjIyLS4wNTIuMzMtLjMxNy4xODQtLjQ5Mi0yLjc4Ni0zLjMwNS04LjAwNS00LjM4OC0xNC42MDUtMi4xMTEtLjI3LjA5My0uNTA2LS4xOC0uMzM3LS40MTIgMS4yOTQtMS43ODMgMy44MjMtMy4xNTUgNS4wNzEtMy43NTYuMjU4LS4xMjQuMjQyLS41MDItLjAzLS41ODhhMjcuODc3IDI3Ljg3NyAwIDAgMCAtMy43NzItLjljLS4zNy0uMDU5LS40MDMtLjY5My0uMDMyLS43NDMgOS4zNTYtMS4yNTkgMTkuMTI1IDEuNTUgMjQuMDI4IDcuNzI2YS4zMjYuMzI2IDAgMCAwIC4xODYuMTE0YzE3Ljk1MiAzLjg1NiAxOS4yMzggMzIuMjM1IDE3LjE3IDMzLjUyOC0uNDA4LjI1NS0xLjcxNS4xMDgtMy40MzgtLjA4NS02Ljk4Ni0uNzgxLTIwLjgxOC0yLjMyOS05LjQwMiAxOC45NDguMTEzLjIxLS4wMzYuNDg4LS4yNzIuNTI1LTYuNDM4IDEgMS44MTIgMjEuMTczIDcuODc1IDM0LjQ2MXoiIGZpbGw9IiNmZmYiLz4KICAgIDxwYXRoIGQ9Im04NC4yOCA5MC42OThjLTEuMzY3LS42MzMtNi42MjEgMy4xMzUtMTAuMTEgNi4wMjgtLjcyOC0xLjAzMS0yLjEwMy0xLjc4LTUuMjAzLTEuMjQyLTIuNzEzLjQ3Mi00LjIxMSAxLjEyNi00Ljg4IDIuMjU0LTQuMjgzLTEuNjIzLTExLjQ4OC00LjEzLTEzLjIyOS0xLjcxLTEuOTAyIDIuNjQ2LjQ3NiAxNS4xNjEgMy4wMDMgMTYuNzg2IDEuMzIuODQ5IDcuNjMtMy4yMDggMTAuOTI2LTYuMDA1LjUzMi43NDkgMS4zODggMS4xNzggMy4xNDggMS4xMzcgMi42NjItLjA2MiA2Ljk3OS0uNjgxIDcuNjQ5LTEuOTIxLjA0LS4wNzUuMDc1LS4xNjQuMTA1LS4yNjYgMy4zODggMS4yNjYgOS4zNSAyLjYwNiAxMC42ODIgMi40MDYgMy40Ny0uNTIxLS40ODQtMTYuNzIzLTIuMDktMTcuNDY3eiIgZmlsbD0iIzNjYTgyYiIvPgogICAgPHBhdGggZD0ibTc0LjQ5IDk3LjA5N2MuMTQ0LjI1Ni4yNi41MjYuMzU4LjguNDgzIDEuMzUyIDEuMjcgNS42NDguNjc0IDYuNzA5LS41OTUgMS4wNjItNC40NTkgMS41NzQtNi44NDMgMS42MTVzLTIuOTItLjgzMS0zLjQwMy0yLjE4MWMtLjM4Ny0xLjA4MS0uNTc3LTMuNjIxLS41NzItNS4wNzUtLjA5OC0yLjE1OC42OS0yLjkxNiA0LjMzNC0zLjUwNiAyLjY5Ni0uNDM2IDQuMTIxLjA3MSA0Ljk0NC45NCAzLjgyOC0yLjg1NyAxMC4yMTUtNi44ODkgMTAuODM4LTYuMTUyIDMuMTA2IDMuNjc0IDMuNDk5IDEyLjQyIDIuODI2IDE1LjkzOS0uMjIgMS4xNTEtMTAuNTA1LTEuMTM5LTEwLjUwNS0yLjM4IDAtNS4xNTItMS4zMzctNi41NjUtMi42NS02Ljcxem0tMjIuNTMtMS42MDljLjg0My0xLjMzMyA3LjY3NC4zMjUgMTEuNDI0IDEuOTkzIDAgMC0uNzcgMy40OTEuNDU2IDcuNjA0LjM1OSAxLjIwMy04LjYyNyA2LjU1OC05LjggNS42MzctMS4zNTUtMS4wNjUtMy44NS0xMi40MzItMi4wOC0xNS4yMzR6IiBmaWxsPSIjNGNiYTNjIi8+CiAgICA8cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Im01NS4yNjkgNjguNDA2Yy41NTMtMi40MDMgMy4xMjctNi45MzIgMTIuMzIxLTYuODIyIDQuNjQ4LS4wMTkgMTAuNDIyLS4wMDIgMTQuMjUtLjQzNmE1MS4zMTIgNTEuMzEyIDAgMCAwIDEyLjcyNi0zLjA5NWMzLjk4LTEuNTE5IDUuMzkyLTEuMTggNS44ODctLjI3Mi41NDQuOTk5LS4wOTcgMi43MjItMS40ODggNC4zMDktMi42NTYgMy4wMy03LjQzMSA1LjM4LTE1Ljg2NSA2LjA3Ni04LjQzMy42OTgtMTQuMDItMS41NjUtMTYuNDI1IDIuMTE4LTEuMDM4IDEuNTg5LS4yMzYgNS4zMzMgNy45MiA2LjUxMiAxMS4wMiAxLjU5IDIwLjA3Mi0xLjkxNyAyMS4xOS4yMDEgMS4xMTkgMi4xMTgtNS4zMjMgNi40MjgtMTYuMzYyIDYuNTE4cy0xNy45MzQtMy44NjUtMjAuMzc5LTUuODNjLTMuMTAyLTIuNDk1LTQuNDktNi4xMzMtMy43NzUtOS4yNzl6IiBmaWxsPSIjZmMzIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz4KICAgIDxnIGZpbGw9IiMxNDMwN2UiIG9wYWNpdHk9Ii44Ij4KICAgICAgPHBhdGggZD0ibTY5LjMyNyA0Mi4xMjdjLjYxNi0xLjAwOCAxLjk4MS0xLjc4NiA0LjIxNi0xLjc4NiAyLjIzNCAwIDMuMjg1Ljg4OSA0LjAxMyAxLjg4LjE0OC4yMDItLjA3Ni40NC0uMzA2LjM0YTU5Ljg2OSA1OS44NjkgMCAwIDEgLS4xNjgtLjA3M2MtLjgxNy0uMzU3LTEuODItLjc5NS0zLjU0LS44Mi0xLjgzOC0uMDI2LTIuOTk3LjQzNS0zLjcyNy44MzEtLjI0Ni4xMzQtLjYzNC0uMTMzLS40ODgtLjM3MnptLTI1LjE1NyAxLjI5YzIuMTctLjkwNyAzLjg3Ni0uNzkgNS4wODEtLjUwNC4yNTQuMDYuNDMtLjIxMy4yMjctLjM3Ny0uOTM1LS43NTUtMy4wMy0xLjY5Mi01Ljc2LS42NzQtMi40MzcuOTA5LTMuNTg1IDIuNzk2LTMuNTkyIDQuMDM4LS4wMDIuMjkyLjYuMzE3Ljc1Ni4wNy40Mi0uNjcgMS4xMi0xLjY0NiAzLjI4OS0yLjU1M3oiLz4KICAgICAgPHBhdGggY2xpcC1ydWxlPSJldmVub2RkIiBkPSJtNzUuNDQgNTUuOTJhMy40NyAzLjQ3IDAgMCAxIC0zLjQ3NC0zLjQ2MiAzLjQ3IDMuNDcgMCAwIDEgMy40NzUtMy40NiAzLjQ3IDMuNDcgMCAwIDEgMy40NzQgMy40NiAzLjQ3IDMuNDcgMCAwIDEgLTMuNDc1IDMuNDYyem0yLjQ0Ny00LjYwOGEuODk5Ljg5OSAwIDAgMCAtMS43OTkgMGMwIC40OTQuNDA1Ljg5NS45Ljg5NS40OTkgMCAuOS0uNC45LS44OTV6bS0yNS40NjQgMy41NDJhNC4wNDIgNC4wNDIgMCAwIDEgLTQuMDQ5IDQuMDM3IDQuMDQ1IDQuMDQ1IDAgMCAxIC00LjA1LTQuMDM3IDQuMDQ1IDQuMDQ1IDAgMCAxIDQuMDUtNC4wMzcgNC4wNDUgNC4wNDUgMCAwIDEgNC4wNSA0LjAzN3ptLTEuMTkzLTEuMzM4YTEuMDUgMS4wNSAwIDAgMCAtMi4wOTcgMCAxLjA0OCAxLjA0OCAwIDAgMCAyLjA5NyAweiIgZmlsbC1ydWxlPSJldmVub2RkIi8+CiAgICA8L2c+CiAgICA8cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Im02NCAxMTcuNzVjMjkuNjg1IDAgNTMuNzUtMjQuMDY1IDUzLjc1LTUzLjc1cy0yNC4wNjUtNTMuNzUtNTMuNzUtNTMuNzUtNTMuNzUgMjQuMDY1LTUzLjc1IDUzLjc1IDI0LjA2NSA1My43NSA1My43NSA1My43NXptMCA1YzMyLjQ0NyAwIDU4Ljc1LTI2LjMwMyA1OC43NS01OC43NXMtMjYuMzAzLTU4Ljc1LTU4Ljc1LTU4Ljc1LTU4Ljc1IDI2LjMwMy01OC43NSA1OC43NSAyNi4zMDMgNTguNzUgNTguNzUgNTguNzV6IiBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz4KPC9zdmc+');\n}\n\n/* Email tooltip specific */\n.tooltip__button--email {\n flex-direction: column;\n justify-content: center;\n align-items: flex-start;\n font-size: 14px;\n padding: 4px 8px;\n}\n.tooltip__button--email__primary-text {\n font-weight: bold;\n}\n.tooltip__button--email__secondary-text {\n font-size: 12px;\n}\n\n/* Email Protection signup notice */\n:not(.top-autofill) .tooltip--email-signup {\n text-align: left;\n color: #222222;\n padding: 16px 20px;\n width: 380px;\n}\n\n.tooltip--email-signup h1 {\n font-weight: 700;\n font-size: 16px;\n line-height: 1.5;\n margin: 0;\n}\n\n.tooltip--email-signup p {\n font-weight: 400;\n font-size: 14px;\n line-height: 1.4;\n}\n\n.notice-controls {\n display: flex;\n}\n\n.tooltip--email-signup .notice-controls > * {\n border-radius: 8px;\n border: 0;\n cursor: pointer;\n display: inline-block;\n font-family: inherit;\n font-style: normal;\n font-weight: bold;\n padding: 8px 12px;\n text-decoration: none;\n}\n\n.notice-controls .ghost {\n margin-left: 1rem;\n}\n\n.tooltip--email-signup a.primary {\n background: #3969EF;\n color: #fff;\n}\n\n.tooltip--email-signup a.primary:hover,\n.tooltip--email-signup a.primary:focus {\n background: #2b55ca;\n}\n\n.tooltip--email-signup a.primary:active {\n background: #1e42a4;\n}\n\n.tooltip--email-signup button.ghost {\n background: transparent;\n color: #3969EF;\n}\n\n.tooltip--email-signup button.ghost:hover,\n.tooltip--email-signup button.ghost:focus {\n background-color: rgba(0, 0, 0, 0.06);\n color: #2b55ca;\n}\n\n.tooltip--email-signup button.ghost:active {\n background-color: rgba(0, 0, 0, 0.12);\n color: #1e42a4;\n}\n\n.tooltip--email-signup button.close-tooltip {\n background-color: transparent;\n background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iMTMiIHZpZXdCb3g9IjAgMCAxMiAxMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0wLjI5Mjg5NCAwLjY1NjkwN0MwLjY4MzQxOCAwLjI2NjM4MyAxLjMxNjU4IDAuMjY2MzgzIDEuNzA3MTEgMC42NTY5MDdMNiA0Ljk0OThMMTAuMjkyOSAwLjY1NjkwN0MxMC42ODM0IDAuMjY2MzgzIDExLjMxNjYgMC4yNjYzODMgMTEuNzA3MSAwLjY1NjkwN0MxMi4wOTc2IDEuMDQ3NDMgMTIuMDk3NiAxLjY4MDYgMTEuNzA3MSAyLjA3MTEyTDcuNDE0MjEgNi4zNjQwMUwxMS43MDcxIDEwLjY1NjlDMTIuMDk3NiAxMS4wNDc0IDEyLjA5NzYgMTEuNjgwNiAxMS43MDcxIDEyLjA3MTFDMTEuMzE2NiAxMi40NjE2IDEwLjY4MzQgMTIuNDYxNiAxMC4yOTI5IDEyLjA3MTFMNiA3Ljc3ODIzTDEuNzA3MTEgMTIuMDcxMUMxLjMxNjU4IDEyLjQ2MTYgMC42ODM0MTcgMTIuNDYxNiAwLjI5Mjg5MyAxMi4wNzExQy0wLjA5NzYzMTEgMTEuNjgwNiAtMC4wOTc2MzExIDExLjA0NzQgMC4yOTI4OTMgMTAuNjU2OUw0LjU4NTc5IDYuMzY0MDFMMC4yOTI4OTQgMi4wNzExMkMtMC4wOTc2MzA2IDEuNjgwNiAtMC4wOTc2MzA2IDEuMDQ3NDMgMC4yOTI4OTQgMC42NTY5MDdaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjg0Ii8+Cjwvc3ZnPgo=);\n background-position: center center;\n background-repeat: no-repeat;\n border: 0;\n cursor: pointer;\n padding: 16px;\n position: absolute;\n right: 12px;\n top: 12px;\n}\n"; - // If 'this.enabled' is a boolean it means we were able to set it correctly and therefor respect its value - if (typeof this.enabled === 'boolean') { - if (!this.enabled) { - return Settings.defaults; - } + // src/UI/HTMLTooltip.js + var defaultOptions = { + wrapperClass: "", + tooltipPositionClass: (top, left) => ` + .tooltip { + transform: translate(${Math.floor(left)}px, ${Math.floor(top)}px) !important; + } + `, + caretPositionClass: (top, left, isAboveInput) => ` + .tooltip--email__caret { + ${isAboveInput ? `transform: translate(${Math.floor(left)}px, ${Math.floor(top)}px) rotate(180deg); transform-origin: 18px !important;` : `transform: translate(${Math.floor(left)}px, ${Math.floor(top)}px) !important;`} + }`, + css: ``, + setSize: void 0, + remove: () => { + }, + testMode: false, + checkVisibility: true, + hasCaret: false, + isIncontextSignupAvailable: () => false + }; + var HTMLTooltip = class { + /** + * @param config + * @param inputType + * @param getPosition + * @param {HTMLTooltipOptions} options + */ + constructor(config, inputType, getPosition, options) { + __publicField(this, "isAboveInput", false); + /** @type {HTMLTooltipOptions} */ + __publicField(this, "options"); + __publicField(this, "resObs", new ResizeObserver((entries) => entries.forEach(() => this.checkPosition()))); + __publicField(this, "mutObsCheckPositionWhenIdle", whenIdle.call(this, this.checkPosition)); + __publicField(this, "mutObs", new MutationObserver((mutationList) => { + for (const mutationRecord of mutationList) { + if (mutationRecord.type === "childList") { + mutationRecord.addedNodes.forEach((el) => { + if (el.nodeName === "DDG-AUTOFILL") + return; + this.ensureIsLastInDOM(); + }); + } + } + this.mutObsCheckPositionWhenIdle(); + })); + __publicField(this, "clickableButtons", /* @__PURE__ */ new Map()); + this.options = options; + this.shadow = document.createElement("ddg-autofill").attachShadow({ + mode: options.testMode ? "open" : "closed" + }); + this.host = this.shadow.host; + this.config = config; + this.subtype = getSubtypeFromType(inputType); + this.variant = getVariantFromType(inputType); + this.tooltip = null; + this.getPosition = getPosition; + const forcedVisibilityStyles = { + "display": "block", + "visibility": "visible", + "opacity": "1" + }; + addInlineStyles(this.host, forcedVisibilityStyles); + this.count = 0; + this.device = null; + this.transformRules = { + caret: { + getRuleString: this.options.caretPositionClass, + index: null + }, + tooltip: { + getRuleString: this.options.tooltipPositionClass, + index: null + } + }; } - return { - featureToggles: this.featureToggles, - availableInputTypes: this.availableInputTypes, - enabled: this.enabled - }; - } - - /** - * Checks if input type is one which we can't autofill - * @param {{ - * mainType: SupportedMainTypes - * subtype: import('./Form/matching.js').SupportedSubTypes | "unknown" - * }} types - * @returns {boolean} - */ - isTypeUnavailable(_ref) { - let { - mainType, - subtype - } = _ref; - if (mainType === 'unknown') return true; - if (!this.featureToggles[`inputType_${mainType}`] && subtype !== 'emailAddress') { - return true; + get isHidden() { + return this.tooltip.parentNode.hidden; } - return false; - } - - /** - * Requests data from remote - * @returns {Promise<>} - */ - async populateData() { - const availableInputTypesFromRemote = await this.getAvailableInputTypes(); - this.setAvailableInputTypes(availableInputTypesFromRemote); - } - - /** - * Requests data from remote if not available - * @param {{ - * mainType: SupportedMainTypes - * subtype: import('./Form/matching.js').SupportedSubTypes | "unknown" - * }} types - * @returns {Promise} - */ - async populateDataIfNeeded(_ref2) { - let { - mainType, - subtype - } = _ref2; - if (this.isTypeUnavailable({ - mainType, - subtype - })) return false; - if (this.availableInputTypes?.[mainType] === undefined) { - await this.populateData(); - return true; + append() { + document.body.appendChild(this.host); } - return false; - } - - /** - * Checks if items will show in the autofill menu, including in-context signup. - * Triggers side-effect if input types is not already available. - * @param {{ - * mainType: SupportedMainTypes - * subtype: import('./Form/matching.js').SupportedSubTypes | "unknown" - * }} types - * @param {import("./InContextSignup.js").InContextSignup?} inContextSignup - * @returns {boolean} - */ - canAutofillType(_ref3, inContextSignup) { - let { - mainType, - subtype - } = _ref3; - if (this.isTypeUnavailable({ - mainType, - subtype - })) return false; - - // If it's an email field and Email Protection is enabled, return true regardless of other options - const isEmailProtectionEnabled = this.featureToggles.emailProtection && this.availableInputTypes.email; - if (subtype === 'emailAddress' && isEmailProtectionEnabled) { - return true; + remove() { + this.device?.activeForm.resetIconStylesToInitial(); + window.removeEventListener("scroll", this, { capture: true }); + this.resObs.disconnect(); + this.mutObs.disconnect(); + this.lift(); } - if (inContextSignup?.isAvailable(subtype)) { - return true; + lift() { + this.left = null; + this.top = null; + document.body.removeChild(this.host); } - if (subtype === 'fullName') { - return Boolean(this.availableInputTypes.identities?.firstName || this.availableInputTypes.identities?.lastName); + handleEvent(event) { + switch (event.type) { + case "scroll": + this.checkPosition(); + break; + } } - if (subtype === 'expiration') { - return Boolean(this.availableInputTypes.creditCards?.expirationMonth || this.availableInputTypes.creditCards?.expirationYear); + focus(x, y) { + const focusableElements = "button"; + const currentFocusClassName = "currentFocus"; + const currentFocused = this.shadow.querySelectorAll(`.${currentFocusClassName}`); + [...currentFocused].forEach((el) => { + el.classList.remove(currentFocusClassName); + }); + this.shadow.elementFromPoint(x, y)?.closest(focusableElements)?.classList.add(currentFocusClassName); } - return Boolean(this.availableInputTypes[mainType]?.[subtype]); - } - - /** @returns {AutofillFeatureToggles} */ - get featureToggles() { - if (this._featureToggles === null) throw new Error('feature toggles accessed before being set'); - return this._featureToggles; - } - - /** @param {AutofillFeatureToggles} input */ - setFeatureToggles(input) { - this._featureToggles = input; - } - - /** @returns {AvailableInputTypes} */ - get availableInputTypes() { - if (this._availableInputTypes === null) throw new Error('available input types accessed before being set'); - return this._availableInputTypes; - } - - /** @param {AvailableInputTypes} value */ - setAvailableInputTypes(value) { - this._availableInputTypes = { - ...this._availableInputTypes, - ...value - }; - } - static defaults = { - /** @type {AutofillFeatureToggles} */ - featureToggles: { - credentials_saving: false, - password_generation: false, - emailProtection: false, - emailProtection_incontext_signup: false, - inputType_identities: false, - inputType_credentials: false, - inputType_creditCards: false, - inlineIcon_credentials: false - }, - /** @type {AvailableInputTypes} */ - availableInputTypes: { - credentials: { - username: false, - password: false - }, - identities: { - firstName: false, - middleName: false, - lastName: false, - birthdayDay: false, - birthdayMonth: false, - birthdayYear: false, - addressStreet: false, - addressStreet2: false, - addressCity: false, - addressProvince: false, - addressPostalCode: false, - addressCountryCode: false, - phone: false, - emailAddress: false - }, - creditCards: { - cardName: false, - cardSecurityCode: false, - expirationMonth: false, - expirationYear: false, - cardNumber: false - }, - email: false - }, - /** @type {boolean | null} */ - enabled: null - }; - static default(globalConfig, deviceApi) { - const settings = new Settings(globalConfig, deviceApi); - settings.setFeatureToggles(Settings.defaults.featureToggles); - settings.setAvailableInputTypes(Settings.defaults.availableInputTypes); - return settings; - } - - /** @returns {boolean|null} */ - get enabled() { - return this._enabled; - } - - /** - * @param {boolean|null} enabled - */ - setEnabled(enabled) { - this._enabled = enabled; - } -} -exports.Settings = Settings; - -},{"../packages/device-api/index.js":2,"./autofill-utils.js":52,"./deviceApiCalls/__generated__/deviceApiCalls.js":56,"./deviceApiCalls/__generated__/validators.zod.js":57}],41:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.ThirdPartyProvider = void 0; -var _deviceApiCalls = require("./deviceApiCalls/__generated__/deviceApiCalls.js"); -var _validatorsZod = require("./deviceApiCalls/__generated__/validators.zod.js"); -var _matching = require("./Form/matching.js"); -var _index = require("../packages/device-api/index.js"); -class ThirdPartyProvider { - /** - * @param {import("./DeviceInterface/InterfacePrototype").default} device - */ - constructor(device) { - this.device = device; - } - init() { - if (this.device.settings.featureToggles.third_party_credentials_provider) { - if (this.device.globalConfig.hasModernWebkitAPI) { - Object.defineProperty(window, 'providerStatusUpdated', { - enumerable: false, - configurable: false, - writable: false, - value: data => { - this.providerStatusUpdated(data); + checkPosition() { + if (this.animationFrame) { + window.cancelAnimationFrame(this.animationFrame); + } + this.animationFrame = window.requestAnimationFrame(() => { + if (this.isHidden) + return; + const { left, bottom, height, top } = this.getPosition(); + if (left !== this.left || bottom !== this.top) { + const coords = { left, top: bottom }; + this.updatePosition("tooltip", coords); + if (this.options.hasCaret) { + const { top: tooltipTop } = this.tooltip.getBoundingClientRect(); + this.isAboveInput = top > tooltipTop; + const borderWidth = 2; + const caretTop = this.isAboveInput ? coords.top - height - borderWidth : coords.top; + this.updatePosition("caret", { ...coords, top: caretTop }); } - }); - } else { - // On Catalina we poll the native layer - setTimeout(() => this._pollForUpdatesToCredentialsProvider(), 2000); + } + this.animationFrame = null; + }); + } + getOverridePosition({ left, top }) { + const tooltipBoundingBox = this.tooltip.getBoundingClientRect(); + const smallScreenWidth = tooltipBoundingBox.width * 2; + const spacing = 5; + if (tooltipBoundingBox.bottom > window.innerHeight) { + const inputPosition = this.getPosition(); + const caretHeight = 14; + const overriddenTopPosition = top - tooltipBoundingBox.height - inputPosition.height - caretHeight; + if (overriddenTopPosition >= 0) + return { left, top: overriddenTopPosition }; + } + if (tooltipBoundingBox.left < 0 && window.innerWidth <= smallScreenWidth) { + const leftOverflow = Math.abs(tooltipBoundingBox.left); + const leftPosWhenCentered = (window.innerWidth - tooltipBoundingBox.width) / 2; + const overriddenLeftPosition = left + leftOverflow + leftPosWhenCentered; + return { left: overriddenLeftPosition, top }; + } + if (tooltipBoundingBox.left < 0 && window.innerWidth > smallScreenWidth) { + const leftOverflow = Math.abs(tooltipBoundingBox.left); + const overriddenLeftPosition = left + leftOverflow + spacing; + return { left: overriddenLeftPosition, top }; + } + if (tooltipBoundingBox.right > window.innerWidth) { + const rightOverflow = tooltipBoundingBox.right - window.innerWidth; + const overriddenLeftPosition = left - rightOverflow - spacing; + return { left: overriddenLeftPosition, top }; } } - } - async askToUnlockProvider() { - const response = await this.device.deviceApi.request(new _deviceApiCalls.AskToUnlockProviderCall(null)); - this.providerStatusUpdated(response); - } - - /** - * Called by the native layer on all tabs when the provider status is updated - * @param {import("./deviceApiCalls/__generated__/validators-ts").ProviderStatusUpdated} data - */ - providerStatusUpdated(data) { - try { - const { - credentials, - availableInputTypes - } = (0, _index.validate)(data, _validatorsZod.providerStatusUpdatedSchema); - - // Update local settings and data - this.device.settings.setAvailableInputTypes(availableInputTypes); - this.device.storeLocalCredentials(credentials); - - // rerender the tooltip - this.device.uiController?.updateItems(credentials); - if (!this.device.globalConfig.isTopFrame) { - // If the tooltip is open on an autofill type that's not available, close it - const currentInputSubtype = (0, _matching.getSubtypeFromType)(this.device.getCurrentInputType()); - if (!availableInputTypes.credentials?.[currentInputSubtype]) { - this.device.removeTooltip(); + /** + * @param {'tooltip' | 'caret'} element + * @param {{ + * left: number, + * top: number + * }} coords + */ + applyPositionalStyles(element, { left, top }) { + const shadow = this.shadow; + const ruleObj = this.transformRules[element]; + if (ruleObj.index) { + if (shadow.styleSheets[0].rules[ruleObj.index]) { + shadow.styleSheets[0].deleteRule(ruleObj.index); } - // Redecorate fields according to the new types - this.device.scanner.forms.forEach(form => form.recategorizeAllInputs()); + } else { + ruleObj.index = shadow.styleSheets[0].rules.length; } - } catch (e) { - if (this.device.globalConfig.isDDGTestMode) { - console.log('isDDGTestMode: providerStatusUpdated error: ❌', e); + const cssRule = ruleObj.getRuleString?.(top, left, this.isAboveInput); + if (typeof cssRule === "string") { + shadow.styleSheets[0].insertRule(cssRule, ruleObj.index); } } - } - - // Only used on Catalina - async _pollForUpdatesToCredentialsProvider() { - try { - const response = await this.device.deviceApi.request(new _deviceApiCalls.CheckCredentialsProviderStatusCall(null)); - if (response.availableInputTypes.credentialsProviderStatus !== this.device.settings.availableInputTypes.credentialsProviderStatus) { - this.providerStatusUpdated(response); + /** + * @param {'tooltip' | 'caret'} element + * @param {{ + * left: number, + * top: number + * }} coords + */ + updatePosition(element, { left, top }) { + if (!this.shadow.styleSheets.length) { + this.stylesheet?.addEventListener("load", () => this.checkPosition()); + return; } - setTimeout(() => this._pollForUpdatesToCredentialsProvider(), 2000); - } catch (e) { - if (this.device.globalConfig.isDDGTestMode) { - console.log('isDDGTestMode: _pollForUpdatesToCredentialsProvider: ❌', e); + this.left = left; + this.top = top; + this.applyPositionalStyles(element, { left, top }); + if (this.options.hasCaret) { + const overridePosition = this.getOverridePosition({ left, top }); + if (overridePosition) + this.updatePosition(element, overridePosition); + } + } + ensureIsLastInDOM() { + this.count = this.count || 0; + if (document.body.lastElementChild !== this.host) { + if (this.count < 15) { + this.lift(); + this.append(); + this.checkPosition(); + this.count++; + } else { + this.options.remove(); + console.info(`DDG autofill bailing out`); + } + } + } + setActiveButton(e) { + this.activeButton = e.target; + } + unsetActiveButton() { + this.activeButton = null; + } + registerClickableButton(btn, handler) { + this.clickableButtons.set(btn, handler); + btn.addEventListener("mouseenter", (e) => this.setActiveButton(e)); + btn.addEventListener("mouseleave", () => this.unsetActiveButton()); + } + dispatchClick() { + const handler = this.clickableButtons.get(this.activeButton); + if (handler) { + if (this.activeButton.matches(".wrapper:not(.top-autofill) button:hover, .wrapper:not(.top-autofill) a:hover, .currentFocus")) { + safeExecute(this.activeButton, handler, { + checkVisibility: this.options.checkVisibility + }); + } else { + console.warn("The button doesn't seem to be hovered. Please check."); + } + } + } + setupSizeListener() { + const observer = new PerformanceObserver(() => { + this.setSize(); + }); + observer.observe({ entryTypes: ["layout-shift", "paint"] }); + } + setSize() { + const innerNode = this.shadow.querySelector(".wrapper--data"); + if (!innerNode) + return; + const details = { height: innerNode.clientHeight, width: innerNode.clientWidth }; + this.options.setSize?.(details); + } + init() { + this.animationFrame = null; + this.top = 0; + this.left = 0; + this.transformRuleIndex = null; + this.stylesheet = this.shadow.querySelector("link, style"); + this.stylesheet?.addEventListener("load", () => { + Promise.allSettled([ + document.fonts.load("normal 13px 'DDG_ProximaNova'"), + document.fonts.load("bold 13px 'DDG_ProximaNova'") + ]).then(() => { + this.tooltip.parentNode.removeAttribute("hidden"); + this.checkPosition(); + }); + }); + this.append(); + this.resObs.observe(document.body); + this.mutObs.observe(document.body, { childList: true, subtree: true, attributes: true }); + window.addEventListener("scroll", this, { capture: true }); + this.setSize(); + if (typeof this.options.setSize === "function") { + this.setupSizeListener(); } } - } -} -exports.ThirdPartyProvider = ThirdPartyProvider; - -},{"../packages/device-api/index.js":2,"./Form/matching.js":33,"./deviceApiCalls/__generated__/deviceApiCalls.js":56,"./deviceApiCalls/__generated__/validators.zod.js":57}],42:[function(require,module,exports){ -"use strict"; + }; + var HTMLTooltip_default = HTMLTooltip; -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _autofillUtils = require("../autofill-utils.js"); -var _HTMLTooltip = _interopRequireDefault(require("./HTMLTooltip.js")); -var _Credentials = require("../InputTypes/Credentials.js"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -class DataHTMLTooltip extends _HTMLTooltip.default { - renderEmailProtectionIncontextSignup(isOtherItems) { - const dataTypeClass = `tooltip__button--data--identities`; - const providerIconClass = 'tooltip__button--data--duckduckgo'; - return ` - ${isOtherItems ? '
' : ''} + // src/UI/DataHTMLTooltip.js + var DataHTMLTooltip = class extends HTMLTooltip_default { + renderEmailProtectionIncontextSignup(isOtherItems) { + const dataTypeClass = `tooltip__button--data--identities`; + const providerIconClass = "tooltip__button--data--duckduckgo"; + return ` + ${isOtherItems ? "
" : ""} `; - } - - /** - * @param {InputTypeConfigs} config - * @param {TooltipItemRenderer[]} items - * @param {{ - * onSelect(id:string): void - * onManage(type:InputTypeConfigs['type']): void - * onIncontextSignupDismissed?(data: { - * hasOtherOptions: Boolean - * }): void - * onIncontextSignup?(): void - * }} callbacks - */ - render(config, items, callbacks) { - const { - wrapperClass, - css - } = this.options; - const isTopAutofill = wrapperClass?.includes('top-autofill'); - let hasAddedSeparator = false; - // Only show an hr above the first duck address button, but it can be either personal or private - const shouldShowSeparator = (dataId, index) => { - const shouldShow = ['personalAddress', 'privateAddress'].includes(dataId) && !hasAddedSeparator; - if (shouldShow) hasAddedSeparator = true; - - // Don't show the separator if we want to show it, but it's unnecessary as the first item in the menu - const isFirst = index === 0; - return shouldShow && !isFirst; - }; - - // Only show manage Manage… when it's topAutofill, the provider is unlocked, and it's not just EmailProtection - const shouldShowManageButton = isTopAutofill && items.some(item => !['personalAddress', 'privateAddress', _Credentials.PROVIDER_LOCKED].includes(item.id())); - const topClass = wrapperClass || ''; - const dataTypeClass = `tooltip__button--data--${config.type}${this.variant ? '__' + this.variant : ''}`; - this.shadow.innerHTML = ` + } + /** + * @param {InputTypeConfigs} config + * @param {TooltipItemRenderer[]} items + * @param {{ + * onSelect(id:string): void + * onManage(type:InputTypeConfigs['type']): void + * onIncontextSignupDismissed?(data: { + * hasOtherOptions: Boolean + * }): void + * onIncontextSignup?(): void + * }} callbacks + */ + render(config, items, callbacks) { + const { wrapperClass, css } = this.options; + const isTopAutofill = wrapperClass?.includes("top-autofill"); + let hasAddedSeparator = false; + const shouldShowSeparator = (dataId, index) => { + const shouldShow = ["personalAddress", "privateAddress"].includes(dataId) && !hasAddedSeparator; + if (shouldShow) + hasAddedSeparator = true; + const isFirst = index === 0; + return shouldShow && !isFirst; + }; + const shouldShowManageButton = isTopAutofill && items.some((item) => !["personalAddress", "privateAddress", PROVIDER_LOCKED].includes(item.id())); + const topClass = wrapperClass || ""; + const dataTypeClass = `tooltip__button--data--${config.type}${this.variant ? "__" + this.variant : ""}`; + this.shadow.innerHTML = ` ${css}