2b0132a81b0b
remove 5 orphaned files: content/palette.ts, content/field-scoring.ts, popup/components/{Pip,Ring,TOTP}.tsx
- time
- 2026-05-09 00:52 UTC · 5 months ago
- machine
- koh2
- parent
- 0b15ab269010
↓ peach-browser-2b0132a8.tar.gzatlas at this save
files changed5 files
src/content/field-scoring.ts+0 −252src/content/palette.ts+0 −2src/popup/components/Pip.tsx+0 −14src/popup/components/Ring.tsx+0 −84src/popup/components/TOTP.tsx+0 −33src/content/field-scoring.ts+0 −252
@@ -1,252 +0,0 @@1-export interface FieldScore {2- element: HTMLInputElement;3- score: number;4- fieldType: "username" | "password" | "email" | "totp" | "unknown";5- factors: {6- autocompleteMatch: number;7- typeMatch: number;8- namePattern: number;9- idPattern: number;10- placeholderPattern: number;11- ariaLabelPattern: number;12- siblingContext: number;13- };14-}15-16-export interface FormFields {17- username: HTMLInputElement | null;18- password: HTMLInputElement | null;19- email: HTMLInputElement | null;20- totp: HTMLInputElement | null;21-}22-23-const USERNAME_PATTERNS = [24- /user/i,25- /login/i,26- /usr/i,27- /uname/i,28- /account/i,29- /acct/i,30- /signin/i,31- /sign-in/i,32- /session/i,33- /member/i,34- /alias/i,35-];36-37-const EMAIL_PATTERNS = [/email/i, /e-mail/i, /mail/i];38-39-const PASSWORD_PATTERNS = [40- /pass/i,41- /password/i,42- /pwd/i,43- /secret/i,44- /passphrase/i,45- /key/i,46- /credential/i,47-];48-49-const TOTP_PATTERNS = [50- /totp/i,51- /otp/i,52- /2fa/i,53- /mfa/i,54- /two-factor/i,55- /twofactor/i,56- /authenticat/i,57- /verification/i,58- /code/i,59- /pin/i,60- /token/i,61-];62-63-const AUTOCOMPLETE_VALUES: Record<64- "username" | "email" | "password" | "totp",65- string[]66-> = {67- username: ["username", "login", "user"],68- email: ["email", "username"],69- password: ["current-password", "password", "new-password"],70- totp: ["one-time-code", "totp"],71-};72-73-function matchesPattern(value: string, patterns: RegExp[]): boolean {74- return patterns.some((pattern) => pattern.test(value));75-}76-77-function getPatternsForType(78- type: "username" | "password" | "email" | "totp"79-): RegExp[] {80- switch (type) {81- case "username":82- return USERNAME_PATTERNS;83- case "password":84- return PASSWORD_PATTERNS;85- case "email":86- return EMAIL_PATTERNS;87- case "totp":88- return TOTP_PATTERNS;89- }90-}91-92-function getLabelText(labelledById: string): string {93- const label = document.getElementById(labelledById);94- return label?.textContent || "";95-}96-97-function analyzeSiblingContext(input: HTMLInputElement, patterns: RegExp[]): number {98- const parent = input.parentElement;99- if (!parent) return 0;100-101- for (const sibling of Array.from(parent.children)) {102- if (sibling === input) continue;103- if (matchesPattern(sibling.textContent || "", patterns)) return 5;104- }105-106- const grandparent = parent.parentElement;107- if (!grandparent) return 0;108- for (const sibling of Array.from(grandparent.children)) {109- if (sibling.contains(input)) continue;110- if (matchesPattern(sibling.textContent || "", patterns)) return 5;111- }112-113- return 0;114-}115-116-function calculateFieldScore(117- input: HTMLInputElement,118- type: "username" | "password" | "email" | "totp"119-): number {120- const patterns = getPatternsForType(type);121- let score = 0;122- const autocomplete = input.getAttribute("autocomplete") || "";123- const expected = AUTOCOMPLETE_VALUES[type] || [];124-125- if (expected.some((value) => autocomplete.includes(value))) score += 10;126- if (type === "password" && input.type === "password") score += 8;127- if (type === "email" && input.type === "email") score += 8;128- if (type === "totp" && (input.type === "tel" || input.inputMode === "numeric")) {129- score += 6;130- }131-132- const name = input.name || "";133- const id = input.id || "";134- const placeholder = input.placeholder || "";135- const ariaLabel = input.getAttribute("aria-label") || "";136- const ariaLabelledBy = input.getAttribute("aria-labelledby") || "";137-138- if (matchesPattern(name, patterns)) score += 6;139- if (matchesPattern(id, patterns)) score += 6;140- if (matchesPattern(placeholder, patterns)) score += 4;141- if (matchesPattern(ariaLabel, patterns)) score += 4;142- if (ariaLabelledBy && matchesPattern(getLabelText(ariaLabelledBy), patterns)) {143- score += 4;144- }145-146- score += analyzeSiblingContext(input, patterns);147- return score;148-}149-150-export function scoreInputField(input: HTMLInputElement): FieldScore {151- const types: Array<"username" | "password" | "email" | "totp"> = [152- "username",153- "password",154- "email",155- "totp",156- ];157-158- const scores = types.map((type) => {159- const factors = {160- autocompleteMatch: 0,161- typeMatch: 0,162- namePattern: 0,163- idPattern: 0,164- placeholderPattern: 0,165- ariaLabelPattern: 0,166- siblingContext: 0,167- };168- const autocomplete = input.getAttribute("autocomplete") || "";169- const patterns = getPatternsForType(type);170- const name = input.name || "";171- const id = input.id || "";172- const placeholder = input.placeholder || "";173- const ariaLabel = input.getAttribute("aria-label") || "";174- if (AUTOCOMPLETE_VALUES[type]?.some((value) => autocomplete.includes(value))) {175- factors.autocompleteMatch = 10;176- }177- if (type === "password" && input.type === "password") factors.typeMatch = 8;178- if (type === "email" && input.type === "email") factors.typeMatch = 8;179- if (type === "totp" && (input.type === "tel" || input.inputMode === "numeric")) {180- factors.typeMatch = 6;181- }182- if (matchesPattern(name, patterns)) factors.namePattern = 6;183- if (matchesPattern(id, patterns)) factors.idPattern = 6;184- if (matchesPattern(placeholder, patterns)) factors.placeholderPattern = 4;185- if (matchesPattern(ariaLabel, patterns)) factors.ariaLabelPattern = 4;186- factors.siblingContext = analyzeSiblingContext(input, patterns);187-188- return {189- type,190- score: calculateFieldScore(input, type),191- factors,192- };193- });194-195- scores.sort((a, b) => b.score - a.score);196- const best = scores[0];197- return {198- element: input,199- score: best.score,200- fieldType: best.score > 5 ? best.type : "unknown",201- factors: best.factors,202- };203-}204-205-export function identifyLoginFields(form: HTMLFormElement | null): FormFields {206- const inputs = Array.from(207- (form || document).querySelectorAll<HTMLInputElement>(208- 'input:not([type="hidden"]):not([type="submit"]):not([type="button"])'209- )210- );211-212- const scoredFields = inputs.map(scoreInputField).sort((a, b) => b.score - a.score);213- const result: FormFields = {214- username: null,215- password: null,216- email: null,217- totp: null,218- };219- const used = new Set<HTMLInputElement>();220-221- for (const field of scoredFields) {222- if (used.has(field.element) || field.score < 3) continue;223- switch (field.fieldType) {224- case "username":225- if (!result.username) {226- result.username = field.element;227- used.add(field.element);228- }229- break;230- case "password":231- if (!result.password) {232- result.password = field.element;233- used.add(field.element);234- }235- break;236- case "email":237- if (!result.email && !result.username) {238- result.email = field.element;239- used.add(field.element);240- }241- break;242- case "totp":243- if (!result.totp) {244- result.totp = field.element;245- used.add(field.element);246- }247- break;248- }249- }250-251- return result;252-}src/content/palette.ts+0 −2
@@ -1,2 +0,0 @@1-// This file has been retired. Logic was consolidated into inject.ts and index.ts.2-export {};src/popup/components/Pip.tsx+0 −14
@@ -1,14 +0,0 @@1-import { createSignal } from "solid-js";2-3-export function Pip() {4- const [pulsing, setPulsing] = createSignal(false);5-6- function pulse() {7- setPulsing(true);8- setTimeout(() => setPulsing(false), 400);9- }10-11- (window as any).__peachPipPulse = pulse;12-13- return <span class={`pip ${pulsing() ? "pulse" : ""}`} />;14-}src/popup/components/Ring.tsx+0 −84
@@ -1,84 +0,0 @@1-import { onMount, onCleanup } from "solid-js";2-3-interface RingProps {4- target: () => HTMLElement | null;5-}6-7-export function Ring(props: RingProps) {8- let svgEl: SVGSVGElement | undefined;9- let rectEl: SVGRectElement | undefined;10- let fadeTimer: ReturnType<typeof setTimeout>;11-12- function calibrate() {13- const el = props.target();14- if (!el || !svgEl || !rectEl) return;15- const { width, height } = el.getBoundingClientRect();16- const r = 12;17- const perim = 2 * (width + height);18- svgEl.setAttribute("viewBox", `0 0 ${width} ${height}`);19- svgEl.setAttribute("width", String(width));20- svgEl.setAttribute("height", String(height));21- rectEl.setAttribute("width", String(width));22- rectEl.setAttribute("height", String(height));23- rectEl.setAttribute("rx", String(r));24- rectEl.setAttribute("ry", String(r));25- rectEl.style.setProperty("--perim", String(perim));26- rectEl.style.strokeDasharray = String(perim);27- rectEl.style.strokeDashoffset = String(perim);28- }29-30- function trigger() {31- if (!rectEl) return;32- rectEl.style.animation = "none";33- void (rectEl as unknown as HTMLElement).offsetWidth;34- rectEl.style.animation = "ringDraw 380ms ease-out both";35- clearTimeout(fadeTimer);36- fadeTimer = setTimeout(beginFade, 380 + 1200);37- }38-39- function beginFade() {40- if (!rectEl) return;41- rectEl.style.transition = "opacity 600ms var(--retreat)";42- rectEl.style.opacity = "0";43- }44-45- onMount(() => {46- calibrate();47- const ro = new ResizeObserver(calibrate);48- const el = props.target();49- if (el) ro.observe(el);50- onCleanup(() => ro.disconnect());51- });52-53- (window as any).__peachRingTrigger = trigger;54-55- return (56- <svg57- ref={svgEl}58- style={{ position: "absolute", inset: 0, "pointer-events": "none", "z-index": 10 }}59- >60- <style>{`61- @keyframes ringDraw {62- 0% { stroke-dashoffset: var(--perim); opacity: 0; }63- 60% { stroke-dashoffset: calc(var(--perim) * -0.035); opacity: 0.85; }64- 78% { stroke-dashoffset: calc(var(--perim) * 0.008); }65- 100% { stroke-dashoffset: 0; opacity: 0.85; }66- }67- `}</style>68- <rect69- ref={rectEl}70- x="0"71- y="0"72- fill="none"73- stroke="var(--ac)"74- stroke-width="1"75- style={{76- filter: "drop-shadow(0 0 5px color-mix(in srgb, var(--peach-accent) 80%, transparent))",77- transform: "rotate(-90deg)",78- "transform-origin": "50% 50%",79- opacity: "0",80- }}81- />82- </svg>83- );84-}src/popup/components/TOTP.tsx+0 −33
@@ -1,33 +0,0 @@1-import { createSignal, onCleanup } from "solid-js";2-import { generateTOTP } from "../../crypto/totp";3-4-interface TOTPProps {5- secret: string;6-}7-8-export function TOTPDisplay(props: TOTPProps) {9- const [totp, setTotp] = createSignal(generateTOTP(props.secret));10-11- const interval = setInterval(() => {12- setTotp(generateTOTP(props.secret));13- }, 1000);14-15- onCleanup(() => clearInterval(interval));16-17- return (18- <div class="peach-totp-band">19- <div>20- <span class="peach-label">2-factor</span>21- <p class="peach-totp-code">22- <span class="peach-totp-code-text">23- {totp().code.slice(0, 3)} {totp().code.slice(3)}24- </span>25- </p>26- </div>27- <div class="peach-totp-meta">28- <span class="peach-label">expires</span>29- <div class="peach-totp-countdown">{totp().remaining}s</div>30- </div>31- </div>32- );33-}