1import { isVisible } from "./detect";2import { extractDomain } from "../shared/domain";34function getRegistrableDomain(hostname: string): string {5 return extractDomain(hostname) ?? hostname;6}78function checkFormAction(form: HTMLFormElement | null, expectedDomain: string): boolean {9 if (!form?.action) return true;10 try {11 const actionUrl = new URL(form.action);12 const actionDomain = getRegistrableDomain(actionUrl.hostname);13 if (actionDomain !== expectedDomain) {14 return false;15 }16 } catch {}17 return true;18}1920export function fillFields(21 usernameEl: HTMLInputElement | null,22 passwordEl: HTMLInputElement | null,23 username: string,24 password: string,25 form?: HTMLFormElement | null,26 expectedDomain?: string,27 emailEl?: HTMLInputElement | null,28 email?: string29): void {30 if (expectedDomain && form && !checkFormAction(form, expectedDomain)) {31 return;32 }33 34 35 if (usernameEl && username) {36 setNativeValue(usernameEl, username);37 }38 if (passwordEl && isVisible(passwordEl)) {39 setNativeValue(passwordEl, password);40 }41 if (emailEl && email && isVisible(emailEl)) {42 setNativeValue(emailEl, email);43 }44}454647484950export function fillOTPDigitBoxes(boxes: HTMLInputElement[], code: string): void {51 const digits = code.replace(/\s/g, "").split("");52 boxes.forEach((box, i) => {53 if (i < digits.length) setNativeValue(box, digits[i]!);54 });55}5657export function fillTOTPCode(58 totpEl: HTMLInputElement | null,59 code: string60): boolean {61 if (!totpEl || !code || !isVisible(totpEl)) return false;62 if (totpEl.value.trim() !== "") return false;63 setNativeValue(totpEl, code);64 return true;65}66676869707172export function setNativeValue(73 el: HTMLInputElement | HTMLTextAreaElement,74 value: string75): void {76 const proto =77 el instanceof HTMLTextAreaElement78 ? window.HTMLTextAreaElement.prototype79 : window.HTMLInputElement.prototype;80 const nativeValueSetter = Object.getOwnPropertyDescriptor(proto, "value")?.set;81 nativeValueSetter?.call(el, value);82 el.dispatchEvent(new Event("input", { bubbles: true }));83 el.dispatchEvent(new Event("change", { bubbles: true }));84}8586export interface CardFillFields {87 number: HTMLInputElement | null;88 expMonth: HTMLInputElement | null;89 expYear: HTMLInputElement | null;90 expiryCombo: HTMLInputElement | null;91 cvc: HTMLInputElement | null;92 name: HTMLInputElement | null;93}9495export function fillCardForm(fields: CardFillFields, card: { cardNumber: string; expMonth: string; expYear: string; cvc: string; cardholderName: string }): void {96 const raw = card.cardNumber.replace(/\s/g, '');97 if (fields.number) setNativeValue(fields.number, formatCardNumber(raw));98 if (fields.expiryCombo) {99 setNativeValue(fields.expiryCombo, `${card.expMonth}/${card.expYear.slice(-2)}`);100 } else {101 if (fields.expMonth) setNativeValue(fields.expMonth, card.expMonth);102 if (fields.expYear) setNativeValue(fields.expYear, card.expYear);103 }104 if (fields.cvc) setNativeValue(fields.cvc, card.cvc);105 if (fields.name) setNativeValue(fields.name, card.cardholderName);106}107108export function formatCardNumber(raw: string): string {109 if (/^3[47]/.test(raw)) return raw.replace(/(\d{4})(\d{6})(\d{5})/, '$1 $2 $3');110 return raw.replace(/(\d{4})(?=\d)/g, '$1 ').trim();111}112113export function detectCardNetwork(number: string): import('../shared/types').CardNetwork {114 const n = number.replace(/\s/g, '');115 if (/^4/.test(n)) return 'visa';116 if (/^5[1-5]|^2[2-7]/.test(n)) return 'mastercard';117 if (/^3[47]/.test(n)) return 'amex';118 if (/^6(?:011|5)/.test(n)) return 'discover';119 return 'unknown';120}121122export function passesLuhn(number: string): boolean {123 const digits = number.replace(/\s/g, '').split('').map(Number);124 let sum = 0, isEven = false;125 for (let i = digits.length - 1; i >= 0; i--) {126 let d = digits[i]!;127 if (isEven) { d *= 2; if (d > 9) d -= 9; }128 sum += d;129 isEven = !isEven;130 }131 return sum % 10 === 0;132}133134export function extractLast4(number: string): string {135 return number.replace(/\s/g, '').slice(-4);136}