cpfgen/extension/generator.js
Lucas F. 7632cfde2c 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.
2026-09-03 17:57:50 -03:00

104 lines
2.6 KiB
JavaScript

// Porte direto dos geradores Go:
// cpf_generator/main.go -> generateCPF
// cnpj_generator/cnpj/generate.go -> generateCNPJ / generateCNPJAlpha
//
// Diferença deliberada: aqui o aleatório vem de crypto.getRandomValues em vez
// de math/rand. Os dígitos verificadores seguem exatamente as mesmas contas.
const ALPHA_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const CNPJ_P1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
const CNPJ_P2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
// Inteiro uniforme em [0, max) sem o viés do módulo.
function randomInt(max) {
const limit = Math.floor(0xffffffff / max) * max;
const buf = new Uint32Array(1);
let v;
do {
crypto.getRandomValues(buf);
v = buf[0];
} while (v >= limit);
return v % max;
}
export function generateCPF() {
const digits = new Array(11);
for (let i = 0; i < 9; i++) {
digits[i] = randomInt(10);
}
let sum = 0;
for (let i = 0; i < 9; i++) {
sum += digits[i] * (10 - i);
}
let rem = sum % 11;
digits[9] = rem < 2 ? 0 : 11 - rem;
sum = 0;
for (let i = 0; i < 10; i++) {
sum += digits[i] * (11 - i);
}
rem = sum % 11;
digits[10] = rem < 2 ? 0 : 11 - rem;
return digits.join("");
}
export function formatCPF(cpf) {
if (cpf.length !== 11) return cpf;
return `${cpf.slice(0, 3)}.${cpf.slice(3, 6)}.${cpf.slice(6, 9)}-${cpf.slice(9)}`;
}
// Peso posicional do caractere: dígitos valem 0-9, letras valem código ASCII - 48
// (A = 17), conforme a regra do CNPJ alfanumérico.
function alphaValue(ch) {
return ch.charCodeAt(0) - 48;
}
function computeDV(s, table) {
let total = 0;
for (let i = 0; i < table.length; i++) {
total += alphaValue(s[i]) * table[i];
}
const r = total % 11;
return r >= 2 ? 11 - r : 0;
}
function isRepetitive(s) {
if (s.length === 0) return false;
for (let i = 1; i < s.length; i++) {
if (s[i] !== s[0]) return false;
}
return true;
}
function generateCNPJBase(alpha) {
for (;;) {
let s = "";
for (let i = 0; i < 12; i++) {
s += alpha
? ALPHA_CHARS[randomInt(ALPHA_CHARS.length)]
: String(randomInt(10));
}
if (isRepetitive(s)) continue;
const dv1 = computeDV(s, CNPJ_P1);
const dv2 = computeDV(s + dv1, CNPJ_P2);
return `${s}${dv1}${dv2}`;
}
}
export function generateCNPJ() {
return generateCNPJBase(false);
}
export function generateCNPJAlpha() {
return generateCNPJBase(true);
}
export function formatCNPJ(cnpj) {
if (cnpj.length !== 14) return cnpj;
return `${cnpj.slice(0, 2)}.${cnpj.slice(2, 5)}.${cnpj.slice(5, 8)}/${cnpj.slice(8, 12)}-${cnpj.slice(12)}`;
}