Integraciones
SDK de TypeScript
Llama a la API desde TypeScript o JavaScript: un método tipado para cada llamada, más funciones para seguir una recopilación y verificar un webhook.
Instálalo
El paquete es gmaps-sdk. No tiene dependencias propias.
npm i gmaps-sdkCrea un cliente
Pásale tu clave, que aquí se lee de una variable de entorno. Cada método hace una llamada y devuelve lo que responde la API en data:
import { createClient } from "gmaps-sdk";
const gmaps = createClient({ apiKey: process.env.GMAPS_API_KEY });
const result = await gmaps.places.search({
keywords: ["panadería"],
geo: { location: "Curitiba, PR" }
});
console.log(result.places.map((place) => place.identity.name));Qué acepta createClient:
| Opción | Por defecto | Qué hace |
|---|---|---|
apiKey | - | Tu clave. Sin ella, solo funciona attributions.list(). |
baseUrl | https://api.gmaps.dev/v1 | Dónde está la API, con /v1 al final. Cámbiala solo para llamar a una API local. |
timeout | 30000 | Cuánto puede tardar cada intento de una llamada, en milisegundos, incluida la lectura de la respuesta. Una búsqueda con wait siempre recibe su espera completa, y un poco más. |
maxRetries | 2 | Cuántas veces se reintenta una llamada que falló, cuando es seguro hacerlo. Con 0, nunca se reintenta. |
fetch | globalThis.fetch | Tu propio fetch, para pruebas o para un entorno que no tenga uno. |
Para darle a una llamada otro límite de tiempo, o para cancelarla, pasa { timeoutMs, signal } como último argumento.
const usage = await gmaps.usage.get({ timeoutMs: 5000 });Si una llamada no recibe respuesta, o recibe un error de nuestro lado (un 5xx), el SDK la envía de nuevo cerca de 1 segundo después, y otra vez cerca de 2 segundos más tarde. Solo lo hace con llamadas que se pueden ejecutar dos veces sin riesgo: lecturas, estimaciones, consultas de cobertura, cancelaciones, y cambios y eliminaciones de webhooks. Una búsqueda, una recopilación nueva, una exportación nueva, un webhook nuevo y una prueba de webhook nunca se reenvían así, porque el primer intento pudo haberse ejecutado ya, y un segundo podría gastar créditos, enviar algo dos veces o crear una segunda exportación en tu lista. Después de un 408, 425 o 429 que pida una espera de 10 segundos o menos, cualquier llamada espera y se envía de nuevo.
Todos los métodos
Cada método recibe un objeto con los mismos campos que su llamada de la API. Los que no tienen campos, como usage.get(), reciben solo las opciones.
| Método | Llamada de la API | Qué hace |
|---|---|---|
places.search(params) | POST /v1/places/search | Encuentra lugares por lo que son y dónde están. Consulta Búsqueda. |
places.get({ place }) | GET /v1/places/{place} | Lee un lugar que ya guardamos, por nuestro id, su cid, su place ID o un enlace de Maps. Consulta Lugares. |
collections.quote(params) | POST /v1/collections/quote | Muestra cuánto costaría recopilar una zona, sin iniciar nada. |
collections.create(params) | POST /v1/collections | Recopila todos los lugares de una zona. Consulta Recopilaciones. |
collections.get({ id }) | GET /v1/collections/{id} | Muestra hasta dónde llegó una recopilación y cuánto ha costado. |
collections.list(params?) | GET /v1/collections | Tus recopilaciones, de la más reciente a la más antigua. |
collections.places({ id }) | GET /v1/collections/{id}/places | Los lugares que encontró una recopilación, una página a la vez. |
collections.cancel({ id }) | DELETE /v1/collections/{id} | Detiene una recopilación. Conservas los lugares que ya llegaron, y los créditos que costaron no se devuelven. |
watchCollection(id, options?) | GET /v1/collections/{id}/stream | Entrega los lugares nuevos de una recopilación a medida que llegan. Mira más abajo. |
exports.create(params) | POST /v1/exports | Crea un enlace a los lugares de una recopilación como archivo csv, json o xlsx. Consulta Exportaciones. |
exports.get({ id }) | GET /v1/exports/{id} | Vuelve a leer una exportación mientras su enlace siga funcionando. |
coverage.get(params) | POST /v1/coverage | Muestra cuánto de una zona hemos buscado y cuántos lugares tenemos ahí. Consulta Cobertura. |
usage.get() | GET /v1/usage | Créditos usados y restantes, hoy y este mes. |
webhooks.create(params) | POST /v1/webhooks | Agrega un endpoint. Su secreto de firma aparece solo en esta respuesta. Consulta Webhooks. |
webhooks.list() | GET /v1/webhooks | Tus endpoints, y si cada uno está activado. |
webhooks.update({ id, … }) | POST /v1/webhooks/{id} | Cambia los eventos o la nota de un endpoint, o lo desactiva y lo vuelve a activar. |
webhooks.test({ id }) | POST /v1/webhooks/{id}/test | Envía un evento de prueba a un endpoint. |
webhooks.deliveries({ id }) | GET /v1/webhooks/{id}/deliveries | Lo que enviamos a un endpoint y lo que respondió. |
webhooks.delete({ id }) | DELETE /v1/webhooks/{id} | Elimina un endpoint y su historial de entregas. |
attributions.list() | GET /v1/attributions | Los proyectos de código abierto en los que nos apoyamos. No necesita clave. |
Mira llegar los lugares nuevos
Cuando los lugares que tenemos guardados no alcanzan para limit, la respuesta de la búsqueda incluye una collection que busca el resto. watchCollection te entrega cada lugar nuevo en cuanto llega:
const result = await gmaps.places.search({
keywords: ["panadería"],
geo: { location: "Curitiba, PR" }
});
if (result.collection) {
for await (const frame of gmaps.watchCollection(result.collection.id)) {
if (frame.type === "place") console.log(frame.data);
}
}- Cada evento tiene un
typey sus datos endata.placetrae un lugar nuevo,progresstrae los conteos hasta ese momento ydonellega al final. Progreso en vivo lista todos los tipos. Una recopilación grande deja de enviar eventosplacedespués de cierto número, así que lee sus lugares concollections.placescuando termine. - El bucle termina solo después de
done. Para detenerlo antes, sal de él conbreak. - Si la conexión se cae, se conecta de nuevo y sigue después del último evento que te entregó, así que nunca recibes un evento dos veces ni te pierdes uno. Si se cae demasiadas veces seguidas, sin ningún evento entre una y otra, lanza un
GmapsErrorconNETWORK_ERROR.
Qué acepta su segundo argumento:
| Opción | Por defecto | Qué hace |
|---|---|---|
lastEventId | - | Empieza después del evento con este id, para seguir donde te quedaste. |
signal | - | Un AbortSignal. Cuando se dispara, el bucle termina en silencio, sin error. |
retryMs | 2000 | Cuántos milisegundos espera antes de volver a conectarse. |
maxRetries | 10 | Cuántas caídas seguidas permite antes de rendirse. Cualquier evento, incluso un heartbeat, reinicia la cuenta. |
No tienes que seguirla en vivo. Puedes repetir la misma búsqueda más tarde, cuando los lugares nuevos llegan gratis, o pedirle a la búsqueda que los espere con wait. Búsqueda explica las dos formas.
Cuando una llamada falla
Toda llamada que falla lanza un GmapsError. Trae lo que dijo la API, para que tu código distinga un problema de otro:
import { GmapsError } from "gmaps-sdk";
try {
await gmaps.usage.get();
} catch (error) {
if (!(error instanceof GmapsError)) throw error;
console.error(error.status, error.code, error.message);
console.error(`Request id: ${error.requestId}`);
}| Campo | Qué contiene |
|---|---|
code | Qué salió mal, como QUOTA_EXCEEDED. Errores lista todos los códigos. |
status | El estado HTTP, o 0 cuando no llegó ninguna respuesta. |
message | Qué pasó y qué hacer ahora, en inglés. |
requestId | Nuestro id de la llamada. Inclúyelo cuando nos escribas. |
retryAfter | Cuántos segundos esperar antes de volver a intentarlo, cuando la API lo indica. |
details | Más datos sobre el problema, como scope en un error de cuota. |
messageKey | Con params: un nombre fijo para el mensaje y sus valores, para que tu app lo muestre en su propio idioma. |
Cuatro códigos vienen del propio SDK, cuando no hay una respuesta que pueda usar:
TIMEOUT: la API no respondió dentro detimeout.NETWORK_ERROR: no se pudo llegar a la API, o la respuesta se cortó a la mitad.INVALID_RESPONSE: el estado indicaba éxito, pero el cuerpo no era una respuesta de la API: no teníadata, traía unerroro no era JSON. Casi siempre falta/v1enbaseUrl.HTTP_ERROR: llegó un error que no tiene nuestro formato, casi siempre de algo que está entre tú y la API.
Si cancelas una llamada con tu propio signal, el SDK lanza el reason de ese signal en lugar del error. Las opciones que no pueden funcionar, como un baseUrl que no es http ni https, lanzan un error en cuanto creas el cliente.
Verifica un webhook
verifyWebhook te dice si una solicitud realmente vino de nosotros. Pásale el secreto de firma de tu endpoint, el cuerpo sin procesar y el encabezado x-gmaps-signature:
import { verifyWebhook } from "gmaps-sdk";
const body = await request.text();
const signed = await verifyWebhook(
process.env.GMAPS_WEBHOOK_SECRET,
body,
request.headers.get("x-gmaps-signature"),
);
if (!signed) return new Response(null, { status: 400 });
const event = JSON.parse(body);Devuelve false ante cualquier falla: sin encabezado, otro secreto, un cuerpo modificado o una firma de más de 5 minutos. Lee el cuerpo como texto y analiza el JSON solo después de verificar: si lo analizas y lo vuelves a escribir, sus bytes cambian. Webhooks explica el resto.
Tipos
Toda respuesta está tipada, y cada tipo de respuesta se exporta por su nombre para que lo uses en tu código:
import type { CollectionDto, PlaceDto, SearchResultDto } from "gmaps-sdk";Los parámetros de cada método también se exportan, como SearchPlacesParams, para que puedas tipar una llamada antes de hacerla:
import type { SearchPlacesParams } from "gmaps-sdk";
const ask: SearchPlacesParams = {
keywords: ["panadería"],
geo: { location: "Curitiba, PR" }
};Dónde funciona
- Node 22 o más reciente, Bun, o cualquier entorno con
fetch,AbortSignal.anyyAbortSignal.timeout. - Es un módulo ES y trae sus propias declaraciones de tipos. Cárgalo con
import. - No tiene dependencias. Las llamadas usan el
fetchdel propio entorno, yverifyWebhookusa Web Crypto. - También funciona en un navegador, pero deja tu clave en un servidor. En un navegador, cualquiera puede leerla.