tabua-mare-sdk
Version:
SDK JavaScript/TypeScript para a API Tábua de Marés do Brasil
195 lines (170 loc) • 6.04 kB
JavaScript
/**
* Tábua de Marés SDK
* SDK JavaScript para integração com a API Tábua de Marés
*/
const BASE_URL = 'https://tabuamare.devtu.qzz.io/api/v1';
/**
* Cliente HTTP universal (funciona em Node.js e Browser)
*/
class HttpClient {
async get(url) {
// Detecta ambiente
if (typeof window !== 'undefined' && window.fetch) {
// Browser
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
return response.json();
} else if (typeof require !== 'undefined') {
// Node.js
const https = require('https');
const urlParsed = new URL(url);
const headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7',
'Connection': 'keep-alive'
};
const options = {
hostname: urlParsed.hostname,
port: urlParsed.port || 443,
path: urlParsed.pathname + urlParsed.search,
method: 'GET',
headers: headers
};
return new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => data += chunk);
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(JSON.parse(data));
} else {
const error = new Error(`HTTP Error: ${res.statusCode}`);
error.response = { status: res.statusCode, url: url };
reject(error);
}
});
});
req.on('error', reject);
req.end();
});
} else {
throw new Error('Ambiente não suportado');
}
}
}
/**
* Cliente principal da API Tábua de Marés
*/
class TabuaMareClient {
constructor(options = {}) {
this.baseUrl = options.baseUrl || BASE_URL;
this.http = new HttpClient();
}
/**
* Lista todos os estados costeiros disponíveis
* @returns {Promise<{data: string[], total: number}>}
*/
async getStates() {
return this.http.get(`${this.baseUrl}/states`);
}
/**
* Lista os portos de um estado específico
* @param {string} state - Sigla do estado (ex: 'sp', 'rj', 'sc')
* @returns {Promise<{data: Array, total: number}>}
*/
async getHarborsByState(state) {
if (!state || typeof state !== 'string') {
throw new Error('Estado inválido');
}
return this.http.get(`${this.baseUrl}/harbor_names/${state.toLowerCase()}`);
}
/**
* Obtém informações detalhadas de um ou mais portos
* @param {number|number[]|string} ids - ID ou IDs dos portos
* @returns {Promise<{data: Array, total: number}>}
*/
async getHarbors(ids) {
const idsStr = Array.isArray(ids) ? ids.join(',') : String(ids);
return this.http.get(`${this.baseUrl}/harbors/${idsStr}`);
}
/**
* Obtém a tábua de maré para um porto específico
* @param {number} harborId - ID do porto
* @param {number} month - Mês (1-12)
* @param {string|number[]} days - Dias no formato "[1,2,3]" ou array [1,2,3]
* @returns {Promise<{data: Array, total: number}>}
*/
async getTabuaMare(harborId, month, days) {
if (!harborId || !month) {
throw new Error('harborId e month são obrigatórios');
}
// Converte array para string no formato esperado
let daysStr = days;
if (Array.isArray(days)) {
daysStr = `[${days.join(',').replace(/\s/g, '')}]`;
} else if (typeof days === 'string' && !days.startsWith('[')) {
daysStr = `[${days}]`;
}
// Encode URL para lidar com caracteres especiais
const encodedDays = encodeURIComponent(daysStr);
return this.http.get(`${this.baseUrl}/tabua-mare/${harborId}/${month}/${encodedDays}`);
}
/**
* Obtém a tábua de maré para um período de dias
* @param {number} harborId - ID do porto
* @param {number} month - Mês (1-12)
* @param {number} startDay - Dia inicial
* @param {number} endDay - Dia final
* @returns {Promise<{data: Array, total: number}>}
*/
async getTabuaMareRange(harborId, month, startDay, endDay) {
return this.getTabuaMare(harborId, month, `[${startDay}-${endDay}]`);
}
/**
* Obtém a tábua de maré para o mês completo
* @param {number} harborId - ID do porto
* @param {number} month - Mês (1-12)
* @returns {Promise<{data: Array, total: number}>}
*/
async getTabuaMareMonth(harborId, month) {
return this.getTabuaMare(harborId, month, '[1-31]');
}
/**
* Obtém o porto mais próximo de uma coordenada geográfica
* @param {number} lat - Latitude (-90 a 90)
* @param {number} lng - Longitude (-180 a 180)
* @returns {Promise<{data: Array, total: number}>}
*/
async getNearestHarbor(lat, lng) {
if (typeof lat !== 'number' || typeof lng !== 'number') {
throw new Error('Latitude e longitude devem ser números');
}
if (isNaN(lat) || isNaN(lng) || !isFinite(lat) || !isFinite(lng)) {
throw new Error('Latitude e longitude devem ser números válidos');
}
if (lat < -90 || lat > 90) {
throw new Error('Latitude deve estar entre -90 e 90 graus');
}
if (lng < -180 || lng > 180) {
throw new Error('Longitude deve estar entre -180 e 180 graus');
}
const latLng = `${lat.toFixed(6)},${lng.toFixed(6)}`;
const response = await this.http.get(`${this.baseUrl}/nearest-harbor-independent-state/${latLng}`);
// O endpoint nearest-harbor retorna o porto diretamente, não envolto em data
// Então precisamos embrulhar na estrutura esperada
return {
data: [response],
total: 1
};
}
}
// Exporta para diferentes ambientes
if (typeof module !== 'undefined' && module.exports) {
module.exports = { TabuaMareClient };
}
if (typeof window !== 'undefined') {
window.TabuaMareClient = TabuaMareClient;
}