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?: string27): void {28 if (expectedDomain && form && !checkFormAction(form, expectedDomain)) {29 return;30 }31 32 33 if (usernameEl && username) {34 setNativeValue(usernameEl, username);35 }36 if (passwordEl && isVisible(passwordEl)) {37 setNativeValue(passwordEl, password);38 }39}4041function setNativeValue(el: HTMLInputElement, value: string): void {42 const nativeInputValueSetter = Object.getOwnPropertyDescriptor(43 window.HTMLInputElement.prototype,44 "value"45 )?.set;46 nativeInputValueSetter?.call(el, value);47 el.dispatchEvent(new Event("input", { bubbles: true }));48 el.dispatchEvent(new Event("change", { bubbles: true }));49}5051export interface CardFillFields {52 number: HTMLInputElement | null;53 expMonth: HTMLInputElement | null;54 expYear: HTMLInputElement | null;55 expiryCombo: HTMLInputElement | null;56 cvc: HTMLInputElement | null;57 name: HTMLInputElement | null;58}5960export function fillCardForm(fields: CardFillFields, card: { cardNumber: string; expMonth: string; expYear: string; cvc: string; cardholderName: string }): void {61 const raw = card.cardNumber.replace(/\s/g, '');62 if (fields.number) setNativeValue(fields.number, formatCardNumber(raw));63 if (fields.expiryCombo) {64 setNativeValue(fields.expiryCombo, `${card.expMonth}/${card.expYear.slice(-2)}`);65 } else {66 if (fields.expMonth) setNativeValue(fields.expMonth, card.expMonth);67 if (fields.expYear) setNativeValue(fields.expYear, card.expYear);68 }69 if (fields.cvc) setNativeValue(fields.cvc, card.cvc);70 if (fields.name) setNativeValue(fields.name, card.cardholderName);71}7273export function formatCardNumber(raw: string): string {74 if (/^3[47]/.test(raw)) return raw.replace(/(\d{4})(\d{6})(\d{5})/, '$1 $2 $3');75 return raw.replace(/(\d{4})(?=\d)/g, '$1 ').trim();76}7778export function detectCardNetwork(number: string): import('../shared/types').CardNetwork {79 const n = number.replace(/\s/g, '');80 if (/^4/.test(n)) return 'visa';81 if (/^5[1-5]|^2[2-7]/.test(n)) return 'mastercard';82 if (/^3[47]/.test(n)) return 'amex';83 if (/^6(?:011|5)/.test(n)) return 'discover';84 return 'unknown';85}8687export function passesLuhn(number: string): boolean {88 const digits = number.replace(/\s/g, '').split('').map(Number);89 let sum = 0, isEven = false;90 for (let i = digits.length - 1; i >= 0; i--) {91 let d = digits[i]!;92 if (isEven) { d *= 2; if (d > 9) d -= 9; }93 sum += d;94 isEven = !isEven;95 }96 return sum % 10 === 0;97}9899export function extractLast4(number: string): string {100 return number.replace(/\s/g, '').slice(-4);101}