1import { isVisible } from "./detect";2import { extractDomain } from "../shared/domain";34function getRegistrableDomain(hostname: string): string {5 return extractDomain(hostname) ?? hostname;6}78export function checkFormAction(form: HTMLFormElement | null, expectedDomain: string): boolean {9 if (!form?.action) return true;10 try {11 const actionUrl = new URL(form.action);12 13 14 15 if (actionUrl.protocol !== "https:") return false;16 const host = actionUrl.hostname;17 if (getRegistrableDomain(host) !== expectedDomain) return false;18 if (host !== expectedDomain && !host.endsWith(`.${expectedDomain}`)) {19 return false;20 }21 } catch {}22 return true;23}2425export function fillFields(26 usernameEl: HTMLInputElement | null,27 passwordEl: HTMLInputElement | null,28 username: string,29 password: string,30 form?: HTMLFormElement | null,31 expectedDomain?: string,32 emailEl?: HTMLInputElement | null,33 email?: string34): void {35 if (expectedDomain && form && !checkFormAction(form, expectedDomain)) {36 return;37 }38 39 40 if (usernameEl && username) {41 setNativeValue(usernameEl, username);42 }43 if (passwordEl && isVisible(passwordEl)) {44 setNativeValue(passwordEl, password);45 }46 if (emailEl && email && isVisible(emailEl)) {47 setNativeValue(emailEl, email);48 }49}505152535455export function fillOTPDigitBoxes(boxes: HTMLInputElement[], code: string): void {56 const digits = code.replace(/\s/g, "").split("");57 boxes.forEach((box, i) => {58 if (i < digits.length) setNativeValue(box, digits[i]!);59 });60}6162export function fillTOTPCode(63 totpEl: HTMLInputElement | null,64 code: string,65 form?: HTMLFormElement | null,66 expectedDomain?: string67): boolean {68 if (!totpEl || !code || !isVisible(totpEl)) return false;69 if (totpEl.value.trim() !== "") return false;70 if (expectedDomain && form && !checkFormAction(form, expectedDomain)) return false;71 setNativeValue(totpEl, code);72 return true;73}74757677787980export function setNativeValue(81 el: HTMLInputElement | HTMLTextAreaElement,82 value: string83): void {84 const proto =85 el instanceof HTMLTextAreaElement86 ? window.HTMLTextAreaElement.prototype87 : window.HTMLInputElement.prototype;88 const nativeValueSetter = Object.getOwnPropertyDescriptor(proto, "value")?.set;89 nativeValueSetter?.call(el, value);90 el.dispatchEvent(new Event("input", { bubbles: true }));91 el.dispatchEvent(new Event("change", { bubbles: true }));92}9394export interface CardFillFields {95 number: HTMLInputElement | null;96 expMonth: HTMLInputElement | null;97 expYear: HTMLInputElement | null;98 expiryCombo: HTMLInputElement | null;99 cvc: HTMLInputElement | null;100 name: HTMLInputElement | null;101}102103export function fillCardForm(fields: CardFillFields, card: { cardNumber: string; expMonth: string; expYear: string; cvc: string; cardholderName: string }): void {104 105 106 const raw = card.cardNumber.replace(/\s/g, '');107 if (fields.number && isVisible(fields.number)) setNativeValue(fields.number, formatCardNumber(raw));108 if (fields.expiryCombo && isVisible(fields.expiryCombo)) {109 setNativeValue(fields.expiryCombo, `${card.expMonth}/${card.expYear.slice(-2)}`);110 } else {111 if (fields.expMonth && isVisible(fields.expMonth)) setNativeValue(fields.expMonth, card.expMonth);112 if (fields.expYear && isVisible(fields.expYear)) setNativeValue(fields.expYear, card.expYear);113 }114 if (fields.cvc && isVisible(fields.cvc)) setNativeValue(fields.cvc, card.cvc);115 if (fields.name && isVisible(fields.name)) setNativeValue(fields.name, card.cardholderName);116}117118export function formatCardNumber(raw: string): string {119 if (/^3[47]/.test(raw)) return raw.replace(/(\d{4})(\d{6})(\d{5})/, '$1 $2 $3');120 return raw.replace(/(\d{4})(?=\d)/g, '$1 ').trim();121}122123export function detectCardNetwork(number: string): import('../shared/types').CardNetwork {124 const n = number.replace(/\s/g, '');125 if (/^4/.test(n)) return 'visa';126 if (/^5[1-5]|^2[2-7]/.test(n)) return 'mastercard';127 if (/^3[47]/.test(n)) return 'amex';128 if (/^6(?:011|5)/.test(n)) return 'discover';129 return 'unknown';130}131132export function passesLuhn(number: string): boolean {133 const digits = number.replace(/\s/g, '').split('').map(Number);134 let sum = 0, isEven = false;135 for (let i = digits.length - 1; i >= 0; i--) {136 let d = digits[i]!;137 if (isEven) { d *= 2; if (d > 9) d -= 9; }138 sum += d;139 isEven = !isEven;140 }141 return sum % 10 === 0;142}143144export function extractLast4(number: string): string {145 return number.replace(/\s/g, '').slice(-4);146}