geo-engine-node 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +135 -0
- package/index.js +88 -62
- package/package.json +24 -8
package/Readme.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
|
|
2
|
+
---
|
|
3
|
+
|
|
4
|
+
# 📦 Geo-Engine Node.js SDK
|
|
5
|
+
|
|
6
|
+
Cliente oficial para interactuar con la plataforma **Geo-Engine**.
|
|
7
|
+
Permite enviar ubicaciones en tiempo real y gestionar **geocercas** desde tus aplicaciones Node.js.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 🚀 Instalación
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install geo-engine-node
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## ⚡ Inicio Rápido
|
|
20
|
+
|
|
21
|
+
Envía la ubicación de un dispositivo en **3 líneas de código**:
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
const GeoEngine = require('geo-engine-node');
|
|
25
|
+
|
|
26
|
+
// Inicializa con tu API Key (consíguela en tu Dashboard)
|
|
27
|
+
const geo = new GeoEngine('sk_test_12345abcdef');
|
|
28
|
+
|
|
29
|
+
// Envía una coordenada (ID, Latitud, Longitud)
|
|
30
|
+
geo.sendLocation('camion-01', 19.4326, -99.1332)
|
|
31
|
+
.then(res => console.log('✅ Ubicación enviada:', res))
|
|
32
|
+
.catch(err => console.error('❌ Error:', err.message));
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 📚 Referencia de la API
|
|
38
|
+
|
|
39
|
+
### `new GeoEngine(apiKey, [options])`
|
|
40
|
+
|
|
41
|
+
Crea una nueva instancia del cliente.
|
|
42
|
+
|
|
43
|
+
**Parámetros:**
|
|
44
|
+
|
|
45
|
+
* `apiKey` (`string`): Tu clave secreta de API.
|
|
46
|
+
* `options` (`object`, opcional):
|
|
47
|
+
|
|
48
|
+
* `timeout`: Tiempo de espera en ms *(default: 10000)*.
|
|
49
|
+
* `ingestUrl`: URL personalizada para ingestión (útil para pruebas locales).
|
|
50
|
+
* `managementUrl`: URL personalizada para gestión.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
### `geo.sendLocation(deviceId, lat, lng)`
|
|
55
|
+
|
|
56
|
+
Envía un punto de rastreo al motor de ingestión.
|
|
57
|
+
El sistema procesará automáticamente si este punto **entra o sale** de alguna geocerca activa.
|
|
58
|
+
|
|
59
|
+
**Parámetros:**
|
|
60
|
+
|
|
61
|
+
* `deviceId` (`string`): Identificador único del activo (ej: placa del vehículo, UUID).
|
|
62
|
+
* `lat` (`number`): Latitud decimal.
|
|
63
|
+
* `lng` (`number`): Longitud decimal.
|
|
64
|
+
|
|
65
|
+
**Retorna:**
|
|
66
|
+
`Promise<Object>` con el estado de la cola (`queued`).
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
### `geo.createGeofence(name, coordinates, webhookUrl)`
|
|
71
|
+
|
|
72
|
+
Crea programáticamente una nueva zona de interés (geocerca).
|
|
73
|
+
|
|
74
|
+
**Parámetros:**
|
|
75
|
+
|
|
76
|
+
* `name` (`string`): Nombre descriptivo (ej: `"Almacén Central"`).
|
|
77
|
+
* `coordinates` (`Array<Array<number>>`):
|
|
78
|
+
Lista de puntos que forman el polígono
|
|
79
|
+
`[[lat, lng], [lat, lng], ...]`
|
|
80
|
+
*(El SDK se encarga de cerrar el polígono si es necesario).*
|
|
81
|
+
* `webhookUrl` (`string`):
|
|
82
|
+
URL donde Geo-Engine enviará una petición **POST** cuando un dispositivo entre en esta zona.
|
|
83
|
+
|
|
84
|
+
**Ejemplo:**
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
const zona = [
|
|
88
|
+
[19.42, -99.16],
|
|
89
|
+
[19.41, -99.16],
|
|
90
|
+
[19.41, -99.15],
|
|
91
|
+
[19.42, -99.15]
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
await geo.createGeofence(
|
|
95
|
+
'Zona Prohibida 1',
|
|
96
|
+
zona,
|
|
97
|
+
'https://mi-api.com/alertas'
|
|
98
|
+
);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 🛡️ Manejo de Errores
|
|
104
|
+
|
|
105
|
+
El SDK lanza errores descriptivos que puedes capturar fácilmente:
|
|
106
|
+
|
|
107
|
+
```js
|
|
108
|
+
try {
|
|
109
|
+
await geo.sendLocation('x', 0, 0);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error(error.message);
|
|
112
|
+
// Ej: "GeoEngine API Error (401): Invalid API Key"
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 🧪 Desarrollo Local
|
|
119
|
+
|
|
120
|
+
Si estás ejecutando **Geo-Engine** en tu máquina local, puedes configurar el cliente para que apunte a `localhost`:
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
const geo = new GeoEngine('mi-clave-local', {
|
|
124
|
+
ingestUrl: 'http://localhost:8080',
|
|
125
|
+
managementUrl: 'http://localhost:8081'
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 📄 Licencia
|
|
132
|
+
|
|
133
|
+
MIT © Geo-Engine Team
|
|
134
|
+
|
|
135
|
+
---
|
package/index.js
CHANGED
|
@@ -1,92 +1,118 @@
|
|
|
1
|
-
import { create } from 'axios';
|
|
2
|
-
|
|
3
1
|
const DEFAULTS = {
|
|
4
2
|
managementUrl: 'https://api.geoengine.dev',
|
|
5
|
-
ingestUrl: '
|
|
3
|
+
ingestUrl: 'https://ingest.geoengine.dev',
|
|
6
4
|
timeout: 10000
|
|
7
5
|
};
|
|
8
6
|
|
|
9
7
|
class GeoEngine {
|
|
10
8
|
/**
|
|
11
|
-
* Inicializa el cliente de Geo-Engine
|
|
12
|
-
* @param {string} apiKey - Tu API Key
|
|
13
|
-
* @param {Object} options - Configuración opcional
|
|
9
|
+
* Inicializa el cliente de Geo-Engine.
|
|
10
|
+
* @param {string} apiKey - Tu API Key.
|
|
11
|
+
* @param {Object} [options] - Configuración opcional.
|
|
14
12
|
*/
|
|
15
13
|
constructor(apiKey, options = {}) {
|
|
16
|
-
if (!apiKey)
|
|
17
|
-
|
|
14
|
+
if (!apiKey) {
|
|
15
|
+
throw new Error('GeoEngine: API Key es requerida.');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
18
|
this.apiKey = apiKey;
|
|
19
19
|
this.config = { ...DEFAULTS, ...options };
|
|
20
|
-
|
|
21
|
-
this.client = create({
|
|
22
|
-
timeout: this.config.timeout,
|
|
23
|
-
headers: {
|
|
24
|
-
'Content-Type': 'application/json',
|
|
25
|
-
'X-API-Key': this.apiKey,
|
|
26
|
-
'User-Agent': 'GeoEngineNode/1.0.0'
|
|
27
|
-
}
|
|
28
|
-
});
|
|
20
|
+
this.userAgent = 'GeoEngineNode/1.0.0';
|
|
29
21
|
}
|
|
30
22
|
|
|
31
23
|
/**
|
|
32
|
-
*
|
|
33
|
-
* @param {string} deviceId - ID del dispositivo (ej: 'camion-01')
|
|
34
|
-
* @param {number} lat - Latitud
|
|
35
|
-
* @param {number} lng - Longitud
|
|
24
|
+
* Helper privado para hacer peticiones con timeout
|
|
36
25
|
*/
|
|
37
|
-
async
|
|
26
|
+
async _request(url, method, body) {
|
|
27
|
+
const controller = new AbortController();
|
|
28
|
+
const id = setTimeout(() => controller.abort(), this.config.timeout);
|
|
29
|
+
|
|
38
30
|
try {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
31
|
+
const response = await fetch(url, {
|
|
32
|
+
method: method,
|
|
33
|
+
headers: {
|
|
34
|
+
'Content-Type': 'application/json',
|
|
35
|
+
'X-API-Key': this.apiKey,
|
|
36
|
+
'User-Agent': this.userAgent
|
|
37
|
+
},
|
|
38
|
+
body: JSON.stringify(body),
|
|
39
|
+
signal: controller.signal
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
clearTimeout(id);
|
|
43
|
+
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
// Intentar leer el error en JSON, si falla, leer texto
|
|
46
|
+
let errorData;
|
|
47
|
+
try {
|
|
48
|
+
errorData = await response.json();
|
|
49
|
+
} catch (e) {
|
|
50
|
+
errorData = { error: await response.text() };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
throw new Error(`GeoEngine API Error (${response.status}): ${errorData.error || JSON.stringify(errorData)}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Si la respuesta es 204 No Content o vacía, devolver null
|
|
57
|
+
if (response.status === 204) return null;
|
|
58
|
+
|
|
59
|
+
return await response.json();
|
|
60
|
+
|
|
48
61
|
} catch (error) {
|
|
49
|
-
|
|
62
|
+
clearTimeout(id);
|
|
63
|
+
if (error.name === 'AbortError') {
|
|
64
|
+
throw new Error(`GeoEngine: La petición excedió el tiempo límite de ${this.config.timeout}ms`);
|
|
65
|
+
}
|
|
66
|
+
throw error;
|
|
50
67
|
}
|
|
51
68
|
}
|
|
52
69
|
|
|
53
70
|
/**
|
|
54
|
-
*
|
|
55
|
-
* @param {string} name - Nombre de la zona
|
|
56
|
-
* @param {Array<Array<number>>} coordinates - Array de puntos [[lat, lng], ...]
|
|
57
|
-
* @param {string} webhookUrl - URL para recibir alertas
|
|
71
|
+
* Envía una ubicación al motor de ingestión.
|
|
58
72
|
*/
|
|
59
|
-
async
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const polygon = coordinates.map(p => [p[1], p[0]]);
|
|
64
|
-
// Cerrar polígono si es necesario
|
|
65
|
-
if (polygon[0][0] !== polygon[polygon.length-1][0]) {
|
|
66
|
-
polygon.push(polygon[0]);
|
|
67
|
-
}
|
|
73
|
+
async sendLocation(deviceId, lat, lng) {
|
|
74
|
+
if (!deviceId || lat === undefined || lng === undefined) {
|
|
75
|
+
throw new Error("GeoEngine: deviceId, lat y lng son obligatorios.");
|
|
76
|
+
}
|
|
68
77
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
};
|
|
78
|
+
const payload = {
|
|
79
|
+
device_id: deviceId,
|
|
80
|
+
latitude: parseFloat(lat),
|
|
81
|
+
longitude: parseFloat(lng),
|
|
82
|
+
timestamp: Math.floor(Date.now() / 1000)
|
|
83
|
+
};
|
|
77
84
|
|
|
78
|
-
|
|
79
|
-
return res.data;
|
|
80
|
-
} catch (error) {
|
|
81
|
-
this._handleError(error);
|
|
82
|
-
}
|
|
85
|
+
return this._request(`${this.config.ingestUrl}/ingest`, 'POST', payload);
|
|
83
86
|
}
|
|
84
87
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Crea una nueva geocerca.
|
|
90
|
+
*/
|
|
91
|
+
async createGeofence(name, coordinates, webhookUrl) {
|
|
92
|
+
if (!name || !coordinates || coordinates.length < 3) {
|
|
93
|
+
throw new Error("GeoEngine: Se requiere un nombre y al menos 3 coordenadas.");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Convertir formato simple [[lat,lng]] a GeoJSON [Lng, Lat]
|
|
97
|
+
const polygon = coordinates.map(p => [p[1], p[0]]);
|
|
98
|
+
|
|
99
|
+
// Cerrar polígono automáticamente
|
|
100
|
+
const first = polygon[0];
|
|
101
|
+
const last = polygon[polygon.length - 1];
|
|
102
|
+
if (first[0] !== last[0] || first[1] !== last[1]) {
|
|
103
|
+
polygon.push(first);
|
|
88
104
|
}
|
|
89
|
-
|
|
105
|
+
|
|
106
|
+
const payload = {
|
|
107
|
+
name,
|
|
108
|
+
webhook_url: webhookUrl,
|
|
109
|
+
geojson: {
|
|
110
|
+
type: 'Polygon',
|
|
111
|
+
coordinates: [polygon]
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
return this._request(`${this.config.managementUrl}/geofences`, 'POST', payload);
|
|
90
116
|
}
|
|
91
117
|
}
|
|
92
118
|
|
package/package.json
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
"name": "geo-engine-node",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "SDK oficial para Geo-Engine. Rastreo de activos y gestión de geocercas.",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
9
|
+
"example": "node examples/simulation.js",
|
|
10
|
+
"release:patch": "npm version patch && npm publish --access public",
|
|
11
|
+
"release:minor": "npm version minor && npm publish --access public"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"geofencing",
|
|
15
|
+
"location",
|
|
16
|
+
"tracking",
|
|
17
|
+
"sdk"
|
|
18
|
+
],
|
|
19
|
+
"author": "Geo-Engine Team",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"dependencies": {},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18.0.0"
|
|
24
|
+
}
|
|
25
|
+
}
|