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}2425262728export function fillAllowed(29 form: HTMLFormElement | null | undefined,30 expectedDomain?: string | null31): boolean {32 if (!form || !expectedDomain) return true;33 return checkFormAction(form, expectedDomain);34}35363738export function fillFields(39 usernameEl: HTMLInputElement | null,40 passwordEl: HTMLInputElement | null,41 username: string,42 password: string,43 form?: HTMLFormElement | null,44 expectedDomain?: string,45 emailEl?: HTMLInputElement | null,46 email?: string47): boolean {48 if (!fillAllowed(form, expectedDomain)) {49 return false;50 }51 52 53 if (usernameEl && username) {54 setNativeValue(usernameEl, username);55 }56 if (passwordEl && isVisible(passwordEl)) {57 setNativeValue(passwordEl, password);58 }59 if (emailEl && email && isVisible(emailEl)) {60 setNativeValue(emailEl, email);61 }62 return true;63}646566676869export function fillOTPDigitBoxes(boxes: HTMLInputElement[], code: string): void {70 const digits = code.replace(/\s/g, "").split("");71 boxes.forEach((box, i) => {72 if (i < digits.length) setNativeValue(box, digits[i]!);73 });74}7576export function fillTOTPCode(77 totpEl: HTMLInputElement | null,78 code: string,79 form?: HTMLFormElement | null,80 expectedDomain?: string81): boolean {82 if (!totpEl || !code || !isVisible(totpEl)) return false;83 if (totpEl.value.trim() !== "") return false;84 if (!fillAllowed(form, expectedDomain)) return false;85 setNativeValue(totpEl, code);86 return true;87}88899091929394export function setNativeValue(95 el: HTMLInputElement | HTMLTextAreaElement,96 value: string97): void {98 const proto =99 el instanceof HTMLTextAreaElement100 ? window.HTMLTextAreaElement.prototype101 : window.HTMLInputElement.prototype;102 const nativeValueSetter = Object.getOwnPropertyDescriptor(proto, "value")?.set;103 nativeValueSetter?.call(el, value);104 el.dispatchEvent(new Event("input", { bubbles: true }));105 el.dispatchEvent(new Event("change", { bubbles: true }));106}107108export interface CardFillFields {109 number: HTMLInputElement | null;110 expMonth: HTMLInputElement | null;111 expYear: HTMLInputElement | null;112 expiryCombo: HTMLInputElement | null;113 cvc: HTMLInputElement | null;114 name: HTMLInputElement | null;115}116117export function fillCardForm(fields: CardFillFields, card: { cardNumber: string; expMonth: string; expYear: string; cvc: string; cardholderName: string }): void {118 119 120 const raw = card.cardNumber.replace(/\s/g, '');121 if (fields.number && isVisible(fields.number)) setNativeValue(fields.number, formatCardNumber(raw));122 if (fields.expiryCombo && isVisible(fields.expiryCombo)) {123 setNativeValue(fields.expiryCombo, `${card.expMonth}/${card.expYear.slice(-2)}`);124 } else {125 if (fields.expMonth && isVisible(fields.expMonth)) setNativeValue(fields.expMonth, card.expMonth);126 if (fields.expYear && isVisible(fields.expYear)) setNativeValue(fields.expYear, card.expYear);127 }128 if (fields.cvc && isVisible(fields.cvc)) setNativeValue(fields.cvc, card.cvc);129 if (fields.name && isVisible(fields.name)) setNativeValue(fields.name, card.cardholderName);130}131132export function formatCardNumber(raw: string): string {133 if (/^3[47]/.test(raw)) return raw.replace(/(\d{4})(\d{6})(\d{5})/, '$1 $2 $3');134 return raw.replace(/(\d{4})(?=\d)/g, '$1 ').trim();135}136137export function detectCardNetwork(number: string): import('../shared/types').CardNetwork {138 const n = number.replace(/\s/g, '');139 if (/^4/.test(n)) return 'visa';140 if (/^5[1-5]|^2[2-7]/.test(n)) return 'mastercard';141 if (/^3[47]/.test(n)) return 'amex';142 if (/^6(?:011|5)/.test(n)) return 'discover';143 return 'unknown';144}145146export function passesLuhn(number: string): boolean {147 const digits = number.replace(/\s/g, '').split('').map(Number);148 let sum = 0, isEven = false;149 for (let i = digits.length - 1; i >= 0; i--) {150 let d = digits[i]!;151 if (isEven) { d *= 2; if (d > 9) d -= 9; }152 sum += d;153 isEven = !isEven;154 }155 return sum % 10 === 0;156}157158export function extractLast4(number: string): string {159 return number.replace(/\s/g, '').slice(-4);160}