npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@closerclick/closer-click-geo

v0.2.0

Published

Cliente del índice geo de Closer Click (geo.closer.click): pins georreferenciados firmados con el vault, consulta por radio (PostGIS)

Readme

@closerclick/closer-click-geo

Índice de descubrimiento georreferenciado del ecosistema CloserClick: geo.closer.click. Una identidad publica un pin firmado (lat/lng + payload) con TTL corto; cualquiera consulta los pins dentro de un radio (PostGIS hace la query espacial). Pensado para casos tipo "Uber", delivery, "cerca de mí", marketplace local, eventos.

Es el cuarto pilar del ecosistema, complementario a los tres existentes:

| Pilar | Paquete | Rol | |-------|---------|-----| | Identidad | closer-click-identity | clave del vault, firma | | Transporte | closer-click-proxy-client | mensajería, canales, WebRTC | | Almacenamiento | closer-click-store | datos del usuario en el navegador | | Descubrimiento geo | closer-click-geo | encontrar identidades cercanas |

El pin es un anuncio efímero, no almacenamiento

Una publicación geo no es persistente: es un anuncio que apunta a otro servicio (el messenger) donde ocurre la transacción real. El pin lleva el pubkey de la identidad, que es el handle con el que se la contacta por el proxy. El dato durable de la transacción vive en el messenger, no acá.

Por eso el techo de vida de un pin es 24 h, alineado con la ventana de mensajes offline del proxy: sin que la identidad lo hidrate (republicar), un anuncio no debe sobrevivir más que un mensaje offline. Apps que necesiten presencia "ahora mismo" usan TTL cortos (default 10 min); anuncios tipo clasificado pueden llegar al cap de 24 h. El server purga los expirados.

Principio de diseño: descubrir acá, hablar por el proxy

Este índice es SOLO descubrimiento. El flujo correcto es:

  1. Publicás un pin firmado con publishPin() (opt-in, TTL corto).
  2. Otra identidad te encuentra con queryRadius().
  3. A partir de ahí, todo el contacto en vivo va por el proxy (@closerclick/closer-click-proxy-client): el "pedido de viaje", el handshake y el streaming de ubicación en tiempo real se hacen con sendByPubkey / canal / WebRTC. No hagas streaming de posición a alta frecuencia contra este índice — eso es transporte y ya está resuelto.

Cada pin (y cada query) trae el pubkey JWK de la identidad: ese es el handle con el que el proxy enruta (sendByPubkey). Identidad de descubrimiento e identidad de transporte coinciden, como manda el ecosistema.

Identidad: el vault es la única fuente

El cliente no genera ni guarda claves. Le inyectás signData y getPublicKeyJwk desde @closerclick/closer-click-identity (el vault id.closer.click). Mismo sobre firmado {data, signature} y misma serialización canónica que el proxy-client.

Instalación

npm i @closerclick/closer-click-geo

Uso (cliente)

import { createGeoClient } from '@closerclick/closer-click-geo'
import identity from '@closerclick/closer-click-identity' // el vault

const geo = createGeoClient({
  signData: identity.signData,
  getPublicKeyJwk: identity.getPublicKeyJwk,
  // baseUrl: 'https://geo.closer.click'  // default
})

// 1) Publicar mi pin (opt-in). Se reemplaza el anterior; no hay historial.
await geo.publishPin({
  lat: -2.1709, lng: -79.9224,
  payload: { role: 'driver', seats: 4 },
  tags: ['comida', 'vegano'],  // etiquetas de búsqueda (se normalizan)
  ttlMs: 10 * 60 * 1000        // 10 min (el server lo capa a 1h)
})

// 2) Buscar cerca, por radio + tags (overlap: alguna de las etiquetas) y/o filter
const { pins } = await geo.queryRadius({
  lat: -2.1700, lng: -79.9200,
  radiusMeters: 3000,
  tags: ['comida', 'bici'],    // pins con ALGUNA de estas etiquetas
  filter: { role: 'driver' }   // containment JSONB sobre payload (opcional)
})
// pins: [{ publickey, lat, lng, geohash, payload, tags, distanceMeters, expiresAt }, ...]

// 3) Contactar al más cercano POR EL PROXY (no por este índice)
//    proxyClient.sendByPubkey([pins[0].publickey], { type: 'ride-request', ... })

// 4) Retirar mi pin antes de tiempo
await geo.removePin()

Privacidad (no rompe la filosofía CloserClick)

Un índice de ubicación consultable es lo más sensible del ecosistema. Por eso:

  • Un pin por identidad, con overwrite — no se guarda historial de ubicaciones.
  • TTL corto por diseño (default 10 min, cap del server 1 h). Un job purga expirados cada minuto.
  • Opt-in siempre: publicar es una acción explícita; removePin() lo retira.
  • Geohash grueso para descubrimiento (default 7 chars ≈ 76 m; bajalo con geohashPrecision para exponer menos). La recomendación es descubrir por bucket y negociar las coords exactas P2P por el proxy tras consentimiento.
  • Sólo metadata de app en payload — nunca datos personales del usuario.

API del servicio (HTTP/JSON)

| Método | Ruta | Auth | Descripción | |--------|------|------|-------------| | PUT | /pins | sobre firmado | publica/reemplaza el pin de la identidad | | DELETE | /pins | sobre firmado | retira el pin (tombstone) | | GET | /pins?lat&lng&r&limit&filter | pública | pins dentro del radio, por distancia | | GET | /health | — | liveness |

El servidor verifica la firma ECDSA P-256 (JWK del vault) sobre el data canónico, chequea frescura (issuedAt dentro de ±5 min, anti-replay) y capa el TTL. Ver server/ y DEPLOY.md.

Estructura

src/            cliente npm (@closerclick/closer-click-geo)
  index.js      createGeoClient: publishPin / queryRadius / removePin
  geohash.js    encode base32 (compartido)
  canonical.js  serialización canónica (igual que el resto del ecosistema)
server/         servicio geo.closer.click
  server.js     HTTP/JSON nativo
  db.js         capa PostGIS (pg)
  schema.sql    tabla pins (geography + GiST)
  signature.js  verificación de sobres firmados

Licencia

MIT