Ir al contenido

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-sdk

Crea 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ónPor defectoQué hace
apiKey-Tu clave. Sin ella, solo funciona attributions.list().
baseUrlhttps://api.gmaps.dev/v1Dónde está la API, con /v1 al final. Cámbiala solo para llamar a una API local.
timeout30000Cuá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.
maxRetries2Cuántas veces se reintenta una llamada que falló, cuando es seguro hacerlo. Con 0, nunca se reintenta.
fetchglobalThis.fetchTu 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étodoLlamada de la APIQué hace
places.search(params)POST /v1/places/searchEncuentra 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/quoteMuestra cuánto costaría recopilar una zona, sin iniciar nada.
collections.create(params)POST /v1/collectionsRecopila 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/collectionsTus recopilaciones, de la más reciente a la más antigua.
collections.places({ id })GET /v1/collections/{id}/placesLos 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}/streamEntrega los lugares nuevos de una recopilación a medida que llegan. Mira más abajo.
exports.create(params)POST /v1/exportsCrea 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/coverageMuestra cuánto de una zona hemos buscado y cuántos lugares tenemos ahí. Consulta Cobertura.
usage.get()GET /v1/usageCréditos usados y restantes, hoy y este mes.
webhooks.create(params)POST /v1/webhooksAgrega un endpoint. Su secreto de firma aparece solo en esta respuesta. Consulta Webhooks.
webhooks.list()GET /v1/webhooksTus 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}/testEnvía un evento de prueba a un endpoint.
webhooks.deliveries({ id })GET /v1/webhooks/{id}/deliveriesLo 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/attributionsLos 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 type y sus datos en data. place trae un lugar nuevo, progress trae los conteos hasta ese momento y done llega al final. Progreso en vivo lista todos los tipos. Una recopilación grande deja de enviar eventos place después de cierto número, así que lee sus lugares con collections.places cuando termine.
  • El bucle termina solo después de done. Para detenerlo antes, sal de él con break.
  • 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 GmapsError con NETWORK_ERROR.

Qué acepta su segundo argumento:

OpciónPor defectoQué 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.
retryMs2000Cuántos milisegundos espera antes de volver a conectarse.
maxRetries10Cuá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}`);
}
CampoQué contiene
codeQué salió mal, como QUOTA_EXCEEDED. Errores lista todos los códigos.
statusEl estado HTTP, o 0 cuando no llegó ninguna respuesta.
messageQué pasó y qué hacer ahora, en inglés.
requestIdNuestro id de la llamada. Inclúyelo cuando nos escribas.
retryAfterCuántos segundos esperar antes de volver a intentarlo, cuando la API lo indica.
detailsMás datos sobre el problema, como scope en un error de cuota.
messageKeyCon 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 de timeout.
  • 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ía data, traía un error o no era JSON. Casi siempre falta /v1 en baseUrl.
  • 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.any y AbortSignal.timeout.
  • Es un módulo ES y trae sus propias declaraciones de tipos. Cárgalo con import.
  • No tiene dependencias. Las llamadas usan el fetch del propio entorno, y verifyWebhook usa Web Crypto.
  • También funciona en un navegador, pero deja tu clave en un servidor. En un navegador, cualquiera puede leerla.