feat: extensão de navegador para gerar CPF/CNPJ no menu de contexto
Adiciona uma extensão MV3 (Firefox e Chromium/Brave) que insere CPF ou CNPJ
válido direto no campo focado, pelo menu de contexto de qualquer input,
textarea ou contenteditable.
Extensões rodam em sandbox sem acesso a processos, então não há como invocar
os binários cpfgen/cnpjgen; fazer isso exigiria um host de native messaging
registrado no SO. Como os algoritmos são curtos e puros, foram portados para
JS em extension/generator.js, a partir de main.go (CPF) e de
cnpj_generator/cnpj/generate.go (CNPJ), trocando math/rand por
crypto.getRandomValues. test_generator.mjs compara as duas implementações
para que não divirjam.
A inserção usa document.execCommand("insertText"), que emite os eventos
nativos e portanto funciona em campos controlados por React/Vue — atribuir
.value diretamente seria descartado por esses frameworks.
Somente desktop: o Firefox para Android não implementa a API menus.
Testes carregam a extensão de verdade nos dois navegadores e verificam que os
8 itens de menu são criados e que a inserção funciona nos três tipos de campo.
This commit is contained in:
parent
688c5a48f3
commit
7632cfde2c
14 changed files with 792 additions and 4 deletions
101
extension/test_generator.mjs
Normal file
101
extension/test_generator.mjs
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
// Valida CPFs/CNPJs vindos do Go e do porte JS usando uma checagem
|
||||
// independente (recalcula os DVs a partir dos 9/12 primeiros caracteres).
|
||||
import {
|
||||
generateCPF, formatCPF,
|
||||
generateCNPJ, generateCNPJAlpha, formatCNPJ,
|
||||
} from "./generator.js";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { existsSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
function validCPF(s) {
|
||||
if (!/^\d{11}$/.test(s)) return false;
|
||||
const d = [...s].map(Number);
|
||||
let sum = 0;
|
||||
for (let i = 0; i < 9; i++) sum += d[i] * (10 - i);
|
||||
let r = sum % 11;
|
||||
if (d[9] !== (r < 2 ? 0 : 11 - r)) return false;
|
||||
sum = 0;
|
||||
for (let i = 0; i < 10; i++) sum += d[i] * (11 - i);
|
||||
r = sum % 11;
|
||||
return d[10] === (r < 2 ? 0 : 11 - r);
|
||||
}
|
||||
|
||||
const P1 = [5,4,3,2,9,8,7,6,5,4,3,2];
|
||||
const P2 = [6,5,4,3,2,9,8,7,6,5,4,3,2];
|
||||
const val = (c) => c.charCodeAt(0) - 48;
|
||||
|
||||
function validCNPJ(s) {
|
||||
if (!/^[0-9A-Z]{12}\d{2}$/.test(s)) return false;
|
||||
const dv = (str, t) => {
|
||||
let tot = 0;
|
||||
for (let i = 0; i < t.length; i++) tot += val(str[i]) * t[i];
|
||||
const r = tot % 11;
|
||||
return r >= 2 ? 11 - r : 0;
|
||||
};
|
||||
if (Number(s[12]) !== dv(s, P1)) return false;
|
||||
return Number(s[13]) === dv(s.slice(0, 13), P2);
|
||||
}
|
||||
|
||||
const here = path.dirname(fileURLToPath(import.meta.url));
|
||||
const CPF = path.join(here, "..", "bin", "cpfgen");
|
||||
// cnpjgen vive em outro repo, ao lado deste.
|
||||
const CNPJ = path.join(here, "..", "..", "cnpj_generator", "bin", "cnpjgen");
|
||||
const run = (bin, args) => execFileSync(bin, args, { encoding: "utf8" }).trim();
|
||||
|
||||
let fail = 0;
|
||||
const check = (label, ok) => {
|
||||
if (!ok) { fail++; console.log(`FAIL ${label}`); }
|
||||
};
|
||||
|
||||
const N = 300;
|
||||
|
||||
// Referência: saída dos binários Go, quando compilados. Servem de oráculo para
|
||||
// o porte JS; se não existirem, os testes do JS abaixo rodam do mesmo jeito.
|
||||
const hasCPF = existsSync(CPF);
|
||||
const hasCNPJ = existsSync(CNPJ);
|
||||
if (!hasCPF) console.log("aviso: bin/cpfgen ausente (rode `make build`), pulando");
|
||||
if (!hasCNPJ) console.log("aviso: cnpj_generator/bin/cnpjgen ausente, pulando");
|
||||
|
||||
for (let i = 0; i < N; i++) {
|
||||
if (hasCPF) check("go cpf", validCPF(run(CPF, ["-num"])));
|
||||
if (hasCNPJ) {
|
||||
check("go cnpj", validCNPJ(run(CNPJ, ["-num"])));
|
||||
check("go cnpj alpha", validCNPJ(run(CNPJ, ["-alpha", "-num"])));
|
||||
}
|
||||
}
|
||||
|
||||
// Porte JS.
|
||||
for (let i = 0; i < N; i++) {
|
||||
check("js cpf", validCPF(generateCPF()));
|
||||
check("js cnpj", validCNPJ(generateCNPJ()));
|
||||
check("js cnpj alpha", validCNPJ(generateCNPJAlpha()));
|
||||
}
|
||||
|
||||
// Formatação deve casar com a do Go.
|
||||
check("fmt cpf", /^\d{3}\.\d{3}\.\d{3}-\d{2}$/.test(formatCPF(generateCPF())));
|
||||
check("fmt cnpj", /^\d{2}\.\d{3}\.\d{3}\/\d{4}-\d{2}$/.test(formatCNPJ(generateCNPJ())));
|
||||
check("fmt cnpj alpha",
|
||||
/^[0-9A-Z]{2}\.[0-9A-Z]{3}\.[0-9A-Z]{3}\/[0-9A-Z]{4}-\d{2}$/
|
||||
.test(formatCNPJ(generateCNPJAlpha())));
|
||||
|
||||
// Formato do Go, lado a lado, para confirmar que o layout é idêntico.
|
||||
console.log("go cpf :", run(CPF, []));
|
||||
console.log("js cpf :", formatCPF(generateCPF()));
|
||||
console.log("go cnpj :", run(CNPJ, []));
|
||||
console.log("js cnpj :", formatCNPJ(generateCNPJ()));
|
||||
console.log("go alpha :", run(CNPJ, ["-alpha"]));
|
||||
console.log("js alpha :", formatCNPJ(generateCNPJAlpha()));
|
||||
|
||||
// Distribuição: nenhum dígito verificador deve ficar preso num valor só.
|
||||
const dvs = new Set();
|
||||
for (let i = 0; i < 200; i++) dvs.add(generateCPF().slice(9));
|
||||
console.log(`\nDVs de CPF distintos em 200: ${dvs.size}`);
|
||||
|
||||
if (fail === 0) {
|
||||
console.log("\nOK: geradores JS conferem com os binários Go");
|
||||
} else {
|
||||
console.log(`\n${fail} falhas`);
|
||||
process.exit(1);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue