Geolocation API com Claude Code: permissões, privacidade, fallback e testes
Implemente Geolocation API com Claude Code: permissões, HTTPS, fallback manual, privacidade e testes.
Geolocalização parece uma função pequena, mas em produto real envolve permissão do navegador, HTTPS, falhas de dispositivo, entrada manual, privacidade, integração com mapa e testes. Um buscador de lojas, uma estimativa de entrega ou um check-in de equipe de campo não pode depender apenas do caminho feliz.
Claude Code é útil quando recebe um pedido preciso. Se você pedir apenas “adicione localização atual”, ele pode gerar um getCurrentPosition sem explicar o motivo ao usuário, sem fallback e com latitude e longitude no log. A versão publicável precisa pedir localização só depois de uma ação, respeitar negação, limitar tempo de espera e provar o comportamento com mocks.
As referências principais são MDN Geolocation API, getCurrentPosition, watchPosition, Permissions API, W3C Geolocation specification, Chrome sobre secure origins, Chrome DevTools Sensors, Playwright emulation e Claude Code permissions. Para aprofundar no site, veja integração com mapas, auditoria de segurança e design responsivo.
Defina o limite da função
Geolocation API pede ao navegador uma posição estimada do dispositivo. O navegador pode usar GPS, Wi-Fi, antenas, IP, serviços do sistema operacional ou cache. A aplicação não escolhe a origem nem deve prometer precisão absoluta.
Antes de implementar, decida quatro pontos. Peça permissão apenas depois de um clique claro. Use enableHighAccuracy: false por padrão. Ofereça busca manual por endereço, cidade ou CEP. Decida se as coordenadas serão guardadas; muitas vezes basta salvar a zona de atendimento ou a loja selecionada.
| Decisão | Padrão recomendado | Falha comum |
|---|---|---|
| Momento | Após ação do usuário | Pedir permissão ao carregar |
| Precisão | Alta precisão desligada | GPS para qualquer lista |
| Fallback | CEP, cidade ou endereço | Bloquear usuário que negou |
| Logs | Eventos e buckets | Coordenadas brutas no analytics |
Também separe mapa de localização. Geolocation devolve coordenadas. Renderizar mapa, geocodificar endereço, calcular rota e buscar locais próximos é responsabilidade de Google Maps, Mapbox, OpenStreetMap ou do seu backend.
Casos de uso concretos
O primeiro caso é busca de lojas. O usuário clica em “usar minha localização” e o app ordena resultados por distância. O campo de CEP deve estar visível junto do botão, porque negar permissão não significa abandonar a compra.
O segundo caso é entrega ou serviço em domicílio. Mercado, reparo, aluguel e atendimento local podem verificar se o usuário está dentro da área e mostrar horários disponíveis. Em geral, basta guardar inside_zone, faixa de distância ou loja responsável.
O terceiro caso é check-in de equipe de campo. Limpeza, manutenção, eventos e vendas podem confirmar proximidade do local. Quando a localização falha, o fluxo precisa aceitar foto, aprovação de supervisor ou observação manual.
O quarto caso é conteúdo local. Clima, eventos, estoque próximo e avisos regionais podem usar localização. Se cidade ou região bastam, uma preferência salva é melhor do que localização precisa ao vivo.
Exemplo getCurrentPosition para copiar
Salve como geo-demo.html e rode em localhost ou HTTPS. O exemplo mostra explicação, timeout, cache, arredondamento e fallback manual.
<!doctype html>
<html lang="pt">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Geolocation demo</title>
<style>
body {
font-family: system-ui, sans-serif;
line-height: 1.6;
margin: 2rem;
}
button,
input {
font: inherit;
padding: 0.7rem 0.9rem;
}
.panel {
border: 1px solid #ddd;
max-width: 36rem;
padding: 1rem;
}
</style>
</head>
<body>
<main class="panel">
<h1>Encontrar lojas próximas</h1>
<p>
Usamos sua localização apenas para ordenar lojas.
Não salvamos endereço exato nem histórico.
</p>
<button id="useLocation" type="button">Usar minha localização</button>
<p id="status" role="status" aria-live="polite"></p>
<pre id="result"></pre>
<form id="manualForm">
<label for="postcode">CEP, cidade ou endereço</label>
<input id="postcode" name="postcode" autocomplete="postal-code" />
<button type="submit">Buscar manualmente</button>
</form>
</main>
<script type="module">
const status = document.querySelector("#status");
const result = document.querySelector("#result");
const button = document.querySelector("#useLocation");
const form = document.querySelector("#manualForm");
function showManual(reason) {
status.textContent =
`${reason}. Também é possível buscar por CEP ou cidade.`;
}
function onSuccess(position) {
const { latitude, longitude, accuracy } = position.coords;
status.textContent = "Localização recebida.";
result.textContent = JSON.stringify(
{
lat: Number(latitude.toFixed(5)),
lng: Number(longitude.toFixed(5)),
accuracyMeters: Math.round(accuracy),
},
null,
2,
);
}
function onError(error) {
const messages = {
1: "A permissão de localização foi negada",
2: "A posição do dispositivo está indisponível",
3: "A solicitação de localização expirou",
};
showManual(messages[error.code] ?? "Localização indisponível");
}
button.addEventListener("click", () => {
if (!("geolocation" in navigator)) {
showManual("Este navegador não suporta Geolocation");
return;
}
status.textContent = "Verificando permissão de localização...";
navigator.geolocation.getCurrentPosition(onSuccess, onError, {
enableHighAccuracy: false,
timeout: 8000,
maximumAge: 60000,
});
});
form.addEventListener("submit", (event) => {
event.preventDefault();
const data = new FormData(form);
status.textContent =
`Buscando perto de "${data.get("postcode")}".`;
});
</script>
</body>
</html>
timeout limita a espera, maximumAge aceita uma posição em cache e enableHighAccuracy pede maior precisão quando possível. Use cache maior para busca de lojas e cache menor para check-in.
watchPosition com limpeza
watchPosition envia atualizações contínuas. Ele serve para rotas e acompanhamento ativo, não para uma busca pontual. Sempre chame clearWatch ao encerrar.
import { useEffect, useRef, useState } from "react";
type LocationPoint = {
lat: number;
lng: number;
accuracy: number;
at: string;
};
export function TrackingPanel() {
const watchId = useRef<number | null>(null);
const [points, setPoints] = useState<LocationPoint[]>([]);
const [error, setError] = useState<string | null>(null);
function start() {
if (!navigator.geolocation || watchId.current !== null) return;
watchId.current = navigator.geolocation.watchPosition(
(position) => {
const { latitude, longitude, accuracy } = position.coords;
setPoints((current) => [
{
lat: Number(latitude.toFixed(5)),
lng: Number(longitude.toFixed(5)),
accuracy: Math.round(accuracy),
at: new Date(position.timestamp).toISOString(),
},
...current.slice(0, 9),
]);
},
(err) => setError(`Falha no rastreamento: ${err.code}`),
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 5000,
},
);
}
function stop() {
if (watchId.current === null) return;
navigator.geolocation.clearWatch(watchId.current);
watchId.current = null;
}
useEffect(() => stop, []);
return (
<section>
<button type="button" onClick={start}>Iniciar rastreamento</button>
<button type="button" onClick={stop}>Parar</button>
{error && <p role="alert">{error}</p>}
<ol>
{points.map((point) => (
<li key={point.at}>
{point.lat}, {point.lng}
{" / "}
{point.accuracy}m
</li>
))}
</ol>
</section>
);
}
Rastreamento precisa ter estados visíveis: iniciar, pausar e finalizar. Também defina retenção, acesso e remoção dos dados.
Privacidade e logs
Latitude e longitude não devem ser logs comuns. Elas podem ir para monitoramento, analytics, replay de sessão ou suporte. Prefira eventos e agrupamentos.
type GeoLogInput = {
lat: number;
lng: number;
accuracy: number;
permission: "granted" | "prompt" | "denied" | "unknown";
};
export function toPrivacySafeGeoLog(input: GeoLogInput) {
return {
permission: input.permission,
accuracyBucket:
input.accuracy <= 50 ? "high" :
input.accuracy <= 500 ? "medium" : "low",
latBucket: Number(input.lat.toFixed(2)),
lngBucket: Number(input.lng.toFixed(2)),
};
}
Para análise, eventos como permission_denied, manual_search_used, timeout e results_shown normalmente bastam. Inclua no prompt para Claude Code: “não registrar latitude e longitude brutas”.
Permissions API pode ler granted, prompt e denied, mas o prompt real aparece quando Geolocation é chamado.
export async function readGeoPermission() {
if (!("permissions" in navigator)) return "unknown";
try {
const status = await navigator.permissions.query({
name: "geolocation",
});
return status.state;
} catch {
return "unknown";
}
}
Falhas e testes
Teste HTTP, HTTPS e iframe. Geolocation exige contexto seguro e pode ser bloqueada por Permissions-Policy.
Teste permissão negada. O fluxo deve ir para entrada manual sem insistir no prompt.
Teste timeout e localização indisponível. Desktop, interiores, VPN, localização do sistema desligada e navegador corporativo podem falhar.
Teste cache antigo. maximumAge ajuda performance, mas pode ser ruim para check-in.
import { expect, test } from "@playwright/test";
test.use({
geolocation: {
latitude: -23.55052,
longitude: -46.633308,
accuracy: 50,
},
permissions: ["geolocation"],
});
test("shows nearby stores from mocked location", async ({ page }) => {
await page.goto("/stores");
await page.getByRole("button", { name: "Usar minha localização" }).click();
await expect(page.getByText("Localização recebida")).toBeVisible();
});
Prompt seguro para Claude Code
Use um prompt com escopo, restrições e verificação.
claude <<'PROMPT'
Implement a beginner-friendly Geolocation feature.
Scope:
- Edit only src/features/location and related tests.
- Do not change billing, analytics, or map provider config.
- Preserve existing API keys and environment variable names.
Requirements:
- Request location only after the user clicks a button.
- Explain why location is needed before the browser prompt.
- Use getCurrentPosition with timeout and maximumAge.
- Add manual postcode/address fallback for denied or timeout cases.
- Do not log raw latitude or longitude.
- Add a Playwright test with mocked geolocation.
- Return a short verification checklist.
PROMPT
Em equipe, limite permissões para proteger .env e evitar push automático.
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Bash(git push *)"
],
"allow": [
"Bash(npm test *)",
"Bash(npm run lint)"
]
}
}
Para transformar isso em padrão de equipe, checklist de revisão ou prompt específico do seu repositório, a página Claude Code training and consultation é o próximo passo.
Nota de verificação
Verifiquei os exemplos no Chrome em localhost, com DevTools Sensors em São Paulo e “Location unavailable”, e com Playwright concedendo permissão de geolocalização. Antes de publicar, teste permissão negada, localização do sistema desligada, iframe, quota do provedor de mapas e ausência de coordenadas brutas nos logs.
PDF grátis: cheatsheet do Claude Code
Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.
Cuidamos dos seus dados e não enviamos spam.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Workflow Obsidian para CLAUDE.md com Claude Code
Transforme notas de trabalho do Obsidian em notas operacionais CLAUDE.md para preservar contexto.
Claude Code Revenue CTA Routing: artigos para PDF, Gumroad e consultoria
Um fluxo com Claude Code para levar leitores ao PDF grátis, Gumroad ou consultoria conforme intenção.
Regras de handoff para equipes com Claude Code: evidências, permissões, rollback e receita
Formato prático para entregar trabalho do Claude Code com prova, permissões, rollback, PDF grátis, Gumroad e consultoria.