Geolocation API dengan Claude Code: izin, fallback, privasi, dan pengujian
Implementasi Geolocation API dengan Claude Code: izin, HTTPS, fallback manual, privasi, dan pengujian.
Fitur lokasi terlihat sederhana, tetapi di produk nyata ia menyentuh izin browser, HTTPS, kegagalan perangkat, input manual, privasi, integrasi peta, logging, dan pengujian. Tombol “gunakan lokasi saya” tidak boleh hanya bekerja pada kondisi ideal.
Claude Code bisa mempercepat implementasi jika instruksinya jelas. Jika hanya diminta “tambahkan lokasi sekarang”, hasilnya mungkin hanya memanggil getCurrentPosition dan menampilkan koordinat. Implementasi yang siap dipublikasikan harus meminta izin setelah aksi pengguna, menjelaskan alasan, menyediakan fallback alamat atau kode pos, membatasi waktu tunggu, dan tidak mencatat latitude serta longitude mentah.
Referensi utama yang dipakai: MDN Geolocation API, getCurrentPosition, watchPosition, Permissions API, W3C Geolocation specification, Chrome tentang secure origins, Chrome DevTools Sensors, Playwright emulation, dan Claude Code permissions. Untuk konteks terkait, baca integrasi peta, audit keamanan, dan desain responsif.
Tetapkan batas fitur dulu
Geolocation API meminta browser memperkirakan lokasi perangkat. Browser dapat memakai GPS, Wi-Fi, menara seluler, IP, layanan sistem operasi, atau cache. Aplikasi web tidak memilih sumber data dan tidak boleh menjanjikan akurasi sempurna.
Sebelum menulis kode, putuskan empat hal. Pertama, minta izin hanya setelah pengguna menekan tombol yang relevan. Kedua, gunakan enableHighAccuracy: false sebagai default. Ketiga, sediakan input manual seperti alamat, kota, atau kode pos. Keempat, tentukan apakah koordinat mentah perlu disimpan; sering kali hasil bisnis seperti zona layanan sudah cukup.
| Keputusan | Default aman | Kesalahan umum |
|---|---|---|
| Waktu izin | Setelah klik pengguna | Prompt muncul saat halaman dibuka |
| Akurasi | High accuracy dimatikan dulu | GPS dipakai untuk semua pencarian |
| Fallback | Alamat, kota, atau kode pos | Flow berhenti setelah izin ditolak |
| Logging | Event dan bucket kasar | position.coords masuk analytics |
Lapisan peta juga harus dipisah. Geolocation hanya mengembalikan koordinat. Rendering peta, geocoding, rute, dan pencarian tempat terdekat adalah tanggung jawab Google Maps, Mapbox, OpenStreetMap, atau backend.
Use case produk
Use case pertama adalah pencari toko. Pengguna menekan “gunakan lokasi saya”, lalu aplikasi mengurutkan toko berdasarkan jarak. Kolom kode pos harus tetap tersedia supaya pengguna yang menolak izin tetap bisa melanjutkan.
Use case kedua adalah area pengiriman atau layanan ke rumah. Grocery, servis, penyewaan, dan appointment dapat menentukan apakah pengguna berada di zona layanan dan menampilkan slot waktu. Server sering cukup menyimpan inside_zone, bucket jarak, atau toko yang dipilih.
Use case ketiga adalah check-in pekerja lapangan. Cleaning, maintenance, event crew, dan sales visit dapat memakai lokasi untuk memastikan kedekatan dengan lokasi kerja. Jika lokasi gagal, sediakan foto, persetujuan supervisor, atau catatan manual.
Use case keempat adalah konten lokal. Cuaca, acara, stok toko, dan pemberitahuan kota dapat memakai lokasi. Jika tingkat kota sudah cukup, preferensi kota tersimpan lebih ramah dibanding lokasi presisi langsung.
Contoh getCurrentPosition siap jalan
Simpan sebagai geo-demo.html dan jalankan dari localhost atau HTTPS. HTTP biasa dapat diblokir oleh browser modern.
<!doctype html>
<html lang="id">
<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>Cari toko terdekat</h1>
<p>
Lokasi dipakai hanya untuk mengurutkan toko.
Alamat persis dan riwayat pergerakan tidak disimpan.
</p>
<button id="useLocation" type="button">Gunakan lokasi saya</button>
<p id="status" role="status" aria-live="polite"></p>
<pre id="result"></pre>
<form id="manualForm">
<label for="postcode">Kode pos, kota, atau alamat</label>
<input id="postcode" name="postcode" autocomplete="postal-code" />
<button type="submit">Cari manual</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}. Anda tetap bisa mencari dengan kode pos atau kota.`;
}
function onSuccess(position) {
const { latitude, longitude, accuracy } = position.coords;
status.textContent = "Lokasi diterima.";
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: "Izin lokasi ditolak",
2: "Lokasi perangkat tidak tersedia",
3: "Permintaan lokasi melewati batas waktu",
};
showManual(messages[error.code] ?? "Lokasi tidak tersedia");
}
button.addEventListener("click", () => {
if (!("geolocation" in navigator)) {
showManual("Browser ini tidak mendukung Geolocation");
return;
}
status.textContent = "Memeriksa izin lokasi...";
navigator.geolocation.getCurrentPosition(onSuccess, onError, {
enableHighAccuracy: false,
timeout: 8000,
maximumAge: 60000,
});
});
form.addEventListener("submit", (event) => {
event.preventDefault();
const data = new FormData(form);
status.textContent =
`Mencari di sekitar "${data.get("postcode")}".`;
});
</script>
</body>
</html>
timeout membatasi waktu tunggu. maximumAge mengizinkan lokasi cache yang cukup baru. enableHighAccuracy meminta presisi lebih tinggi, tetapi bisa lebih lambat dan boros baterai.
watchPosition harus dihentikan
watchPosition mengirim update berulang. Gunakan untuk rute atau tugas aktif, bukan pencarian toko sekali jalan. ID yang dikembalikan harus dibersihkan dengan clearWatch.
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(`Pelacakan gagal: ${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}>Mulai pelacakan</button>
<button type="button" onClick={stop}>Berhenti</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>
);
}
Produk yang melacak lokasi harus punya status yang terlihat: mulai, jeda, selesai. Retensi data, akses internal, dan cara hapus juga perlu jelas.
Privasi dan logging
Latitude dan longitude bukan log biasa. Data itu bisa masuk monitoring, analytics, replay sesi, screenshot dukungan, atau tiket pelanggan. Gunakan event dan bucket kasar.
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)),
};
}
Untuk analitik, event seperti permission_denied, manual_search_used, timeout, dan results_shown sering cukup. Tulis jelas di prompt Claude Code: jangan log latitude dan longitude mentah.
Permissions API bisa membaca granted, prompt, dan denied, tetapi prompt browser sebenarnya muncul ketika Geolocation dipanggil.
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";
}
}
Mode gagal dan pengujian
Uji HTTP, HTTPS, dan iframe. Geolocation butuh secure context, dan Permissions-Policy dapat memblokir fitur di iframe lintas origin.
Uji izin ditolak. UI harus berpindah ke input manual tanpa memaksa pengguna.
Uji timeout dan lokasi tidak tersedia. Desktop, ruangan tertutup, VPN, layanan lokasi OS mati, dan browser perusahaan dapat gagal.
Uji cache lama. maximumAge membuat UX cepat, tetapi bisa salah untuk check-in.
import { expect, test } from "@playwright/test";
test.use({
geolocation: {
latitude: -6.208763,
longitude: 106.845599,
accuracy: 50,
},
permissions: ["geolocation"],
});
test("shows nearby stores from mocked location", async ({ page }) => {
await page.goto("/stores");
await page.getByRole("button", { name: "Gunakan lokasi saya" }).click();
await expect(page.getByText("Lokasi diterima")).toBeVisible();
});
Layer peta harus diuji terpisah. Geolocation berhasil tidak berarti map tile, geocoding, routing, kuota, atau API key juga benar.
Prompt Claude Code yang aman
Prompt harus menyebut scope, larangan, dan bukti verifikasi.
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
Untuk tim, batasi permission Claude Code agar tidak membaca .env dan tidak melakukan push otomatis.
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Bash(git push *)"
],
"allow": [
"Bash(npm test *)",
"Bash(npm run lint)"
]
}
}
Jika ingin menjadikan pola ini sebagai standar tim, checklist review, atau prompt khusus repositori, halaman Claude Code training and consultation adalah langkah praktis berikutnya.
Catatan verifikasi langsung
Contoh diuji di Chrome lewat localhost, DevTools Sensors dengan lokasi Jakarta dan “Location unavailable”, serta Playwright dengan izin geolocation. Sebelum rilis, tambahkan pengujian untuk izin ditolak, layanan lokasi OS mati, browser perusahaan, iframe, kuota penyedia peta, dan pencarian log yang mungkin masih berisi koordinat mentah.
PDF gratis: cheatsheet Claude Code
Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.
Kami menjaga datamu dan tidak mengirim spam.
Tentang penulis
Masa
Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.
Artikel terkait
Workflow Obsidian ke CLAUDE.md untuk Claude Code
Ubah catatan kerja Obsidian menjadi operating note CLAUDE.md agar konteks tidak dijelaskan ulang.
Claude Code Revenue CTA Routing: dari artikel ke PDF, Gumroad, dan konsultasi
Workflow Claude Code untuk mengarahkan pembaca ke PDF gratis, Gumroad, atau konsultasi sesuai intent.
Aturan handoff tim Claude Code: bukti review, permission, rollback, dan jalur revenue
Format handoff Claude Code untuk tim: bukti, permission rule, rollback, PDF gratis, Gumroad, dan konsultasi.