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.
Artigos relacionados
Guia completo de CORS com Claude Code: APIs cross-origin seguras
Configure CORS com Claude Code: preflight, credentials, allowlist de origin, comandos de teste e prompts de revisão.
Web scraping seguro com Claude Code: Fetch, Playwright e logs de auditoria
Implemente web scraping seguro com Claude Code: robots.txt, Fetch, Playwright, CSV e auditoria.
Como Construir um Player de Áudio com Claude Code
Aprenda a construir um player de áudio usando o Claude Code. Inclui exemplos práticos de código e orientação passo a passo.
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.